package service import ( "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "gin-template/model" "strings" "time" "gorm.io/gorm" ) type ReleaseResult struct { Version *model.ConfigVersion `json:"version"` Routes []*model.ProxyRoute `json:"routes"` } func ListConfigVersions() ([]*model.ConfigVersion, error) { return model.ListConfigVersions() } func GetActiveConfigVersion() (*model.ConfigVersion, error) { return model.GetActiveConfigVersion() } func PublishConfigVersion(createdBy string) (*ReleaseResult, error) { routes, err := model.GetEnabledProxyRoutes() if err != nil { return nil, err } if len(routes) == 0 { return nil, errors.New("没有可发布的启用规则") } snapshotJSON, err := renderSnapshot(routes) if err != nil { return nil, err } renderedConfig := renderNginxConfig(routes) version, err := nextVersionNumber(time.Now()) if err != nil { return nil, err } record := &model.ConfigVersion{ Version: version, SnapshotJSON: snapshotJSON, RenderedConfig: renderedConfig, Checksum: checksum(renderedConfig), IsActive: true, CreatedBy: createdBy, } err = model.DB.Transaction(func(tx *gorm.DB) error { if err := tx.Model(&model.ConfigVersion{}).Where("is_active = ?", true).Update("is_active", false).Error; err != nil { return err } if err := tx.Create(record).Error; err != nil { return err } return nil }) if err != nil { if isUniqueConstraintError(err) { return nil, errors.New("版本号生成冲突,请重试") } return nil, err } return &ReleaseResult{ Version: record, Routes: routes, }, nil } func ActivateConfigVersion(id uint) (*model.ConfigVersion, error) { version, err := model.GetConfigVersionByID(id) if err != nil { return nil, err } err = model.DB.Transaction(func(tx *gorm.DB) error { if err := tx.Model(&model.ConfigVersion{}).Where("is_active = ?", true).Update("is_active", false).Error; err != nil { return err } if err := tx.Model(version).Update("is_active", true).Error; err != nil { return err } return nil }) if err != nil { return nil, err } version.IsActive = true return version, nil } func renderSnapshot(routes []*model.ProxyRoute) (string, error) { type snapshotRoute struct { Domain string `json:"domain"` OriginURL string `json:"origin_url"` Enabled bool `json:"enabled"` Remark string `json:"remark,omitempty"` } items := make([]snapshotRoute, 0, len(routes)) for _, route := range routes { items = append(items, snapshotRoute{ Domain: route.Domain, OriginURL: route.OriginURL, Enabled: route.Enabled, Remark: route.Remark, }) } data, err := json.Marshal(items) if err != nil { return "", err } return string(data), nil } func renderNginxConfig(routes []*model.ProxyRoute) string { var builder strings.Builder builder.WriteString("# This file is generated by ATSFlare. Do not edit manually.\n") for _, route := range routes { builder.WriteString(fmt.Sprintf("server {\n listen 80;\n server_name %s;\n\n location / {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_pass %s;\n }\n}\n\n", route.Domain, route.OriginURL)) } return builder.String() } func checksum(content string) string { sum := sha256.Sum256([]byte(content)) return hex.EncodeToString(sum[:]) } func nextVersionNumber(now time.Time) (string, error) { prefix := now.Format("20060102") var count int64 if err := model.DB.Model(&model.ConfigVersion{}).Where("version LIKE ?", prefix+"-%").Count(&count).Error; err != nil { return "", err } return fmt.Sprintf("%s-%03d", prefix, count+1), nil }