--- description: soul-api 编码风格与规范(GORM、依赖使用、项目约定) globs: soul-api/**/*.go alwaysApply: false --- # soul-api 编码规范 ## 1. 数据访问:优先 ORM(GORM) - **一律使用 GORM 进行数据读写**,通过 `database.DB()` 获取 `*gorm.DB`,操作集中在 `internal/model` 中定义的模型上。 - **禁止**在 handler 中手写 `db.Exec("INSERT ...")`、`db.Raw("SELECT ...")` 等裸 SQL,除非满足下方「例外」。 - 常规 CRUD 必须用 GORM 链式 API: - 查询:`db.Where(...).First/Find/Count`、`db.Preload`、`db.Select`、`db.Order` - 写入:`db.Create`、`db.Save`、`db.Model(...).Updates(map/struct)`、`db.Where(...).Delete` - 原子更新:用 `gorm.Expr`,例如 `Update("pending_earnings", gorm.Expr("pending_earnings + ?", delta))`,而不是多行 Raw/Exec。 - **例外**(允许少量 Raw/Exec): - 单条复杂统计 SQL 且用 GORM 表达冗长时,可用 `db.Raw(...).Scan(&struct)`,并加简短注释说明原因。 - 必须用原生 SQL 的原子多列更新(如多字段 `SET a=a+?, b=b+?`)可保留 `db.Exec`,其余尽量改为 `Model().Update(Expr(...))`。 ## 2. Model 与表结构 - 所有表对应结构体放在 `internal/model`,文件名与业务一致(如 `user.go`、`order.go`)。 - 结构体必须包含: - `gorm` 标签:`column`、`primaryKey`、`type` 等; - `json` 标签:对外 API 字段用小写驼峰(如 `openId`); - 实现 `TableName() string` 返回表名(若表名与默认规则不一致)。 - 不对外暴露的字段用 `json:"-"`(如 `SessionKey`、`PurchasedSections`)。 ## 3. 依赖物尽其用 - **Gin**:入参用 `c.ShouldBindJSON(&req)` + `binding:"required"` 等做校验;统一用 `c.JSON(status, gin.H{...})` 或结构体返回;路由按功能挂在 `router.Setup` 的对应 Group(如 `/admin` 用 `middleware.AdminAuth()`)。 - **GORM**:能用链式条件、Scopes、预加载完成的,不写 Raw;事务用 `db.Transaction(func(tx *gorm.DB) error { ... })`。 - **配置**:仅通过 `internal/config` 的 `config.Load()` 读环境变量;业务代码不直接 `os.Getenv`;新配置项加到 `Config` 结构体并在 `Load()` 中解析。 - **中间件**:安全头用 `middleware.Secure()`,跨域用 `cors`,限流用 `middleware.NewRateLimiter(...).Middleware()`;新路由按需挂到已有或新 Group,避免重复造轮子。 - **微信/支付**:小程序、支付、转账相关统一走 `internal/wechat` 封装,handler 只做参数与结果转换。 ## 4. 接口按使用方归类(小程序 vs 管理端) 新增或修改接口时,**必须先明确使用方**,再挂到对应路由组,避免混用: | 使用方 | 路由组 | 路径前缀 | 鉴权 | |--------|--------|----------|------| | **管理端** | `admin := api.Group("/admin")` | `/api/admin/...` | `middleware.AdminAuth()` | | **管理端(数据/配置)** | `db := api.Group("/db")` | `/api/db/...` | `middleware.AdminAuth()` | | **小程序** | `miniprogram := api.Group("/miniprogram")` | `/api/miniprogram/...` | 按接口需要(如 token) | | **两端共用** | 在 `api` 下挂通用路径,并在 `miniprogram` 下挂同 path | `/api/xxx` 与 `/api/miniprogram/xxx` | 各自鉴权 | **规则:** - **仅管理端用的接口**:只挂在 `admin` 或 `db` 下,不要出现在 `miniprogram`。 - **仅小程序用的接口**:只挂在 `miniprogram` 下(如登录、支付、提现、小程序专属配置等)。 - **两端共用的接口**:在 `router.go` 里两处都注册同一 handler:先写在 `api` 的对应区块(如「推荐」「用户」),再在 `// ----- 小程序组 -----` 里用 `miniprogram.GET/POST(... path, handler.XXX)` 挂一遍,保证小程序统一走 `/api/miniprogram/xxx`。 - handler 注释和路由注释中标明使用方,例如:`// GET /api/miniprogram/withdraw/records 小程序-提现记录`、`// GET /api/admin/withdrawals 管理端-提现列表`。 ## 5. 目录与包约定 - `cmd/server/main.go`:入口,只做 config/database/wechat/router 的初始化与启停。 - `internal/handler`:HTTP 处理函数,只做绑定、校验、调 DB/wechat、写响应;不写复杂业务逻辑时可暂时放在 handler,逻辑变复杂时再抽到 `internal/service`。 - `internal/router`:注册路由与中间件,不写业务逻辑;新增路由时按「4. 接口按使用方归类」决定挂到 admin / db / miniprogram 或 api+miniprogram。 - `internal/database`:仅提供 `Init(dsn)` 与 `DB() *gorm.DB`。 - 新增接口时:先确定**使用方(小程序 / 管理端 / 共用)** → 再确定路由与 Group → 在对应 handler 中实现,数据库操作用 GORM + model。 ## 6. 响应与错误 - 统一约定:成功 `gin.H{"success": true, "data": ...}` 或 `"message": "..."`;失败 `gin.H{"success": false, "error": "..."}`。 - 不吞掉错误:DB/wechat 返回的 `err` 必须处理,并向前端返回明确错误信息或日志。 - HTTP 状态码:业务错误可用 200 + `success: false`(与现网一致);需要明确表达「未授权/禁止」时用 401/403。 ## 7. 代码风格 - 遵循 `gofmt`,提交前保持格式一致。 - 包内命名:导出函数用 PascalCase,内部用 camelCase;与接口语义一致(如 `ReferralBind`、`GetPublicDBConfig`)。 - 注释:公开 handler 或复杂逻辑处写清用途(如 `// POST /api/referral/bind 推荐码绑定`)。