package service import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "fmt" v1 "github.com/go-nunu/nunu-layout-advanced/api/v1" "github.com/go-nunu/nunu-layout-advanced/internal/repository" ) type SslCertService interface { ParseCert(ctx context.Context, httpsCert string, httpKey string) (serverName string, commonName []string, DNSNames []string, before int64, after int64, isSelfSigned bool, err error) AddSSLCert(ctx context.Context, req v1.SSL) (int64, error) EditSSLCert(ctx context.Context, req v1.SSL) error AddSslPolicy(ctx context.Context, CertIds []int64) (sslPolicyId int64, err error) EditSslPolicy(ctx context.Context, sslPolicyId int64, CertIds []int64, action string) error } func NewSslCertService( service *Service, webForwardingRep repository.WebForwardingRepository, cdn CdnService, ) SslCertService { return &sslCertService{ Service: service, webForwardingRep: webForwardingRep, cdn: cdn, } } type sslCertService struct { *Service webForwardingRep repository.WebForwardingRepository cdn CdnService } func (s *sslCertService) ParseCert(ctx context.Context, httpsCert string, httpKey string) (serverName string, commonName []string, DNSNames []string, before int64, after int64, isSelfSigned bool, err error) { cert, err := tls.X509KeyPair([]byte(httpsCert), []byte(httpKey)) if err != nil { return "", nil, nil, 0, 0, false, fmt.Errorf("无法从字符串加载密钥对: %v", err) } if len(cert.Certificate) == 0 { return "", nil, nil, 0, 0, false, fmt.Errorf("提供的证书数据中没有找到证书。") } // 解析第一个证书(通常是叶子证书) x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { return "", nil, nil, 0, 0, false, fmt.Errorf("无法解析证书: %v", err) } // 1. 获取 Common Name (通用名称) // Common Name 位于 Subject 字段内. [1] serverName = x509Cert.Subject.CommonName // 2. 获取 DNS Names (备用主题名称中的DNS条目) // DNS Names 直接是证书结构体的一个字段. [1] DNSNames = x509Cert.DNSNames // 检查证书是否为自签名 // 判断条件:颁发者(Issuer)和主题(Subject)相同,并且证书的签名可以由其自身的公钥验证 if err := x509Cert.CheckSignatureFrom(x509Cert); err == nil { isSelfSigned = true } // 将CommonName放入一个切片,以匹配[]string的类型要求 var commonNames []string if x509Cert.Subject.CommonName != "" { commonNames = []string{x509Cert.Subject.CommonName} } return serverName, commonNames, DNSNames, x509Cert.NotBefore.Unix(), x509Cert.NotAfter.Unix(), isSelfSigned, nil } func (s *sslCertService) AddSslPolicy(ctx context.Context, CertIds []int64) (sslPolicyId int64, err error) { // 构造策略中引用的证书列表 type sslCerts struct { IsOn bool `json:"isOn" form:"isOn"` CertId int64 `json:"certId" form:"certId"` } var sslCertsSlice []sslCerts for _, certId := range CertIds { sslCertsSlice = append(sslCertsSlice, sslCerts{ IsOn: true, CertId: certId, }) } sslCertsJson, err := json.Marshal(sslCertsSlice) if err != nil { return 0, fmt.Errorf("序列化SSL证书引用失败: %w", err) } // 调用CDN服务创建策略 newSslPolicyId, err := s.cdn.AddSSLPolicy(ctx, v1.AddSSLPolicy{ Http2Enabled: true, SslCertsJSON: sslCertsJson, MinVersion: "TLS 1.1", // 可根据安全要求调整 }) if err != nil { // 如果策略创建失败,需要考虑回滚或记录错误,这里直接返回错误 return 0, fmt.Errorf("通过CDN添加SSL策略失败: %w", err) } return newSslPolicyId, nil } func (s *sslCertService) EditSslPolicy(ctx context.Context, sslPolicyId int64, CertIds []int64, action string) error { type sslCerts struct { IsOn bool `json:"isOn" form:"isOn"` CertId int64 `json:"certId" form:"certId"` } oldCertIds, err := s.webForwardingRep.GetSslCertId(ctx, sslPolicyId) if err != nil { return fmt.Errorf("获取SSL证书失败: %w", err) } var sslCertsSlice []sslCerts newCertIdSet := make(map[int64]struct{}, len(CertIds)) for _, certId := range CertIds { newCertIdSet[certId] = struct{}{} } oldCertIdSet := make(map[int64]struct{}, len(oldCertIds)) for _, oldCert := range oldCertIds { oldCertIdSet[oldCert.CertId] = struct{}{} } switch action { case "add": for _, certId := range CertIds { // 使用 oldCertIdSet 进行 O(1) 复杂度的查找。 if _, found := oldCertIdSet[certId]; !found { // 如果在旧的集合中没找到,说明是新增的。 sslCertsSlice = append(sslCertsSlice, sslCerts{ IsOn: true, CertId: certId, }) } } case "del": for _, oldCert := range oldCertIds { // 使用 newCertIdSet 进行 O(1) 复杂度的查找。 if _, found := newCertIdSet[oldCert.CertId]; !found { // 如果在新的集合中没找到,说明被删除了。 sslCertsSlice = append(sslCertsSlice, sslCerts{ IsOn: false, CertId: oldCert.CertId, }) } } } sslCertsJson, err := json.Marshal(sslCertsSlice) if err != nil { return fmt.Errorf("序列化SSL证书引用失败: %w", err) } // 调用CDN服务创建策略 err = s.cdn.EditSSLPolicy(ctx, v1.SSLPolicy{ SslPolicyId: sslPolicyId, Http2Enabled: true, SslCertsJSON: sslCertsJson, MinVersion: "TLS 1.1", // 可根据安全要求调整 }) if err != nil { // 如果策略创建失败,需要考虑回滚或记录错误,这里直接返回错误 return fmt.Errorf("通过CDN添加SSL策略失败: %w", err) } return nil } func (s *sslCertService) AddSSLCert(ctx context.Context, req v1.SSL) (int64, error) { // 1. 解析证书文件,提取元数据 serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData) if err != nil { return 0, fmt.Errorf("解析证书失败: %w", err) } // 2. 将证书添加到CDN提供商 // 这是获取可以在策略中引用的 `sslCertId` 的前提 newSslCertId, err := s.cdn.AddSSLCert(ctx, v1.SSlCert{ IsOn: true, UserId: int64(req.CdnUserId), Name: req.Domain, // 使用域名作为证书名称 ServerName: serverName, Description: req.Description, CertData: []byte(req.CertData), KeyData: []byte(req.KeyData), TimeBeginAt: before, TimeEndAt: after, DnsNames: DNSNames, CommonNames: commonNames, IsSelfSigned: isSelfSigned, }) if err != nil { return 0, fmt.Errorf("添加SSL证书到CDN失败: %w", err) } return newSslCertId, nil } func (s *sslCertService) EditSSLCert(ctx context.Context, req v1.SSL) error { if req.CertData == "" && req.KeyData == "" { return nil } // 1. 解析证书文件,提取元数据 serverName, commonNames, DNSNames, before, after, isSelfSigned, err := s.ParseCert(ctx, req.CertData, req.KeyData) if err != nil { return fmt.Errorf("解析证书失败: %w", err) } // 2. 将证书添加到CDN提供商 // 这是获取可以在策略中引用的 `sslCertId` 的前提 err = s.cdn.EditSSLCert(ctx, v1.SSlCert{ IsOn: true, UserId: int64(req.CdnUserId), Name: req.Domain, // 使用域名作为证书名称 ServerName: serverName, Description: req.Description, CertData: []byte(req.CertData), KeyData: []byte(req.KeyData), TimeBeginAt: before, TimeEndAt: after, DnsNames: DNSNames, CommonNames: commonNames, IsSelfSigned: isSelfSigned, }) if err != nil { return fmt.Errorf("添加SSL证书到CDN失败: %w", err) } return nil }