Files
zt-closet-manage-backend/.codebuddy/plans/zt-closet-manage-backend_2515957e.md
T
2026-09-11 01:47:20 +08:00

230 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: zt-closet-manage-backend
overview: 参考 /root/zt-closet-backend 业务服务的代码风格(Gin+GORM+Redis+viper+JWT+OTel+slog 分层架构),在 /root/zt-closet-manage-backend 从零实现管理后台后端:管理员账号体系、仪表盘统计、用户/商品/订单退款/内容审核/通知推送七大模块,与业务服务共用同一 MySQL 库。
todos:
- id: setup-skeleton
content: 搭建项目骨架:go.mod 依赖裁剪、configs 模板、global、pkg/{setting,app,errcode,lzcwyjwt,tracing}、中间件链、redismodel/redisdao、main.go 初始化与优雅关闭、路由骨架与 .gitignore
status: completed
- id: data-layer
content: 定义数据层:mysqlmodel 引擎与全部业务表模型、admin_users 表、AutoMigrate 与初始超级管理员 seed
status: completed
dependencies:
- setup-skeleton
- id: admin-module
content: 实现管理员账号模块:登录/登出/刷新 token/改密、管理员 CRUD 与超级管理员权限控制
status: completed
dependencies:
- data-layer
- id: dashboard-module
content: 实现仪表盘统计模块:用户/订单/营收/待办等核心指标聚合接口
status: completed
dependencies:
- data-layer
- id: user-module
content: 实现用户管理模块:分页搜索/详情/封禁解封/额度与 VIP 人工调整(事务+流水)
status: completed
dependencies:
- data-layer
- id: goods-order-module
content: 实现商品管理(CRUD、上下架)与订单退款管理(列表/详情/退款审核状态流转)
status: completed
dependencies:
- data-layer
- id: content-notify-module
content: 实现内容审核(穿搭审核封禁、衣物管理、分享封禁)与通知推送(单用户/全量广播)
status: completed
dependencies:
- data-layer
- id: finalize
content: 编写 README 与接口文档,执行 go mod tidy、go build、go vet 验证整体可用性
status: completed
dependencies:
- admin-module
- dashboard-module
- user-module
- goods-order-module
- content-notify-module
---
## 产品概述
为「智能衣橱」小程序业务服务(zt-closet-backend)配套的管理后台后端服务(zt-closet-manage-backend)。采用 Go 语言实现,整体代码风格、目录分层、技术栈与业务服务保持一致。管理后台与业务服务共用同一 MySQL 业务库,直接读写业务数据表;管理员账号表(admin_users)新建在同一库中。管理员通过「账号密码 + JWT 双 token」方式登录鉴权。
## 核心功能
- **管理员账号**:账号密码登录(bcrypt 加密存储)、access/refresh 双 token 签发与刷新、登出、修改自己密码;超级管理员可增删改普通管理员、禁用/启用账号;初始超级管理员由配置在启动时幂等创建;被禁用账号的 token 立即失效。
- **仪表盘统计**:用户总数/今日新增、订单总数/各状态分布、累计营收与今日营收(已支付订单,金额单位分)、待处理退款数、上架商品数、待审核穿搭数等核心指标一次接口返回。
- **用户管理**:用户分页列表(按昵称/手机号/OpenID/状态搜索)、用户详情(含四类额度与 VIP 信息)、封禁/解封(临时/永久,写封禁原因与期限)、额度人工调整(事务更新 user_quotas + 写 quota_flow_logs 人工调整流水)、VIP 人工调整(事务更新 users + 写 vip_flow_logs 流水)。
- **商品管理**:商品分页列表(含下架商品)、新增、编辑、上下架、软删除;维护会员天数与四类额度权益字段,价格单位分。
- **订单与退款管理**:订单分页列表(按状态/订单号/用户筛选)、订单详情(含支付与退款信息)、退款申请列表与审核——审核通过将退款单从「待处理」置为「退款中」(实际打款由运营在微信商户平台人工完成,业务服务回调联动状态),驳回置为「失败」并记录原因。
- **内容审核**:公开穿搭列表与详情(含衣物明细)、审核通过/驳回(写 outfit_audit_logs)、穿搭封禁/解封;衣物分页列表与详情(按用户/处理状态筛选);衣柜分享列表、封禁/解封。
- **通知推送**:向单个用户或全量用户发送系统通知(写 user_notifications,msg_type=3 系统、sender_id=0,并同步 Redis 未读计数)。
所有接口沿用业务服务约定:统一前缀 `/api/v1`、统一响应 `{code, msg, data}`、分页 `page/page_size`、金额单位分、错误码分段管理。
## 技术栈
- 语言:Go(go.mod 现为 go 1.26.1,依赖版本全部对齐业务服务 /root/zt-closet-backend/go.mod)
- Web 框架:Gin v1.12.0
- ORM/数据库:GORM v1.31.2 + MySQL(含 soft_delete 软删除插件)
- 缓存:go-redis v9(管理员 token 会话、通知未读计数)
- 配置:spf13/viper(仅读 configs/config.yaml,不支持环境变量覆盖,与业务服务一致)
- 鉴权:golang-jwt/v5(HS256 双 token)+ golang.org/x/crypto/bcrypt(密码哈希)
- 可观测性:OpenTelemetry(trace/metric/log 三信号 OTLP gRPC 上报,otelgin + otelslog)+ log/slog + lumberjack(JSON 日志按大小轮转压缩,注入 trace_id/span_id)
- 依赖裁剪:不引入 COS / 微信支付 / Tokenhub / 上传 / 迁移(golang-migrate)相关依赖
## 实现方案
### 总体策略
完全复刻业务服务已验证的「handler → service → dao → model」四层架构与工程规范:`pkg/app` 统一响应、`pkg/errcode` 注册式错误码、`internal/dto` 请求响应 DTO、`internal/middleware` 中间件链、`internal/service/constants.go` 枚举常量、每层方法开启 tracing span、`global/` 全局单例。管理后台连接业务库与 Redis,读写业务表时严格遵守业务服务的枚举语义与流水口径(额度/VIP 人工调整流水、退款状态机、穿搭可见性规则等)。
```mermaid
flowchart LR
subgraph 请求链
A[HTTP 请求] --> B[中间件链<br/>Recovery/RequestID/OTel/Slog/CORS]
B --> C[JWTAuth 管理员鉴权]
C --> D[Handler routers/api/v1]
end
D --> E[Service 业务逻辑层]
E --> F[mysqldao GORM 数据访问]
E --> G[redisdao 会话/计数]
F --> H[(共享 MySQL 业务库<br/>users/goods/orders...<br/>admin_users 新建)]
G --> I[(Redis<br/>admin token 会话/未读计数)]
```
### 关键设计决策
1. **建表方案**:业务库的 schema_migrations 由业务服务的 golang-migrate 管理,管理后台**不引入** golang-migrate,改用启动时 GORM AutoMigrate 创建 `admin_users` 表并幂等 seed 初始超级管理员(账号密码来自配置 Admin 段),避免版本冲突。
2. **JWT 隔离**:管理后台独立的 `pkg/lzcwyjwt`(Claims 用 `AdminID` 替代 `UserID`,函数签名风格保持一致),配置使用独立 `JWT` 段的 SecretKey/Issuer,与业务服务 token 不互通(Issuer + SecretKey 双重隔离);access token 会话写 Redis(仿业务服务 redisdao/auth.go),管理员禁用/删除立即失效。
3. **权限控制**:`admin_users.Role`(1 超级管理员 / 2 普通管理员);管理员账号管理接口用 `AdminSuperAuth` 中间件校验角色;普通管理员禁用状态在 JWTAuth 中回查数据库拒绝(仿业务服务「Redis 失败 fail-open + DB 状态回查」策略)。
4. **额度人工调整**:事务内 `SELECT ... FOR UPDATE` 锁 user_quotas 行 → 更新 Total(保证 total ≥ used)→ 写 quota_flow_logs(ChangeType=5 人工调整、ChangeTarget=1、ChangeNum/OldValue/NewValue/Reason,OrderNo 用流水 UUID 填充以满足 `(order_no, quota_type, change_type)` 联合唯一索引)。
5. **VIP 人工调整**:事务内更新 users.VIPType/VIPStart/VIPExpire → 写 vip_flow_logs(ChangeType=5 人工调整、Source=4、OrderNo 用流水 UUID)。
6. **退款审核状态机**:仅允许 `0 待处理 → 1 退款中`(通过,写审核备注)与 `0 待处理 → 3 失败`(驳回,写原因);实际微信打款由运营在商户平台手动完成,业务服务退款回调负责后续 2 成功流转与权益回收。
7. **通知推送**:写 user_notifications(msg_type=3、sender_id=0、is_read=1)成功后 best-effort 对 Redis 未读计数自增(仿业务服务通知实现);全量广播按用户分批(每批 500)遍历插入,避免单事务过大与内存暴涨。
8. **仪表盘统计**:对业务表做 COUNT/SUM 聚合查询(用户、订单各状态、已支付订单营收、今日新增、待处理退款、待审穿搭等),一次接口内并行/顺序聚合后组装返回。
9. **错误码**:复用公共段(10000 参数 / 11000 鉴权 / 12000 权限 / 19000 系统),管理域新开段位:15000-15999 管理员域、16000-16999 用户管理域、17000-17999 商品域、18000-18999 订单退款域、20000-20999 内容审核域、21000-21999 通知域(独立进程,与业务服务码值无冲突)。
### 实现注意事项
- **性能**:列表一律分页(page_size ≤ 50)+ 索引字段筛选(username/wx_open_id/phone/order_no/status);用户详情一次 IN 查询四类额度避免 N+1;穿搭详情连查 outfit_items + clothes;全量广播分批插入。
- **安全**:密码 bcrypt 哈希;初始管理员密码仅存配置(不落库明文);JWT SecretKey 启动强校验非空;所有 SQL 走 GORM 参数绑定;请求日志沿用脱敏规则(token/password 等字段打码);封禁/调整操作记录管理员 ID 与原因(CreatedBy/Reason)。
- **日志与可观测性**:沿用业务服务 setup 顺序(setting → otel → slog → mysql → redis),ServiceName 使用 `zt-closet-manage-backend`;OTel 初始化失败降级 no-op 不阻断启动;优雅关闭含 OTel flush(独立 5s context)。
- **blast radius**:只读业务表时一律走 soft_delete 过滤(deleted_at 为空);不修改业务服务已有代码;商品软删沿用 Model 基座;业务表结构不 AutoMigrate(避免误改业务表),仅 admin_users 由管理后台迁移。
## 目录结构
```
/root/zt-closet-manage-backend/
├── main.go # [MODIFY] 入口:setupSetting→setupOTel→setupSlogLogger→setupMysqlEngine→setupRedisEngine→seed 管理员→路由→http.Server→优雅关闭
├── go.mod # [MODIFY] 按业务服务裁剪依赖清单
├── .gitignore # [NEW] 忽略 config.yaml/storage/二进制
├── README.md # [NEW] 部署、配置、接口说明(仿业务服务风格)
├── configs/
│ └── config.example.yaml # [NEW] Server/Log/Mysql/Redis/OTel/JWT/Admin 段模板
├── global/
│ ├── setting.go # [NEW] 各配置段全局指针(Server/Log/Mysql/Redis/Otel/JWT/Admin)
│ ├── mysql.go # [NEW] global.MysqlEngine
│ ├── redis.go # [NEW] global.RedisEngine
│ └── otel.go # [NEW] global.Tracer/Meter/OTelShutdown
├── pkg/
│ ├── app/response.go # [NEW] 统一响应 {code,msg,data}(复刻业务服务)
│ ├── errcode/
│ │ ├── errcode.go # [NEW] NewError 注册式错误码 + StatusCode 映射
│ │ ├── common_code.go # [NEW] 公共段 10000-19999(复刻)
│ │ └── admin_code.go # [NEW] 管理域 15000-21999 分段错误码
│ ├── lzcwyjwt/lzcwyjwt.go # [NEW] Claims{AdminID,TokenType},GenerateTokens/ParseAccessToken/ParseRefreshToken
│ ├── setting/
│ │ ├── setting.go # [NEW] viper 仅读 configs/config.yaml
│ │ └── section.go # [NEW] Server/Log/Mysql/Redis/Otel/JWT/Admin 配置结构体
│ └── tracing/
│ ├── provider.go # [NEW] OTel Provider(失败降级 no-op)
│ ├── otel.go # [NEW] otelgin 中间件适配
│ └── fanout.go # [NEW] slog fanout handler + OtelContextHandler
├── internal/
│ ├── dto/
│ │ ├── common.go # [NEW] PageRequest/PageResponse
│ │ ├── admin.go # [NEW] 登录/刷新/改密/管理员 CRUD DTO
│ │ ├── dashboard.go # [NEW] 统计响应 DTO
│ │ ├── user.go # [NEW] 用户列表/详情/封禁/额度/VIP 调整 DTO
│ │ ├── goods.go # [NEW] 商品 CRUD DTO
│ │ ├── order.go # [NEW] 订单/退款审核 DTO
│ │ ├── content.go # [NEW] 穿搭审核/衣物/分享 DTO
│ │ └── notification.go # [NEW] 通知推送 DTO
│ ├── middleware/
│ │ ├── requestid.go # [NEW] X-Request-ID(复刻)
│ │ ├── otel.go # [NEW] GinOTelMiddleware(复刻)
│ │ ├── logger.go # [NEW] GinSlogMiddleware 访问日志+脱敏(复刻)
│ │ ├── cors.go # [NEW] CORS(复刻)
│ │ ├── jwtauth.go # [NEW] 管理员 JWTAuth(token→Redis 会话→DB 状态回查→AdminID 注入 ctx)+ AdminSuperAuth 超管校验
│ ├── mysqldao/
│ │ ├── admin.go # [NEW] 管理员账号 DAO(含按用户名查询、分页)
│ │ ├── user.go # [NEW] 用户分页/详情/封禁/额度/VIP 更新 DAO
│ │ ├── goods.go # [NEW] 商品 CRUD DAO
│ │ ├── order.go # [NEW] 订单分页/详情 DAO
│ │ ├── refund.go # [NEW] 退款单分页/详情/状态流转 DAO
│ │ ├── outfit.go # [NEW] 穿搭分页/详情/审核/封禁 + audit log DAO
│ │ ├── clothes.go # [NEW] 衣物分页/详情 DAO
│ │ ├── closet.go # [NEW] 分享分页/封禁 DAO
│ │ ├── notification.go # [NEW] 通知批量插入 DAO
│ │ └── stats.go # [NEW] 仪表盘聚合统计 DAO
│ ├── mysqlmodel/
│ │ ├── model.go # [NEW] Model 基座 + NewMysqlEngine(复刻)+ AutoMigrate(admin_users)
│ │ ├── adminuser.go # [NEW] AdminUser 模型(表 admin_users)
│ │ └── business.go # [NEW] 业务表模型:User/Goods/Order/RefundFlowLog/Outfit/OutfitItem/OutfitAuditLog/Clothes/ClosetShare/Quota/QuotaFlowLog/VIPFlowLog/UserNotification(结构与业务服务一致)
│ ├── redisdao/
│ │ ├── auth.go # [NEW] 管理员 token 会话存取/删除
│ │ └── notification.go # [NEW] 通知未读计数自增
│ ├── redismodel/redismodel.go # [NEW] NewRedisEngine + key 常量
│ ├── routers/
│ │ ├── router.go # [NEW] 中间件链 + /ping + 公开/鉴权路由注册
│ │ └── api/v1/
│ │ ├── admin.go # [NEW] 登录/刷新/登出/改密/管理员 CRUD handler
│ │ ├── dashboard.go # [NEW] 仪表盘 handler
│ │ ├── user.go # [NEW] 用户管理 handler
│ │ ├── goods.go # [NEW] 商品管理 handler
│ │ ├── order.go # [NEW] 订单/退款 handler
│ │ ├── content.go # [NEW] 穿搭/衣物/分享 handler
│ │ └── notification.go # [NEW] 通知推送 handler
│ └── service/
│ ├── service.go # [NEW] normalizePage/isNotFound 等公共函数
│ ├── constants.go # [NEW] 管理后台枚举常量(对齐业务服务语义)
│ ├── admin.go # [NEW] 登录鉴权/密码/管理员管理逻辑(bcrypt、seed)
│ ├── dashboard.go # [NEW] 统计组装
│ ├── user.go # [NEW] 用户管理(封禁/额度/VIP 调整事务+流水)
│ ├── goods.go # [NEW] 商品业务逻辑
│ ├── order.go # [NEW] 订单/退款审核状态机
│ ├── content.go # [NEW] 穿搭审核/封禁、衣物、分享封禁
│ └── notification.go # [NEW] 单用户/全量广播
```
## 关键代码结构
```
// internal/mysqlmodel/adminuser.go —— 管理员账号表(管理后台唯一新建表)
type AdminUser struct {
Model
Username string `gorm:"column:username;uniqueIndex:uk_admin_username"` // 登录账号
PasswordHash string `gorm:"column:password_hash"` // bcrypt 哈希
Nickname string `gorm:"column:nickname"` // 显示名
Role int8 `gorm:"column:role"` // 1=超级管理员 2=普通管理员
Status int8 `gorm:"column:status"` // 1=启用 2=禁用
LastLoginTime int64 `gorm:"column:last_login_time"` // 最近登录时间
}
// pkg/lzcwyjwt/lzcwyjwt.go —— 管理员 JWT Claims(与业务服务接口风格一致,字段替换为 AdminID)
type Claims struct {
AdminID int64 `json:"admin_id"`
TokenType TokenType `json:"token_type"`
jwt.RegisteredClaims
}
func GenerateTokens(adminID int64) (accessToken, refreshToken string, err error)
func ParseAccessToken(tokenString string) (*Claims, error)
func ParseRefreshToken(tokenString string) (*Claims, error)
```
## API 路由规划(前缀 /api/v1)
- 公开:`GET /ping`、`POST /admin/login`、`POST /admin/refresh-token`
- 鉴权(JWT):`POST /admin/logout`、`PUT /admin/password`;`GET /dashboard/summary`;`GET /users`、`GET /users/:uuid`、`PUT /users/:uuid/ban`、`PUT /users/:uuid/unban`、`PUT /users/:uuid/quota`、`PUT /users/:uuid/vip`;`GET /goods`、`POST /goods`、`PUT /goods/:id`、`PUT /goods/:id/status`、`DELETE /goods/:id`;`GET /orders`、`GET /orders/:orderNo`;`GET /refunds`、`POST /refunds/:refundNo/approve`、`POST /refunds/:refundNo/reject`;`GET /outfits`、`GET /outfits/:uuid`、`POST /outfits/:uuid/audit`、`POST /outfits/:uuid/ban`、`POST /outfits/:uuid/unban`;`GET /clothes`、`GET /clothes/:uuid`;`GET /closet-shares`、`POST /closet-shares/:uuid/ban`、`POST /closet-shares/:uuid/unban`;`POST /notifications`、`POST /notifications/broadcast`
- 超管专属:`GET /admins`、`POST /admins`、`PUT /admins/:id`、`DELETE /admins/:id`