zhengchiliu 0863234bc5 feat(backend): 对齐业务服务额度与内容治理口径
仪表盘待办与穿搭概况改为公开/私密/已封禁口径,移除恒为零的待审核统计;补齐额度凭证来源 source=4 与流水 change_type=7 枚举及订单商品类型、额度有效期快照字段;管理员发放与回收额度凭证在同一事务内写入人工调整流水,回收流水使用独立 order_no 避免撞唯一索引,并增加凭证归属校验;对齐 Quota 配置免费额度值,同步更新 README 与配置说明。
2026-09-16 02:58:56 +08:00
2026-09-14 00:54:48 +08:00
2026-09-11 01:47:20 +08:00
2026-09-14 01:16:47 +08:00
2026-09-12 10:40:06 +08:00
2026-09-12 10:40:06 +08:00
2026-09-11 01:47:20 +08:00
2026-09-11 01:47:20 +08:00
2026-09-11 01:47:20 +08:00
2026-09-11 01:47:20 +08:00

zt-closet-manage-backend

「智能衣橱」小程序(zt-closet-backend)的管理后台后端服务。采用与业务服务一致的 Go + Gin 技术栈与代码风格(四层架构、统一响应、注册式错误码、OpenTelemetry 可观测性),与业务服务共用同一 MySQL 业务库,直接读写业务数据表;管理员账号表(admin_users)由本服务自行维护。

核心功能

  • 管理员账号:账号密码登录(bcrypt 存储)、JWT 双 token(access/refresh)签发与刷新、登出、修改密码;超级管理员可增删改普通管理员、禁用/启用账号;初始超级管理员由配置在启动时幂等创建;禁用/删除账号的会话立即失效。
  • 仪表盘统计:用户总数/今日新增/各状态分布、订单总数/各状态分布、累计与今日营收、累计退款、待处理退款数、公开展示中的穿搭数、已封禁穿搭数、上架商品数,一次接口聚合返回。
  • 用户管理:分页搜索(昵称/手机号/OpenID/状态)、详情(含永久额度 + 每日额度 + 额度凭证列表)、封禁/解封(临时按天/永久,写原因与期限)、额度发放(有有效期额度凭证,同事务写人工调整流水)、额度凭证回收(同事务写人工调整流水)、VIP 人工调整(事务 + 人工调整流水)。
  • 商品管理:分页列表(含下架)、新增、编辑(含 valid_days 额度有效期天数)、上下架、软删除;支持登记/修改微信虚拟支付道具 ID 与发布状态(仅「未发布/已发布」两态,已发布必须填道具 ID,道具 ID 全局唯一),运营无需再手工改库。运营约定:道具在微信后台手动创建;改价后必须同步微信侧道具价格,否则用户付款后发货金额校验会拒绝(拿不到权益)。
  • 订单与退款管理:订单列表/详情(含商品类型与额度有效期天数快照、虚拟支付 pay_channel=2 与 XPay 平台/环境/道具字段);退款申请审核——通过置「退款中」(实际打款由运营在微信商户平台手动完成)、驳回置「失败」并记录原因。
  • 内容审核:公开穿搭列表/详情(含衣物明细)、封禁/解封(主要治理手段)、审核通过/驳回(保留但已无待审数据,见「关键业务规则」);衣物列表/详情;衣柜分享列表、封禁/解封。
  • 通知推送:单用户系统通知、全量广播(分批写入 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 就绪):

  1. AutoMigrate 仅 admin_users 表(业务表结构由业务服务迁移脚本维护,本服务不做任何业务表 DDL);
  2. 幂等 seed 初始超级管理员(Admin 配置段)。

验证健康检查:

curl http://localhost:8081/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/3/0;未配置则回落内置默认 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 核心指标聚合(todo 为:refund_pending 待处理退款、outfit_public_total 公开展示中的穿搭数、outfit_banned 已封禁穿搭数、goods_on_shelf 上架商品数;content.outfit 为公开/私密/已封禁分布)

用户管理(路径参数 :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);同事务写 quota_flow_logs 人工调整流水
PUT /users/:uuid/vip VIP 人工调整(json:vip_type、vip_days、reason)
DELETE /users/:uuid/grants/:id 回收额度凭证(软删 + 归属校验);同事务写 quota_flow_logs 人工调整流水

商品管理

