api
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
description: 在 internal/handler 下新增或修改 HTTP 接口后,询问用户是否需要同步更新 .codebuddy/docs 中对应的接口文档(不强制,由用户决定)。适用于所有 gin 路由接口的开发与变更。
|
||||||
|
globs: "internal/handler/**/*.go"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# 接口文档同步提示(非强制)
|
||||||
|
|
||||||
|
每次在 `internal/handler/` 下**新增或修改** HTTP 接口(gin 路由,如 `r.GET` / `r.POST` / `r.PUT` / `r.DELETE` 及对应的 handler 方法)后,**询问用户是否需要**同步更新 `.codebuddy/docs` 下对应的接口文档。是否补充由用户决定,不强制。
|
||||||
|
|
||||||
|
## 1. 文档归属
|
||||||
|
|
||||||
|
根据接口挂载的路由前缀判断归属,写入对应文件:
|
||||||
|
|
||||||
|
| 接口归属 | 路由前缀特征 | 文档文件 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 后台管理接口 | `/api/admin` | `.codebuddy/docs/admin-api.md` |
|
||||||
|
| 小程序端接口 | 小程序相关(如 `/api/miniapp`) | `.codebuddy/docs/miniapp-api.md` |
|
||||||
|
| 新增独立模块 | 既不在 admin 也不在 miniapp | 在 `.codebuddy/docs/` 下新建 `<module>-api.md`,并在本规则 §1 登记 |
|
||||||
|
|
||||||
|
> 归属以路由注册时挂载的 group 前缀为准,而非文件名。
|
||||||
|
|
||||||
|
## 2. 若用户同意补充,需包含的内容
|
||||||
|
|
||||||
|
每个接口在文档中至少包含:
|
||||||
|
|
||||||
|
- **名称与简介**:一句话说明用途。
|
||||||
|
- **请求**:方法 + 完整路径、是否需要鉴权、相关请求头(如 `Authorization`)。
|
||||||
|
- **请求参数**:
|
||||||
|
- 路径参数 / 查询参数 / Body 字段,用表格列出:字段名、类型、是否必填、校验规则(`binding` tag,如 `required`、`max=32`)、说明。
|
||||||
|
- 复杂结构给出 JSON 示例。
|
||||||
|
- **响应**:
|
||||||
|
- 成功响应结构与字段说明(字段名、类型、含义)及示例 JSON。
|
||||||
|
- 关键业务错误码(非 `0` 的 `code`)及其含义。
|
||||||
|
- **示例**:至少一个成功调用示例(`curl` 或 JSON)。
|
||||||
|
|
||||||
|
## 3. 文档格式约定
|
||||||
|
|
||||||
|
- 对齐 `.codebuddy/docs` 已有文件的风格:参数/响应用 Markdown 表格描述,示例用 ```` ```json ```` / ```` ```bash ```` 代码块。
|
||||||
|
- 文档应可独立阅读,前端/测试无需看代码即可对接。
|
||||||
|
- 描述用中文;字段名、路径、代码保持英文原样。
|
||||||
|
|
||||||
|
## 4. 变更同步(仅当用户选择维护文档时)
|
||||||
|
|
||||||
|
- 若用户选择维护文档,接口**删除 / 重命名 / 改路径 / 改参数 / 改响应结构**时,同步更新文档对应段落。
|
||||||
|
- 不得遗留过期、错误的接口描述(仅在维护该文档的前提下)。
|
||||||
|
|
||||||
|
## 5. 提示时机与方式
|
||||||
|
|
||||||
|
- 完成接口代码后,主动询问用户:"是否需要更新接口文档?(admin-api.md / miniapp-api.md)"
|
||||||
|
- 用户确认后才执行文档更新;用户拒绝或忽略则跳过,不强制。
|
||||||
|
- 若接口改动较大(删除 / 重命名 / 改路径 / 改参 / 改响应),仍只提示,不强制执行。
|
||||||
|
- 用户同意补充时,参照 §2、§3 的约定撰写,并保证示例与代码行为一致。
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
// Package userhdl 是后台管理小程序端用户的 HTTP 接入层。
|
||||||
|
package userhdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"portal-mini-go/internal/pkg/errors"
|
||||||
|
"portal-mini-go/internal/pkg/response"
|
||||||
|
usersvc "portal-mini-go/internal/service/admin/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler 用户管理请求处理。
|
||||||
|
type Handler struct {
|
||||||
|
svc *usersvc.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(svc *usersvc.Service) *Handler { return &Handler{svc: svc} }
|
||||||
|
|
||||||
|
// List 分页列表用户。支持 query:mini_id(0 表示全部)、keyword、page、size。
|
||||||
|
func (h *Handler) List(c *gin.Context) {
|
||||||
|
miniID, _ := strconv.ParseUint(c.DefaultQuery("mini_id", "0"), 10, 64)
|
||||||
|
keyword := c.Query("keyword")
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||||
|
list, total, err := h.svc.List(c.Request.Context(), usersvc.ListReq{
|
||||||
|
MiniID: miniID,
|
||||||
|
Keyword: keyword,
|
||||||
|
Page: page,
|
||||||
|
Size: size,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, gin.H{"list": list, "total": total, "page": page, "size": size})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 查询单个用户详情。
|
||||||
|
func (h *Handler) Get(c *gin.Context) {
|
||||||
|
id, ok := parseID(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u, err := h.svc.GetByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReq 编辑用户资料请求;留空字段不更新。
|
||||||
|
type UpdateReq struct {
|
||||||
|
Nickname *string `json:"nickname"`
|
||||||
|
Avatar *string `json:"avatar"`
|
||||||
|
Status *int8 `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 编辑用户资料(昵称 / 头像 / 状态)。
|
||||||
|
func (h *Handler) Update(c *gin.Context) {
|
||||||
|
id, ok := parseID(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req UpdateReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.Error(errors.ErrBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u, err := h.svc.Update(c.Request.Context(), id, usersvc.UpdateReq{
|
||||||
|
Nickname: req.Nickname,
|
||||||
|
Avatar: req.Avatar,
|
||||||
|
Status: req.Status,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusReq 禁用 / 启用请求。
|
||||||
|
type StatusReq struct {
|
||||||
|
Status int8 `json:"status" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus 禁用 / 启用用户。
|
||||||
|
func (h *Handler) SetStatus(c *gin.Context) {
|
||||||
|
id, ok := parseID(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req StatusReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.Error(errors.ErrBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u, err := h.svc.SetStatus(c.Request.Context(), id, req.Status)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OK(c, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseID 从路径参数解析并校验 uint64 主键。
|
||||||
|
func parseID(c *gin.Context) (uint64, bool) {
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(errors.ErrBadRequest)
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return id, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 注册后台用户管理路由。g 已带 /api/admin 前缀与 JWT 鉴权中间件。
|
||||||
|
func Register(g *gin.RouterGroup, h *Handler) {
|
||||||
|
rg := g.Group("/users")
|
||||||
|
{
|
||||||
|
rg.GET("", h.List)
|
||||||
|
rg.GET("/:id", h.Get)
|
||||||
|
rg.PUT("/:id", h.Update)
|
||||||
|
rg.POST("/:id/status", h.SetStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// Package usersvc 是后台管理小程序端用户的业务逻辑。
|
||||||
|
package usersvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"portal-mini-go/internal/model"
|
||||||
|
"portal-mini-go/internal/pkg/errors"
|
||||||
|
"portal-mini-go/internal/repository/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service 用户管理业务服务,依赖用户仓储。
|
||||||
|
type Service struct {
|
||||||
|
repo *user.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo *user.Repository) *Service { return &Service{repo: repo} }
|
||||||
|
|
||||||
|
// ListReq 列表查询条件。
|
||||||
|
type ListReq struct {
|
||||||
|
MiniID uint64
|
||||||
|
Keyword string
|
||||||
|
Page int
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// List 分页查询用户,缺省第 1 页、每页 20 条。
|
||||||
|
func (s *Service) List(ctx context.Context, req ListReq) ([]model.User, int64, error) {
|
||||||
|
page, size := req.Page, req.Size
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if size <= 0 {
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
return s.repo.AdminList(ctx, user.AdminListFilter{
|
||||||
|
MiniID: req.MiniID,
|
||||||
|
Keyword: req.Keyword,
|
||||||
|
Page: page,
|
||||||
|
Size: size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID 按 ID 查询用户详情。
|
||||||
|
func (s *Service) GetByID(ctx context.Context, id uint64) (*model.User, error) {
|
||||||
|
return s.repo.AdminGet(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateReq 编辑用户资料请求;指针区分“未提供”与“置空”。
|
||||||
|
type UpdateReq struct {
|
||||||
|
Nickname *string
|
||||||
|
Avatar *string
|
||||||
|
Status *int8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update 编辑用户资料与状态,返回更新后的用户。
|
||||||
|
func (s *Service) Update(ctx context.Context, id uint64, req UpdateReq) (*model.User, error) {
|
||||||
|
if req.Status != nil {
|
||||||
|
if *req.Status != model.UserStatusEnabled && *req.Status != model.UserStatusDisabled {
|
||||||
|
return nil, errors.New(40000, "非法的用户状态")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.repo.AdminUpdate(ctx, id, req.Nickname, req.Avatar, req.Status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.repo.AdminGet(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatus 禁用 / 启用用户,返回更新后的用户。
|
||||||
|
func (s *Service) SetStatus(ctx context.Context, id uint64, status int8) (*model.User, error) {
|
||||||
|
if status != model.UserStatusEnabled && status != model.UserStatusDisabled {
|
||||||
|
return nil, errors.New(40000, "非法的用户状态")
|
||||||
|
}
|
||||||
|
if err := s.repo.AdminUpdate(ctx, id, nil, nil, &status); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.repo.AdminGet(ctx, id)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user