// Package httpx 提供 HTTP 接口的公共辅助:统一错误响应、分页参数与路径参数解析。 package httpx import ( "errors" "log/slog" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" ) const ( defaultPageSize = 20 maxPageSize = 100 ) // ErrorResponse 统一错误响应。 type ErrorResponse struct { Error string `json:"error" example:"record not found"` } // ParsePagination 解析 page/page_size,非法值回落默认。 func ParsePagination(c *gin.Context) (int, int) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", strconv.Itoa(defaultPageSize))) if page < 1 { page = 1 } if pageSize < 1 || pageSize > maxPageSize { pageSize = defaultPageSize } return page, pageSize } // ParseID 解析路径参数 id,0 或非法值返回 false 并写入 400。 func ParseID(c *gin.Context) (uint, bool) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil || id == 0 { c.JSON(http.StatusBadRequest, ErrorResponse{Error: "invalid id"}) return 0, false } return uint(id), true } // RespondDBError 记录数据库错误并返回 500。 func RespondDBError(c *gin.Context, err error) { slog.ErrorContext(c.Request.Context(), "数据库操作失败", "err", err, "path", c.Request.URL.Path) c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "internal server error"}) } // RespondServerError 记录业务错误并返回 500。 func RespondServerError(c *gin.Context, err error, msg string) { slog.ErrorContext(c.Request.Context(), msg, "err", err, "path", c.Request.URL.Path) c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "internal server error"}) } // RespondGetError 查询类错误:记录不存在返回 404,其余按数据库错误处理。 func RespondGetError(c *gin.Context, err error) { if errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, ErrorResponse{Error: "record not found"}) return } RespondDBError(c, err) } // RespondDuplicateOrDBError 写入类错误:唯一约束冲突返回 409,其余按数据库错误处理。 func RespondDuplicateOrDBError(c *gin.Context, err error, duplicateMsg string) { if errors.Is(err, gorm.ErrDuplicatedKey) { c.JSON(http.StatusConflict, ErrorResponse{Error: duplicateMsg}) return } RespondDBError(c, err) } // RespondUnauthorized 中止请求并返回 401。 func RespondUnauthorized(c *gin.Context) { c.AbortWithStatusJSON(http.StatusUnauthorized, ErrorResponse{Error: "unauthorized or session expired"}) }