chore(init): 初始化解忧商行项目并完成 Phase 0 基座

This commit is contained in:
yml2213
2026-10-30 11:46:10 +08:00
commit 8d9481c251
689 changed files with 138703 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
# 业务规则
## 商品
- 用户完成登录和实名认证后才能发布租赁商品。
- 商品提交审核并通过后才可公开上架;处于交易中的商品不能被另一订单锁定。
- 主动下架、交易离架、异常和封存是不同状态,必须保留状态事件。
## 订单
- 创建订单后进入待支付;支付成功后进入待交接并锁定商品。
- 号主提交交接信息,租客确认后开始计算租期。
- 租客归还后进入结账协商,双方确认或客服仲裁后完成结算。
- 结账内容区分「结账备注」与「系统结算信息」:备注仅为双方留言,不参与结算计算;结算金额以系统结算信息(额外消耗品、哈夫币实际消耗、押金赔付扣除)为准。
- 订单状态、交接状态和结算状态共同描述流程,禁止仅修改其中一个字段绕过状态机。
## 支付与退款
- 金额均使用整数分,支付单和退款单必须具有唯一业务单号。
- 渠道通知必须先验签,再按幂等规则推进本地状态。
- 已进入终态的支付或退款记录不能被迟到通知回退。
- 人工退款、关闭订单、封存订单和仲裁退款的业务语义不同,均需审计记录。
## 钱包与提现
- 钱包区分可用余额和冻结余额,每次变更必须产生流水。
- 创建提现申请时,可用余额转为冻结余额;取消或拒绝时原额返还。
- 审核通过后只有确认打款才能最终扣减冻结余额,重复审核或重复确认必须被拒绝。
## 管理操作
- 管理后台使用独立认证和 RBAC 权限。
- 关闭、退款、封存、异常、仲裁和余额调整等高风险操作必须写入审计日志。
- 客服只能通过服务端返回的可用动作执行订单状态转换。
+25
View File
@@ -0,0 +1,25 @@
# 数据库设计
## 设计原则
- MySQL 8.4 是生产事实源,Redis 只保存限流、验证码、会话辅助和缓存数据。
- 金额统一以 `*_cent` 的整数分存储;数量和比例字段按业务精度单独定义。
- 核心写入通过 GORM 事务完成,钱包账户更新使用行锁并同步写入不可变流水。
- 表结构由 `backend/migrations` 下的 Goose 迁移管理,应用启动不自动修改生产表结构。
## 核心领域
- 用户与权限:`users`、`admin_users`、角色、权限及关联表。
- 商品:租赁商品、账号、资产截图、审核记录和状态事件。
- 订单:租赁订单、交接记录、结账协商、过程事件和申诉。
- 资金:支付单、退款单、钱包账户、钱包流水、收款账号和提现申请。
- 运营:系统配置、公告、通知、聊天、二维码池、推送规则和撞车商品。
## 一致性约束
- 下单时在事务内锁定可租商品,避免重复出租。
- 支付状态、订单状态和商品状态在同一业务事务内推进。
- 钱包每次余额变化必须生成对应流水,提现在待审核阶段将可用余额转为冻结余额。
- 支付通知和业务单号使用唯一索引实现幂等。
索引、慢查询、连接池和备份恢复细则见[数据库 SQL 约束与运维](数据库SQL约束与运维.md)。
@@ -0,0 +1,77 @@
# 数据库 SQL 约束与运维
本项目使用 MySQL 8.4、GORM 和 Goose。本文是提交、上线和故障排查的数据库基线;SQL 静态检查不能替代真实数据量下的执行计划。
## 提交前约束
新增或修改 SQL、GORM 查询或迁移必须满足:
1. 列表查询必须有分页、最大数量或明确日期范围,禁止无界读取订单、流水、消息和日志历史。
2. `GROUP BY`、`COUNT(DISTINCT)`、窗口函数、跨表聚合、`UNION` 和相关子查询必须在接近生产数据量的 MySQL 上保存 `EXPLAIN FORMAT=JSON` 或 `EXPLAIN ANALYZE` 结果。
3. 高频查询先限定当前时间范围或当前页 ID,再做关联和聚合;禁止读取用户全部历史记录后在应用层过滤。
4. 新增索引必须通过新的 Goose migration 提交,并在迁移注释中说明覆盖的查询条件、排序和基数;大表索引要评估锁影响和上线窗口。
5. SQL 值必须使用 GORM 参数绑定或 `?` 占位符,禁止拼接请求参数。动态排序字段只能来自白名单。
6. 已应用的迁移文件禁止修改或删除,只能新增迁移完成结构修复;除 `000001_init.sql` 外的新迁移必须包含 `-- +goose Up` 和 `-- +goose Down`。
7. 角色、权限、推送渠道、客服组等小型配置表允许全量读取,但必须保持数量边界或在本文件的白名单中登记;业务数据不得套用该例外。
本地 `scripts/check.sh` 会执行 `scripts/check-sql-guard.sh`,保护关键索引、时间范围和迁移完整性。它只防止关键代码被重构移除,不判断执行计划是否优秀。
## 执行计划
在维护库或只读副本上替换真实参数后执行:
```sql
EXPLAIN FORMAT=JSON
SELECT ...;
EXPLAIN ANALYZE
SELECT ...;
```
重点检查扫描行数、实际返回行数、索引使用、临时表、文件排序和 join 顺序。财务首页的仪表盘、结算明细和提现合并列表属于必须定期复核的查询。
## 生产观测
生产 MySQL 应保持 `performance_schema` 开启,并启用慢查询日志。项目的 `deploy/mysql/my.cnf` 默认记录超过 500ms 的语句;应用日志只记录 SQL 形状、耗时和返回行数,不记录绑定参数值。
常用排查 SQL:
```sql
SELECT
DIGEST_TEXT,
COUNT_STAR,
ROUND(SUM_TIMER_WAIT / 1000000000000, 2) AS total_seconds,
ROUND(AVG_TIMER_WAIT / 1000000000, 2) AS avg_ms,
SUM_ROWS_SENT,
SUM_ROWS_EXAMINED
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 30;
SELECT THREAD_ID, EVENT_NAME, TIMER_WAIT, SQL_TEXT, ROWS_EXAMINED
FROM performance_schema.events_statements_current
WHERE SQL_TEXT IS NOT NULL
ORDER BY TIMER_WAIT DESC;
```
应用慢查询阈值由 `DATABASE_SLOW_QUERY_THRESHOLD_MS` 配置。阈值调整后要同步检查 MySQL `long_query_time`,避免应用和数据库观测口径长期不一致。
## 连接、超时与容量
- `DATABASE_MAX_OPEN_CONNS` 是单个 backend 实例的连接上限,总量必须按实例数核算,不能简单随实例扩容同比增加。
- `DATABASE_MAX_IDLE_CONNS`、`DATABASE_CONN_MAX_LIFETIME_MINUTES` 和 `DATABASE_CONN_MAX_IDLE_TIME_MINUTES` 控制空闲连接和连接轮换。
- 请求必须设置 Go `context` 截止时间;锁等待由 MySQL `innodb_lock_wait_timeout` 控制。长报表查询应进入异步任务或汇总表。
- 数据量增长后,排行榜、财务历史统计和审计报表应迁移到日汇总表、缓存或只读副本,不能持续依赖在线全表聚合。
- 修改连接池或容器资源后,用 `docker compose config` 核对最终插值;资源限额应高于稳定 RSS 峰值并留出数据库连接和备份余量。
## 备份与恢复
生产使用 XtraBackup 全量备份加 MySQL ROW binlog 归档实现时间点恢复。备份对象先客户端加密,再使用独立 OSS bucket 的服务端加密;恢复必须在隔离目录和隔离 MySQL 容器演练,禁止直接向生产实例回放。
```bash
./scripts/backup-online.sh status
./scripts/archive-binlog.sh verify
./scripts/restore-online.sh prepare <complete.env-OSS-URI>
```
备份完成标识、SHA-256、manifest、server UUID 和 Goose 版本必须一致;恢复演练至少按月验证一次,并记录恢复时间点和结果。