Files
cmautobuy/docs/admin/06-quality-security.md
T
chengmaandClaude Opus 5 4f920f9d8b docs: 排除蝦皮原始报表,并同步主按钮文案
raw_data 不进 Git
原始报表含逐商品台币销售额等商业数据,进了 Git 就是永久历史。
- .gitignore 排除 raw_data/
- 改掉三处"仓库里有一份样本"的失真表述,改为向项目负责人索取
- 06 §2.1 相应加强:既然大样本不进库,admin/testdata/ 下的脱敏小样本
  就必须提交,否则别人拉下来测试跑不了;并写明脱敏做法

主按钮文案 开始自动获取 → 获取任务 ⇄ 停止获取
只改按钮标签。"自动获取"作为功能名保留(状态栏、Tab 顺序、
协调器开关等处不动),05 §4.1 加了一句说明两者不是一回事。

已知遗留:pdd_ui.py 自身仍不一致——构造时用「获取任务」,
但状态机 485/487 行仍是「开始自动获取」/「停止自动获取」,
会覆盖掉构造时的文字。该文件有未提交改动,本次未触碰,
差异已记入 02 §3.1,需另开工单修。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:58:09 +08:00

197 lines
8.5 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.
# 06 Admin 质量、安全与测试基线
- 文档状态:基线草案,待质量评审
- 适用范围:Admin 源码、模板、数据库和发布产物
本文档中 `[必须]` / `[建议]` / `[待定]` 的含义见 [文档索引](../README.md#文档标注说明)。
没有标注的默认是 `[必须]`。看不懂的词查 [术语表](00-glossary.md)。
## 1. 质量目标
- 导入不丢人工维护的数据(PDD 链接、SKU 映射、手动新增的行)。
- 不把校验不过的任务发给 Client——发出去 Client 也执行不了。
- 同一任务不会被两个客户端同时领走。
- Client 提交的结果一定收得下,哪怕任务已取消。
- 页面和日志不泄露 token、密码、Cookie。
## 2. 测试分层
### 2.1 单元测试
`[必须]` 覆盖:
- **规格原文解析**:两种格式各占一半,两种都要有用例,
再加解析失败的用例(确认 `parse_ok=0` 且不瞎猜);
- **Excel 导入分行**:商品汇总行 vs SKU 行的判断;
- **upsert 不覆盖人工字段**:先填 PDD 链接,再导入一次,断言链接还在;
- 任务创建校验(六条规则逐条);
- 金额换算显示(分 ↔ 元),边界值 0 和大额;
- SKU 映射复用:第二次匹配同一 SKU 应自动带出;
- 在线状态派生:`last_seen_at` 刚好在边界前后。
`[必须]` 导入相关的测试用 `admin/testdata/` 下的**小样本**(几十行),
不要读完整报表。
`[必须]` 这份小样本**必须提交进 Git**,否则别人拉下来测试跑不了。
制作方法:从真实报表里挑几十行,**删掉全部销售额、曝光、转化率等指标列**,
只保留导入用得到的字段(商品ID、商品名稱、商品規格ID、商品規格、貨號等),
并把商品名称改成无意义的占位文字。
完整报表含商业数据,**不进 Git**(见 `.gitignore`)。
### 2.2 集成测试
用临时 SQLite 覆盖:
- 完整导入 → 建采集任务 → 模拟 Client 提交结果 → `collect_status` 变 `collected`;
- 并发 `claim`:两个客户端同时领,只有一个拿到;
- 幂等:同键同内容重复提交,只落库一次;
- 同键不同内容 → `409`;
- **任务已取消,Client 提交结果仍被接受**;
- **任务已重派给别人,原客户端提交仍被接受**;
- 同一任务收到两个客户端的两份结果,都存下来;
- 数据库从上一版本迁移,人工数据不丢。
中间三条是 [04](04-client-api.md) §4.1 那条最容易写错的规则,**必须有测试盯着**。
### 2.3 页面测试
- `go vet ./...` 无告警;
- 所有模板能正常渲染(起服务跑一遍四个页面,断言 200);
- 空数据、少量数据、大量数据三种情况;
- 批量删除的二次确认存在;
- 校验失败时输入不丢。
### 2.4 契约测试
`[必须]` 拿 [04 §9 的实现清单](04-client-api.md) 逐条写测试。
那张清单本身就是验收标准,可以直接照着做。
## 3. 数据安全
`[必须]` 下面几条错一条就会丢数据:
| 规则 | 为什么 |
|---|---|
| 导入只能 upsert,**禁止先清空再导入** | 人工填了几个月的 PDD 链接会被洗掉 |
| upsert 的 `DO UPDATE SET` 里**不得出现** `pdd_goods_url` / `pdd_data` / `collect_status` | 报表里没这些列,写进去会被更新成空 |
| **不得删除报表里没出现的行** | 手动新增的(`is_manual=1`)会被误删;报表本身就不是全量 |
| 迁移前备份或用可回滚步骤 | `data/` 在升级时保留,迁移失败会毁掉全部历史 |
| **不得在启动时删库重建** | 同上 |
`[建议]` 导入前自动把 `admin.db` 复制一份到 `data/backup/`,保留最近几份。
## 4. Web 安全
- `[必须]` 所有写操作(新增/编辑/删除/导入/建任务)加 **CSRF 防护**。
- `[必须]` 破坏性操作用 **POST**,不得用 GET。
- `[必须]` SQL **一律参数化查询**,禁止字符串拼接。
搜索框的内容是用户可控的,拼进 SQL 就是注入。
- `[必须]` 模板输出走 `html/template` 的自动转义。
**禁止用 `template.HTML` 包裹用户可控内容**——商品名、订单号都来自外部。
- `[必须]` 上传的 Excel 限制**大小和扩展名**,解析失败要返回明确错误,
不能 panic 把整个进程带崩。
- `[必须]` 文件名不得直接用于拼路径(路径穿越),落盘时用自己生成的名字。
- `[建议]` MVP 只监听 `127.0.0.1`,不对外暴露。要给内网用再单独评估。
## 5. 错误处理
`[必须]` 页面出错渲染错误页,接口出错返回 JSON,**两者不要混**。
`[必须]` 错误信息要说清三件事:**发生了什么、保住了什么、下一步做什么**。
```text
好:导入失败:第 128 行「商品規格ID」为空且不是汇总行。
前 127 行已成功导入,请修正后重新导入。
差:导入失败
差:runtime error: index out of range [40] with length 39
```
`[必须]` Go 的错误堆栈只写日志,不上页面。
`[建议]` 用稳定错误码,方便排查:
| 前缀 | 场景 |
|---|---|
| `IMPORT_*` | Excel 格式、列缺失、行解析失败 |
| `TASK_*` | 建任务校验不通过 |
| `CLIENT_*` | 客户端注册、领取冲突 |
| `DB_*` | 迁移、写入、锁超时 |
## 6. 日志
`[必须]` 日志写 `data/logs/`,至少包含时间、级别、模块、事件名、关键 ID。
`[建议]` 记录这些事件:
- `shopee_import_started/completed/failed`(带条数统计)
- `collect_task_created`
- `purchase_task_created`
- `task_claimed`(带 client_id、task_id)
- `task_result_received`
- `task_result_received_but_cancelled` ← 这条要单独记,方便事后对账
- `client_registered`
`[必须]` **不得记录** token、密码、Cookie,也不要把完整请求体无脑打进日志。
## 7. 性能
规模很小(个位数操作员、单机运行),不要提前优化。但下面几条是基本功:
- `[必须]` 搜索、排序、分页走**数据库查询**,不要一次查全量再在内存里过滤。
- `[必须]` 常用查询有索引(见 [03 数据模型](03-data-model.md) 里的 `CREATE INDEX`)。
- `[必须]` 导入 1 万行要在一个事务里批量写,不要一行一个事务。
- `[建议]` 导入时不要把整个文件读进内存,用 excelize 的流式行读取。
## 8. 发布门禁
| # | 门禁项 | 怎么验证 | 谁负责 |
|---|---|---|---|
| 1 | 依赖版本已固定 | `go.mod` / `go.sum` 已提交,`go mod verify` 通过 | 开发者 |
| 2 | `go vet ./...` 无告警 | | 开发者 |
| 3 | `go test ./...` 全绿 | 不允许有跳过而未说明的用例 | 开发者 |
| 4 | 四个页面能正常打开 | 起服务跑一遍 | 开发者 |
| 5 | 样本导入条数正确 | 导入参考样本(需向项目负责人索取,放 `raw_data/`),应得 5195 商品 + 6092 SKU | 开发者 |
| 6 | 数据库从上一版本迁移成功 | 拿旧 `admin.db` 副本启动新版本,人工数据不丢 | 开发者 |
| 7 | 契约测试通过 | [04 §9 清单](04-client-api.md)逐条 | 开发者 |
| 8 | 干净环境启动 | 没装过本项目的机器上 `go run .` 或跑 exe | 开发者 |
| 9 | 日志和页面无敏感信息 | 翻一遍 `data/logs/` | 开发者 |
| 10 | 开源许可证已确认 | 新增依赖的许可证 | **项目负责人** |
第 10 项开发者**不要自己判断放行**,把包名和许可证类型报给项目负责人。
## 9. 打包
`[待定]` 打包动作属于 MVP 之后,但策略现在定下来:
- `go build` 直接出**单个 exe**,不需要 C 编译器
(这就是选 `modernc.org/sqlite` 的原因);
- 模板和静态文件用 `//go:embed` 打进 exe,**不要散在外面**,
否则用户可能改坏或删掉;
- `data/` 留在 exe 旁边,**升级时保留**,见 [02 架构](02-architecture.md) §6;
- 升级方式:换掉 exe,`data/` 不动,靠 `PRAGMA user_version` 自动迁移。
产出目录:
```text
CMAutoBuyAdmin/
├── admin.exe 单文件,模板和静态资源都在里面
└── data/ 数据,升级时保留
├── admin.db
├── logs/
└── uploads/
```
比 Client 简单——Go 没有 Python 那种依赖收集问题,不需要 `app/` 目录。
## 10. 任务完成定义
单元任务同时满足下面几条才算完成:
1. 工单验收标准逐项通过;
2. 代码、迁移、测试和必要文档同步完成;
3. 没有静默跳过的测试;
4. 变更不泄露敏感信息;
5. Gitea 工单更新最终结果和提交哈希;
6. 完成记录归档到 `docs/task`。