Files
cmautobuy/admin/AGENTS.md
T
chengmaandClaude Opus 5 d2de7331bb fix: 修复迁移被原地改写导致老库缺表 (#20)
#16 原地改写了 migration v1 而不是新增一条。Migrate 只在
user_version < 版本数时才跑,老库版本号已越过 v1,改写后的语句
永远不会重跑——程序拿着对不上的库静默启动,点到 PDD 商品页才 500。

修法:v1 逐字恢复成 7ad82b7 的原样,#16 的结构改动全部挪进 v3。
全新库也走 v1→v2→v3,与老库升级跑的是同一份 v3 代码,
不需要维护两条路径。

v3 必须认两种 user_version=2:#16 的原地改写让这个版本号对应
两种不同结构(原始 v1 建的没有 pdd_products,改写后的 v1 建的已经有)。
所以 v3 每一步先查 PRAGMA table_info / sqlite_master 看实际结构
再决定做不做,只有版本号推进是无条件的;已是最终结构的库
只推版本号,日志也照实说,不谎称"新增 pdd_products"。

旧 sku_mappings 数据丢弃并打日志:新主键需要 pdd_option_key,
那是 Go 的 OptionKey() 用 json.Marshal 算的,SQL 复现不了。
硬凑一个键出来,轻则映射静默失效,重则撞上别的规格静默买错东西——
后者正是 #16 存在的全部意义。

collecting 映射成 pending:原样保留会让 MarkCollecting 永远不成功,
那个商品再也建不了采集任务,界面上表现为按钮永远置灰且无法解开。

表重建按 SQLite 官方 12 步顺序:先建 _new 再 RENAME。
实测 ALTER TABLE RENAME TO 会自动重写别的表里指向它的外键子句,
先 RENAME 让位会把 shopee_skus 的外键改成指向一张马上被删的表。

另加两道闸:启动时 CheckSchema 缺表即拒绝启动(不是警告后继续);
admin/AGENTS.md 写死"migrations 只追加、不得修改已发布条目"。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:23:24 +08:00

128 lines
6.8 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.
# Admin 子项目规则
本文件适用于 `admin/` 下的全部代码、模板和测试,并继承仓库根目录 `AGENTS.md` 的
**红线**、工作流、Git、安全、文档和初级程序员维护规则。
## 开始工作前
- 从仓库根目录执行 Admin 任务时,也必须先读取本文件。
- 第一次接手,先看这两份:
- 环境和运行:[00 上手指南](../docs/admin/00-getting-started.md)
- 看不懂的词:[00 术语表](../docs/admin/00-glossary.md)
- 只读取与当前任务有关的长期基线,不必每次加载全部文档:
- 需求边界:[01 产品需求基线](../docs/admin/01-requirements.md)
- 分层和目录:[02 系统架构](../docs/admin/02-architecture.md)
- 数据库表:[03 数据模型](../docs/admin/03-data-model.md)
- 给 Client 的接口:[04 Client 接口实现](../docs/admin/04-client-api.md)
- 页面和交互:[05 界面规范](../docs/admin/05-ui-specification.md)
- 测试和安全:[06 质量与安全](../docs/admin/06-quality-security.md)
- Admin 提供的接口必须满足 Client 侧契约
[docs/client/04-admin-api-contract.md](../docs/client/04-admin-api-contract.md),
两边不一致时以 Client 契约为准,要改先走工单。
## 技术栈
**Go 版本固定 1.23.0。** 依赖版本受它约束,见下面的表。
- 固定使用 **Go + Gin + Go 标准库 `html/template`**。
- 数据库固定 **SQLite**,驱动固定 `modernc.org/sqlite`。
**不得改用 `mattn/go-sqlite3`** —— 那个要 cgo,Windows 上得装 gcc,
交叉编译和打包 exe 都会变得很麻烦。
- Excel 读取固定 `github.com/xuri/excelize/v2`。
### 依赖版本已钉死,不要随手升
| 依赖 | 固定版本 | 为什么不能用更新的 |
|---|---|---|
| `github.com/gin-gonic/gin` | **v1.11.0** | v1.12.0 起要求 Go ≥ 1.25.0 |
| `modernc.org/sqlite` | **v1.38.0** | v1.40.0 起要求 Go ≥ 1.24.0,v1.48.0 起要求 ≥ 1.25.0 |
| `github.com/xuri/excelize/v2` | **v2.9.1** | v2.10.0 要求 Go ≥ 1.24.0,v2.11.0 要求 ≥ 1.25.0 |
> excelize 目前**还不在 `go.mod` 里**——导入功能还没写,没有代码 import 它,
> `go mod tidy` 会把它去掉,这是 Go 的正常行为。写导入功能时用
> `go get github.com/xuri/excelize/v2@v2.9.1` 加进来。
这三个版本是**在 Go 1.23.0 下实测能编译通过的最高版本**。
`[必须]` 直接跑 `go get <包名>`(不带版本)会拉到最新版,然后报
`requires go >= 1.25.0`。拉依赖要带版本号,见
[00 上手指南](../docs/admin/00-getting-started.md) §2。
`[必须]` **要升依赖就得先升 Go**,这是一个决定不是两个。
升 Go 属于会影响构建的变更,先过工单。
- 实际版本以 `admin/go.mod` / `go.sum` 为准,这两个文件都要提交 Git。
- 新增或升级依赖前先过工单。
### 前端约束
- **不得引入 React、Vue、Angular 等前端框架,不得引入 npm 构建流程。**
页面一律服务端渲染。
- 搜索、删除、导入这类操作用普通表单提交,**零 JavaScript**。
- 勾选、弹窗、导入进度确实需要 JS,只允许两种做法:
- 原生 JavaScript,直接写在模板里或放 `static/js/` 下的独立文件;
- htmx(单个 js 文件,无构建步骤)。
- **不得引入打包器**(webpack/vite/esbuild)。CSS 手写,放 `static/css/`。
## 代码分层
- `handler` 只负责解析请求、调用 service、渲染模板或返回 JSON,**不写业务逻辑,不拼 SQL**。
- `service` 放业务逻辑(导入解析、创建任务、匹配复用),不认识 Gin 的 `*gin.Context`。
- `repository` 封装 SQLite,**只有这一层能写 SQL**。
- `model` 只放数据结构,不导入 Gin 和数据库驱动。
- 给 Client 的接口和给浏览器的页面**分开放**(`handler/api/` 和 `handler/web/`),
两者的错误格式、认证方式都不一样,混在一起迟早出事。
## 数据与接口
- 表结构以 [03 数据模型](../docs/admin/03-data-model.md) 为准。
- 给 Client 的接口以 [docs/client/04-admin-api-contract.md](../docs/client/04-admin-api-contract.md) 为准。
- `[必须]` **金额一律用整数存**,人民币用分、台币用分,字段名带单位后缀(`_cent`)。
**禁止用 float 存金额。**
- `[必须]` 时间一律带时区 ISO 8601,库里存 UTC,页面上转本地时区显示。
- `[必须]` Excel 导入只做 **upsert(更新或新增)**,
**绝不允许先清空再导入** —— 会把人工填的 PDD 链接全洗掉。
- `[必须]` 蝦皮规格原文(`spec_raw`)永远保留,解析不出来就留空,不要瞎猜。
- `[必须]` Client 提交结果时,**不管任务是否已取消、是否已重派,一律接受**,
理由见 Client 契约 §6.1。这条最容易被顺手违反。
- `[必须]` `repository/db.go` 里的 `migrations` 只追加,**不得修改已经发布过的条目**。
改了的话,已经建过库的机器 `user_version` 已经越过它,永远不会重跑,
程序会拿着对不上的库静默启动(见 #20)。需要改结构就加新的一条。
## 界面规则
- 四个模块统一使用三段式布局:**顶部工具条 / 中间带勾选的表格 / 底部状态条**。
第一个页面写完,其余三个照抄结构改字段,不要给某个模块搞特殊。
- 表格行的身份用业务主键,**不得用行号**。
- 批量删除必须二次确认,并显示"将删除 N 条"。
- 破坏性操作(删除、导入覆盖)用 POST,**不得用 GET**。
- 页面必须能在 1366×768 上正常使用,表格横向滚动而不是压缩列宽。
## 安全
- 根目录 `AGENTS.md` 的五条红线同样适用。
- `[必须]` 所有写操作(新增/编辑/删除/导入/建任务)加 CSRF 防护。
- `[必须]` SQL 一律用参数化查询,**禁止字符串拼接 SQL**。
- `[必须]` 模板输出走 `html/template` 的自动转义,
**禁止用 `template.HTML` 包裹用户可控内容**。
- `[必须]` 上传的 Excel 限制大小和扩展名,解析失败要有明确报错,不能让整个进程崩掉。
- `[必须]` 日志、页面、导出里不得出现 token、密码、Cookie。
## 验证
全部命令从 `admin/` 目录执行。
```powershell
go vet ./...
go build ./...
go test ./...
go run .
```
- `go run .` 启动后访问 `http://localhost:8080`,**不会连手机、不会下单**,可随时运行。
- 修改数据库时测试首次建库和从上一版本迁移。
- 修改给 Client 的接口时,跑契约测试,确认仍满足 Client 侧 §6.1 的无条件接受。
- 修改 Excel 导入时用 `raw_data/` 下的样本跑一遍,核对导入条数。
该样本含商业数据、**不在仓库里**,需向项目负责人索取;自动化测试用 `testdata/` 下的脱敏小样本。
- 交付时说明已运行的命令、结果和未验证的部分。