From eebef06c77d64df9a79b039dced12e8155d40a54 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Thu, 2 Nov 2023 18:36:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=9F=E5=90=8D=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=B7=BB=E5=8A=A0=20(#2774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/2633 --- backend/app/dto/request/website.go | 5 +- backend/app/service/website.go | 118 ++++-------------- backend/app/service/website_utils.go | 70 +++++++++++ backend/i18n/lang/en.yaml | 1 + backend/i18n/lang/zh-Hant.yaml | 1 + backend/i18n/lang/zh.yaml | 1 + backend/utils/common/common.go | 6 + frontend/src/api/interface/website.ts | 5 +- .../config/basic/domain/create/index.vue | 30 ++--- 9 files changed, 125 insertions(+), 112 deletions(-) diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 99c819a96..4314e8220 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -111,9 +111,8 @@ type WebsiteGroupUpdate struct { } type WebsiteDomainCreate struct { - WebsiteID uint `json:"websiteId" validate:"required"` - Port int `json:"port" validate:"required"` - Domain string `json:"domain" validate:"required"` + WebsiteID uint `json:"websiteID" validate:"required"` + Domains string `json:"domains" validate:"required"` } type WebsiteDomainDelete struct { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 4d52e0f6e..0c5df1905 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -19,7 +19,6 @@ import ( "time" "github.com/1Panel-dev/1Panel/backend/i18n" - "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/spf13/afero" "github.com/1Panel-dev/1Panel/backend/utils/compose" @@ -59,7 +58,7 @@ type IWebsiteService interface { UpdateWebsite(req request.WebsiteUpdate) error DeleteWebsite(req request.WebsiteDelete) error GetWebsite(id uint) (response.WebsiteDTO, error) - CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error) + CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) DeleteWebsiteDomain(domainId uint) error @@ -188,56 +187,18 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) defaultHttpPort := nginxInstall.HttpPort var ( - domains []model.WebsiteDomain - ports = make(map[int]struct{}) + otherDomains []model.WebsiteDomain + domains []model.WebsiteDomain ) - domainArray := strings.Split(create.OtherDomains, "\n") - domainArray = append(domainArray, create.PrimaryDomain) - for _, domain := range domainArray { - if domain == "" { - continue - } - domainModel, err := getDomain(domain, defaultHttpPort) - if err != nil { - return err - } - if reflect.DeepEqual(domainModel, model.WebsiteDomain{}) { - continue - } - domains = append(domains, domainModel) - if domainModel.Port != defaultHttpPort { - ports[domainModel.Port] = struct{}{} - } + domains, _, _, err = getWebsiteDomains(create.PrimaryDomain, defaultHttpPort, 0) + if err != nil { + return err } - - for _, domain := range domains { - if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domain.Domain), websiteDomainRepo.WithPort(domain.Port)); exist.ID > 0 { - website, _ := websiteRepo.GetFirst(commonRepo.WithByID(exist.WebsiteID)) - return buserr.WithName(constant.ErrDomainIsUsed, website.PrimaryDomain) - } - } - - for port := range ports { - if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 { - errMap := make(map[string]interface{}) - errMap["port"] = port - appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) - if appInstall.ID > 0 { - errMap["type"] = i18n.GetMsgByKey("TYPE_APP") - errMap["name"] = appInstall.Name - return buserr.WithMap("ErrPortExist", errMap, nil) - } - runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithPort(port)) - if runtime != nil { - errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") - errMap["name"] = runtime.Name - return buserr.WithMap("ErrPortExist", errMap, nil) - } - if common.ScanPort(port) { - return buserr.WithDetail(constant.ErrPortInUsed, port, nil) - } - } + otherDomains, _, _, err = getWebsiteDomains(create.OtherDomains, defaultHttpPort, 0) + if err != nil { + return err } + domains = append(domains, otherDomains...) defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate) website := &model.Website{ @@ -487,59 +448,34 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error { return nil } -func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error) { +func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) { var ( - domainModel model.WebsiteDomain - ports []int - domains []string + domainModels []model.WebsiteDomain + addPorts []int + addDomains []string ) httpPort, _, err := getAppInstallPort(constant.AppOpenresty) if err != nil { - return domainModel, err + return nil, err } - - if create.Port != httpPort { - if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(create.Port)); len(existPorts) == 0 { - if existAppInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(create.Port)); existAppInstall.ID > 0 { - return domainModel, buserr.WithMap(constant.ErrPortInOtherApp, map[string]interface{}{"port": create.Port, "apps": existAppInstall.App.Name}, nil) - } - if common.ScanPort(create.Port) { - return domainModel, buserr.WithDetail(constant.ErrPortInUsed, create.Port, nil) - } - } - } - - if existDomain, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(create.Domain), websiteDomainRepo.WithPort(create.Port)); existDomain.ID > 0 { - website, _ := websiteRepo.GetFirst(commonRepo.WithByID(existDomain.WebsiteID)) - return domainModel, buserr.WithName(constant.ErrDomainIsUsed, website.PrimaryDomain) - } - website, err := websiteRepo.GetFirst(commonRepo.WithByID(create.WebsiteID)) if err != nil { - return domainModel, err - } - if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(create.WebsiteID), websiteDomainRepo.WithPort(create.Port)); len(oldDomains) == 0 { - ports = append(ports, create.Port) + return nil, err } - if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(create.WebsiteID), websiteDomainRepo.WithDomain(create.Domain)); len(oldDomains) == 0 { - domains = append(domains, create.Domain) + domainModels, addPorts, addDomains, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID) + if err != nil { + return nil, err + } + go func() { + _ = OperateFirewallPort(nil, addPorts) + }() + + if err := addListenAndServerName(website, addPorts, addDomains); err != nil { + return nil, err } - if err := addListenAndServerName(website, ports, domains); err != nil { - return domainModel, err - } - domainModel = model.WebsiteDomain{ - Domain: create.Domain, - Port: create.Port, - WebsiteID: create.WebsiteID, - } - if create.Port != httpPort { - go func() { - _ = OperateFirewallPort(nil, []int{create.Port}) - }() - } - return domainModel, websiteDomainRepo.Create(context.TODO(), &domainModel) + return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels) } func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) { diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 35a4ecaa2..a7b853832 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -3,10 +3,13 @@ package service import ( "fmt" "github.com/1Panel-dev/1Panel/backend/buserr" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/cmd" + "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/nginx/components" "gopkg.in/yaml.v3" "path" + "reflect" "strconv" "strings" "time" @@ -673,3 +676,70 @@ func changeServiceName(composePath, newServiceName string) (composeByte []byte, return yaml.Marshal(composeMap) } + +func getWebsiteDomains(domains string, defaultPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) { + var ( + ports map[int]struct{} + ) + domainArray := strings.Split(domains, "\n") + for _, domain := range domainArray { + if domain == "" { + continue + } + if !common.IsValidDomain(domain) { + err = buserr.WithName("ErrDomainFormat", domain) + return + } + var domainModel model.WebsiteDomain + domainModel, err = getDomain(domain, defaultPort) + if err != nil { + return + } + if reflect.DeepEqual(domainModel, model.WebsiteDomain{}) { + continue + } + domainModel.WebsiteID = websiteID + domainModels = append(domainModels, domainModel) + if domainModel.Port != defaultPort { + ports[domainModel.Port] = struct{}{} + } + if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domainModel.Domain), websiteDomainRepo.WithWebsiteId(websiteID)); exist.ID == 0 { + addDomains = append(addDomains, domainModel.Domain) + } + } + for _, domain := range domainModels { + if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domain.Domain), websiteDomainRepo.WithPort(domain.Port)); exist.ID > 0 { + website, _ := websiteRepo.GetFirst(commonRepo.WithByID(exist.WebsiteID)) + err = buserr.WithName(constant.ErrDomainIsUsed, website.PrimaryDomain) + return + } + } + + for port := range ports { + addPorts = append(addPorts, port) + if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 { + errMap := make(map[string]interface{}) + errMap["port"] = port + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) + if appInstall.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_APP") + errMap["name"] = appInstall.Name + err = buserr.WithMap("ErrPortExist", errMap, nil) + return + } + runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithPort(port)) + if runtime != nil { + errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") + errMap["name"] = runtime.Name + err = buserr.WithMap("ErrPortExist", errMap, nil) + return + } + if common.ScanPort(port) { + err = buserr.WithDetail(constant.ErrPortInUsed, port, nil) + return + } + } + } + + return +} diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 985ce19fc..b21e340db 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -76,6 +76,7 @@ ErrBackupExist: 'the backup file corresponds to a portion of the original data t ErrPHPResource: 'The local runtime does not support switching!' ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause Access denied errors when accessing the website.' ErrDomainIsUsed: "Domain is already used by website {{ .name }}" +ErrDomainFormat: "{{ .name }} domain format error" #ssl ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 38f1680aa..d175a4d73 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -76,6 +76,7 @@ ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}"' ErrPHPResource: '本地運行環境不支持切換!' ErrPathPermission: 'index 目錄下檢測到非 1000:1000 權限文件夾,可能導致網站訪問 Access denied 錯誤' ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用" +ErrDomainFormat: "{{ .name }} 域名格式不正確" #ssl ErrSSLCannotDelete: "證書正在被網站使用,無法刪除" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index eab7adcc4..82a62e6d5 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -76,6 +76,7 @@ ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}"' ErrPHPResource: '本地运行环境不支持切换!' ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误' ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用" +ErrDomainFormat: "{{ .name }} 域名格式不正确" #ssl ErrSSLCannotDelete: "证书正在被网站使用,无法删除" diff --git a/backend/utils/common/common.go b/backend/utils/common/common.go index 487c2d8e1..d13b69cfc 100644 --- a/backend/utils/common/common.go +++ b/backend/utils/common/common.go @@ -220,3 +220,9 @@ func ConvertToPinyin(text string) string { return strings.Join(strArr, "") } + +func IsValidDomain(domain string) bool { + pattern := `^([\w\\u4e00-\\u9fa5\\-\\*]{1,100}\.){1,10}([\w\\u4e00-\\u9fa5\\-]{1,24}|[\w\\u4e00-\\u9fa5\\-]{1,24}\.[\w\\u4e00-\\u9fa5\\-]{1,24})$` + match, _ := regexp.MatchString(pattern, domain) + return match +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 11009f546..f6d14f201 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -101,9 +101,8 @@ export namespace Website { } export interface DomainCreate { - websiteId: number; - port: number; - domain: string; + websiteID: number; + domains: string; } export interface DomainDelete { diff --git a/frontend/src/views/website/website/config/basic/domain/create/index.vue b/frontend/src/views/website/website/config/basic/domain/create/index.vue index 40e36b8ce..7e849b3dd 100644 --- a/frontend/src/views/website/website/config/basic/domain/create/index.vue +++ b/frontend/src/views/website/website/config/basic/domain/create/index.vue @@ -13,11 +13,13 @@ - - - - - + + @@ -44,17 +46,15 @@ import { MsgSuccess } from '@/utils/message'; const domainForm = ref(); -let rules = ref({ - domain: [Rules.requiredInput, Rules.domain], - port: [Rules.requiredInput], +const rules = ref({ + domains: [Rules.requiredInput], }); -let open = ref(false); -let loading = ref(false); -let domain = ref({ - websiteId: 0, - domain: '', - port: 80, +const open = ref(false); +const loading = ref(false); +const domain = ref({ + websiteID: 0, + domains: '', }); const em = defineEmits(['close']); @@ -65,7 +65,7 @@ const handleClose = () => { }; const acceptParams = async (websiteId: number) => { - domain.value.websiteId = Number(websiteId); + domain.value.websiteID = Number(websiteId); open.value = true; };