Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 31s
refactor(api/user): 重构用户相关接口使用token验证替代user_id路径参数 docs: 更新API文档规范,明确私有接口需携带token及返回字段要求 fix(service/user): 避免写入未使用字段的零值导致MySQL校验错误 style: 统一格式化部分代码缩进和导入顺序 chore: 更新DS_Store等IDE配置文件
6.2 KiB
6.2 KiB
APP 端开发统一规范
1. 目标
统一 APP 端接口的目录结构、编码范式、错误处理与文档注释,确保与管理端(admin 模块)保持一致的风格与契约。
2. 目录结构
- 模块放置:
internal/api/activity/ - 每个端点单文件:
activity_create.goactivity_issue_add.goactivity_list.goactivity_detail.goactivity_issues_list.goissue_rewards_list.go
3. Handler 初始化
- 在模块根定义
handler与New(logger, db),注入writeDB/readDB - 路由绑定示例:
appHandler := app.New(logger, db),方法统一返回core.HandlerFunc
4. 请求/响应范式
- 所有接口必须定义
Request与Response结构体 - 成功统一:
ctx.Payload(res),res.Message使用统一文案操作成功
4.1 认证与鉴权(强制)
- 所有“用户私有数据”接口必须携带请求头
Authorization: <token>(微信登录返回的令牌),后端基于令牌解析当前用户。 - 路径中的
user_id为 REST 占位参数,后端不使用该值进行身份识别;实际的用户身份以令牌为准,禁止越权访问他人数据。 - 未携带或令牌无效时返回
401与业务码JWTAuthVerifyError。
4.2 统一返回字段(示例:优惠券)
- 优惠券列表返回
list中的元素为:id:持券记录IDname:优惠券名称amount:优惠面值(分;折扣为千分比已转百分比文案)valid_start/valid_end:有效期(yyyy-MM-dd HH:mm:ss)status:状态(1未使用 2已使用 3已过期)rules:使用规则说明(直减/满减/折扣)
5. 参数绑定与校验
- JSON:
ctx.ShouldBindJSON(req) - 表单/查询:
ctx.ShouldBindForm(req) - 绑定错误:
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
6. 业务错误处理
- 统一:
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) - 业务码:在
internal/code/中约定并使用具体业务码
7. 分页与过滤规范
- 分页:
page默认 1、page_size默认 20、最大 100,超过返回参数错误 - 过滤:字符串使用
Like("%xxx%"),数值/枚举使用Eq(...),is_boss仅允许0/1
8. 路径参数规范
- 使用
strconv.Atoi并校验> 0,非法返回ParamBindError
9. 时间规范
- 入参解析:
timeutil.ParseCSTInLocation - 响应时间:
FriendlyTime或固定格式化2006-01-02 15:04
10. Swagger 注释规范
- 标签:APP 端统一使用
@Tags APP端.活动(或模块名) - 必填注释:
@Summary/@Description/@Accept/@Produce/@Param/@Success/@Failure/@Router - 安全注释:用户私有接口必须添加
@Security LoginVerifyToken user_id参数文档需注明“占位,不参与鉴权,按令牌解析用户”,示例:@Param user_id path integer true "用户ID(占位,按令牌解析)"
11. 统一 Handler 模板(示例)
以下示例来自 internal/api/activity/activity_issues_list.go:46-127,作为 APP 端列表接口标准参考:
// ListActivityIssues 活动期列表
// @Summary 活动期列表
// @Description 获取指定活动的期列表,支持分页
// @Tags APP端.活动
// @Accept json
// @Produce json
// @Param activity_id path int true "活动ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量,最多100" default(20)
// @Success 200 {object} listIssuesResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/activities/{activity_id}/issues [get]
func (h *handler) ListActivityIssues() core.HandlerFunc {
return func(ctx core.Context) {
idStr := ctx.Param("activity_id")
id, err := strconv.Atoi(idStr)
if err != nil || id <= 0 {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
"活动ID无效"),
)
return
}
req := new(listIssuesRequest)
res := new(listIssuesResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)),
)
return
}
if req.Page == 0 { req.Page = 1 }
if req.PageSize == 0 { req.PageSize = 20 }
if req.PageSize > 100 {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
"每页最多100条"),
)
return
}
query := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).
Where(h.readDB.ActivityIssues.ActivityID.Eq(int64(id)))
listQuery := query
countQuery := query
issues, err := listQuery.Order(h.readDB.ActivityIssues.ID.Desc()).
Limit(req.PageSize).Offset((req.Page-1)*req.PageSize).Find()
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ServerError,
err.Error()),
)
return
}
total, err := countQuery.Count()
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ServerError,
err.Error()),
)
return
}
res.Page = req.Page
res.PageSize = req.PageSize
res.Total = total
res.List = make([]issueListItem, len(issues))
for i, v := range issues {
res.List[i] = issueListItem{
ID: v.ID,
IssueNumber: v.IssueNumber,
Status: v.Status,
Sort: v.Sort,
CreatedAt: timeutil.FriendlyTime(v.CreatedAt),
UpdatedAt: timeutil.FriendlyTime(v.UpdatedAt),
}
}
ctx.Payload(res)
}
}
12. 文档生成与预览
- 生成:
make gen-swagger - 预览:
make serve-swagger(默认端口36666,访问http://localhost:36666/docs) - 预览时在右上角
Authorize设置Authorization值,接口将自动附带令牌进行校验