方法 路径 说明
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/transaction_id/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)。兼容保留:业务侧公开即自动过审,实际已无 audit_status=1 的数据可操作,仅允许操作待审记录,否则返回状态不允许
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 广告激励 / 4 体验会员赠送)。管理后台「额度发放」写入 quota_grants(source=2 加量包、ref_id=manual_<uuid>、expire_time=now+expire_days*86400):Cos 为一次性库存,AI/社区/衣柜 为有效期内每日加量;「凭证回收」软删除对应凭证,仅「未用完的部分」失效(Amount-Used)。有效额度 = 免费配置值(Quota 段,须与业务服务 UserDefaultQuota 一致)+ 有效凭证,每日已用读 user_daily_usages。
  • 额度流水闭环:管理后台的「额度发放」「凭证回收」在同一事务内写 quota_flow_logs(change_type=5 人工调整、change_target=1),流水的 old_value/new_value 取事务内真实剩余额度,保证「变更前 + 变更量 = 变更后」;发放流水的 order_no 用凭证 ref_id(与业务侧到期失效流水按 ref_id 关联的口径一致),回收流水使用独立的 revoke_<uuid>,避免撞 uk_quota_flow_idem(order_no, quota_type, change_type) 唯一索引。
  • 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(公开即自动过审),因此本管理后台的「审核通过/驳回」已无待审数据(接口保留仅为兼容),仪表盘也不再输出「待审核穿搭」,改为输出「公开穿搭总数(不含已封禁)」与「已封禁穿搭数」。对外可见条件为 is_public=2 且 audit_status=2 且 status=1,内容治理以封禁(status=2)为主:封禁后立即从小程序社区、衣物社区入口与穿搭分享中隐藏,且用户自行「取消公开 → 再次公开」也无法恢复可见(公开动作只改 is_public/audit_status,不改 status)。
  • 通知未读计数:管理后台自增业务服务的同一 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。

CI/CD 与部署

流水线(Gitea Actions)

.gitea/workflows/deploy.yaml 提供「构建并部署」流水线:

触发方式 说明
推送 v* 标签 如 git tag v1.0.0 && git push origin v1.0.0
手动触发 Gitea 仓库页面 Actions 标签页 → 选择「构建并部署」→ Run workflow

流程:拉取代码 → 构建镜像(多阶段 Dockerfile,静态编译)→ 推送 git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend(tag + latest)→ SSH 部署生产机 → /ping 健康检查;检查失败自动输出日志并回滚到旧镜像。流程结束后通过钉钉机器人推送成功/失败通知(含仓库、分支/标签、提交、触发者、镜像、耗时与运行详情链接)。

管理后台无数据库迁移命令(admin_users 表由服务启动时 GORM AutoMigrate 幂等创建),因此没有业务服务那样的迁移流水线。

Gitea 仓库 Secrets / Variables(需自行创建)

类型 名称 说明
Secret ZT_CLOSET_REGISTRY_USER Gitea 镜像仓库登录用户名
Secret ZT_CLOSET_REGISTRY_TOKEN Gitea 镜像仓库登录令牌
Secret ZT_CLOSET_PROD_SSH_KEY 生产机部署 SSH 私钥
Secret ZT_CLOSET_DINGTALK_WEBHOOK 钉钉机器人 Webhook 完整地址(构建部署成功/失败通知;未配置则自动跳过通知)
Variable ZT_CLOSET_PROD_SSH_HOST 生产机 IP/域名
Variable ZT_CLOSET_PROD_SSH_USER 生产机 SSH 用户

以上名称与业务服务 zt-closet-backend 完全一致(两个服务部署在同一台生产机,镜像仓库与钉钉机器人也共用)。建议建在组织级,两个仓库自动继承,无需重复配置;若建在仓库级,则需在本仓库再建一份同名同值的条目。 注意:因名称相同,ZT_CLOSET_PROD_SSH_HOST 的值必须同时适用于两个服务;若将来管理后台部署到独立机器,需改回独立前缀(如 ZT_CLOSET_MANAGE_*)。

生产机准备

  • 部署目录:/opt/zt-closet-manage(流水线会自动创建 configs/ 与 storage/logs/)。
  • 手动准备 configs/config.yaml(见上文配置说明);其 Server.HttpPort 必须为 8081(与流水线健康检查端口一致)。
  • 容器以非 root 用户(uid 10001)运行,日志目录权限由流水线自动设置。
  • 本地构建验证:docker build --build-arg GOPROXY=https://goproxy.cn,direct -f scripts/dockerfile/Dockerfile -t zt-closet-manage-backend:test .

部署提示

  • 工作目录:启动时必须位于项目根目录(配置路径固定为 configs/config.yaml)。
  • 与业务服务的关系:共用 MySQL 业务库与 Redis;业务表结构变更由业务服务迁移脚本负责,本服务只维护 admin_users 表,不做任何业务表 DDL。
  • 多实例:支持水平扩展;写操作通过事务与条件更新保证幂等(退款审核条件流转、额度调整行锁)。
  • 可观测性后端:按需配置 OTel.ExporterEndpoint 指向 OTLP Collector;无需可观测性时设置 Enabled: false 即可零开销关闭。
S
Description
No description provided
Readme
272 KiB
Languages
Go 99%
Dockerfile 1%