diff --git a/.codebuddy/docs/API.md b/.codebuddy/docs/API.md new file mode 100644 index 0000000..1e5292f --- /dev/null +++ b/.codebuddy/docs/API.md @@ -0,0 +1,343 @@ +# portal-mini-go 接口文档 + +> 基于 Go + gin 的中后台/门户后端服务。本文档描述当前已实现的 HTTP 接口、鉴权方式、统一响应结构、错误码与数据模型。 + +## 1. 概述 + +- **基础路径**:`/` +- **服务地址**:由 `configs/config.yaml` 中 `server.port` 决定(默认 `8080`),例如 `http://localhost:8080`。 +- **统一响应**:所有接口均返回 `HTTP 200`,业务结果通过响应体中的 `code` 字段判断(见 §3)。 +- **路由分层**: + - `/api/ma`:小程序端接口,通过请求头 `X-App-ID` 鉴权(校验小程序是否被纳管且启用)。 + - `/api/admin`:后台管理接口,通过 `Authorization: Bearer ` 鉴权。 + - `/healthz`:健康检查,无需鉴权。 + +## 2. 公共约定 + +### 2.1 请求头 + +| Header | 必填 | 说明 | +| --- | --- | --- | +| `Content-Type` | 是(POST/PUT) | 固定为 `application/json`。 | +| `X-App-ID` | 是(小程序接口) | 微信小程序的 AppID,标识调用方小程序身份。 | +| `Authorization` | 是(后台接口) | 后台登录后下发的 JWT,格式 `Bearer `。 | + +### 2.2 统一响应结构 + +```json +{ + "code": 0, + "msg": "ok", + "data": { } +} +``` + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `code` | int | 业务码。`0` 表示成功;非 `0` 表示失败(见 §3 错误码)。 | +| `msg` | string | 结果描述。`0` 时为 `ok`,失败时为人可读信息。 | +| `data` | object/null | 业务数据,成功时有内容,失败时通常省略。 | + +> 约定:错误统一由 `ErrorHandler` 中间件转换,handler 内不直接散落 `c.JSON` 处理错误。 + +### 2.3 鉴权中间件 + +| 分组 | 中间件 | 校验逻辑 | +| --- | --- | --- | +| `/api/ma` | `MiniAppAuth` | 校验请求头 `X-App-ID` 对应一个「已启用且未删除」的被纳管小程序;通过后将 `app_id` 与小程序主键 `mini_id` 注入上下文。 | +| `/api/admin` | `AdminAuth` | 校验 `Authorization: Bearer `(HS256)。校验通过后将 `admin_id` 注入上下文。 | +| 全局 | `Recovery` / `Trace` / `ErrorHandler` | panic 恢复、trace_id 注入、错误统一转换。 | + +## 3. 错误码 + +| 业务码 | 常量 | 含义 | +| --- | --- | --- | +| `0` | — | 成功 | +| `40000` | `ErrBadRequest` | 请求参数错误 / 非法输入(`ext` 非合法 JSON 等) | +| `40100` | `ErrUnauthorized` | 未认证 / 登录失败 / token 无效或过期 | +| `40300` | `ErrForbidden` | 禁止访问 | +| `40400` | `ErrNotFound` | 资源不存在 / 小程序未注册 | +| `50000` | `ErrServer` | 服务内部错误(含微信调用、会话写入、token 签发等) | + +> 非 `BizError` 的未知错误一律归为 `50000`。 + +## 4. 接口列表 + +### 4.1 健康检查 + +#### `GET /healthz` + +无需鉴权。 + +**响应示例** + +```json +{ "code": 0, "msg": "ok", "data": { "status": "ok" } } +``` + +--- + +### 4.2 小程序接口(前缀 `/api/ma`) + +所有接口需在请求头携带 `X-App-ID`(已启用且被纳管的小程序)。当前示例实现为 `app1`,后续小程序按相同结构扩展。 + +#### `POST /api/ma/login` + +小程序登录:通过微信 `code2session` 换取 `openid`/`unionid`,落库用户并向客户端下发会话 token。 + +**请求头** + +``` +X-App-ID: <微信小程序 AppID> +``` + +**请求体** + +```json +{ + "code": "081abc..." // 必填,微信登录 code +} +``` + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `code` | string | 是 | 小程序 `wx.login` 获取的临时登录 code。 | + +**响应** + +```json +{ + "code": 0, + "msg": "ok", + "data": { + "token": "a1b2c3...48位hex字符串", + "user": { + "id": 1, + "mini_id": 3, + "openid": "oABC...", + "union_id": "oXYZ...", + "nickname": "", + "avatar": "", + "created_at": "2026-10-25T10:00:00Z", + "updated_at": "2026-10-25T10:00:00Z" + } + } +} +``` + +> `token` 为 `24` 字节随机值的 hex 编码(48 字符),对应会话在 Redis/内存存储中保留 `7` 天。后续小程序业务接口应携带该 token 完成用户身份识别。 + +**curl 示例** + +```bash +curl -X POST http://localhost:8080/api/ma/login \ + -H "Content-Type: application/json" \ + -H "X-App-ID: wx1234567890" \ + -d '{"code":"081abc..."}' +``` + +--- + +### 4.3 后台管理接口(前缀 `/api/admin`) + +所有接口需在请求头携带 `Authorization: Bearer `。 + +#### `POST /api/admin/login` + +后台管理员登录,签发 JWT。 + +**请求体** + +```json +{ + "username": "admin", + "password": "admin123" +} +``` + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `username` | string | 是 | 管理员账号(当前示例固定 `admin`)。 | +| `password` | string | 是 | 密码(当前示例固定 `admin123`;生产应接 DB + 密码哈希)。 | + +**响应** + +```json +{ "code": 0, "msg": "ok", "data": { "token": "" } } +``` + +**curl 示例** + +```bash +curl -X POST http://localhost:8080/api/admin/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' +``` + +#### `GET /api/admin/info` + +受保护接口示例,需有效 JWT,返回当前管理员 ID。 + +**响应** + +```json +{ "code": 0, "msg": "ok", "data": { "admin_id": "1" } } +``` + +#### `POST /api/admin/miniapps` + +新建被纳管小程序。 + +**请求体** + +```json +{ + "name": "示例小程序", + "description": "用于演示", + "app_id": "wx1234567890", + "app_secret": "secret-xxxx", + "status": 1, + "ext": "{\"k\":\"v\"}" +} +``` + +| 字段 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `name` | string | 是 | 小程序名称。 | +| `description` | string | 否 | 描述。 | +| `app_id` | string | 是 | 微信 AppID,全局唯一。 | +| `app_secret` | string | 是 | 微信 AppSecret(不会在响应中返回)。 | +| `status` | int8 | 否 | 状态:`0` 停用,`1` 启用。缺省为 `1`。 | +| `ext` | string | 否 | 扩展字段,必须为合法 JSON 字符串(允许空串)。 | + +**响应**:返回创建后的 `MiniProgram` 对象(见 §5.1),`app_secret` 字段不返回。 + +#### `PUT /api/admin/miniapps/:id` + +按 ID 局部更新小程序。 + +**路径参数** + +| 参数 | 类型 | 说明 | +| --- | --- | --- | +| `id` | uint64 | 小程序主键。 | + +**请求体**(所有字段均为可选,仅在提供时更新;`ext` 若提供需为合法 JSON) + +```json +{ + "name": "新名称", + "description": "新描述", + "app_secret": "new-secret", + "status": 0, + "ext": "{\"theme\":\"dark\"}" +} +``` + +> 字段以指针区分「未提供」与「置空」;若全部为空则不更新,直接返回原记录。 + +**响应**:返回更新后的 `MiniProgram` 对象。 + +#### `GET /api/admin/miniapps/:id` + +查询单个小程序。 + +**路径参数**:`id`(uint64)。 + +**响应**:返回 `MiniProgram` 对象;不存在返回 `40400`。 + +#### `GET /api/admin/miniapps` + +分页列表查询小程序。 + +**查询参数** + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `status` | int8 | `-1`(全部) | 按状态筛选:`0` 停用,`1` 启用,`-1` 全部。 | +| `page` | int | `1` | 页码,从 1 开始。 | +| `size` | int | `20` | 每页条数。 | + +**响应** + +```json +{ + "code": 0, + "msg": "ok", + "data": { + "list": [ { "id": 1, "name": "示例小程序", "app_id": "wx123...", "status": 1, "...": "..." } ], + "total": 1, + "page": 1, + "size": 20 + } +} +``` + +#### `DELETE /api/admin/miniapps/:id` + +软删除小程序(置 `deleted_at`)。 + +**路径参数**:`id`(uint64)。 + +**响应** + +```json +{ "code": 0, "msg": "ok" } +``` + +**curl 示例(列表)** + +```bash +curl "http://localhost:8080/api/admin/miniapps?status=1&page=1&size=20" \ + -H "Authorization: Bearer " +``` + +## 5. 数据模型 + +### 5.1 MiniProgram(被纳管小程序) + +| 字段 | 类型 | JSON | 说明 | +| --- | --- | --- | --- | +| `ID` | uint64 | `id` | 主键,自增。 | +| `Name` | string | `name` | 名称。 | +| `Description` | string | `description` | 描述。 | +| `AppID` | string | `app_id` | 微信 AppID,全局唯一。 | +| `AppSecret` | string | `app_secret` | **不返回**(json `-`)。 | +| `Status` | int8 | `status` | `0` 停用,`1` 启用。 | +| `Ext` | string | `ext` | 扩展 JSON 文本。 | +| `CreatedAt` | time | `created_at` | 创建时间。 | +| `UpdatedAt` | time | `updated_at` | 更新时间。 | + +### 5.2 User(小程序用户) + +| 字段 | 类型 | JSON | 说明 | +| --- | --- | --- | --- | +| `ID` | uint64 | `id` | 主键,自增。 | +| `MiniID` | uint64 | `mini_id` | 所属小程序主键(关联 `mini_programs.id`)。 | +| `OpenID` | string | `openid` | 微信 openid。 | +| `UnionID` | string | `union_id` | 微信 unionid(可为空)。 | +| `Nickname` | string | `nickname` | 昵称。 | +| `Avatar` | string | `avatar` | 头像 URL。 | +| `CreatedAt` | time | `created_at` | 创建时间。 | +| `UpdatedAt` | time | `updated_at` | 更新时间。 | + +> 用户由 `(mini_id, open_id)` 唯一标识。 + +## 6. 配置说明 + +配置由 `configs/config.yaml` + 环境变量(支持 `${ENV}` 占位符)加载。敏感信息(DB 密码、JWT Secret)应通过环境变量注入,禁止硬编码。 + +| 配置项 | 说明 | +| --- | --- | +| `server.port` / `server.mode` | 服务端口、运行模式(`release` 时 gin 进入 ReleaseMode)。 | +| `mysql.*` | MySQL 连接与连接池参数,拼装为 DSN。 | +| `logging.level` / `logging.format` | 日志级别与格式(text/json)。 | +| `admin.jwt_secret` | 后台 JWT 签名密钥。 | +| `admin.token_ttl` | 后台 JWT 有效期(如 `24h`)。 | + +## 7. 备注与后续扩展 + +- **小程序会话**:`/api/ma/login` 返回的 token 为会话凭证,当前示例在 `/api/ma` 下仅实现 `login`;业务接口应在路由中增加基于该 token 的用户身份中间件后再登记。 +- **后台账号**:`/api/admin/login` 当前为固定示例账号,生产环境需接入数据库与密码哈希校验。 +- **会话存储**:默认 `MemoryStore`(单实例),多实例部署应替换为 Redis 实现(复用 `session.Store` 接口)。 +- **多小程序**:新增 `app2`..`app10` 时,按 `internal/handler/miniapp/app1`、`internal/service/miniapp/app1` 的目录结构各自建包,在 `router.New` 中注册即可,互不耦合。 diff --git a/.codebuddy/rules/go-backend-standards.md b/.codebuddy/rules/go-backend-standards.md new file mode 100644 index 0000000..0e9e3ed --- /dev/null +++ b/.codebuddy/rules/go-backend-standards.md @@ -0,0 +1,104 @@ +--- +description: Go 后端项目 portal-mini-go 的核心开发规范,涵盖错误处理、目录结构、API 约定、Context、并发、测试与命名等需要人工/AI 遵守的约定。适用于本项目所有 .go 代码。 +globs: "**/*.go" +alwaysApply: true +--- + +# portal-mini-go 后端开发规范 + +你是一名高级 Go 后端工程师。在编写、修改或审查本项目(portal-mini-go)的 Go 代码时,必须遵循以下规范。机器可自动处理的(gofmt/goimports/go vet)交由 lint 工具,以下约定需主动遵守。 + +## 0. 项目定位 +- portal-mini-go 是基于 Go 的中后台/门户类后端服务。 +- 语言版本:Go 1.22+。module 名:`portal-mini-go`。 +- 未在本规则中明确约定的,遵循 [Effective Go](https://go.dev/doc/effective_go) 与 Google Go Style Guide。 + +## 1. 推荐技术栈(默认;若本项目实际采用不同框架,修改本规则对应条目并保持一致) +- Web 框架:gin +- 参数校验:go-playground/validator/v10(`binding` tag) +- 配置:viper + YAML,支持环境变量覆盖 +- 日志:`log/slog`(结构化;生产 JSON、本地 text) +- 数据库:GORM(MySQL / PostgreSQL) +- 缓存:go-redis/redis +- 测试:`testing` + `testify`;mock 用 `gomock` 或 testify mock +- 错误包装:`fmt.Errorf("...: %w", err)` + +## 2. 目录结构(强制) +``` +cmd/ # 各可执行程序入口(main 包) +internal/ # 私有业务代码,禁止被外部模块 import + handler/ # HTTP 接入层 + service/ # 业务逻辑 + repository/ # 数据访问(DB/Redis) + model/ # 领域模型/实体 + pkg/ # 内部可复用工具(非对外公开) +pkg/ # 对外公开、可安全被外部引用的公共库 +configs/ # 配置文件模板 +scripts/ # 构建/部署脚本 +``` +- 业务代码放 `internal/`,对外公开库放 `pkg/`。 +- 依赖方向:`handler → service → repository → model`,禁止反向依赖与循环依赖。 +- 禁止在 handler 中直接写 SQL 或调用 DB,必须经 service / repository 层。 + +## 3. 错误处理(重点,必须) +- 永远不要忽略 error;`_ = f()` 必须加注释说明为何可忽略,否则必须处理。 +- 向上传播错误使用 `fmt.Errorf("上下文: %w", err)` 保留错误链路。 +- 本层能合理处理才处理,否则向上返回;最终在 handler 层统一转换为 HTTP 状态码/响应。 +- 禁止用 `panic` 控制业务流程;`recover` 仅允许用于 goroutine 顶层与框架中间件。 +- 业务错误用哨兵变量或实现 `error` 接口的类型,并携带可被前端消费的错误码(如 `var ErrNotFound = errors.New("not found")`)。 + +## 4. 命名与风格 +- 遵循 Go 惯例:导出标识符用驼峰;缩略语全大写(`ID`、`URL`、`HTTP`、`API`)。 +- 文件名小写下划线:`user_handler.go`。 +- 方法接收者用类型缩写(`u *User`),禁止 `this` / `self`。 +- 接口优先做小接口(单方法,常以 `-er` 结尾,如 `Reader`、`Closer`)。 +- 错误变量用 `Err` 前缀;包级常量用 `const` 块,避免裸魔法数字。 + +## 5. 日志 +- 统一使用 `log/slog`,禁止 `fmt.Println` / `log.Print` 记录业务日志。 +- 关键路径记录 trace_id(从 context 取)、处理耗时、参数摘要;**禁止打印敏感信息与超大对象**。 +- 级别:debug(开发)、info(正常)、warn(可恢复异常)、error(需告警)。 +- 记录错误时必须带原始 error:`slog.Any("err", err)`。 + +## 6. Context 与超时 +- 所有跨进程调用(HTTP / DB / Redis / RPC)必须把 `context.Context` 作为**第一个参数**接收并向下透传。 +- 不在 struct 字段中存储 context;用 `context.WithTimeout` / `WithDeadline` 设置超时。 +- 长生命周期场景禁止用 `time.After`(会泄漏),改用 `time.NewTimer` 并显式 `Stop`。 + +## 7. 并发 +- 共享状态必须用 `sync.Mutex` / `atomic` / channel 同步,明确数据归属。 +- 每个 `go` 语句必须有明确的生命周期与退出条件;主 goroutine 退出前用 `sync.WaitGroup` 或 `errgroup` 等待。 +- 禁止无限制启动 goroutine;批量任务用受限 worker / 带缓冲 channel。 +- 一组相关 goroutine 优先用 `golang.org/x/sync/errgroup` 管理错误与取消。 + +## 8. HTTP / API +- RESTful 风格;统一响应结构: + ```go + type Response struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data,omitempty"` + } + ``` +- 用统一中间件将 error 转换为响应,handler 内不直接散落 `c.JSON(http.StatusXxx, ...)` 处理错误。 +- 入参用 struct + validator 校验(`binding:"required"` 等),禁止信任未校验的客户端输入。 +- 所有 DB 查询使用参数化(GORM 占位符 / 预编译),禁止字符串拼接 SQL,杜绝注入。 + +## 9. 测试 +- 核心 service / repository 必须写**表驱动测试**。 +- 断言用 `testify/require`(失败即停)或 `assert`;测试文件 `_test.go` 与被测同包或 `_test` 包。 +- 外部依赖(DB / Redis / 下游服务)用接口 + mock 隔离,不在单测中依赖真实中间件。 +- 提交前保证 `go test ./...` 通过;核心业务逻辑覆盖率目标 ≥ 60%。 + +## 10. 配置与密钥 +- 密钥、连接串、token 绝不硬编码或提交仓库;从环境变量 / 密钥管理服务读取。 +- 配置优先级:默认值 < 配置文件 < 环境变量 < 命令行参数。 +- 本地 `.env` 必须加入 `.gitignore`。 + +## 11. 提交与协作 +- 提交信息遵循 Conventional Commits(`feat` / `fix` / `refactor` / `chore` / `docs` / `test`)。 +- 每次变更须通过 `go vet`、`golangci-lint run`、`go test ./...` 后再提交。 +- 不向主干(如 `main`)直接 push;重大改动走 PR / 评审。 + +--- +> 当本规则与具体实现的第三方库文档冲突时,以库文档为准;若确定采用不同于"推荐技术栈"的框架,请同步修改本规则第 1 条,保持规则与代码一致。 diff --git a/.gitignore b/.gitignore index 848e57c..a8e65fa 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,3 @@ vendor/ # 测试与覆盖 *.test coverage.out - -# Agent/IDE 项目元数据 -.codebuddy/