# 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`**。 - 生产数据库固定 **MySQL 8.4**,驱动固定 `github.com/go-sql-driver/mysql` **v1.9.2**(纯 Go,兼容 Go 1.23)。 - Admin 默认与 MySQL 同机或走私有网络;生产默认连接 `127.0.0.1:3307`。 项目负责人已于 2026-08-11 明确批准生产 MySQL 3307 接受所有公网来源,公网业务账号 可以使用 `%`,但必须同时满足:只开放专用业务账号、权限仅限 `autobuy`、 `REQUIRE SSL`、Admin 使用 `verify_ca`,且 root 仍仅限本机。不得把这项例外扩展到 root、其他数据库或关闭 TLS;恢复固定公网 IP 后应优先收回到 `/32` 白名单。 - Admin 公网连接必须使用 `database.tls_mode: verify_ca` 和服务器公开 CA:要求 TLS 1.2+、验证证书链、禁止回退明文。`disabled` 只允许同机或 SSH 隧道连接; 不得使用驱动的 `preferred` 或单纯 `skip-verify`。 - Windows 本地运行允许把 MySQL 账号密码写入已被 Git 忽略的 `admin/config.yaml`; 线上部署仍优先使用权限为 `600` 的环境文件。`CMAUTOBUY_DB_*` 环境变量按字段 覆盖 YAML,真实 `config.yaml` 不得提交、打包、截图或复制到工单和日志。 - `modernc.org/sqlite` 只保留给 SQLite → MySQL 单向迁移工具和历史库回归, 不得用于生产运行时,也不得做 SQLite/MySQL 双写或自动同步。 - 仍然**不得改用 `mattn/go-sqlite3`**,避免引入 cgo 和 gcc。 - PDD 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 | | `github.com/go-sql-driver/mysql` | **v1.9.2** | v1.9.3 起要求 Go ≥ 1.24.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` 封装 MySQL 读写,迁移命令中的 SQLite 读取也只能放在 repository/cmd 边界内;**只有这一层能写业务 SQL**。 - `model` 只放数据结构,不导入 Gin 和数据库驱动。 - 给 Client 的接口和给浏览器的页面**分开放**(`handler/api/` 和 `handler/web/`), 两者的错误格式、认证方式都不一样,混在一起迟早出事。 ## 数据与接口 - Admin 新建采购任务固定为真实下单(不支付),当前账号可见的全部 Client 都允许被指派;创建时不得按 Client 的 `purchase_mode` 禁用候选或拒绝任务。`purchase_mode` 只用于领取期判断 Client 的自动运行就绪能力。 - 表结构以 [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,页面上转本地时区显示。 - `[必须]` PDD Excel 和商品目录接口导入只做 **upsert(更新或新增)**, **绝不允许先清空再导入** —— 会把人工填的 PDD 链接全洗掉。 - `[必须]` 蝦皮规格原文(`spec_raw`)永远保留,解析不出来就留空,不要瞎猜。 - `[必须]` Client 提交结果时,**不管任务是否已取消、是否已重派,一律接受**, 理由见 Client 契约 §6.1。这条最容易被顺手违反。 - `[必须]` MySQL 的 `schema_migrations` 只追加,已经发布的版本不得改写。 MySQL DDL 会隐式提交:每条 DDL 必须可重放,整版完成并通过 schema 自检后 才记录版本。历史 SQLite migrations 已冻结,只供迁移工具读取旧库。 ## 界面规则 - 五个模块统一使用三段式布局:**顶部工具条 / 中间带勾选的表格 / 底部状态条**。 第一个页面写完,其余四个照抄结构改字段,不要给某个模块搞特殊。 - 表格行的身份用业务主键,**不得用行号**。 - 批量删除必须二次确认,并显示"将删除 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 . ``` `[必须]` **交付前至少跑一次带 `GOTOOLCHAIN=go1.23.0` 的验证**: ```powershell $env:GOTOOLCHAIN="go1.23.0"; go build ./...; go test ./... -count=1 Remove-Item Env:GOTOOLCHAIN ``` 本项目固定 Go 1.23.0(见 [00 上手指南](../docs/admin/00-getting-started.md)), 但开发机上装的可能是更新的版本,Go 会默默用它编译,**测过的不是要交付的那个版本**。 本项目已经发生过:连续六份归档写着「Go 1.23.0 验证通过」,实际全跑在 1.26.5 上。 新加依赖时尤其要跑——依赖的 `go` 指令高于 1.23.0 的话,只有这条命令能发现。 - `go run .` 启动后访问 `http://localhost:8080`,**不会连手机、不会下单**,可随时运行。 - 修改数据库时必须使用独立 MySQL 8.4 测试库验证首次建库、重复迁移和上一版本升级。 测试库名必须以 `_test` 结尾,禁止测试清理连接生产库。 - 修改 SQLite 单向迁移时,同时用各历史 SQLite schema 状态演练;源库只读, 目标库必须为空,逐表计数和关键业务关系必须核对。 - 修改给 Client 的接口时,跑契约测试,确认仍满足 Client 侧 §6.1 的无条件接受。 - 修改 Excel 导入时用 `raw_data/` 下的样本跑一遍,核对导入条数。 该样本含商业数据、**不在仓库里**,需向项目负责人索取;自动化测试用 `testdata/` 下的脱敏小样本。 - 交付时说明已运行的命令、结果和未验证的部分。