|
@@ -30,13 +30,21 @@ import (
|
|
|
//
|
|
//
|
|
|
// 前端约定:一律读 body 而非 HTTP 状态码——
|
|
// 前端约定:一律读 body 而非 HTTP 状态码——
|
|
|
// 成功判断 res.success === true(兼容旧前端 res.success 写法);
|
|
// 成功判断 res.success === true(兼容旧前端 res.success 写法);
|
|
|
-// 分页列表读顶层 res.total(统一放顶层,解决旧系统 Success/SuccessPage 两处 total 位置不一致的坑);
|
|
|
|
|
|
|
+// 分页列表读 res.data.list / res.data.total(统一嵌套在 data 内,彻底消灭旧系统
|
|
|
|
|
+// Success(total 顶层) 与 SuccessPage(total 在 data 内) 两处结构不一致的坑);
|
|
|
// 失败时 res.code 为分段错误码(1xxx/2xxx/3xxx/4xxx/5xxx),res.message 为可直接展示给用户的文案。
|
|
// 失败时 res.code 为分段错误码(1xxx/2xxx/3xxx/4xxx/5xxx),res.message 为可直接展示给用户的文案。
|
|
|
//
|
|
//
|
|
|
|
|
+// Data 语义(统一约定):
|
|
|
|
|
+// - 分页列表接口 → *PageData(对象,含 list/total/page/pageSize/pages);
|
|
|
|
|
+// - 单实体接口 → 实体对象或 nil(操作成功无数据体)。
|
|
|
|
|
+// 即"分页接口 data 恒为对象、单实体接口 data 恒为对象/省略",前端 useListPage 无需区分两套读法。
|
|
|
|
|
+//
|
|
|
// 典型 JSON 形态:
|
|
// 典型 JSON 形态:
|
|
|
//
|
|
//
|
|
|
-// // 成功(带分页)
|
|
|
|
|
-// {"code":200,"message":"ok","data":[...],"total":42,"success":true}
|
|
|
|
|
|
|
+// // 成功(分页,Page 函数写出)
|
|
|
|
|
+// {"code":200,"message":"ok","data":{"list":[...],"total":42,"page":1,"pageSize":10,"pages":5},"success":true}
|
|
|
|
|
+// // 成功(单实体,OK 函数写出)
|
|
|
|
|
+// {"code":200,"message":"ok","data":{"id":"...","name":"..."},"success":true}
|
|
|
// // 业务失败(注意:HTTP 状态码仍是 200)
|
|
// // 业务失败(注意:HTTP 状态码仍是 200)
|
|
|
// {"code":3001,"message":"记录不存在","success":false}
|
|
// {"code":3001,"message":"记录不存在","success":false}
|
|
|
type R struct {
|
|
type R struct {
|
|
@@ -44,14 +52,34 @@ type R struct {
|
|
|
Code int `json:"code"`
|
|
Code int `json:"code"`
|
|
|
// Message 面向用户的错误文案。脱敏铁律:不得包含 SQL 片段、堆栈、密码哈希等敏感信息。
|
|
// Message 面向用户的错误文案。脱敏铁律:不得包含 SQL 片段、堆栈、密码哈希等敏感信息。
|
|
|
Message string `json:"message"`
|
|
Message string `json:"message"`
|
|
|
- // Data 业务数据体(成功时填充;失败时省略)。
|
|
|
|
|
|
|
+ // Data 业务数据体(成功时填充;失败时省略)。分页接口传 *PageData,单实体接口传实体对象。
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
- // Total 分页总数,仅在列表分页响应中填充(omitempty 省略非分页场景)。
|
|
|
|
|
- Total int64 `json:"total,omitempty"`
|
|
|
|
|
// Success 成功标志,兼容旧前端 res.success 判断,与 Code 冗余但保留。
|
|
// Success 成功标志,兼容旧前端 res.success 判断,与 Code 冗余但保留。
|
|
|
Success bool `json:"success"`
|
|
Success bool `json:"success"`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// PageData 统一分页数据体(分页列表接口的 Data 固定结构,前端 useListPage 统一读法)。
|
|
|
|
|
+//
|
|
|
|
|
+// 设计要点:分页元信息(total/page/pageSize/pages)与数据(list)**同聚于 data 内**,
|
|
|
|
|
+// 不再散落顶层——旧系统 Success 的 total 在顶层、SuccessPage 的 total 在 data 内,
|
|
|
|
|
+// 前端两套读法各异的坑由此彻底消灭。
|
|
|
|
|
+//
|
|
|
|
|
+// 典型 JSON 形态:
|
|
|
|
|
+//
|
|
|
|
|
+// {"list":[{...},{...}],"total":42,"page":1,"pageSize":10,"pages":5}
|
|
|
|
|
+type PageData struct {
|
|
|
|
|
+ // List 当前页数据切片;空列表必须传非 nil 的 []T{}(零值 nil 会序列化为 JSON null,前端取 list.map 报错)。
|
|
|
|
|
+ List interface{} `json:"list"`
|
|
|
|
|
+ // Total 符合筛选条件的总记录数(分页器显示"共 N 条")。
|
|
|
|
|
+ Total int64 `json:"total"`
|
|
|
|
|
+ // Page 当前页码(从 1 开始,与请求 query 的 page 回显一致)。
|
|
|
|
|
+ Page int `json:"page"`
|
|
|
|
|
+ // PageSize 每页条数(与请求 query 的 pageSize 回显一致;被 ParsePage 钳制后 ≤ 200)。
|
|
|
|
|
+ PageSize int `json:"pageSize"`
|
|
|
|
|
+ // Pages 总页数,= ceil(total / pageSize)(pageSize 为 0 时恒 0,防除零)。
|
|
|
|
|
+ Pages int64 `json:"pages"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// OK 写出成功响应(HTTP 200,body code=200,Data 填充业务数据)。
|
|
// OK 写出成功响应(HTTP 200,body code=200,Data 填充业务数据)。
|
|
|
//
|
|
//
|
|
|
// 入参:
|
|
// 入参:
|
|
@@ -68,24 +96,42 @@ func OK(c *fiber.Ctx, data interface{}) error {
|
|
|
return c.Status(fiber.StatusOK).JSON(R{Code: 200, Message: "ok", Data: data, Success: true})
|
|
return c.Status(fiber.StatusOK).JSON(R{Code: 200, Message: "ok", Data: data, Success: true})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Page 写出成功分页响应(HTTP 200,body code=200,Data 为列表、Total 为总数)。
|
|
|
|
|
|
|
+// Page 写出成功分页响应(HTTP 200,body code=200,Data 为 PageData 嵌套对象)。
|
|
|
|
|
+//
|
|
|
|
|
+// 出参形态:
|
|
|
|
|
+//
|
|
|
|
|
+// {"code":200,"message":"ok","data":{"list":[...],"total":42,"page":1,"pageSize":10,"pages":5},"success":true}
|
|
|
//
|
|
//
|
|
|
// 入参:
|
|
// 入参:
|
|
|
// - c *fiber.Ctx:Fiber 请求上下文。
|
|
// - c *fiber.Ctx:Fiber 请求上下文。
|
|
|
// - list interface{}:当前页数据切片(通常 []T);空列表请传非 nil 的 `[]T{}`,避免 JSON 输出 null。
|
|
// - list interface{}:当前页数据切片(通常 []T);空列表请传非 nil 的 `[]T{}`,避免 JSON 输出 null。
|
|
|
|
|
+// - page int:当前页码(从 1 开始);惯例经 ParsePage(c) 解析,缺省 1。
|
|
|
|
|
+// - pageSize int:每页条数;惯例经 ParsePage(c) 解析,缺省 10、上限钳制 200。
|
|
|
// - total int64:符合筛选条件的总记录数(用于前端分页器;查询惯例见架构规格 §3.1"先 pluck 分页 id → 批量取详情"防 N+1)。
|
|
// - total int64:符合筛选条件的总记录数(用于前端分页器;查询惯例见架构规格 §3.1"先 pluck 分页 id → 批量取详情"防 N+1)。
|
|
|
//
|
|
//
|
|
|
// 出参:
|
|
// 出参:
|
|
|
// - error:同 OK,仅写出层失败时返回。
|
|
// - error:同 OK,仅写出层失败时返回。
|
|
|
//
|
|
//
|
|
|
|
|
+// 异常场景:
|
|
|
|
|
+// - list 传 nil:PageData.List 序列化为 JSON null,前端 list.map 报错——调用方必须保证传非 nil 切片;
|
|
|
|
|
+// - total < 0:不钳制(视为调用方 bug,应单测暴露);Pages 按 ceil 计算,负数 total 会得到 0 页。
|
|
|
|
|
+//
|
|
|
// 样例:
|
|
// 样例:
|
|
|
//
|
|
//
|
|
|
-// page, size := parsePage(c) // page 默认 1、pageSize 默认 10
|
|
|
|
|
|
|
+// page, size := response.ParsePage(c) // page 默认 1、pageSize 默认 10(上限 200)
|
|
|
// ids, total, err := repo.PagedIDs(ctx, filter, page, size)
|
|
// ids, total, err := repo.PagedIDs(ctx, filter, page, size)
|
|
|
// users, err := repo.BatchGet(ctx, ids) // 一次批量取,防 N+1
|
|
// users, err := repo.BatchGet(ctx, ids) // 一次批量取,防 N+1
|
|
|
-// return response.Page(c, users, total)
|
|
|
|
|
-func Page(c *fiber.Ctx, list interface{}, total int64) error {
|
|
|
|
|
- return c.Status(fiber.StatusOK).JSON(R{Code: 200, Message: "ok", Data: list, Total: total, Success: true})
|
|
|
|
|
|
|
+// return response.Page(c, users, page, size, total)
|
|
|
|
|
+func Page(c *fiber.Ctx, list interface{}, page, pageSize int, total int64) error {
|
|
|
|
|
+ pages := int64(0)
|
|
|
|
|
+ if pageSize > 0 {
|
|
|
|
|
+ pages = (total + int64(pageSize) - 1) / int64(pageSize) // ceil,防除零
|
|
|
|
|
+ }
|
|
|
|
|
+ return c.Status(fiber.StatusOK).JSON(R{
|
|
|
|
|
+ Code: 200, Message: "ok",
|
|
|
|
|
+ Data: PageData{List: list, Total: total, Page: page, PageSize: pageSize, Pages: pages},
|
|
|
|
|
+ Success: true,
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Fail 写出业务失败响应(HTTP 仍为 200,body code 为业务错误码,Data 省略)。
|
|
// Fail 写出业务失败响应(HTTP 仍为 200,body code 为业务错误码,Data 省略)。
|