feat: 网站增加反代缓存配置 (#6473)

Refs https://github.com/1Panel-dev/1Panel/issues/3060
This commit is contained in:
zhengkunwang 2024-09-12 18:06:22 +08:00 committed by GitHub
parent 88961e4ab7
commit 68433c922b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 365 additions and 50 deletions

View File

@ -905,3 +905,44 @@ func (b *BaseApi) ChangeWebsiteGroup(c *gin.Context) {
}
helper.SuccessWithOutData(c)
}
// @Tags Website
// @Summary update website proxy cache config
// @Description 更新网站反代缓存配置
// @Accept json
// @Param request body request.NginxProxyCacheUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /websites/proxy/config [post]
func (b *BaseApi) UpdateProxyCache(c *gin.Context) {
var req request.NginxProxyCacheUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := websiteService.UpdateProxyCache(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Summary Get website proxy cache config
// @Description 获取网站反代缓存配置
// @Accept json
// @Param id path int true "id"
// @Success 200 {object} response.NginxProxyCache
// @Security ApiKeyAuth
// @Router /websites/proxy/config/{id} [get]
func (b *BaseApi) GetProxyCache(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
res, err := websiteService.GetProxyCache(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}

View File

@ -36,6 +36,17 @@ type NginxProxyUpdate struct {
Name string `json:"name" validate:"required"`
}
type NginxProxyCacheUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Open bool `json:"open"`
CacheLimit int `json:"cacheLimit" validate:"required"`
CacheLimitUnit string `json:"cacheLimitUnit" validate:"required"`
ShareCache int `json:"shareCache" validate:"required"`
ShareCacheUnit string `json:"shareCacheUnit" validate:"required"`
CacheExpire int `json:"cacheExpire" validate:"required"`
CacheExpireUnit string `json:"cacheExpireUnit" validate:"required"`
}
type NginxAuthUpdate struct {
WebsiteID uint `json:"websiteID" validate:"required"`
Operate string `json:"operate" validate:"required"`

View File

@ -57,3 +57,13 @@ type NginxRedirectConfig struct {
type NginxFile struct {
Content string `json:"content"`
}
type NginxProxyCache struct {
Open bool `json:"open"`
CacheLimit float64 `json:"cacheLimit"`
CacheLimitUnit string `json:"cacheLimitUnit" `
ShareCache int `json:"shareCache" `
ShareCacheUnit string `json:"shareCacheUnit" `
CacheExpire int `json:"cacheExpire" `
CacheExpireUnit string `json:"cacheExpireUnit" `
}

View File

@ -84,6 +84,8 @@ type IWebsiteService interface {
OperateProxy(req request.WebsiteProxyConfig) (err error)
GetProxies(id uint) (res []request.WebsiteProxyConfig, err error)
UpdateProxyFile(req request.NginxProxyUpdate) (err error)
UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error)
GetProxyCache(id uint) (res response.NginxProxyCache, err error)
GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error)
UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error)
@ -868,19 +870,6 @@ func (w WebsiteService) GetWebsiteNginxConfig(websiteID uint, configType string)
switch configType {
case constant.AppOpenresty:
configPath = GetSitePath(website, SiteConf)
//TODO 删除下面的代码
case constant.ConfigFPM:
runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return nil, err
}
configPath = path.Join(runtimeInstall.GetPath(), "conf", "php-fpm.conf")
case constant.ConfigPHP:
runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return nil, err
}
configPath = path.Join(runtimeInstall.GetPath(), "conf", "php.ini")
}
info, err := files.NewFileInfo(files.FileOption{
Path: configPath,
@ -1486,9 +1475,7 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss
func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
var (
website model.Website
//params []response.NginxParam
//nginxInstall model.AppInstall
website model.Website
par *parser.Parser
oldContent []byte
)
@ -1497,32 +1484,7 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
if err != nil {
return
}
//params, err = getNginxParamsByKeys(constant.NginxScopeHttp, []string{"proxy_cache"}, &website)
//if err != nil {
// return
//}
//nginxInstall, err = getAppInstallByKey(constant.AppOpenresty)
//if err != nil {
// return
//}
fileOp := files.NewFileOp()
//TODO 代理缓存改为单独使用配置
//if len(params) == 0 || len(params[0].Params) == 0 {
// commonDir := path.Join(nginxInstall.GetPath(), "www", "common", "proxy")
// proxyTempPath := path.Join(commonDir, "proxy_temp_dir")
// if !fileOp.Stat(proxyTempPath) {
// _ = fileOp.CreateDir(proxyTempPath, 0755)
// }
// proxyCacheDir := path.Join(commonDir, "proxy_temp_dir")
// if !fileOp.Stat(proxyCacheDir) {
// _ = fileOp.CreateDir(proxyCacheDir, 0755)
// }
// nginxParams := getNginxParamsFromStaticFile(dto.CACHE, nil)
// if err = updateNginxConfig(constant.NginxScopeHttp, nginxParams, &website); err != nil {
// return
// }
//}
includeDir := GetSitePath(website, SiteProxyDir)
if !fileOp.Stat(includeDir) {
_ = fileOp.CreateDir(includeDir, 0755)
@ -1592,9 +1554,12 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
location.ChangePath(req.Modifier, req.Match)
if req.Cache {
location.AddCache(req.CacheTime, req.CacheUnit)
if err = openProxyCache(website); err != nil {
return
}
location.AddCache(req.CacheTime, req.CacheUnit, fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
} else {
location.RemoveCache()
location.RemoveCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
}
if len(req.Replaces) > 0 {
location.AddSubFilter(req.Replaces)
@ -1610,9 +1575,97 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
}
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website)
}
func openProxyCache(website model.Website) error {
cacheDir := GetSitePath(website, SiteCacheDir)
fileOp := files.NewFileOp()
if !fileOp.Stat(cacheDir) {
_ = fileOp.CreateDir(cacheDir, 0755)
}
content, err := fileOp.GetContent(GetSitePath(website, SiteConf))
if err != nil {
return err
}
if strings.Contains(string(content), "proxy_cache_path") {
return nil
}
proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias)
return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
}
func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) {
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
if err != nil {
return
}
cacheDir := GetSitePath(website, SiteCacheDir)
fileOp := files.NewFileOp()
if !fileOp.Stat(cacheDir) {
_ = fileOp.CreateDir(cacheDir, 0755)
}
if req.Open {
proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:%d%s max_size=%d%s inactive=%d%s", website.Alias, website.Alias, req.ShareCache, req.ShareCacheUnit, req.CacheLimit, req.CacheLimitUnit, req.CacheExpire, req.CacheExpireUnit)
return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
}
return deleteNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path"}}, &website)
}
func (w WebsiteService) GetProxyCache(id uint) (res response.NginxProxyCache, err error) {
var (
website model.Website
)
website, err = websiteRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return
}
parser, err := parser.NewParser(GetSitePath(website, SiteConf))
if err != nil {
return
}
config, err := parser.Parse()
if err != nil {
return
}
var params []string
for _, d := range config.GetDirectives() {
if d.GetName() == "proxy_cache_path" {
params = d.GetParameters()
}
}
if len(params) == 0 {
return
}
for _, param := range params {
if match, _ := regexp.MatchString(`keys_zone=proxy_cache_zone_of_[\w.]+:\d+[kmgt]?`, param); match {
r := regexp.MustCompile(`keys_zone=proxy_cache_zone_of_[\w.]+:(\d+)([kmgt]?)`)
matches := r.FindStringSubmatch(param)
if len(matches) > 0 {
res.ShareCache, _ = strconv.Atoi(matches[1])
res.ShareCacheUnit = matches[2]
}
}
if match, _ := regexp.MatchString(`max_size=\d+(\.\d+)?[kmgt]?`, param); match {
r := regexp.MustCompile(`max_size=([0-9.]+)([kmgt]?)`)
matches := r.FindStringSubmatch(param)
if len(matches) > 0 {
res.CacheLimit, _ = strconv.ParseFloat(matches[1], 64)
res.CacheLimitUnit = matches[2]
}
}
if match, _ := regexp.MatchString(`inactive=\d+[smhd]`, param); match {
r := regexp.MustCompile(`inactive=(\d+)([smhd])`)
matches := r.FindStringSubmatch(param)
if len(matches) > 0 {
res.CacheExpire, _ = strconv.Atoi(matches[1])
res.CacheExpireUnit = matches[2]
}
}
}
res.Open = true
return
}

