// Package handler HTTP 处理层。 // // 职责边界:只做三件事 —— 解析请求参数、调用 service、写出响应。 // 不包含任何 SQL 与业务规则。 package handler import ( "errors" "net/http" "strconv" "strings" "fashionapi/internal/pkg/response" "fashionapi/internal/service" "github.com/gin-gonic/gin" ) // fail 把 service 层错误翻译为 HTTP 响应。 // // 业务错误携带自己的状态码与文案,原样透传(保证与原项目逐字一致); // 其余未预期错误统一按 500 处理,避免内部细节泄漏成不可控的响应。 func fail(c *gin.Context, err error) { var svcErr *service.Error if errors.As(err, &svcErr) { response.Error(c, svcErr.Status, svcErr.Message) return } response.Error(c, http.StatusInternalServerError, err.Error()) } // queryInt 读取整型查询参数,缺失或非法时返回默认值。 func queryInt(c *gin.Context, key string, def int) int { v := strings.TrimSpace(c.Query(key)) if v == "" { return def } n, err := strconv.Atoi(v) if err != nil { return def } return n } // queryBool 读取布尔查询参数。 // // def 为该参数缺失时的取值: // - def=true(如某开关默认开启):只有显式传 0 / false 才关闭; // - def=false(如 featured):只有显式传 1 / true 才开启。 func queryBool(c *gin.Context, key string, def bool) bool { v := strings.ToLower(strings.TrimSpace(c.Query(key))) if v == "" { return def } if def { return v != "0" && v != "false" } return v == "1" || v == "true" } // parseUint32CSV 解析逗号分隔的正整数列表(如 brand_ids=1,2,3),忽略非法项。 func parseUint32CSV(s string) []uint32 { if strings.TrimSpace(s) == "" { return nil } var out []uint32 for _, part := range strings.Split(s, ",") { if n, err := strconv.Atoi(strings.TrimSpace(part)); err == nil && n > 0 { out = append(out, uint32(n)) } } return out } // parseIntSlice 把字符串数组解析为整型数组,忽略非法项。 func parseIntSlice(in []string) []int { if len(in) == 0 { return nil } var out []int for _, v := range in { if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil { out = append(out, n) } } return out } // parseLocale 解析请求语言,优先级:?locale=cn|en > Accept-Language > 默认 en。 // 与前端 i18n/config.ts 的 LOCALES=['en','cn']、DEFAULT_LOCALE='en' 保持一致。 func parseLocale(c *gin.Context) string { if l := strings.ToLower(strings.TrimSpace(c.Query("locale"))); l == "cn" || l == "en" { return l } if al := c.GetHeader("Accept-Language"); al != "" { for _, part := range strings.Split(al, ",") { code := strings.ToLower(strings.TrimSpace(strings.SplitN(part, ";", 2)[0])) switch { case code == "zh" || strings.HasPrefix(code, "zh-"): return "cn" case strings.HasPrefix(code, "en"): return "en" } } } return "en" }