303207d3ccc2a0696d2ac5593db42f99aa7638a1
zt-closet-manage-backend
「智能衣橱」小程序(zt-closet-backend)的管理后台后端服务。采用与业务服务一致的 Go + Gin 技术栈与代码风格(四层架构、统一响应、注册式错误码、OpenTelemetry 可观测性),与业务服务共用同一 MySQL 业务库,直接读写业务数据表;管理员账号表(admin_users)由本服务自行维护。
核心功能
- 管理员账号:账号密码登录(bcrypt 存储)、JWT 双 token(access/refresh)签发与刷新、登出、修改密码;超级管理员可增删改普通管理员、禁用/启用账号;初始超级管理员由配置在启动时幂等创建;禁用/删除账号的会话立即失效。
- 仪表盘统计:用户总数/今日新增/各状态分布、订单总数/各状态分布、累计与今日营收、累计退款、待处理退款数、待审核穿搭数、上架商品数,一次接口聚合返回。
- 用户管理:分页搜索(昵称/手机号/OpenID/状态)、详情(含永久额度 + 每日额度 + 额度凭证列表)、封禁/解封(临时按天/永久,写原因与期限)、额度发放(有有效期额度凭证)、额度凭证回收、VIP 人工调整(事务 + 人工调整流水)。
- 商品管理:分页列表(含下架)、新增、编辑(含
valid_days额度有效期天数)、上下架、软删除;虚拟支付道具 ID 与发布状态只读展示。 - 订单与退款管理:订单列表/详情(含虚拟支付 pay_channel=2 与 XPay 平台/环境/道具字段);退款申请审核——通过置「退款中」(实际打款由运营在微信商户平台手动完成)、驳回置「失败」并记录原因。
- 内容审核:公开穿搭列表/详情(含衣物明细)、审核通过/驳回(写
outfit_audit_logs)、封禁/解封;衣物列表/详情;衣柜分享列表、封禁/解封。 - 通知推送:单用户系统通知、全量广播(分批写入
user_notifications+ 同步 Redis 未读计数,小程序端实时可见)。
技术栈
| 分类 | 技术 |
|---|---|
| 语言 | Go 1.26 |
| Web 框架 | Gin v1.12 |
| ORM / 数据库 | GORM + MySQL(含 soft_delete 软删除) |
| 缓存 | go-redis v9(管理员 token 会话、通知未读计数) |
| 配置 | spf13/viper(仅读 YAML 文件) |
| 鉴权 | golang-jwt/v5(HS256 双 token)+ bcrypt |
| 可观测性 | OpenTelemetry(otelgin、otelslog、OTLP gRPC 三信号) |
| 日志 | log/slog + lumberjack(JSON、按大小轮转压缩) |
目录结构
zt-closet-manage-backend/
├── main.go # 入口:初始化配置/OTel/日志/MySQL/Redis,建表+seed 超管,启动 HTTP 服务
├── configs/
│ └── config.example.yaml # 配置模板(含注释说明)
├── global/ # 全局单例:配置指针、DB/Redis 引擎、OTel Tracer/Meter
├── internal/
│ ├── dto/ # 请求/响应 DTO 与分页结构
│ ├── middleware/ # RequestID / OTel / 请求日志(脱敏) / CORS / 管理员 JWT / 超管权限
│ ├── mysqldao/ # MySQL 数据访问层(tracing span 命名 mysqldao.Xxx)
│ ├── mysqlmodel/ # GORM 模型:admin_users(自建)+ 业务表模型镜像
│ ├── redisdao/ # Redis 数据访问层(管理员会话、通知未读计数)
│ ├── redismodel/ # Redis 引擎初始化与 key 常量
│ ├── routers/ # 路由注册(router.go)与各 handler(api/v1)
│ └── service/ # 业务逻辑层(常量 + 各业务域 Service)
└── pkg/
├── app/ # HTTP 统一响应封装 {code, msg, data}
├── errcode/ # 统一业务错误码(公共段 + 管理域分段)
├── lzcwyjwt/ # 管理员 JWT 生成/解析(Claims.AdminID)
├── setting/ # viper 配置读取与配置结构体
└── tracing/ # OpenTelemetry 初始化与日志桥接
快速开始
1. 环境依赖
- Go 1.26+
- MySQL(与业务服务共用同一个库,业务表由业务服务的迁移脚本维护)
- Redis(与业务服务共用同一实例与 DB,保证通知未读计数互通)
2. 配置准备
cp configs/config.example.yaml configs/config.yaml
编辑 configs/config.yaml,关键项:
Mysql:填写与业务服务相同的数据库连接信息(DBName一致)。Redis:填写与业务服务相同的 Redis 连接信息(DB一致,保证未读计数 key 互通)。JWT.SecretKey:必填,须为强随机字符串,且与业务服务使用不同密钥(防止跨服务 token 混用)。Admin.InitialUsername/Admin.InitialPassword:初始超级管理员账号,服务启动时幂等创建(已存在则跳过)。
3. 启动
# 必须在项目根目录执行(配置路径固定为相对路径 configs/config.yaml)
go run .
启动时自动完成两件事(均在 main 中,依赖 MySQL 就绪):
- AutoMigrate 仅
admin_users表(业务表结构由业务服务迁移脚本维护,本服务不做任何业务表 DDL); - 幂等 seed 初始超级管理员(
Admin配置段)。
验证健康检查:
curl http://localhost:8080/ping
# {"message":"pong"}
配置说明
| 配置段 | 说明 | 关键字段 |
|---|---|---|
Server |
HTTP 服务 | Domain(CORS 允许域名,* 或不填允许所有来源)、RunMode、HttpPort、ReadTimeout/WriteTimeout |
Log |
日志 | LogSavePath、LogFileName、MaxSize/MaxBackups/MaxAge、LogLevel |
Mysql |
MySQL 连接池 | 用户名/密码/Host/Port/DBName 及连接池参数 |
Redis |
Redis 连接 | Host/Port/Password/DB 及连接池、重试、超时参数 |
OTel |
OpenTelemetry | Enabled、ServiceName、ExporterEndpoint(OTLP gRPC)、Insecure、SamplerRatio |
JWT |
鉴权 | SecretKey(必填)、Issuer、AccessTokenExpire、RefreshTokenExpire |
Admin |
初始超管 | InitialUsername、InitialPassword(仅首次创建时使用) |
Quota |
免费额度底值 | Cos/AI/OutfitShare/ClosetShare(仅用于展示「总额度」,须与业务服务 UserDefaultQuota 免费值一致,未配回落 20/3/10/5) |
Cos |
CDN 下载 | CosDownloadDomain、CdnDownloadSecretKey(用于把衣物图片/头像的相对 COS key 拼接为带鉴权签名的可访问 URL,须与业务服务 Cos 段一致) |
API 概览
- 统一前缀:
/api/v1 - 统一响应:成功
{"code":0,"msg":"成功","data":{...}};失败{"code":<错误码>,"msg":<文案>,"details":[...]}。判断业务结果一律以code为准。 - HTTP 状态码:鉴权类错误 401、权限 403、系统错误 500,其余 200。
- 鉴权方式:
Authorization: Bearer <access_token>(登录接口获取);access 过期(业务码11002)后用 refresh token 换取新 token。 - 分页约定:
page(≥1)、page_size(1~50);响应total/page/page_size/list。 - 金额单位:所有金额字段(
price、discount_price、pay_amount、refund_amount等)均为分(int64)。
公开路由(无需登录)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /ping |
健康检查 |
| POST | /admin/login |
管理员登录 |
| POST | /admin/refresh-token |
刷新 token(需旧 access + refresh) |
鉴权路由(需登录)
管理员(自己)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /admin/logout |
登出 |
| PUT | /admin/password |
修改自己密码 |
仪表盘
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /dashboard/summary |
核心指标聚合 |
用户管理(路径参数 :uuid 为用户 UUID)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /users |
用户列表(query:page/page_size/keyword/status) |
| GET | /users/:uuid |
用户详情(含永久额度 + 每日额度 + 额度凭证列表 grants) |
| PUT | /users/:uuid/ban |
封禁(json:ban_type 1临时/2永久、ban_days、reason) |
| PUT | /users/:uuid/unban |
解封 |
| PUT | /users/:uuid/quota |
额度发放(json:quota_type 1上传/2AI/3社区/4衣柜、amount、expire_days) |
| PUT | /users/:uuid/vip |
VIP 人工调整(json:vip_type、vip_days、reason) |
| DELETE | /users/:uuid/grants/:id |
回收额度凭证(软删) |
商品管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /goods |
商品列表(query:page/page_size/goods_type/status/keyword) |
| POST | /goods |
创建商品 |
| PUT | /goods/:id |
更新商品(传什么改什么) |
| PUT | /goods/:id/status |
上下架(json:status 1上架/2下架) |
| DELETE | /goods/:id |
软删除商品 |
订单与退款
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /orders |
订单列表(query:page/page_size/order_no/user_id/status) |
| GET | /orders/:orderNo |
订单详情 |
| GET | /refunds |
退款单列表(query:page/page_size/status/order_no) |
| POST | /refunds/:refundNo/approve |
审核通过(待处理→退款中) |
| POST | /refunds/:refundNo/reject |
审核驳回(待处理→失败,写原因) |
内容审核
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /outfits |
穿搭列表(query:page/page_size/user_id/is_public/audit_status/status/keyword) |
| GET | /outfits/:uuid |
穿搭详情(含衣物明细) |
| POST | /outfits/:uuid/audit |
审核(json:result 1通过/2驳回、reason) |
| POST | /outfits/:uuid/ban |
封禁穿搭 |
| POST | /outfits/:uuid/unban |
解封穿搭 |
| GET | /clothes |
衣物列表(query:page/page_size/user_id/image_process_status/category_main/keyword) |
| GET | /clothes/:uuid |
衣物详情 |
| GET | /closet-shares |
衣柜分享列表(query:page/page_size/user_id/status) |
| POST | /closet-shares/:uuid/ban |
封禁分享 |
| POST | /closet-shares/:uuid/unban |
解封分享 |
通知推送
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /notifications |
单用户通知(json:user_id、title、content) |
| POST | /notifications/broadcast |
全量广播(json:title、content;响应 sent_count) |
超级管理员专属路由
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admins |
管理员列表 |
| POST | /admins |
创建管理员 |
| PUT | /admins/:id |
更新管理员(昵称/密码/角色/状态) |
| DELETE | /admins/:id |
删除管理员(软删) |
关键业务规则
- 退款审核状态机:仅允许
0 待处理 → 1 退款中(通过)与0 待处理 → 3 失败(驳回)。实际微信打款由运营在微信商户平台手动完成,业务服务的退款回调负责后续「成功(2)」流转与权益回收。 - 额度体系(与业务服务一致):
user_quotas仅存 Cos 永久额度;AI/社区/衣柜 为每日额度;有有效期赠送额度走quota_grants凭证(source:1 会员包 / 2 加量包 / 3 广告激励)。管理后台「额度发放」写入quota_grants(source=2 加量包、ref_id=manual_<uuid>、expire_time=now+expire_days*86400):Cos 为一次性库存,AI/社区/衣柜 为有效期内每日加量;「凭证回收」软删除对应凭证。有效额度 = 免费配置值(Quota段)+ 有效凭证,每日已用读user_daily_usages。 - VIP 人工调整:叠加语义(
vip_expire = max(当前到期, now) + vip_days*86400),vip_days=0表示取消会员;事务内更新users+ 写vip_flow_logs(change_type=5、source=4)。 - 用户封禁:改
users.status(2 临时/3 永久)+ 原因与期限,业务服务鉴权中间件每次请求校验状态,改库即生效。 - 穿搭可见性:
is_public=2且audit_status=2且status=1三者同时满足才对外可见;审核驳回置audit_status=3、封禁置status=2。 - 通知未读计数:管理后台自增业务服务的同一 Redis key(
ztcloset:notification:unread:<user_id>),要求两者共用同一 Redis 实例与 DB。 - 管理员会话:access/refresh 双 token 会话写 Redis;禁用/删除管理员会失效其全部会话;JWTAuth 每次请求回查数据库状态,禁用立即生效(Redis 故障时 fail-open,但 DB 回查兜底拦截禁用账号)。
错误码
公共段与业务服务一致(0 成功、10000 参数、11000 鉴权、12000 权限、19000 系统);管理域独立分段:
| 段位 | 业务域 |
|---|---|
| 15000–15999 | 管理员(登录/账号) |
| 16000–16999 | 用户管理 |
| 17000–17999 | 商品 |
| 18000–18999 | 订单/退款 |
| 20000–20999 | 内容审核(穿搭/衣物/分享) |
| 21000–21999 | 通知 |
安全注意事项
JWT.SecretKey必须为强随机值且与业务服务不同,留空会导致启动失败;请勿使用默认占位值。Cos.CdnDownloadSecretKey为 CDN 下载鉴权密钥(机密),仅存configs/config.yaml(已被.gitignore忽略),且须与业务服务Cos段一致。- 管理员密码一律 bcrypt 哈希入库;初始密码仅存在于
configs/config.yaml(已被.gitignore忽略),首次登录后请立即修改。 - 所有 SQL 走 GORM 参数绑定;请求日志对
token/password等敏感参数自动脱敏。 - 登录接口对「账号不存在」与「密码错误」返回同一错误码,避免账号枚举攻击。
- 生产环境请将
Server.RunMode设为release,并通过反向代理对外提供 HTTPS。
部署提示
- 工作目录:启动时必须位于项目根目录(配置路径固定为
configs/config.yaml)。 - 与业务服务的关系:共用 MySQL 业务库与 Redis;业务表结构变更由业务服务迁移脚本负责,本服务只维护
admin_users表,不做任何业务表 DDL。 - 多实例:支持水平扩展;写操作通过事务与条件更新保证幂等(退款审核条件流转、额度调整行锁)。
- 可观测性后端:按需配置
OTel.ExporterEndpoint指向 OTLP Collector;无需可观测性时设置Enabled: false即可零开销关闭。
Languages
Go
99%
Dockerfile
1%