- 新增 /api/auth/register、/api/auth/login,JWT 签发与 Bearer 鉴权中间件 - notes 需登录,users/user-groups 仅管理员;auth 配置项随版本 1→2 自动补全 - internal/api 仅保留路由装配,拆分为 auth/user/usergroup/note/httpx/testutil - 同步更新 Swagger 文档与前端注册接口路径
81 lines
2.5 KiB
Go
81 lines
2.5 KiB
Go
// 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:"记录不存在"`
|
||
}
|
||
|
||
// 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: "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: "服务器内部错误"})
|
||
}
|
||
|
||
// 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: "服务器内部错误"})
|
||
}
|
||
|
||
// RespondGetError 查询类错误:记录不存在返回 404,其余按数据库错误处理。
|
||
func RespondGetError(c *gin.Context, err error) {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
c.JSON(http.StatusNotFound, ErrorResponse{Error: "记录不存在"})
|
||
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: "未登录或登录已过期"})
|
||
}
|