package httputil import ( "encoding/json" "errors" "io" "net" "net/http" "net/netip" "net/url" "server/internal/config" "server/internal/model/common" "server/internal/pkg/errs" "server/internal/pkg/validator" "strconv" "strings" "github.com/go-chi/chi/v5" ) // Pagination 分页请求参数 func Pagination(r *http.Request) *common.Pagination { q := r.URL.Query() // page 默认 1 page := int32(1) if p := q.Get("page"); p != "" { if v, err := strconv.Atoi(p); err == nil && v >= 1 { page = int32(v) } } // pageSize 默认 10 pageSize := int32(10) if ps := q.Get("page_size"); ps != "" { if v, err := strconv.Atoi(ps); err == nil && v >= 1 && v <= 100 { pageSize = int32(v) } } return &common.Pagination{ Page: page, PageSize: pageSize, } } func URLParamInt32(r *http.Request, key string) (int32, error) { v := chi.URLParam(r, key) if v == "" { return 0, errs.ErrIDRequired } n, err := strconv.ParseInt(v, 10, 32) if err != nil { return 0, errs.ErrInvalidID } return int32(n), nil } // maxJSONBodySize JSON 请求体上限(1MB) const maxJSONBodySize = 1 << 20 func BindJson(w http.ResponseWriter, r *http.Request, dest any) error { // 超限时 Decode 返回 *http.MaxBytesError → 413 r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize) if err := json.NewDecoder(r.Body).Decode(dest); err != nil { var maxBytesErr *http.MaxBytesError if errors.As(err, &maxBytesErr) { return errs.ErrBodyTooLarge } if errors.Is(err, io.EOF) { return errs.ErrEmptyBody } return errs.ErrInvalidJSON } if err := validator.Struct(dest); err != nil { return err } return nil } // ClientIP 获取客户端 IP;trust_proxy=true 时信任代理头,否则用 RemoteAddr(防伪造) func ClientIP(r *http.Request) netip.Addr { addr, _ := netip.ParseAddr(clientIPString(r)) return addr } func clientIPString(r *http.Request) string { // 只在 trust_proxy=true 时信任代理头(防伪造) if config.GetBool("server.trust_proxy") { if xff := r.Header.Get("X-Forwarded-For"); xff != "" { parts := strings.Split(xff, ",") for _, v := range parts { if ip := strings.TrimSpace(v); ip != "" { return ip } } } // 再取 X-Real-IP(nginx 单层代理覆盖时最准) if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" { return realIP } } // RemoteAddr: IP:port host, _, err := net.SplitHostPort(r.RemoteAddr) if err == nil { return host } return r.RemoteAddr } func BuildFileUrl(path *string) string { if path == nil || *path == "" { return "" } baseURL := config.GetString("file.base_url") if baseURL == "" { return "" } result, err := url.JoinPath(baseURL, *path) if err != nil { return "" } return result }