update
This commit is contained in:
109
internal/handler/handler.go
Normal file
109
internal/handler/handler.go
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user