View File

@ -1168,6 +1168,7 @@ const (
SiteSSLDir = "SiteSSLDir"
SiteReWritePath = "SiteReWritePath"
SiteRedirectDir = "SiteRedirectDir"
SiteCacheDir = "SiteCacheDir"
)
func GetSitePath(website model.Website, confType string) string {
@ -1184,6 +1185,8 @@ func GetSitePath(website model.Website, confType string) string {
return GteSiteDir(website.Alias)
case SiteIndexDir:
return path.Join(GteSiteDir(website.Alias), "index")
case SiteCacheDir:
return path.Join(GteSiteDir(website.Alias), "cache")
case SiteProxyDir:
return path.Join(GteSiteDir(website.Alias), "proxy")
case SiteSSLDir:

View File

@ -49,6 +49,8 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.POST("/proxies", baseApi.GetProxyConfig)
websiteRouter.POST("/proxies/update", baseApi.UpdateProxyConfig)
websiteRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile)
websiteRouter.POST("/proxy/config", baseApi.UpdateProxyCache)
websiteRouter.GET("/proxy/config/:id", baseApi.GetProxyCache)
websiteRouter.POST("/auths", baseApi.GetAuthConfig)
websiteRouter.POST("/auths/update", baseApi.UpdateAuthConfig)

View File

@ -186,7 +186,7 @@ func (l *Location) ChangePath(Modifier string, Match string) {
l.Match = Match
}
func (l *Location) AddCache(cacheTime int, cacheUint string) {
func (l *Location) AddCache(cacheTime int, cacheUint, cacheKey string) {
l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"})
l.RemoveDirective("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
directives := l.GetDirectives()
@ -204,7 +204,7 @@ func (l *Location) AddCache(cacheTime int, cacheUint string) {
directives = append(directives, newDir)
l.Directives = directives
l.UpdateDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"})
l.UpdateDirective("proxy_cache", []string{"proxy_cache_panel"})
l.UpdateDirective("proxy_cache", []string{cacheKey})
l.UpdateDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
l.UpdateDirective("proxy_cache_valid", []string{"200", "304", "301", "302", "10m"})
l.Cache = true
@ -212,10 +212,10 @@ func (l *Location) AddCache(cacheTime int, cacheUint string) {
l.CacheUint = cacheUint
}
func (l *Location) RemoveCache() {
func (l *Location) RemoveCache(cacheKey string) {
l.RemoveDirective("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
l.RemoveDirective("proxy_ignore_headers", []string{"Set-Cookie"})
l.RemoveDirective("proxy_cache", []string{"proxy_cache_panel"})
l.RemoveDirective("proxy_cache", []string{cacheKey})
l.RemoveDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
l.RemoveDirective("proxy_cache_valid", []string{"200"})

View File

@ -588,4 +588,14 @@ export namespace Website {
name: string;
content: string;
}
export interface WebsiteCacheConfig {
open: boolean;
cacheLimit: number;
cacheLimitUnit: string;
shareCache: number;
shareCacheUnit: string;
cacheExpire: number;
cacheExpireUnit: string;
}
}

View File

@ -303,3 +303,11 @@ export const UpdateLoadBalance = (req: Website.LoadBalanceReq) => {
export const UpdateLoadBalanceFile = (req: Website.WebsiteLBUpdateFile) => {
return http.post(`/websites/lbs/file`, req);
};
export const UpdateCacheConfig = (req: Website.WebsiteCacheConfig) => {
return http.post(`/websites/proxy/config`, req);
};
export const GetCacheConfig = (id: number) => {
return http.get<Website.WebsiteCacheConfig>(`/websites/proxy/config/${id}`);
};

View File

@ -140,6 +140,13 @@ export const Units = [
{ label: i18n.global.t('commons.units.year'), value: 'y' },
];
export const sizeUnits = [
{ label: 'B', value: 'b' },
{ label: 'KB', value: 'k' },
{ label: 'MB', value: 'm' },
{ label: 'GB', value: 'g' },
];
export const AcmeAccountTypes = [
{ label: "Let's Encrypt", value: 'letsencrypt' },
{ label: 'ZeroSSL', value: 'zerossl' },

View File

@ -2187,6 +2187,13 @@ const message = {
strategyDown: 'Down',
strategyBackup: 'Backup',
staticChangePHPHelper: 'Currently a static website, you can switch to a PHP website',
proxyCache: 'Reverse Proxy Cache',
cacheLimit: 'Cache Space Limit',
shareCahe: 'Cache Count Memory Size',
cacheExpire: 'Cache Expiration Time',
shareCaheHelper: 'Approximately 8000 cache objects can be stored per 1M of memory',
cacheLimitHelper: 'Old cache will be automatically deleted when the limit is exceeded',
cacheExpireJHelper: 'Cache will be deleted if it misses after the expiration time',
},
php: {
short_open_tag: 'Short tag support',

View File

@ -2035,6 +2035,13 @@ const message = {
strategyDown: '停用',
strategyBackup: '備用',
staticChangePHPHelper: '目前為靜態網站可切換為 PHP 網站',
proxyCache: '反向代理快取',
cacheLimit: '快取空間限制',
shareCahe: '快取計數內存大小',
cacheExpire: '快取過期時間',
shareCaheHelper: '每1M內存可以存儲約8000個快取對象',
cacheLimitHelper: '超過限制會自動刪除舊的快取',
cacheExpireJHelper: '超出時間快取未命中將會被刪除',
},
php: {
short_open_tag: '短標簽支持',

View File

@ -2036,6 +2036,13 @@ const message = {
strategyDown: '停用',
strategyBackup: '备用',
staticChangePHPHelper: '当前为静态网站可以切换为 PHP 网站',
proxyCache: '反代缓存',
cacheLimit: '缓存空间限制',
shareCahe: '缓存计数内存大小',
cacheExpire: '缓存过期时间',
shareCaheHelper: '每1M内存可以存储约8000个缓存对象',
cacheLimitHelper: '超过限制会自动删除旧的缓存',
cacheExpireJHelper: '超出时间缓存未命中将会被删除',
},
php: {
short_open_tag: '短标签支持',

View File

@ -0,0 +1,141 @@
<template>
<DrawerPro v-model="open" :header="$t('website.proxyCache')" size="normal" :back="handleClose">
<el-form
v-loading="loading"
@submit.prevent
ref="proxyForm"
label-position="top"
:model="req"
:rules="rules"
:validate-on-rule-change="false"
>
<el-form-item :label="$t('commons.button.start')" prop="open">
<el-switch v-model="req.open"></el-switch>
</el-form-item>
<el-form-item :label="$t('website.cacheLimit')" prop="cacheLimit">
<el-input v-model.number="req.cacheLimit" class="p-w-200">
<template #append>
<el-select v-model="req.cacheLimitUnit" class="p-w-100">
<el-option
v-for="(unit, index) in sizeUnits"
:key="index"
:label="unit.label"
:value="unit.value"
></el-option>
</el-select>
</template>
</el-input>
<span class="input-help">{{ $t('website.cacheLimitHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('website.shareCahe')" prop="shareCache">
<el-input v-model.number="req.shareCache" class="p-w-200">
<template #append>
<el-select v-model="req.shareCacheUnit" class="p-w-100">
<el-option
v-for="(unit, index) in sizeUnits"
:key="index"
:label="unit.label"
:value="unit.value"
></el-option>
</el-select>
</template>
</el-input>
<span class="input-help">{{ $t('website.shareCaheHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('website.cacheExpire')" prop="cacheExpire">
<el-input v-model.number="req.cacheExpire" class="p-w-200">
<template #append>
<el-select v-model="req.cacheExpireUnit" class="p-w-100">
<el-option
v-for="(unit, index) in Units"
:key="index"
:label="unit.label"
:value="unit.value"
></el-option>
</el-select>
</template>
</el-input>
<span class="input-help">{{ $t('website.cacheExpireJHelper') }}</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(proxyForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Units, sizeUnits } from '@/global/mimetype';
import { Rules, checkNumberRange } from '@/global/form-rules';
import { FormInstance } from 'element-plus';
import { GetCacheConfig, UpdateCacheConfig } from '@/api/modules/website';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
const open = ref(false);
const loading = ref(false);
const proxyForm = ref<FormInstance>();
const req = reactive({
open: false,
cacheLimit: 1,
cacheLimitUnit: 'g',
shareCache: 5,
shareCacheUnit: 'm',
cacheExpire: 24,
cacheExpireUnit: 'h',
websiteID: 0,
});
const rules = {
cacheLimit: [Rules.requiredInput, checkNumberRange(0, 9999)],
shareCache: [Rules.requiredInput, checkNumberRange(0, 9999)],
cacheExpire: [Rules.requiredInput, checkNumberRange(0, 9999)],
};
const handleClose = () => {
open.value = false;
};
const acceptParams = (websiteID: number) => {
req.websiteID = websiteID;
get();
open.value = true;
};
const get = async () => {
try {
const res = await GetCacheConfig(req.websiteID);
req.open = res.data.open;
if (req.open) {
req.cacheLimit = res.data.cacheLimit;
req.cacheLimitUnit = res.data.cacheLimitUnit;
req.shareCache = res.data.shareCache;
req.shareCacheUnit = res.data.shareCacheUnit;
req.cacheExpire = res.data.cacheExpire;
req.cacheExpireUnit = res.data.cacheExpireUnit;
}
} catch (error) {}
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate(async (valid) => {
if (!valid) {
return;
}
try {
await UpdateCacheConfig(req);
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
} catch (error) {}
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -2,6 +2,7 @@
<ComplexTable :data="data" @search="search" v-loading="loading">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">{{ $t('commons.button.create') }}</el-button>
<el-button @click="openCache">{{ $t('website.proxyCache') }}</el-button>
</template>
<el-table-column :label="$t('commons.table.name')" prop="name"></el-table-column>
<el-table-column :label="$t('website.proxyPath')" prop="match"></el-table-column>
@ -34,19 +35,21 @@
<Create ref="createRef" @close="search()" />
<File ref="fileRef" @close="search()" />
<OpDialog ref="opRef" @search="search()" />
<Cache ref="cacheRef" @close="search()" />
</template>
<script lang="ts" setup name="proxy">
import { Website } from '@/api/interface/website';
import { OperateProxyConfig, GetProxyConfig } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import Create from './create/index.vue';
import File from './file/index.vue';
import { VideoPlay, VideoPause } from '@element-plus/icons-vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { ElMessageBox } from 'element-plus';
import { GlobalStore } from '@/store';
import Create from './create/index.vue';
import File from './file/index.vue';
import Cache from './cache/index.vue';
const globalStore = GlobalStore();
const props = defineProps({
@ -67,6 +70,7 @@ const data = ref();
const createRef = ref();
const fileRef = ref();
const opRef = ref();
const cacheRef = ref();
const buttons = [
{
@ -114,6 +118,10 @@ const openCreate = () => {
createRef.value.acceptParams(initData(id.value));
};
const openCache = () => {
cacheRef.value.acceptParams(id.value);
};
const openEdit = (proxyConfig: Website.ProxyConfig) => {
let proxy = JSON.parse(JSON.stringify(proxyConfig));
proxy.operate = 'edit';