Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 39s
refactor(service): 修改banner和guild删除逻辑为软删除 fix(service): 修复删除操作使用软删除而非物理删除 build: 添加SQLite测试仓库实现 docs: 新增奖励管理字段拆分和批量抽奖UI改造文档 ci: 更新CI忽略文件 style: 清理无用资源文件
566 lines
24 KiB
Markdown
566 lines
24 KiB
Markdown
# Go API 开发规范
|
||
|
||
## 1. 概述
|
||
|
||
### 1.1 目的
|
||
本文档旨在为 `bindbox-game` 项目制定一套统一、清晰的 Go API 开发规范。通过标准化代码结构、命名约定、错误处理和文档注释,我们致力于提高代码的可读性、可维护性和团队协作效率,并从根本上避免之前开发中遇到的问题(如包导入不一致、特殊字符使用不当等)。
|
||
|
||
### 1.2 适用范围
|
||
本规范主要适用于项目 `internal/api/` 目录下的所有 API 层的开发工作。所有新的 API 模块以及对现有模块的修改,都必须严格遵守本规范。
|
||
|
||
### 1.3 核心原则
|
||
* **一致性**:所有代码都应遵循相同的模式和风格。
|
||
* **清晰性**:代码应易于阅读和理解,避免复杂的或晦涩的实现。
|
||
* **可维护性**:代码结构应清晰,便于未来扩展和重构。
|
||
* **api/service**:代码结构应清晰,api 和 service 应该保持一致的命名规范。必须: 一个 api 对应一个 service。一个函数功能对应一个 service 方法。单独一个文件
|
||
* **接口文档注释**:所有 API 端点都必须包含详细的 Swagger 注释,描述请求参数、响应体、可能的错误码等。
|
||
* **测试功能**:所有 API 端点都必须包含详细的测试用例,确保功能的正确性和稳定性。(cmd/testchain/ 中写)
|
||
|
||
---
|
||
|
||
## 2. 目录与文件结构
|
||
|
||
规范的目录结构是项目可维护性的基础。
|
||
|
||
* **模块化目录**:每一个独立的业务功能模块都应在 `internal/api/` 下创建一个专属目录。
|
||
```
|
||
internal/api/
|
||
├── admin/
|
||
├── activity/
|
||
└── ... (其他模块)
|
||
```
|
||
|
||
* **Handler 结构体定义**:在每个模块目录下,应有一个与模块同名的 `go` 文件(如 `admin.go`),用于定义该模块的 `handler` 结构体和 `New` 初始化函数(注:Handler 不直接进行数据库读写)。
|
||
```go
|
||
// internal/api/admin/admin.go
|
||
package admin
|
||
|
||
type handler struct {
|
||
logger logger.CustomLogger
|
||
writeDB *dao.Query // 仅用于注入(避免在 Handler 直接使用)
|
||
readDB *dao.Query // 仅用于注入(避免在 Handler 直接使用)
|
||
svc adminsvc.Service
|
||
}
|
||
|
||
func New(logger logger.CustomLogger, db mysql.Repo) *handler {
|
||
return &handler{
|
||
logger: logger,
|
||
writeDB: dao.Use(db.GetDbW()),
|
||
readDB: dao.Use(db.GetDbR()),
|
||
svc: adminsvc.New(logger, db),
|
||
}
|
||
}
|
||
```
|
||
|
||
* **端点文件分离**:每个 API 端点(Endpoint)的实现应放在一个独立的 `go` 文件中,文件名应清晰描述其功能,示例对齐当前 `admin` 模块:
|
||
```
|
||
internal/api/admin/
|
||
├── admin.go // Handler 定义
|
||
├── login.go // 管理员登录
|
||
├── admin_create.go // 新增客服
|
||
├── admin_modify.go // 编辑客服(含路径参数)
|
||
├── admin_delete.go // 删除客服(批量)
|
||
└── admin_list.go // 客服列表(分页查询)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 命名规范
|
||
|
||
统一的命名规范能极大提升代码的可读性。
|
||
|
||
* **Go 文件**:使用小写蛇形命名法 (`snake_case`),如 `admin_create.go`。
|
||
* **Handler 函数**:使用大驼峰命名法 (`PascalCase`),且函数名应清晰表达其动作,如 `CreateAdmin()`。
|
||
* **请求/响应结构体**:使用小驼峰命名法 (`camelCase`),并以 `Request`/`Response` 作为后缀,如 `createAdminRequest`, `createAdminResponse`。
|
||
|
||
---
|
||
|
||
## 4. API Handler 规范
|
||
|
||
API Handler 是业务逻辑的入口,其规范性至关重要。
|
||
|
||
### 4.1 函数签名
|
||
所有 API Handler 函数都必须返回 `core.HandlerFunc` 类型。这是框架的核心设计,用于统一处理请求上下文。
|
||
|
||
```go
|
||
// CreateAdmin 新增客服
|
||
func (h *handler) CreateAdmin() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
// ... 实现
|
||
}
|
||
}
|
||
```
|
||
|
||
### 9.7 权限矩阵与中间件
|
||
- 管理端所有接口统一要求携带 `Authorization`,经 JWT 校验后方可访问;未登录或令牌无效返回 `401`。
|
||
- 管理端认证组统一前置 `RequireAdminRole` 校验:非超管必须至少绑定一个角色,否则返回 `403`。
|
||
- 敏感接口增加动作级权限校验 `RequireAdminAction(action_mark)`:
|
||
- 工会:
|
||
- `POST /api/admin/guilds` → `guild:create`
|
||
- `PUT /api/admin/guilds/:guild_id` → `guild:modify`
|
||
- `DELETE /api/admin/guilds/:guild_id` → `guild:delete`
|
||
- `GET /api/admin/guilds/:guild_id` → `guild:view`
|
||
- `GET /api/admin/guilds/:guild_id/members` → `guild:view`
|
||
- `GET /api/admin/guilds/:guild_id/applications` → `guild:view`
|
||
- `POST /api/admin/guilds/:guild_id/applications/:member_id/approve` → `guild:member:approve`
|
||
- `POST /api/admin/guilds/:guild_id/applications/:member_id/reject` → `guild:member:reject`
|
||
- `DELETE /api/admin/guilds/:guild_id/members/:user_id` → `guild:member:delete`
|
||
- 商品:`product:create`、`product:modify`、`product:batch:modify`、`product:delete`、`product:view`
|
||
- 轮播图:`banner:create`、`banner:modify`、`banner:delete`、`banner:view`
|
||
- 系统称号与效果:`title:view`、`title:create`、`title:modify`、`title:delete`、`title:effect:create`、`title:effect:modify`、`title:effect:delete`、`title:assign`
|
||
- 道具卡:`itemcard:create`、`itemcard:modify`、`itemcard:delete`、`itemcard:view`
|
||
- 优惠券:`coupon:create`、`coupon:modify`、`coupon:delete`、`coupon:view`
|
||
- 订单与退款:`order:view`、`order:modify`、`order:cancel`、`order:consume`、`order:export`、`refund:create`、`refund:view`
|
||
- 发货统计:`ops:shipping:view`、`ops:shipping:write`
|
||
- 角色-动作分配通过现有接口维护:
|
||
- `POST /api/role/:role_id/actions` 分配动作
|
||
- `GET /api/menu/:menu_id/actions` 查看动作
|
||
- 权限校验失败统一返回 `403`,响应结构为标准 `code.Failure`。
|
||
|
||
### 4.2 参数绑定与校验
|
||
* **绑定(JSON)**:POST/PUT 等带请求体的接口使用 `ctx.ShouldBindJSON(req)` 绑定到预定义的 `Request` 结构体。
|
||
* **绑定(Query)**:GET 查询接口使用 `ctx.ShouldBindForm(req)` 绑定到预定义的 `Request` 结构体(字段使用 `form:"..."` tag)。
|
||
* **路径参数**:通过 `ctx.Param("id")` 等读取路径参数并进行类型转换与校验。
|
||
* **校验**:绑定方法会自动执行 `binding` tag 定义的校验规则。如果校验失败,必须立即中断请求并返回错误。
|
||
|
||
```go
|
||
req := new(createAdminRequest)
|
||
res := new(createAdminResponse)
|
||
if err := ctx.ShouldBindJSON(req); err != nil {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.ParamBindError,
|
||
validation.Error(err)),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
```go
|
||
// GET 查询绑定示例(与 admin_list 对齐)
|
||
req := new(listRequest)
|
||
res := new(listResponse)
|
||
if err := ctx.ShouldBindForm(req); err != nil {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.ParamBindError,
|
||
validation.Error(err)),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
```go
|
||
// 路径参数读取示例(与 admin_modify 对齐)
|
||
id, err := strconv.Atoi(ctx.Param("id"))
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.ParamBindError,
|
||
"未传递编号ID"),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
### 4.3 响应处理
|
||
* **成功响应**:当业务逻辑成功执行后,必须使用 `ctx.Payload(res)` 方法来返回标准的成功响应。`res` 是预定义的 `Response` 结构体实例。
|
||
```go
|
||
res.Message = "操作成功"
|
||
ctx.Payload(res)
|
||
```
|
||
* **错误响应**:见第 6 章“错误处理规范”。
|
||
|
||
---
|
||
|
||
## 5. 数据传输对象 (DTO) 规范
|
||
|
||
DTO (Data Transfer Object) 是 API 契约的直接体现。
|
||
|
||
* **结构体定义**:每个 API 都应定义清晰的 `Request` 和 `Response` 结构体。
|
||
* **字段注释**:所有结构体字段都必须有清晰的中文注释,解释其含义和用途。
|
||
* **JSON Tag**:所有字段都必须包含 `json:"..."` tag,以确保与前端交互的 JSON 字段名一致。
|
||
* **Validation Tag**:对于 `Request` 结构体中需要校验的字段,必须添加 `binding:"..."` tag 来定义校验规则(如 `required`)。
|
||
|
||
```go
|
||
// internal/api/admin/admin_create.go
|
||
|
||
type createAdminRequest struct {
|
||
UserName string `json:"username" binding:"required"` // 用户名
|
||
NickName string `json:"nickname" binding:"required"` // 昵称
|
||
Password string `json:"password" binding:"required"` // 密码
|
||
Mobile string `json:"mobile"` // 手机号 (非必填)
|
||
}
|
||
|
||
type createAdminResponse struct {
|
||
Message string `json:"message"` // 提示信息
|
||
}
|
||
```
|
||
|
||
```go
|
||
// internal/api/admin/admin_list.go
|
||
// GET 查询示例(字段使用 form 标签)
|
||
type listRequest struct {
|
||
Username string `form:"username"` // 用户名
|
||
Nickname string `form:"nickname"` // 昵称
|
||
Page int `form:"page"` // 当前页码,默认 1
|
||
PageSize int `form:"page_size"` // 每页返回的数据量,最多 100 条
|
||
}
|
||
|
||
type listData struct {
|
||
ID int32 `json:"id"`
|
||
UserName string `json:"username"`
|
||
NickName string `json:"nickname"`
|
||
Mobile string `json:"mobile"`
|
||
Avatar string `json:"avatar"`
|
||
CreatedAt string `json:"created_at"`
|
||
UpdatedAt string `json:"updated_at"`
|
||
}
|
||
|
||
type listResponse struct {
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
Total int64 `json:"total"`
|
||
List []listData `json:"list"`
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 错误处理规范
|
||
|
||
统一的错误处理是保证 API 健壮性和可预测性的关键。
|
||
|
||
* **统一入口**:所有在 Handler 中捕获的错误(参数校验失败、数据库操作失败、权限不足等)都必须通过 `ctx.AbortWithError()` 函数来处理。
|
||
* **`core.Error` 结构**:`ctx.AbortWithError` 接收一个 `core.Error` 对象,该对象封装了错误的三个核心要素:
|
||
1. **HTTP 状态码**:如 `http.StatusBadRequest`。
|
||
2. **业务错误码**:在 `internal/code/` 中预定义的 `code.Code`,如 `code.CreateAdminError`。
|
||
3. **详细错误信息**:一个描述错误具体原因的字符串。
|
||
|
||
* **未找到记录**:当数据库查询返回 `gorm.ErrRecordNotFound` 时,需要按具体业务错误码返回明确的提示信息(如“账号不存在”)。
|
||
* **权限校验失败**:当 `ctx.SessionUserInfo().IsSuper != 1` 时,禁止操作,返回对应业务错误码和提示“禁止操作”。
|
||
|
||
```go
|
||
// 示例:用户已存在
|
||
if info != nil {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.CreateAdminError,
|
||
fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "该账号已存在")),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
```go
|
||
// 示例:未找到记录(与 admin_modify/login 对齐)
|
||
if err == gorm.ErrRecordNotFound {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.AdminLoginError,
|
||
fmt.Sprintf("%s: %s", code.Text(code.AdminLoginError), "账号不存在,请联系管理员。")),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
```go
|
||
// 示例:权限校验失败(与 create/modify/delete/list 对齐)
|
||
if ctx.SessionUserInfo().IsSuper != 1 {
|
||
ctx.AbortWithError(core.Error(
|
||
http.StatusBadRequest,
|
||
code.CreateAdminError,
|
||
fmt.Sprintf("%s: %s", code.Text(code.CreateAdminError), "禁止操作")),
|
||
)
|
||
return
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.1 数据库上下文与读写分离(Service 层)
|
||
* **上下文传递**:所有数据库操作必须调用 `WithContext(ctx)`(Service 层接收 `context.Context`)。
|
||
* **读写分离**:读取使用 `s.readDB`,写入使用 `s.writeDB`。
|
||
* **分页查询会话**:分页列表与计数需要分别创建独立会话,避免互相影响。
|
||
|
||
```go
|
||
query := s.readDB.Admin.WithContext(ctx)
|
||
listQueryDB := query.Session(&gorm.Session{})
|
||
countQueryDB := query.Session(&gorm.Session{})
|
||
|
||
resultData, err := listQueryDB.Order(s.readDB.Admin.ID.Desc()).Limit(in.PageSize).Offset((in.Page-1)*in.PageSize).Find()
|
||
if err != nil { /* 错误处理 */ }
|
||
|
||
count, err := countQueryDB.Count()
|
||
if err != nil { /* 错误处理 */ }
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 日志记录规范
|
||
|
||
虽然在 `admin` 模块的示例中未显式大量使用,但规范的日志记录对于问题排查至关重要。
|
||
|
||
* **使用注入的 Logger**:应通过 `handler` 结构体中注入的 `h.logger` 实例来记录日志。
|
||
* **记录关键信息**:在关键业务节点或错误发生时,记录必要的上下文信息,如请求参数、用户 ID 等。
|
||
|
||
```go
|
||
// 推荐实践 (示例)
|
||
h.logger.Error("创建管理员失败", zap.Error(err), zap.String("username", req.UserName))
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 注释与文档 (Swagger)
|
||
|
||
API 的可发现性和易用性依赖于完善的文档。
|
||
|
||
* **强制 Swagger 注解**:每个 API Handler 函数上方都必须添加完整的 Swagger 注解块。
|
||
* **注解内容**:必须包含以下核心注解:
|
||
* `@Summary`:一句话功能简介。
|
||
* `@Description`:更详细的功能描述。
|
||
* `@Tags`:API 分组标签,便于在 Swagger UI 中分类。
|
||
* `@Accept` / `@Produce`:通常为 `json`。
|
||
* `@Param`:描述请求参数,包括参数位置(`body`)、类型、是否必需和说明。
|
||
* `@Success`:描述成功响应的结构。
|
||
* `@Failure`:描述可能发生的错误响应。
|
||
* `@Router`:API 的路由路径和 HTTP 方法。
|
||
* `@Security`:如果接口需要认证,需添加此注解。
|
||
|
||
```go
|
||
// internal/api/admin/admin_create.go
|
||
|
||
// CreateAdmin 新增客服
|
||
// @Summary 新增客服
|
||
// @Description 新增客服
|
||
// @Tags 管理端.客服管理
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param RequestBody body createAdminRequest true "请求参数"
|
||
// @Success 200 {object} createAdminResponse
|
||
// @Failure 400 {object} code.Failure
|
||
// @Router /api/admin/create [post]
|
||
// @Security LoginVerifyToken
|
||
func (h *handler) CreateAdmin() core.HandlerFunc { /* ... */ }
|
||
|
||
// internal/api/admin/admin_list.go(GET 查询示例)
|
||
// @Param username query string false "用户名"
|
||
// @Param nickname query string false "昵称"
|
||
// @Param page query int true "当前页码" default(1)
|
||
// @Param page_size query int true "每页返回的数据量,最多 100 条" default(20)
|
||
// @Router /api/admin/list [get]
|
||
// @Security LoginVerifyToken
|
||
|
||
// internal/api/admin/admin_modify.go(路径参数示例)
|
||
// @Param id path string true "编号ID"
|
||
// @Router /api/admin/{id} [put]
|
||
// @Security LoginVerifyToken
|
||
|
||
// internal/api/admin/login.go(登录接口不加 Security)
|
||
// @Router /api/admin/login [post]
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 路由与中间件规范
|
||
|
||
路由是 API 对外的入口,必须与模块职责、认证策略保持一致,并在 `internal/router/router.go` 统一注册。
|
||
|
||
### 9.1 分组与路径前缀
|
||
- **管理端**:统一前缀 `"/api/admin"`
|
||
- 非认证组:用于登录、系统状态等不需要鉴权的接口。
|
||
- 认证组:使用鉴权中间件保护,适用于敏感操作(新增、编辑、删除、列表)。
|
||
- **用户端(APP)**:统一前缀 `"/api/app"`,用于对外业务接口(如活动相关)。
|
||
|
||
### 9.2 鉴权中间件
|
||
- **封装方式**:通过 `core.WrapAuthHandler(interceptor.AdminTokenAuthVerify)` 将会话信息写入上下文,认证失败统一返回业务错误。
|
||
- **接口签名**:`internal/router/interceptor/interceptor.go:12-18` 定义了鉴权接口:
|
||
`AdminTokenAuthVerify(ctx core.Context) (sessionUserInfo proposal.SessionUserInfo, err core.BusinessError)`。
|
||
- **实现参考**:管理端鉴权实现见 `internal/router/interceptor/admin_auth.go:1-80`。
|
||
|
||
### 9.3 路由注册示例(与当前 admin 模块对齐)
|
||
```go
|
||
// internal/router/router.go
|
||
|
||
// 管理端非认证接口路由组(登录)
|
||
adminNonAuthApiRouter := mux.Group("/api/admin")
|
||
{
|
||
adminNonAuthApiRouter.GET("/license/status", func(ctx core.Context) { /* ... */ })
|
||
adminNonAuthApiRouter.POST("/login", adminHandler.Login())
|
||
}
|
||
|
||
// 管理端认证接口路由组(需要 Authorization)
|
||
adminAuthApiRouter := mux.Group("/api/admin", core.WrapAuthHandler(intc.AdminTokenAuthVerify))
|
||
{
|
||
adminAuthApiRouter.POST("/create", adminHandler.CreateAdmin())
|
||
adminAuthApiRouter.PUT("/:id", adminHandler.ModifyAdmin())
|
||
adminAuthApiRouter.POST("/delete", adminHandler.DeleteAdmin())
|
||
adminAuthApiRouter.GET("/list", adminHandler.PageList())
|
||
}
|
||
|
||
// APP 端接口路由组
|
||
appApiRouter := mux.Group("/api/app")
|
||
{
|
||
appHandler := app.New(logger, db)
|
||
appApiRouter.GET("/activities", appHandler.ListActivities())
|
||
// ... 其他 APP 端接口
|
||
}
|
||
```
|
||
|
||
### 9.4 Swagger 安全定义与使用
|
||
- **安全定义位置**:`main.go:23-26` 定义认证方案 `LoginVerifyToken`,从请求头读取 `Authorization`。
|
||
- **注解使用规范**:
|
||
- 管理端认证组接口均需添加 `@Security LoginVerifyToken`。
|
||
- 登录接口不需要添加 `@Security` 注解。
|
||
|
||
### 9.5 路径命名与语义
|
||
- **资源语义**:
|
||
- `POST /api/admin/create`:创建资源(支持复杂校验与默认值设置)。
|
||
- `PUT /api/admin/:id`:更新单个资源,路径参数 `id` 必须有效。
|
||
- `POST /api/admin/delete`:批量删除,参数 `ids` 以逗号分隔。
|
||
- `GET /api/admin/list`:分页检索,`page`/`page_size` 合理限制(最大 100)。
|
||
- **用户端**:保持 RESTful 风格,如 `GET /api/app/activities`、`GET /api/app/activities/:activity_id`。
|
||
|
||
### 9.6 指标与别名(可选)
|
||
- 为避免路径参数导致指标维度爆炸,可在 Handler 前添加别名记录指标:
|
||
```go
|
||
// 例如:为 /api/app/activities/:activity_id 设置指标记录别名
|
||
appApiRouter.GET("/activities/:activity_id",
|
||
core.AliasForRecordMetrics("/api/app/activities/:activity_id"),
|
||
appHandler.GetActivityDetail(),
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 服务层分离规范
|
||
|
||
为避免 Handler 同时承担传输与业务职责,必须在 `internal/service/{module}` 引入服务层,统一承载业务规则、数据协作与事务处理。
|
||
|
||
### 10.1 目录与命名
|
||
- 目录:`internal/service/admin`、`internal/service/activity` 等按模块划分。
|
||
- 包名:与模块名一致,Handler 导入时使用别名避免冲突(如 `svc "bindbox-game/internal/service/admin"`)。
|
||
|
||
### 10.1.1 文件拆分(与 API 端点保持一致)
|
||
- 每个端点在服务层对应一个实现文件,避免所有方法堆叠在单文件:
|
||
```
|
||
internal/service/admin/
|
||
├── service.go // 接口、构造
|
||
├── login.go // 登录
|
||
├── create.go // 新增客服
|
||
├── modify.go // 编辑客服
|
||
├── delete.go // 删除客服
|
||
└── list.go // 客服列表
|
||
```
|
||
- 规则:每个端点的输入/输出类型在对应文件中定义(例如 `LoginInput`/`LoginResult` 在 `login.go`)。
|
||
|
||
---
|
||
|
||
## 11. 流程规范(Router → API → Service)
|
||
|
||
### 11.1 职责边界
|
||
- Router:负责分组、认证拦截与端点注册;不承载业务逻辑。
|
||
- 管理端非认证路由:`internal/router/router.go:44-51`
|
||
- 管理端认证路由(鉴权中间件):`internal/router/router.go:53-60`
|
||
- 用户端 APP 路由:`internal/router/router.go:62-72`
|
||
- API(Handler):负责参数绑定、权限校验、错误码映射与响应输出;不直接操作数据库。
|
||
- 绑定参数并校验:如登录 `internal/api/admin/login.go:35-45`、列表 `internal/api/admin/admin_list.go:57-64`
|
||
- 权限校验:如列表 `internal/api/admin/admin_list.go:83-90`、创建 `internal/api/admin/admin_create.go:49-56`
|
||
- 错误码映射与响应:如登录 `internal/api/admin/login.go:53-59,61-64`
|
||
- Service:承载领域逻辑、数据访问与事务;输入/输出类型在对应端点文件中定义。
|
||
- 登录实现:`internal/service/admin/login.go:16-52`
|
||
- 创建实现:`internal/service/admin/admin_create.go:14-42`
|
||
- 编辑实现:`internal/service/admin/admin_modify.go:13-50`
|
||
- 删除实现:`internal/service/admin/admin_delete.go:9-15`
|
||
- 列表实现:`internal/service/admin/admin_list.go:12-38`
|
||
|
||
### 11.2 端到端流程(以管理端为例)
|
||
- Router 接收请求并路由到对应分组:非认证或认证(携带 `Authorization`)。
|
||
- 若为认证接口,鉴权中间件注入会话信息:`internal/router/interceptor/admin_auth.go:1-80`。
|
||
- Handler:
|
||
- `ShouldBindJSON` 或 `ShouldBindForm` 解析请求(含路径参数 `ctx.Param`)。
|
||
- 权限检查(如 `ctx.SessionUserInfo().IsSuper`)。
|
||
- 将 DTO 转换为 Service 输入,调用 `svc.*(ctx.RequestContext(), input)`。
|
||
- 根据 Service 返回的错误映射业务错误码,或输出成功响应 `ctx.Payload(res)`。
|
||
- Service:
|
||
- 使用 `dao.Query.WithContext` 与读写分离实现查询或写入。
|
||
- 必要场景内使用事务保障原子性(参考 `dao.gen.go` 的 `Query.Transaction`)。
|
||
|
||
### 11.3 错误处理策略
|
||
- Service 返回语义化错误信息(`error`);不直接决定 HTTP 或业务码。
|
||
- Handler 统一映射到 `core.Error(httpCode, code.* , message)` 并 `AbortWithError`。
|
||
- 登录错误映射:`internal/api/admin/login.go:53-59`
|
||
- 创建错误映射:`internal/api/admin/admin_create.go:66-71`
|
||
- 编辑错误映射:`internal/api/admin/admin_modify.go:87-93`
|
||
- 删除错误映射:`internal/api/admin/admin_delete.go:95-102`
|
||
- 列表错误映射:`internal/api/admin/admin_list.go:98-105`
|
||
|
||
### 11.4 数据绑定与类型转换
|
||
- 登录:`loginRequest` → `LoginInput` 映射:`internal/api/admin/login.go:47-51`
|
||
- 创建:`createAdminRequest` → `CreateInput` 映射:`internal/api/admin/admin_create.go:58-65`
|
||
- 编辑:`modifyAdminRequest` → `ModifyInput` 映射:`internal/api/admin/admin_modify.go:79-86`
|
||
- 列表:`listRequest(form)` → `ListInput` 映射:`internal/api/admin/admin_list.go:92-97`
|
||
- 删除:解析 `ids` 列表后直接传入 `svc.Delete`:`internal/api/admin/admin_delete.go:95`
|
||
|
||
### 11.5 事务与读写分离
|
||
- 读操作统一使用 `readDB`,写操作统一使用 `writeDB`。
|
||
- 所有 DAO 调用必须附带上下文:`WithContext(ctx)`。
|
||
- 多步骤写操作建议在 Service 层使用事务(`Query.Transaction`):`internal/repository/mysql/dao/gen.go:211-213`。
|
||
|
||
### 11.6 鉴权与会话
|
||
- 路由层通过 `core.WrapAuthHandler(intc.AdminTokenAuthVerify)` 进行会话注入:`internal/router/router.go:53-60`。
|
||
- 鉴权逻辑参考:`internal/router/interceptor/admin_auth.go:1-80`。
|
||
- Handler 使用 `ctx.SessionUserInfo()` 获取操作者信息(如 `CreatedBy`、`UpdatedBy`)。
|
||
|
||
### 11.7 命名与拆分
|
||
- API 与 Service 均按端点拆分文件,命名对齐,便于定位与维护。
|
||
- Service 的输入/输出类型在对应端点文件定义(如 `login.go` 定义 `LoginInput/LoginResult`)。
|
||
|
||
---
|
||
|
||
## 12. Repository 规范
|
||
|
||
### 12.1 层职责
|
||
- 负责数据访问(CRUD、分页、事务),不包含业务规则与 HTTP 细节。
|
||
- 仅依赖底层 DAO/Model,向 Service 层提供稳定的数据操作能力。
|
||
|
||
### 12.2 目录与组件
|
||
- 目录:`internal/repository/mysql`
|
||
- `dao/`:由 `gorm/gen` 生成的查询对象与读写分离封装,参考:
|
||
- 读写分离方法:`internal/repository/mysql/dao/gen.go:135-143`
|
||
- 事务封装:`internal/repository/mysql/dao/gen.go:211-213`
|
||
- 上下文绑定:`internal/repository/mysql/dao/gen.go:188-209`
|
||
- `model/`:数据库对应的实体结构体(字段、标签)。
|
||
|
||
### 12.3 使用规范(在 Service 层)
|
||
- 初始化查询对象:
|
||
- 读库:`readDB := dao.Use(db.GetDbR())`
|
||
- 写库:`writeDB := dao.Use(db.GetDbW())`
|
||
- 所有操作必须绑定上下文:`WithContext(ctx)`,示例(查询管理员):
|
||
```go
|
||
info, err := s.readDB.Admin.WithContext(ctx).
|
||
Where(s.readDB.Admin.Username.Eq(in.Username)).
|
||
First()
|
||
```
|
||
- 分页与计数使用独立会话,避免互相影响:
|
||
```go
|
||
listQueryDB := query.Session(&gorm.Session{})
|
||
countQueryDB := query.Session(&gorm.Session{})
|
||
items, _ := listQueryDB.Order(...).Limit(...).Offset(...).Find()
|
||
total, _ := countQueryDB.Count()
|
||
```
|
||
- 多步骤写操作建议在事务中完成:
|
||
```go
|
||
_ = s.writeDB.Transaction(func(tx *dao.Query) error {
|
||
// 在 tx 上执行写操作
|
||
return nil
|
||
})
|
||
```
|
||
|
||
### 12.4 分层建议
|
||
- 如需进一步解耦 DAO,可在 `internal/repository/{module}` 定义模块级仓库接口(如 `adminrepo`),内部封装 DAO 细节;Service 只依赖仓库接口,便于单元测试与替换数据源。
|
||
- 目前项目直接在 Service 层使用 DAO,符合轻量项目场景;当跨表协作复杂时可引入仓库接口以降低耦合。
|
||
|
||
|
||
|