This commit is contained in:
toom1996
2026-08-26 10:45:21 +08:00
parent 3cf1368e68
commit 30c9f21da4
48 changed files with 3442 additions and 0 deletions

109
internal/handler/handler.go Normal file
View File

@ -0,0 +1,109 @@
// 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(如 only_with_articles):只有显式传 0 / false 才关闭;
// - def=false(如 featured):只有显式传 1 / true 才开启。
//
// 这两种语义都来自原项目,必须保持,否则前端 A-Z 索引的数据量会突变。
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"
}