Selaa lähdekoodia

更新域名处理逻辑和日志格式

修改了域名处理逻辑,增加了对不同类型的域名(CDN 和 DCDN)的支持,并优化了日志输出格式。同时更新了数据库表结构以适应新的域名类型。
SongZihuan 2 kuukautta sitten
vanhempi
sitoutus
d0e5ed08a9
11 muutettua tiedostoa jossa 395 lisäystä ja 94 poistoa
  1. 3 1
      .gitignore
  2. 9 2
      go.mod
  3. 22 0
      go.sum
  4. 74 8
      src/aliyun/action.go
  5. 33 0
      src/aliyun/client.go
  6. 70 38
      src/aliyun/main.go
  7. 54 17
      src/config/domainconfg.go
  8. 106 17
      src/database/db.go
  9. 1 1
      src/database/init.go
  10. 17 3
      src/database/module.go
  11. 6 7
      src/server/server.go

+ 3 - 1
.gitignore

@@ -15,4 +15,6 @@ pkg
 http-demo
 .htaccess
 
-*.bak
+*.bak
+
+go-remote.sh

+ 9 - 2
go.mod

@@ -6,10 +6,13 @@ require (
 	github.com/alibabacloud-go/cas-20200407/v3 v3.0.4
 	github.com/alibabacloud-go/cdn-20180510/v5 v5.2.2
 	github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
+	github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0
 	github.com/alibabacloud-go/tea v1.2.2
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.7
 	github.com/mattn/go-isatty v0.0.20
 	gopkg.in/yaml.v3 v3.0.1
+	gorm.io/driver/sqlite v1.5.7
+	gorm.io/gorm v1.25.12
 )
 
 require (
@@ -17,6 +20,11 @@ require (
 	github.com/alibabacloud-go/debug v1.0.1 // indirect
 	github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
 	github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
+	github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
+	github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
+	github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
+	github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
+	github.com/alibabacloud-go/tea-utils v1.3.6 // indirect
 	github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
 	github.com/aliyun/credentials-go v1.3.10 // indirect
 	github.com/clbanning/mxj/v2 v2.5.5 // indirect
@@ -30,7 +38,6 @@ require (
 	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
+	golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
-	gorm.io/driver/sqlite v1.5.7 // indirect
-	gorm.io/gorm v1.25.12 // indirect
 )

+ 22 - 0
go.sum

@@ -15,34 +15,54 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC
 github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
 github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
 github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
+github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
 github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
 github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
 github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
 github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
+github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0 h1:EQmKhYju6y38kJ1ZvZROeJG2Q1Wk6hlc8KQrVhvGyaw=
+github.com/alibabacloud-go/dcdn-20180115/v3 v3.5.0/go.mod h1:b9qzvr/2V1f0r1Z6xUmkLqEouKcPGy4LCC22yV+6HQo=
 github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
 github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
 github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
 github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
 github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
 github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
+github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
 github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
 github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28=
 github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw=
+github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
+github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
 github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
 github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
 github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
 github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
+github.com/alibabacloud-go/tea v1.1.12/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
 github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
+github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
 github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
 github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
 github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
+github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
+github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
+github.com/alibabacloud-go/tea-oss-sdk v1.1.3 h1:EhAHI6edMeqgkZEqP7r4nc9iMWAUBKGxJHoBsOSKTtU=
+github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
+github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc=
+github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY=
 github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils v1.3.6 h1:bVjrxHztM8hAs6nOfLWCgxQfAtKb9RgFFMV6J3rdvB4=
+github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
+github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
 github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
+github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
+github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
 github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
 github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
@@ -201,6 +221,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

+ 74 - 8
src/aliyun/operation.go → src/aliyun/action.go

@@ -9,6 +9,7 @@ import (
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/utils"
 	cas "github.com/alibabacloud-go/cas-20200407/v3/client"
 	cdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
+	dcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
 	util "github.com/alibabacloud-go/tea-utils/v2/service"
 	"github.com/alibabacloud-go/tea/tea"
 	"strings"
@@ -52,7 +53,33 @@ func uploadCert(certData []byte, keyData []byte) (certID int64, name string, sub
 	return tea.Int64Value(resp.Body.CertId), fingerprint, subject, nil
 }
 
-func setDomainServerCertificate(domainName string, certID int64, certName string) (err error) {
+func setCDNServerCertificateNotPanic(domainName string, certID int64, certName string) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			if _err, ok := r.(error); ok {
+				logger.Panicf("更新CDN 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,类型为error,错误消息是:%s", domainName, _err.Error())
+				if err != nil {
+					err = _err
+				}
+			} else {
+				logger.Panicf("更新CDN 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,错误消息是:%v", domainName, r)
+				if err != nil {
+					err = fmt.Errorf("%v", r)
+				}
+			}
+		}
+	}()
+
+	err = setCDNServerCertificate(domainName, certID, certName)
+	if err != nil {
+		logger.Infof("CDN加速 域名 (%s) 证书(%s)更新失败:%s", domainName, certName, err.Error())
+		return err
+	}
+
+	return nil
+}
+
+func setCDNServerCertificate(domainName string, certID int64, certName string) (err error) {
 	request := &cdn.SetCdnDomainSSLCertificateRequest{}
 	request.DomainName = tea.String(strings.TrimPrefix(domainName, "*")) // 泛域名去除星号
 	request.CertName = tea.String(certName)
@@ -78,23 +105,62 @@ func setDomainServerCertificate(domainName string, certID int64, certName string
 		return tryErr
 	}
 
-	logger.Infof("CDN加速域名(%s)证书(%s)更新成功, 并启用SSL", domainName, certName)
+	logger.Infof("CDN加速 域名(%s)证书(%s)更新成功, 并启用SSL", domainName, certName)
 	return nil
 }
 
-func setDomainServerCertificateNotError(domainName string, certID int64, certName string) {
+func setDCDNServerCertificateNotPanic(domainName string, certID int64, certName string) (err error) {
 	defer func() {
 		if r := recover(); r != nil {
-			if err, ok := r.(error); ok {
-				logger.Panicf("更新 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,类型为error,错误消息是:%s", domainName, err.Error())
+			if _err, ok := r.(error); ok {
+				logger.Panicf("更新DCDN 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,类型为error,错误消息是:%s", domainName, _err.Error())
+				if err != nil {
+					err = _err
+				}
 			} else {
-				logger.Panicf("更新 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,错误消息是:%v", domainName, r)
+				logger.Panicf("更新DCDN 域名 (%s) 证书时发生了 不可预期的验证错误 被recover捕获,错误消息是:%v", domainName, r)
+				if err != nil {
+					err = fmt.Errorf("%v", r)
+				}
 			}
 		}
 	}()
 
-	err := setDomainServerCertificate(domainName, certID, certName)
+	err = setDCDNServerCertificate(domainName, certID, certName)
 	if err != nil {
-		logger.Infof("CDN加速 域名 (%s) 证书(%s)更新失败:%s", domainName, certName, err.Error())
+		logger.Infof("DCDN加速 域名 (%s) 证书(%s)更新失败:%s", domainName, certName, err.Error())
+		return err
+	}
+
+	return nil
+}
+
+func setDCDNServerCertificate(domainName string, certID int64, certName string) (err error) {
+	request := &dcdn.SetDcdnDomainSSLCertificateRequest{}
+	request.DomainName = tea.String(strings.TrimPrefix(domainName, "*")) // 泛域名去除星号
+	request.CertName = tea.String(certName)
+	request.CertId = tea.Int64(certID)
+	request.CertType = tea.String("cas")
+	request.SSLProtocol = tea.String("on")
+	if international {
+		request.CertRegion = tea.String("ap-southeast-1") // 面向国际用农户
+	} else {
+		request.CertRegion = tea.String("cn-hangzhou") // 默认
 	}
+
+	_, tryErr := func() (resp *dcdn.SetDcdnDomainSSLCertificateResponse, err error) {
+		defer func() {
+			if r := tea.Recover(recover()); r != nil {
+				err = r
+			}
+		}()
+
+		return dcdnClient.SetDcdnDomainSSLCertificate(request)
+	}()
+	if tryErr != nil {
+		return tryErr
+	}
+
+	logger.Infof("DCDN加速 域名(%s)证书(%s)更新成功, 并启用SSL", domainName, certName)
+	return nil
 }

+ 33 - 0
src/aliyun/client.go

@@ -5,6 +5,7 @@ import (
 	cas "github.com/alibabacloud-go/cas-20200407/v3/client"
 	cdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
 	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
+	dcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
 	"github.com/alibabacloud-go/tea/tea"
 )
 
@@ -12,6 +13,26 @@ var ErrCertExists = fmt.Errorf("cert exists")
 
 var casClient *cas.Client
 var cdnClient *cdn.Client
+var dcdnClient *dcdn.Client
+
+func createClient(key string, secret string) (err error) {
+	casClient, err = createCASClient(key, secret)
+	if err != nil {
+		return fmt.Errorf("init alibaba cloud sdk CAS client error: %s", err.Error())
+	}
+
+	cdnClient, err = createCDNClient(key, secret)
+	if err != nil {
+		return fmt.Errorf("init alibaba cloud sdk CDN client error: %s", err.Error())
+	}
+
+	dcdnClient, err = createDCDNClient(key, secret)
+	if err != nil {
+		return fmt.Errorf("init alibaba cloud sdk CDN client error: %s", err.Error())
+	}
+
+	return nil
+}
 
 func createCASClient(key string, secret string) (*cas.Client, error) {
 	result, err := cas.NewClient(&openapi.Config{
@@ -36,3 +57,15 @@ func createCDNClient(key string, secret string) (*cdn.Client, error) {
 	}
 	return result, nil
 }
+
+func createDCDNClient(key string, secret string) (*dcdn.Client, error) {
+	result, err := dcdn.NewClient(&openapi.Config{
+		AccessKeyId:     tea.String(key),
+		AccessKeySecret: tea.String(secret),
+		Endpoint:        tea.String("dcdn.aliyuncs.com"),
+	})
+	if err != nil {
+		return nil, err
+	}
+	return result, nil
+}

+ 70 - 38
src/aliyun/main.go

@@ -7,12 +7,11 @@ import (
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/database"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
 	"os"
-	"strings"
 )
 
 var international = false
 
-func Init() (err error) {
+func Init() error {
 	if !config.IsReady() {
 		panic("config is not ready")
 	}
@@ -21,20 +20,15 @@ func Init() (err error) {
 	key := config.GetConfig().Aliyun.Key
 	secret := config.GetConfig().Aliyun.Secret
 
-	casClient, err = createCASClient(key, secret)
+	err := createClient(key, secret)
 	if err != nil {
-		return fmt.Errorf("init alibaba cloud sdk CAS client error: %s", err.Error())
-	}
-
-	cdnClient, err = createCDNClient(key, secret)
-	if err != nil {
-		return fmt.Errorf("init alibaba cloud sdk CDN client error: %s", err.Error())
+		return err
 	}
 
 	return nil
 }
 
-func UpdateCDNHttpsByFilePath(domainList []string, cert string, prikey string) error {
+func UpdateDomainHttpsByFilePath(collection *config.DomainListCollection, cert string, prikey string) error {
 	certData, err := os.ReadFile(cert)
 	if err != nil {
 		return fmt.Errorf("read cert file error: %s", err.Error())
@@ -45,32 +39,56 @@ func UpdateCDNHttpsByFilePath(domainList []string, cert string, prikey string) e
 		return fmt.Errorf("read private key error: %s", err.Error())
 	}
 
-	logger.Infof("成功从 %s 读取证书,从 %s 读取密钥,这些证书将被用于域名:%s", cert, prikey, strings.Join(domainList, ", "))
-	return UpdateCDNHttps(domainList, certData, privateKeyData)
+	logger.Infof("成功从 %s 读取证书,从 %s 读取密钥,这些证书将被用于域名:%s", cert, prikey, collection.Domain2Str())
+	return UpdateDomainHttps(collection, certData, privateKeyData)
 }
 
-func UpdateCDNHttps(domainList []string, certData []byte, privateKeyData []byte) error {
+func UpdateDomainHttps(collection *config.DomainListCollection, certData []byte, privateKeyData []byte) error {
 	certID, certName, subject, err := uploadCert(certData, privateKeyData)
 	if err != nil && errors.Is(err, ErrCertExists) && certName != "" {
-		logger.Infof("证书已存在, 尝试检测 CDN域名 (%s) 证书记录并更新", strings.Join(domainList, ", "))
+		logger.Infof("证书已存在, 尝试检测 CDN域名 (%s) 证书记录并更新", collection.Domain2Str())
 
-		for _, domain := range domainList {
-			cert, need, err := database.CheckNeedUpdateDomain(certName, domain)
-			if err != nil {
-				logger.Errorf("在检测 域名 (%s) 是否应该更新时遇到了错误,但不影响后续检查: %s", domain, err.Error())
-			} else if need && cert != nil {
-				logger.Infof("确认域名 (%s) 需要更新, 证书Subject: %s, 证书名字:%s, 证书ID:%d", domain, cert.Subject, cert.Name, cert.CertID)
-				setDomainServerCertificateNotError(domain, cert.CertID, cert.Name)
-				err = database.UpdateDomain(cert.CertID, cert.Name, cert.Subject, domain)
+		for _, domain := range collection.Domain {
+			if domain.Type == config.DomainTypeCDN {
+				cert, need, err := database.CheckNeedUpdateCDNDomain(certName, domain.Domain)
+				if err != nil {
+					logger.Errorf("在检测 域名 (%s) 是否应该更新时遇到了错误,但不影响后续检查: %s", domain.Domain, err.Error())
+				} else if cert == nil {
+					logger.Infof("检测到 域名 (%s) 无需更新证书,未能找到证书相关记录", domain.Domain)
+				} else if need {
+					logger.Infof("确认域名 (%s) 需要更新, 证书Subject: %s, 证书名字:%s, 证书ID:%d", domain.Domain, cert.Subject, cert.Name, cert.CertID)
+					err = setCDNServerCertificateNotPanic(domain.Domain, cert.CertID, cert.Name)
+					if err == nil {
+						err = database.UpdateCDNDomain(cert.CertID, cert.Name, cert.Subject, domain.Domain)
+						if err != nil {
+							logger.Errorf("在更新 域名 (%s) 状态到数据库时遇到了错误,但不影响后续检查: %s", domain.Domain, err.Error())
+						}
+					}
+				} else {
+					// 无需更新
+					logger.Infof("确认域名 (%s) 无需更新证书,并找到证书,证书Subject: %s, 证书名字:%s, 证书ID:%d", domain.Domain, cert.Subject, cert.Name, cert.CertID)
+				}
+			} else if domain.Type == config.DomainTypeDCDN {
+				cert, need, err := database.CheckNeedUpdateDCDNDomain(certName, domain.Domain)
 				if err != nil {
-					logger.Errorf("在更新 域名 (%s) 状态到数据库时遇到了错误,但不影响后续检查: %s", domain, err.Error())
+					logger.Errorf("在检测 域名 (%s) 是否应该更新时遇到了错误,但不影响后续检查: %s", domain.Domain, err.Error())
+				} else if cert == nil {
+					logger.Infof("检测到 域名 (%s) 无需更新证书,未能找到证书相关记录", domain.Domain)
+				} else if need {
+					logger.Infof("确认域名 (%s) 需要更新, 证书Subject: %s, 证书名字:%s, 证书ID:%d", domain.Domain, cert.Subject, cert.Name, cert.CertID)
+					err = setDCDNServerCertificateNotPanic(domain.Domain, cert.CertID, cert.Name)
+					if err == nil {
+						err := database.UpdateDCDNDomain(cert.CertID, cert.Name, cert.Subject, domain.Domain)
+						if err != nil {
+							logger.Errorf("在更新 域名 (%s) 状态到数据库时遇到了错误,但不影响后续检查: %s", domain.Domain, err.Error())
+						}
+					}
+				} else {
+					// 无需更新
+					logger.Infof("确认域名 (%s) 无需更新证书,并找到证书,证书Subject: %s, 证书名字:%s, 证书ID:%d", domain.Domain, cert.Subject, cert.Name, cert.CertID)
 				}
-			} else if !need && cert != nil {
-				// 无需更新
-				logger.Infof("确认域名 (%s) 无需更新证书,并找到证书,证书Subject: %s, 证书名字:%s, 证书ID:%d", domain, cert.Subject, cert.Name, cert.CertID)
-			} else if !need { // cert == nil
-				// 无需更新
-				logger.Infof("检测到 域名 (%s) 无需更新证书,未能找到证书相关记录", domain)
+			} else {
+				logger.Errorf("域名(%s)未知类型(%s)", domain.Domain, domain.Type)
 			}
 		}
 
@@ -80,18 +98,32 @@ func UpdateCDNHttps(domainList []string, certData []byte, privateKeyData []byte)
 	} else {
 		dbcerterr := database.UpdateCert(certID, certName, subject)
 		if dbcerterr != nil {
-			logger.Errorf("保存域名(%s)证书信息到数据库时发生了错误: %s", strings.Join(domainList, ", "), dbcerterr.Error())
+			logger.Errorf("保存域名(%s)证书信息到数据库时发生了错误: %s", collection.Domain2Str(), dbcerterr.Error())
 		}
 
-		for _, domain := range domainList {
-			setDomainServerCertificateNotError(domain, certID, certName)
-			if dbcerterr != nil {
-				logger.Error("因为证书信息未能写入数据库,所以 域名 (%s) 信息不尝试写入数据库")
-			} else {
-				err = database.UpdateDomain(certID, certName, subject, domain)
-				if err != nil {
-					logger.Errorf("保存 域名 (%s) 信息到数据库发生了错误,但不影响后续域名更新: %s", dbcerterr, err.Error())
+		for _, domain := range collection.Domain {
+			if domain.Type == config.DomainTypeCDN {
+				err := setCDNServerCertificateNotPanic(domain.Domain, certID, certName)
+				if dbcerterr != nil {
+					logger.Errorf("因为证书信息未能写入数据库,所以 域名 (%s) 信息不尝试写入数据库", domain.Domain)
+				} else if err == nil {
+					err := database.UpdateCDNDomain(certID, certName, subject, domain.Domain)
+					if err != nil {
+						logger.Errorf("保存 域名 (%s) 信息到数据库发生了错误,但不影响后续域名更新: %s", domain.Domain, err.Error())
+					}
 				}
+			} else if domain.Type == config.DomainTypeDCDN {
+				err := setDCDNServerCertificateNotPanic(domain.Domain, certID, certName)
+				if dbcerterr != nil {
+					logger.Errorf("因为证书信息未能写入数据库,所以 域名 (%s) 信息不尝试写入数据库", domain.Domain)
+				} else if err == nil {
+					err := database.UpdateCDNDomain(certID, certName, subject, domain.Domain)
+					if err != nil {
+						logger.Errorf("保存 域名 (%s) 信息到数据库发生了错误,但不影响后续域名更新: %s", domain.Domain, err.Error())
+					}
+				}
+			} else {
+				logger.Errorf("域名(%s)未知类型(%s)", domain.Domain, domain.Type)
 			}
 		}
 	}

+ 54 - 17
src/config/domainconfg.go

@@ -6,38 +6,71 @@ import (
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/utils"
 	"path"
 	"path/filepath"
+	"strings"
 )
 
+const (
+	DomainTypeCDN  = "cdn"
+	DomainTypeDCDN = "dcdn"
+)
+
+type Domain struct {
+	Domain string `yaml:"domain"`
+	Type   string `yaml:"type"`
+}
+
 type DomainListCollection struct {
-	Domain []string `yaml:"domain"`
-	Dir    string   `yaml:"dir"`
-	Cert   string   `yaml:"cert"`
-	Key    string   `yaml:"pri"`
+	Domain []*Domain `yaml:"domain"`
+	Dir    string    `yaml:"dir"`
+	Cert   string    `yaml:"cert"`
+	Key    string    `yaml:"pri"`
 }
 
 type DomainListsGroup struct {
-	SQLFilePath    string                 `yaml:"sqlfilepath"`
-	ActiveShutdown utils.StringBool       `yaml:"activeshutdown"`
-	RootDir        string                 `yaml:"rootrir"`
-	Collection     []DomainListCollection `yaml:"collection"`
+	SQLFilePath    string                  `yaml:"sqlfilepath"`
+	ActiveShutdown utils.StringBool        `yaml:"activeshutdown"`
+	RootDir        string                  `yaml:"rootrir"`
+	Collection     []*DomainListCollection `yaml:"collection"`
+}
+
+func (c *DomainListCollection) Domain2Str() string {
+	var builder strings.Builder
+	var seq = ", "
+
+	for _, domain := range c.Domain {
+		builder.WriteString(fmt.Sprintf("%s(%s)%s", domain.Domain, domain.Type, seq))
+	}
+
+	res := strings.TrimRight(builder.String(), seq)
+	return res
 }
 
 func (d *DomainListsGroup) SetDefault(configPath string) {
-	if d.RootDir == "" {
+	if d.SQLFilePath == "" {
 		if baota.HasBaoTaLetsEncrypt() {
-			d.RootDir = baota.GetBaoTaLetsEncryptDir()
+			d.SQLFilePath = path.Join(configPath, "auto-aliyun-cdn-ssl.db")
 		} else {
-			d.RootDir = path.Dir(configPath)
+			d.SQLFilePath = "./auto-aliyun-cdn-ssl.db"
 		}
 	}
 
 	d.ActiveShutdown.SetDefaultDisable()
 
-	if d.SQLFilePath == "" {
+	if d.RootDir == "" {
 		if baota.HasBaoTaLetsEncrypt() {
-			d.SQLFilePath = path.Join(configPath, "auto-aliyun-cdn-ssl.db")
+			d.RootDir = baota.GetBaoTaLetsEncryptDir()
 		} else {
-			d.SQLFilePath = "./auto-aliyun-cdn-ssl.db"
+			d.RootDir = path.Dir(configPath)
+		}
+	}
+
+	for _, c := range d.Collection {
+		for _, domain := range c.Domain {
+			domain.Domain = strings.TrimSpace(strings.ToLower(domain.Domain))
+			domain.Type = strings.TrimSpace(strings.ToLower(domain.Type))
+			if domain.Type == "" {
+				domain.Type = DomainTypeCDN
+			}
 		}
 	}
 
@@ -55,11 +88,15 @@ func (d *DomainListsGroup) Check() ConfigError {
 		}
 
 		for _, domain := range domainLst.Domain {
-			if domain == "" {
+			if domain.Domain == "" {
 				return NewConfigError("domain is empty")
-			} else if !utils.IsValidDomain(domain) && !utils.IsValidWildcardDomain(domain) {
+			} else if !utils.IsValidDomain(domain.Domain) && !utils.IsValidWildcardDomain(domain.Domain) {
 				return NewConfigError("domain is not valid")
 			}
+
+			if domain.Type != DomainTypeCDN && domain.Type != DomainTypeDCDN {
+				return NewConfigError("domain type is not valid")
+			}
 		}
 	}
 
@@ -84,7 +121,7 @@ func (d *DomainListCollection) GetFilePath() (certPath string, prikeyPath string
 			rootDir = path.Join(rootDir, d.Dir)
 		}
 	} else {
-		rootDir = path.Join(rootDir, d.Domain[0])
+		rootDir = path.Join(rootDir, d.Domain[0].Domain)
 	}
 
 	if rootDir == "" {

+ 106 - 17
src/database/db.go

@@ -34,7 +34,7 @@ func UpdateCert(certID int64, name string, subject string) error {
 				return err
 			}
 
-			err = tx.Model(&DomainRecord{}).Updates(map[string]interface{}{
+			err = tx.Model(&CDNDomainRecord{}).Updates(map[string]interface{}{
 				"cert_id": cert.CertID,
 				"name":    cert.Name,
 				"subject": cert.Subject,
@@ -42,6 +42,15 @@ func UpdateCert(certID int64, name string, subject string) error {
 			if err != nil {
 				logger.Errorf("try update CDN domain SSL record because information does not match, but failed: %s", err.Error())
 			}
+
+			err = tx.Model(&DCDNDomainRecord{}).Updates(map[string]interface{}{
+				"cert_id": cert.CertID,
+				"name":    cert.Name,
+				"subject": cert.Subject,
+			}).Error
+			if err != nil {
+				logger.Errorf("try update DCDN domain SSL record because information does not match, but failed: %s", err.Error())
+			}
 		}
 
 		return nil
@@ -53,7 +62,7 @@ func UpdateCert(certID int64, name string, subject string) error {
 	return nil
 }
 
-func UpdateDomain(certID int64, name string, subject string, domain string) error {
+func UpdateCDNDomain(certID int64, name string, subject string, domain string) error {
 	err := db.Transaction(func(tx *gorm.DB) error {
 		var cert CertRecord
 		err := tx.Model(&CertRecord{}).Where("name = ?", name).Order("created_at desc").First(&cert).Error
@@ -65,7 +74,7 @@ func UpdateDomain(certID int64, name string, subject string, domain string) erro
 			logger.Errorf("Update CDN domain SSL record to SQLite failed: information does not match (sqlite: cert-id=%d; name=%s; subject=%s) (be given: cert-id=%d; name=%s; subject=%s)", cert.CertID, cert.Name, cert.Subject, certID, name, subject)
 		}
 
-		record := DomainRecord{
+		record := CDNDomainRecord{
 			CertRecordID: cert.ID,
 			CertID:       cert.CertID,
 			Name:         cert.Name,
@@ -88,37 +97,39 @@ func UpdateDomain(certID int64, name string, subject string, domain string) erro
 	return nil
 }
 
-func CheckNeedUpdateDomain(certName string, domainName string) (cr *CertRecord, res bool, err error) {
+func CheckNeedUpdateCDNDomain(certName string, domainName string) (cert *CertRecord, need bool, err error) {
 	defer func() {
-		if err != nil {
-			res = false
-			cr = nil
+		if err != nil || recover() != nil {
+			need = false
+			cert = nil
 		}
 	}()
 
-	var cert CertRecord
+	cert = new(CertRecord)
 
 	err = db.Transaction(func(tx *gorm.DB) error {
-		err := tx.Model(&CertRecord{}).Where("name = ?", certName).Order("created_at desc").First(&cert).Error
+		err := tx.Model(&CertRecord{}).Where("name = ?", certName).Order("created_at desc").First(cert).Error
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return fmt.Errorf("check CDN domain SSL record from SQLite failed: cert record not found")
+			cert = nil
+			need = false
+			return nil
 		} else if err != nil {
 			return err
 		}
 
-		var domain DomainRecord
-		err = tx.Model(&DomainRecord{}).Where("domain = ?", domainName).Order("created_at desc").First(&domain).Error
+		var domain CDNDomainRecord
+		err = tx.Model(&CDNDomainRecord{}).Where("domain = ?", domainName).Order("created_at desc").First(&domain).Error
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			res = true
+			need = true
 			return nil
 		} else if err != nil {
-			res = false
+			need = false
 			return err
 		} else if cert.UpdatedAt.After(domain.CertUpdateAt) {
-			res = true
+			need = true
 			return nil
 		} else {
-			res = false
+			need = false
 			return nil
 		}
 	})
@@ -126,5 +137,83 @@ func CheckNeedUpdateDomain(certName string, domainName string) (cr *CertRecord,
 		return nil, false, fmt.Errorf("check CDN domain SSL record from SQLite failed: %s", err.Error())
 	}
 
-	return &cert, res, nil
+	return cert, need, nil
+}
+
+func UpdateDCDNDomain(certID int64, name string, subject string, domain string) error {
+	err := db.Transaction(func(tx *gorm.DB) error {
+		var cert CertRecord
+		err := tx.Model(&CertRecord{}).Where("name = ?", name).Order("created_at desc").First(&cert).Error
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return fmt.Errorf("update DCDN domain SSL record to SQLite failed: cert record not found")
+		} else if err != nil {
+			return err
+		} else if cert.CertID != certID || cert.Name != name || cert.Subject != subject {
+			logger.Errorf("Update DCDN domain SSL record to SQLite failed: information does not match (sqlite: cert-id=%d; name=%s; subject=%s) (be given: cert-id=%d; name=%s; subject=%s)", cert.CertID, cert.Name, cert.Subject, certID, name, subject)
+		}
+
+		record := DCDNDomainRecord{
+			CertRecordID: cert.ID,
+			CertID:       cert.CertID,
+			Name:         cert.Name,
+			Subject:      cert.Subject,
+			Domain:       domain,
+			CertUpdateAt: cert.UpdatedAt,
+		}
+
+		err = tx.Create(&record).Error
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return fmt.Errorf("create/update DCDN domain SSL record to SQLite failed: %s", err.Error())
+	}
+
+	return nil
+}
+
+func CheckNeedUpdateDCDNDomain(certName string, domainName string) (cert *CertRecord, need bool, err error) {
+	defer func() {
+		if err != nil || recover() != nil {
+			need = false
+			cert = nil
+		}
+	}()
+
+	cert = new(CertRecord)
+
+	err = db.Transaction(func(tx *gorm.DB) error {
+		err := tx.Model(&CertRecord{}).Where("name = ?", certName).Order("created_at desc").First(cert).Error
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			cert = nil
+			need = false
+			return nil
+		} else if err != nil {
+			return err
+		}
+
+		var domain DCDNDomainRecord
+		err = tx.Model(&DCDNDomainRecord{}).Where("domain = ?", domainName).Order("created_at desc").First(&domain).Error
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			need = true
+			return nil
+		} else if err != nil {
+			need = false
+			return err
+		} else if cert.UpdatedAt.After(domain.CertUpdateAt) {
+			need = true
+			return nil
+		} else {
+			need = false
+			return nil
+		}
+	})
+	if err != nil {
+		return nil, false, fmt.Errorf("check DCDN domain SSL record from SQLite failed: %s", err.Error())
+	}
+
+	return cert, need, nil
 }

+ 1 - 1
src/database/init.go

@@ -21,7 +21,7 @@ func InitSQLite() error {
 		return fmt.Errorf("connect to sqlite (%s) failed: %s", sqlfilepath, err)
 	}
 
-	err = _db.AutoMigrate(&CertRecord{}, &DomainRecord{})
+	err = _db.AutoMigrate(&CertRecord{}, &CDNDomainRecord{}, &DCDNDomainRecord{})
 	if err != nil {
 		return fmt.Errorf("migrate sqlite (%s) failed: %s", sqlfilepath, err)
 	}

+ 17 - 3
src/database/module.go

@@ -24,7 +24,7 @@ func (*CertRecord) TableName() string {
 	return "cert_record"
 }
 
-type DomainRecord struct {
+type CDNDomainRecord struct {
 	Model
 	CertRecordID uint      `gorm:"column:cert_record_id;not null;"`
 	CertID       int64     `gorm:"column:cert_id;not null"`
@@ -34,6 +34,20 @@ type DomainRecord struct {
 	CertUpdateAt time.Time `gorm:"column:cert_update_time;not null"`
 }
 
-func (*DomainRecord) TableName() string {
-	return "domain_record"
+func (*CDNDomainRecord) TableName() string {
+	return "cdn_domain_record"
+}
+
+type DCDNDomainRecord struct {
+	Model
+	CertRecordID uint      `gorm:"column:cert_record_id;not null;"`
+	CertID       int64     `gorm:"column:cert_id;not null"`
+	Name         string    `gorm:"column:name;type:VARCHAR(100);not null"`
+	Subject      string    `gorm:"column:subject;type:VARCHAR(100);not null;"`
+	Domain       string    `gorm:"type:VARCHAR(100);not null;"` // 允许多次重复记录
+	CertUpdateAt time.Time `gorm:"column:cert_update_time;not null"`
+}
+
+func (*DCDNDomainRecord) TableName() string {
+	return "dcdn_domain_record"
 }

+ 6 - 7
src/server/server.go

@@ -4,7 +4,6 @@ import (
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/aliyun"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/config"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
-	"strings"
 )
 
 func Server() error {
@@ -16,21 +15,21 @@ func Server() error {
 			defer func() {
 				if r := recover(); r != nil {
 					if err, ok := r.(error); ok {
-						logger.Panicf("本集合的域名在进行更新时遇到了 不可预期的严重错误 被recover捕获,类型为error,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %s", strings.Join(collection.Domain, ", "), index, err.Error())
+						logger.Panicf("本集合的域名在进行更新时遇到了 不可预期的严重错误 被recover捕获,类型为error,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %s", collection.Domain2Str(), index, err.Error())
 					} else {
-						logger.Panicf("本集合的域名在进行更新时遇到了 不可预期的严重错误 被recover捕获,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %v", strings.Join(collection.Domain, ", "), index, r)
+						logger.Panicf("本集合的域名在进行更新时遇到了 不可预期的严重错误 被recover捕获,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %v", collection.Domain2Str(), index, r)
 					}
 				}
 			}()
 
-			logger.Infof("开始第 %d 组集合(从0算起)的更新服务,包括域名:%s", index, strings.Join(collection.Domain, ", "))
+			logger.Infof("开始第 %d 组集合(从0算起)的更新服务,包括域名:%s", index, collection.Domain2Str())
 			certPath, prikeyPath := collection.GetFilePath()
 			logger.Infof("获取到证书路径:%s,密钥路径:%s", certPath, prikeyPath)
-			err := aliyun.UpdateCDNHttpsByFilePath(collection.Domain, certPath, prikeyPath)
+			err := aliyun.UpdateDomainHttpsByFilePath(collection, certPath, prikeyPath)
 			if err != nil {
-				logger.Errorf("本集合的域名在进行更新时遇到了 错误 被recover捕获,类型为error,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %s", strings.Join(collection.Domain, ", "), index, err.Error())
+				logger.Errorf("本集合的域名在进行更新时遇到了 错误 被recover捕获,类型为error,其他集合继续运行 (域名:%s / 序号:%d) 错误消息: %s", collection.Domain2Str(), index, err.Error())
 			}
-			logger.Infof("完成第 %d 组集合(从0算起)的更新服务,包括域名:%s", index, strings.Join(collection.Domain, ", "))
+			logger.Infof("完成第 %d 组集合(从0算起)的更新服务,包括域名:%s", index, collection.Domain2Str())
 		}()
 	}
 	logger.Info("服务结束...")