// Package integration 提供给受信任第三方脚本的 JSON 接口。 // 它与网页登录和 /api/v1/client 完全隔离,不能复用 Cookie 或 X-Client-Id。 package integration import ( "crypto/subtle" "net/http" "strings" "github.com/gin-gonic/gin" "cmautobuy/admin/config" ) const sourceContextKey = "catalog_integration_source" // RequireCatalogToken 校验商品目录接口的专用 Bearer Token。 func RequireCatalogToken(cfg config.CatalogIntegrationConfig) gin.HandlerFunc { return func(c *gin.Context) { if !cfg.Enabled() { integrationError(c, http.StatusServiceUnavailable, "INTEGRATION_DISABLED", "商品目录接口尚未配置", true, nil) c.Abort() return } provided, ok := bearerToken(c.GetHeader("Authorization")) if !ok || subtle.ConstantTimeCompare([]byte(provided), []byte(cfg.Token)) != 1 { integrationError(c, http.StatusUnauthorized, "UNAUTHORIZED", "接口凭据无效", false, nil) c.Abort() return } c.Set(sourceContextKey, cfg.Source) c.Next() } } func bearerToken(header string) (string, bool) { parts := strings.Fields(header) if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") || parts[1] == "" { return "", false } return parts[1], true } func integrationSource(c *gin.Context) string { value, _ := c.Get(sourceContextKey) source, _ := value.(string) return source } func integrationError(c *gin.Context, status int, code, message string, retryable bool, details any) { if details == nil { details = gin.H{} } c.JSON(status, gin.H{"error": gin.H{ "code": code, "message": message, "retryable": retryable, "request_id": c.GetHeader("X-Request-Id"), "details": details, }}) }