package handler import ( "crypto/subtle" "encoding/json" "io" "net/http" "strings" "time" "aijianzhan/platform/internal/authx" "aijianzhan/platform/internal/license" "aijianzhan/platform/internal/svc" "github.com/zeromicro/go-zero/rest/httpx" ) func requireLicenseSecret(svcCtx *svc.ServiceContext, r *http.Request) error { want := strings.TrimSpace(svcCtx.Config.License.ControlSecret) if want == "" { want = strings.TrimSpace(svcCtx.Config.Auth.IssueSecret) } if want == "" { return errMsg("未配置 License.ControlSecret") } got := strings.TrimSpace(r.Header.Get("X-License-Secret")) if got == "" { got = strings.TrimSpace(r.URL.Query().Get("secret")) } if subtle.ConstantTimeCompare([]byte(got), []byte(want)) != 1 { return errMsg("invalid license control secret") } return nil } type errMsg string func (e errMsg) Error() string { return string(e) } func licenseStatusHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if svcCtx.License == nil || !svcCtx.License.Enabled() { httpx.OkJson(w, map[string]any{"enabled": false, "message": "本实例未启用授权租约"}) return } httpx.OkJson(w, svcCtx.License.Status(time.Now())) } } func licenseRenewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := requireLicenseSecret(svcCtx, r); err != nil { authx.WriteError(w, http.StatusUnauthorized, err.Error()) return } if svcCtx.License == nil || !svcCtx.License.Enabled() { authx.WriteError(w, http.StatusBadRequest, "license 未启用") return } var body struct { NotAfter string `json:"not_after"` Note string `json:"note"` By string `json:"by"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } na, err := license.ParseNotAfter(body.NotAfter) if err != nil || na.IsZero() { authx.WriteError(w, http.StatusBadRequest, "not_after 无效") return } by := strings.TrimSpace(body.By) if by == "" { by = "remote" } lease, err := svcCtx.License.Renew(na, by, body.Note) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) } } func licenseExtendHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := requireLicenseSecret(svcCtx, r); err != nil { authx.WriteError(w, http.StatusUnauthorized, err.Error()) return } if svcCtx.License == nil || !svcCtx.License.Enabled() { authx.WriteError(w, http.StatusBadRequest, "license 未启用") return } var body struct { Days int `json:"days"` Note string `json:"note"` By string `json:"by"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } if body.Days <= 0 { body.Days = license.DefaultExtensionMaxDays } by := strings.TrimSpace(body.By) if by == "" { by = "remote" } lease, err := svcCtx.License.Extend(body.Days, by, body.Note) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) } } func licensePutLeaseHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := requireLicenseSecret(svcCtx, r); err != nil { authx.WriteError(w, http.StatusUnauthorized, err.Error()) return } if svcCtx.License == nil || !svcCtx.License.Enabled() { authx.WriteError(w, http.StatusBadRequest, "license 未启用") return } var body struct { Customer string `json:"customer"` NotAfter string `json:"not_after"` ExtensionsUsed int `json:"extensions_used"` ExtensionsMax int `json:"extensions_max"` ExtensionMaxDays int `json:"extension_max_days"` Note string `json:"note"` By string `json:"by"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } na, err := license.ParseNotAfter(body.NotAfter) if err != nil || na.IsZero() { authx.WriteError(w, http.StatusBadRequest, "not_after 无效") return } by := strings.TrimSpace(body.By) if by == "" { by = "watchdog" } lease, err := svcCtx.License.PutLease(&license.Lease{ Customer: body.Customer, NotAfter: na, ExtensionsUsed: body.ExtensionsUsed, ExtensionsMax: body.ExtensionsMax, ExtensionMaxDays: body.ExtensionMaxDays, Note: body.Note, }, by) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) } } func licenseImportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := requireLicenseSecret(svcCtx, r); err != nil { authx.WriteError(w, http.StatusUnauthorized, err.Error()) return } if svcCtx.License == nil || !svcCtx.License.Enabled() { authx.WriteError(w, http.StatusBadRequest, "license 未启用") return } raw, err := io.ReadAll(r.Body) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } by := strings.TrimSpace(r.URL.Query().Get("by")) if by == "" { by = "offline" } lease, err := svcCtx.License.ImportSigned(raw, by) if err != nil { authx.WriteError(w, http.StatusBadRequest, err.Error()) return } httpx.OkJson(w, map[string]any{"ok": true, "lease": lease, "status": svcCtx.License.Status(time.Now())}) } }