From d5aee147f49537c62b06cf406c9d92d4894e8b7a Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Tue, 8 Nov 2022 15:42:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B5=81=E9=87=8F?= =?UTF-8?q?=E9=99=90=E5=88=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/nginx.go | 34 ++++- backend/app/service/website.go | 16 +-- backend/app/service/website_utils.go | 124 ++++++++++++++-- backend/utils/cmd/cmd.go | 50 ++----- backend/utils/nginx/components/block.go | 23 ++- backend/utils/nginx/components/server.go | 36 ++++- cmd/server/nginx_conf/limit.conf | 5 +- cmd/server/nginx_conf/nginx_conf.go | 3 + frontend/src/api/interface/website.ts | 8 ++ frontend/src/api/modules/website.ts | 2 +- frontend/src/lang/modules/zh.ts | 7 + .../config/basic/default-doc/index.vue | 33 +++-- .../website/project/config/basic/index.vue | 6 +- .../project/config/basic/limit-conn/index.vue | 135 ++++++++++++++++++ 14 files changed, 383 insertions(+), 99 deletions(-) create mode 100644 frontend/src/views/website/project/config/basic/limit-conn/index.vue diff --git a/backend/app/dto/nginx.go b/backend/app/dto/nginx.go index 85d9d6fed..38544a598 100644 --- a/backend/app/dto/nginx.go +++ b/backend/app/dto/nginx.go @@ -10,17 +10,41 @@ type NginxConfig struct { } type NginxConfigReq struct { - Scope NginxScope `json:"scope"` - WebSiteID uint `json:"webSiteId" validate:"required"` - Params map[string]string `json:"params"` + Scope NginxScope `json:"scope"` + Operate NginxOp `json:"operate"` + WebSiteID uint `json:"webSiteId" validate:"required"` + Params interface{} `json:"params"` } type NginxScope string const ( - Index NginxScope = "index" + Index NginxScope = "index" + LimitConn NginxScope = "limit-conn" +) + +type NginxOp string + +const ( + ConfigNew NginxOp = "add" + ConfigUpdate NginxOp = "update" + ConfigDel NginxOp = "delete" ) var ScopeKeyMap = map[NginxScope][]string{ - Index: {"index"}, + Index: {"index"}, + LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"}, +} + +var RepeatKeys = map[string]struct { +}{ + "limit_conn": {}, + "limit_conn_zone": {}, +} + +type NginxParam struct { + Name string `json:"name"` + SecondKey string `json:"secondKey"` + IsRepeatKey bool `json:"isRepeatKey"` + Params []string `json:"params"` } diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 60259078d..ac1e34d3d 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -186,7 +186,7 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error { return websiteDomainRepo.DeleteBy(context.TODO(), commonRepo.WithByID(domainId)) } -func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) (map[string]interface{}, error) { +func (w WebsiteService) GetNginxConfigByScope(req dto.NginxConfigReq) ([]dto.NginxParam, error) { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { @@ -207,19 +207,13 @@ func (w WebsiteService) UpdateNginxConfigByScope(req dto.NginxConfigReq) error { if !ok || len(keys) == 0 { return nil } - keyValues := make(map[string][]string, len(keys)) - for k, v := range req.Params { - for _, name := range keys { - if name == k { - keyValues[k] = getNginxParams(k, v) - } - } - } - website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebSiteID)) if err != nil { return err } + if req.Operate == dto.ConfigDel { + return deleteNginxConfig(website, keys, req.Scope) + } - return updateNginxConfig(website, keyValues) + return updateNginxConfig(website, getNginxParams(req.Params, keys), req.Scope) } diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 8c1d310b7..b4ca2504b 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -91,8 +91,8 @@ func opNginx(containerName, operate string) error { if operate == "check" { nginxCmd = fmt.Sprintf("docker exec -i %s %s", containerName, "nginx -t") } - if _, err := cmd.Exec(nginxCmd); err != nil { - return err + if out, err := cmd.Exec(nginxCmd); err != nil { + return errors.New(out) } return nil } @@ -206,36 +206,50 @@ func deleteListenAndServerName(website model.WebSite, ports []int, domains []str return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxConfig.ContainerName) } -func getNginxConfigByKeys(website model.WebSite, keys []string) (map[string]interface{}, error) { +func getNginxConfigByKeys(website model.WebSite, keys []string) ([]dto.NginxParam, error) { nginxConfig, err := getNginxConfig(website.PrimaryDomain) if err != nil { return nil, err } config := nginxConfig.Config server := config.FindServers()[0] - res := make(map[string]interface{}) + + var res []dto.NginxParam for _, key := range keys { dirs := server.FindDirectives(key) for _, dir := range dirs { - res[dir.GetName()] = dir.GetParameters() + nginxParam := dto.NginxParam{ + Name: dir.GetName(), + Params: dir.GetParameters(), + } + if isRepeatKey(key) { + nginxParam.IsRepeatKey = true + nginxParam.SecondKey = dir.GetParameters()[0] + } + res = append(res, nginxParam) } } return res, nil } -func updateNginxConfig(website model.WebSite, keyValues map[string][]string) error { +func updateNginxConfig(website model.WebSite, params []dto.NginxParam, scope dto.NginxScope) error { nginxConfig, err := getNginxConfig(website.PrimaryDomain) if err != nil { return err } config := nginxConfig.Config + updateConfig(config, scope) server := config.FindServers()[0] - for k, v := range keyValues { + for _, p := range params { newDir := components.Directive{ - Name: k, - Parameters: v, + Name: p.Name, + Parameters: p.Params, + } + if p.IsRepeatKey { + server.UpdateDirectiveBySecondKey(p.Name, p.SecondKey, newDir) + } else { + server.UpdateDirectives(p.Name, newDir) } - server.UpdateDirectives(k, newDir) } if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return err @@ -243,13 +257,101 @@ func updateNginxConfig(website model.WebSite, keyValues map[string][]string) err return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxConfig.ContainerName) } -func getNginxParams(key string, param interface{}) []string { +func updateConfig(config *components.Config, scope dto.NginxScope) { + if scope == dto.LimitConn { + limit := parser.NewStringParser(string(nginx_conf.Limit)).Parse() + for _, dir := range limit.GetDirectives() { + newDir := components.Directive{ + Name: dir.GetName(), + Parameters: dir.GetParameters(), + } + config.UpdateDirectiveBySecondKey(dir.GetName(), dir.GetParameters()[0], newDir) + } + } +} + +func deleteNginxConfig(website model.WebSite, keys []string, scope dto.NginxScope) error { + nginxConfig, err := getNginxConfig(website.PrimaryDomain) + if err != nil { + return err + } + config := nginxConfig.Config + config.RemoveDirectives(keys) + server := config.FindServers()[0] + server.RemoveDirectives(keys) + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxConfig.ContainerName) +} + +func getParamArray(key string, param interface{}) []string { var res []string switch param.(type) { case string: if key == "index" { res = strings.Split(param.(string), "\n") + return res } + + res = strings.Split(param.(string), " ") + return res } return res } + +func handleParamMap(paramMap map[string]string, keys []string) []dto.NginxParam { + var nginxParams []dto.NginxParam + for k, v := range paramMap { + for _, name := range keys { + if name == k { + param := dto.NginxParam{ + Name: k, + Params: getParamArray(k, v), + } + if isRepeatKey(k) { + param.IsRepeatKey = true + param.SecondKey = param.Params[0] + } + nginxParams = append(nginxParams, param) + } + } + } + return nginxParams +} + +func getNginxParams(params interface{}, keys []string) []dto.NginxParam { + var nginxParams []dto.NginxParam + + switch params.(type) { + case map[string]string: + return handleParamMap(params.(map[string]string), keys) + case []interface{}: + + if mArray, ok := params.([]interface{}); ok { + for _, mA := range mArray { + if m, ok := mA.(map[string]interface{}); ok { + nginxParams = append(nginxParams, handleParamMap(toMapStr(m), keys)...) + } + } + } + + } + return nginxParams +} + +func isRepeatKey(key string) bool { + + if _, ok := dto.RepeatKeys[key]; ok { + return true + } + return false +} + +func toMapStr(m map[string]interface{}) map[string]string { + ret := make(map[string]string, len(m)) + for k, v := range m { + ret[k] = fmt.Sprint(v) + } + return ret +} diff --git a/backend/utils/cmd/cmd.go b/backend/utils/cmd/cmd.go index 8aca285be..f470387dd 100644 --- a/backend/utils/cmd/cmd.go +++ b/backend/utils/cmd/cmd.go @@ -1,50 +1,18 @@ package cmd import ( - "bufio" - "context" - "io" + "bytes" "os/exec" - "sync" ) -func Exec(cmdStr string) (out string, err error) { - command := exec.CommandContext(context.Background(), "bash", "-c", cmdStr) - - var wg sync.WaitGroup - wg.Add(1) - - stdout, err := command.StdoutPipe() - if err != nil { - return - } - readout := bufio.NewReader(stdout) - go func() { - defer wg.Done() - out = getOutput(readout) - }() - - err = command.Run() +func Exec(cmdStr string) (string, error) { + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() if err != nil { - return - } - wg.Wait() - return -} - -func getOutput(reader *bufio.Reader) string { - var sumOutput string - outputBytes := make([]byte, 200) - for { - n, err := reader.Read(outputBytes) - if err != nil { - if err == io.EOF { - break - } - sumOutput += err.Error() - } - output := string(outputBytes[:n]) - sumOutput += output + return string(stderr.Bytes()), err } - return sumOutput + return string(stdout.Bytes()), nil } diff --git a/backend/utils/nginx/components/block.go b/backend/utils/nginx/components/block.go index d308b94ac..d1e7fb607 100644 --- a/backend/utils/nginx/components/block.go +++ b/backend/utils/nginx/components/block.go @@ -28,9 +28,9 @@ func (b *Block) FindDirectives(directiveName string) []IDirective { } func (b *Block) UpdateDirectives(directiveName string, directive Directive) { - directives := make([]IDirective, len(b.GetDirectives())) + directives := b.GetDirectives() index := -1 - for i, dir := range b.GetDirectives() { + for i, dir := range directives { if dir.GetName() == directiveName { index = i break @@ -44,6 +44,25 @@ func (b *Block) UpdateDirectives(directiveName string, directive Directive) { b.Directives = directives } +func (b *Block) UpdateDirectiveBySecondKey(name string, key string, directive Directive) { + + directives := b.GetDirectives() + + index := -1 + for i, dir := range directives { + if dir.GetName() == name && dir.GetParameters()[0] == key { + index = i + break + } + } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } + b.Directives = directives +} + func (b *Block) AddDirectives(directive Directive) { directives := append(b.GetDirectives(), &directive) b.Directives = directives diff --git a/backend/utils/nginx/components/server.go b/backend/utils/nginx/components/server.go index 98af79ccf..b4c23fb84 100644 --- a/backend/utils/nginx/components/server.go +++ b/backend/utils/nginx/components/server.go @@ -144,11 +144,28 @@ func (s *Server) UpdateRootProxy(proxy []string) { Parameters: proxy, }) newDir.Block = block - s.UpdateDirectives("location", newDir) + s.UpdateDirectiveBySecondKey("location", "/", newDir) } } } +func (s *Server) UpdateDirectiveBySecondKey(name string, key string, directive Directive) { + directives := s.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == name && dir.GetParameters()[0] == key { + index = i + break + } + } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } + s.Directives = directives +} + func (s *Server) RemoveListenByBind(bind string) { index := 0 listens := s.Listens @@ -176,14 +193,19 @@ func (s *Server) FindDirectives(directiveName string) []IDirective { } func (s *Server) UpdateDirectives(directiveName string, directive Directive) { - directives := make([]IDirective, 0) - for _, dir := range s.Directives { + directives := s.Directives + index := -1 + for i, dir := range directives { if dir.GetName() == directiveName { - directives = append(directives, &directive) - } else { - directives = append(directives, dir) + index = i + break } } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } s.Directives = directives } @@ -197,7 +219,7 @@ func (s *Server) RemoveDirectives(names []string) { for _, name := range names { nameMaps[name] = struct{}{} } - directives := s.GetDirectives() + directives := s.Directives var newDirectives []IDirective for _, dir := range directives { if _, ok := nameMaps[dir.GetName()]; ok { diff --git a/cmd/server/nginx_conf/limit.conf b/cmd/server/nginx_conf/limit.conf index 2ebd43727..2a755a006 100644 --- a/cmd/server/nginx_conf/limit.conf +++ b/cmd/server/nginx_conf/limit.conf @@ -1,3 +1,2 @@ -limit_conn perserver 300; -limit_conn perip 25; -limit_rate 512k; \ No newline at end of file +limit_conn_zone $binary_remote_addr zone=perip:10m; +limit_conn_zone $server_name zone=perserver:10m; \ No newline at end of file diff --git a/cmd/server/nginx_conf/nginx_conf.go b/cmd/server/nginx_conf/nginx_conf.go index 61128b0f9..085e873ec 100644 --- a/cmd/server/nginx_conf/nginx_conf.go +++ b/cmd/server/nginx_conf/nginx_conf.go @@ -12,3 +12,6 @@ var HTTPS []byte //go:embed website_default.conf var WebsiteDefault []byte + +//go:embed limit.conf +var Limit []byte diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 856b43eec..9bae9aac9 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -65,8 +65,16 @@ export namespace WebSite { } export interface NginxConfigReq { + operate: string; websiteId: number; scope: string; params?: any; } + + export interface NginxParam { + name: string; + secondKey: string; + isRepeatKey: string; + params: string[]; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 7c5f06d72..5cdfe9d06 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -43,7 +43,7 @@ export const CreateDomain = (req: WebSite.DomainCreate) => { }; export const GetNginxConfig = (req: WebSite.NginxConfigReq) => { - return http.post(`/websites/config`, req); + return http.post(`/websites/config`, req); }; export const UpdateNginxConfig = (req: WebSite.NginxConfigReq) => { diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index c15567dfb..a03f45fad 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -687,5 +687,12 @@ export default { addDomain: '新增域名', domainConfig: '域名设置', defaultDoc: '默认文档', + perserver: '并发限制', + perserverHelper: '限制当前站点最大并发数', + perip: '单IP限制', + peripHelper: '限制单个IP访问最大并发数', + rate: '流量限制', + rateHelper: '限制每个请求的流量上限(单位:KB)', + limtHelper: '启用流量控制', }, }; diff --git a/frontend/src/views/website/project/config/basic/default-doc/index.vue b/frontend/src/views/website/project/config/basic/default-doc/index.vue index c4a4601ba..cf988ef4c 100644 --- a/frontend/src/views/website/project/config/basic/default-doc/index.vue +++ b/frontend/src/views/website/project/config/basic/default-doc/index.vue @@ -1,23 +1,20 @@