Files
xiaotutongxue/AGENTS.md
T
lovife db6a51d617 docs: 清掉三处"会静默过期"的小账 + check-test-profile 加固(F-98)
一、附录 A 从"手抄表"改成"可再生成的命令"
   check-api-parity.py 加 --extra,现算"代码有、契约 §4 没有"的接口 = 41 个
   (旧手抄表写 83/62/21,已差近一倍,而且没人会记得回来改它)。
   并按"谁去改什么"分六类 —— 只有 E 类(8 个)要客户定口径,其余是文档侧补记。

二、F-6 已修:frontend-pc 的 Mock 说明
   AGENTS.md 那句改正之外,连带修了 .agents/skills/project-api/SKILL.md ——
   它整篇仍以 vite-plugin-mock / mock/** 为主线,而该项目里 mock/ 目录与插件都已删除。
   只改指路牌、不改路的尽头,等于没改。

三、docs/03 两处文档内部口径冲突加 ⚠️ 注(不改口径,只把冲突摆明)
   §2 notifyBizType 的 5/6 与 §13.5、落库值不一致 → 实现以 §13.5 为准(催报去重按 5);
   §3.6/§9.5 的 collect_file.source 字段表(入口来源)与建表注释(谁传的)冲突 →
   实现以建表口径为准。两处都写明"待客户确认",见 F-30 / F-38 / F-39。

四、check-test-profile.py 加固(新增 F-98)
   · 子进程输出落盘(不再用没人读的 PIPE):失败时能看见"停在哪一行",
     成功时打出启动用时(基线 ~10 秒 / 72 行 / 9.5KB);
   · 开跑前按 jar + profile 清残留 JVM,且保留 dev 后端(E2E 正打着它);
   · 等待期间每 15 秒报一次进度,卡住时不留沉默。
   教训:随机端口这个防撞设计把残留 JVM 藏起来了 —— 撞不到端口就永远发现不了,
   于是表现成"时好时坏",而失败时连日志都读不到。

验证:python scripts/verify-all.py --with-e2e → 通过 8 / 失败 0 / 跳过 0
      (E2E 36 脚本 / 1548 断言 / 278s;prodguard 9/9;test profile 能起)
      python scripts/verify-docs.py → 22 项全部通过
2026-09-30 09:10:34 +08:00

152 lines
9.4 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.
# AGENTS.md
面向 AI 编码助手的项目指南。**用简体中文交流、用中文写文档。**
需求已冻结:**文档是需求源,代码是它的实现**,不要按"更合理"去改业务口径。
`docs/交接摘要.md` 是**唯一状态源** —— 动手前先读它最近的两节(§一·二十、§一·二十一)
与 `## 二、已锁定的铁律`;那里面大量决定"读代码看不出来、但都有理由"。
## 一、四个可运行部分
| 目录 | 是什么 | 入口 |
|---|---|---|
| `backend/` | Java 21 + Spring Boot 4.1.1 + MyBatis-Plus + PG16 + Redis7 | `xt-server/`(`xt-common/` 放响应体/错误码/加解密) |
| `frontend-pc/` | 管理端 Vue3 + Arco Design Vue(基于 **Gi Admin Pro**) | `pnpm dev` → 5173 |
| `frontend-mp/` | 学生端 **uni-app**(H5 与微信小程序同一套代码) | `pnpm build:h5` / `pnpm dev:mp-weixin` |
| `deploy/` | 本地基础设施(PG + Redis,**没有对象存储**) | `docker compose up -d` |
`docs/prototype/` 是视觉与交互的**唯一基准**(单文件自包含、零外部依赖、**不用 emoji**)。
**一期范围只做「学校收集 + 自主认定」**:`collectMode=2` / `certType=2,3` 一律返回 `40044`,
别顺手把二期功能实现出来。
## 二、常用命令(备注里是容易猜错的地方)
### 基础设施
```bash
cd deploy && docker compose up -d # xt-pg(5432) + xt-redis(16379)
```
⚠️ 本机还有别的项目的 `pg-verify`(55432) 与 `redis-local`(6379),**不要动它们**。
### 后端
```bash
cd backend
mvn -DskipTests package # 改了 Java 必须重新打包
java -jar xt-server/target/xt-server-1.0.0-SNAPSHOT.jar --spring.profiles.active=dev
```
- ⚠️ **工作目录必须是 `backend/`**,两处相对路径依赖它:
`application-local.yml`(不提交的本地私有配置)就在 `backend/`;
H5 静态目录配的是 `file:../frontend-mp/dist/build/h5/`
- 跑的是 **jar 不是热加载** → 改后端要重新 package(先停进程,否则 jar 被占用)
- **动过 `db/migration/` 必须 `mvn clean package`**(Flyway 已到 V6;迁移文件发布后不许改)
- 本地私有配置机制是 `profiles.active=dev` + `spring.profiles.include: local`
(模板 `backend/application-local.yml.example`)。
**不要改用 `spring.config.import`** —— 它优先级低于 profile 文件,会静默失效(见 `application.yml` 注释)
### 管理端(PC)
```bash
cd frontend-pc && pnpm install && pnpm dev
pnpm typecheck # vue-tsc pnpm lint # eslint
```
- 用 **pnpm**(仓库里是 `pnpm-lock.yaml`;别 `npm install`,会多出 package-lock.json)
- dev 通过 vite proxy 把 `/api` 转发到 `http://localhost:8080`(见 `.env.development`),
**没有 Mock**(`mock/` 与 `vite-plugin-mock` 都已删除)→ 后端不起着就没有数据。
⚠️ `frontend-pc/.agents/skills/project-api/SKILL.md` 仍以 Mock 为主线(Gi 模板原文),
该文件开头已加"本仓库不用 Mock"的说明 —— 看它时先看那段
- 我们的接口模块都在 `src/apis/xt/`;`apis/system|user|person|cate|area|test` 是 Gi 模板自带的,别往那儿加
- **PC 前端有独立的规范来源:`frontend-pc/.agents/`**(`AGENTS.md` + `skills/*`)。
改那里的代码前按它的表加载对应 skill,不要另起一套写法
### 学生端(uni-app)
```bash
cd frontend-mp && pnpm install
pnpm build:h5 # → dist/build/h5(由后端托管)
pnpm dev:mp-weixin # → dist/dev/mp-weixin(导入微信开发者工具)
```
- **H5 现在有独立 dev server 了**(`pnpm dev:h5` → http://localhost:5174):
`vite.config.ts` 加了 `/api` 代理,`.env.development` 把 `VITE_API_BASE` 设成空串,
于是请求走相对路径、由代理转发到 8080。**改一行不用再 `build:h5`**。
⚠️ 三端行为不同,改动这块前先看 `src/config/index.ts` 的注释:
dev server → 代理;构建产物(后端托管)→ `location.origin`;小程序 → 写死地址
- 改前端后浏览器要**硬刷新**(`/assets/**` 是 immutable 缓存)
- 构建时 Dart Sass 的 `legacy-js-api` DEPRECATION 属正常
- `vue-tsc` 有 2 条**已知**报错(`src/config/index.ts` 的 `#ifdef` 重复导出),不是新引入的
- 当前节奏:**先做 H5**(可自动化测试、不依赖小程序资质);小程序端代码同步维护
## 三、怎么验证(本仓库最不显然的一点)
**后端没有单元测试**(`src/test` 是空的)。验证靠 `scripts/e2e-*.py`:
打真实接口 + `docker exec` 进 PG 查库断言。
```bash
python scripts/e2e-m3-file.py # 单跑一个;前提是后端 8080 在跑、xt-pg 容器在跑
```
- 全量:把 `scripts/e2e-*.py` 依次跑一遍(`e2e-m0*` / `e2e-m1-*` / `e2e-m2-roster` / `e2e-m3-*`),
目前合计 613 条断言
- 脚本**自己造 MP 的 JWT**:读 `application-dev.yml` 的 `fill.jwt.secret` +
`application.yml` 的 `issuer` 手搓 HS512 —— 不需要先登录
- 改了后端**忘了重新打包/重启**,测试会对着旧 jar 通过,这是最容易踩的空转
- 跑完 `e2e-m3-*` 会**清掉名册绑定**(`collect_roster.bind_user_id`);
想继续用浏览器验演示态,要先重新绑定(见交接摘要的"演示态"一节)
- 清理残留:`python scripts/dev-clean-e2e.py`、`python scripts/dev-clean-test-users.py`
- 文档一致性:`python scripts/verify-docs.py`
## 四、接口与数据契约(不显然、容易做错)
- 响应恒为 `{code,msg,data}`;**业务错误也用 HTTP 200**,只有 401/403/429/5xxxx 用真状态码
- 软删除字段名是 **`deleted`**(不是 `is_deleted`);业务表都带 `tenant_id`
- **租户拦截器默认拒绝**:匿名请求触及租户表会直接 `40101`。匿名接口(如扫码落地页)
必须走专用 Mapper + `@InterceptorIgnore(tenantLine="true")` + **显式带 tenant_id**,
且集中放(`module/mp/mapper/MpSceneMapper.java`),就是为了"关掉框架保护后由人保证隔离"这件事有地方可审
- **权限码写在代码里**(`security/PermissionDef`、`security/BuiltinRole`);库里只有
`sys_role_permission` 做「角色 → 权限码」的分配,**没有** `sys_permission` 定义表 ——
加权限要改代码,不要想着往表里插一行
- 动态表单 = 内置字段(固定列、可统计)+ 扩展字段(`extras_json`,只展示与导出)。
**字段 key 全是 snake_case**,权威来源 `module/form/service/BuiltinFields.java`(21 项)——
改口径要同步它,别按直觉造 key
- `collect_task.status`:`0` 草稿 / `1` 收集中 / `2` 已截止 / `3` 已归档 / `4` 暂停
(接口设计文档曾写反,**以代码为准**)
- `collect_roster.id_card_no` 存的是 **AES 密文**(另有 `id_card_hash` / `id_card_mask`),
明文只在导入与门禁时存在 → 提交环节**做不到**身份证校验位校验,别去"补"这个
- 材料上传是**一步**:`POST /mp/files/upload` 收上来就压缩 + 按模板改名,默认**只存规范件**
(`fill.storage.keep-raw=false`);`process` 只是"有原始件时的重新规范化"
- 图片处理有两条**踩过真 bug**的规则,动之前先读交接摘要 §一·二十 / §一·二十一:
① 服务端必须按 EXIF 转正,否则手机竖拍的身份证会被存成躺倒的(且 E2E 的测试图不带 EXIF,抓不到);
② 浏览器与小程序对 EXIF 的处理**不一致**,前端必须"量出来再决定",写死必错一半
## 五、环境与流程坑
- 本地 H5 走花生壳穿透(`http://394nb68658le.vicp.fun` → `192.168.3.139:8080`,
配在 `application-dev.yml` 的 `mp-h5`)。**硬刷新后首屏常要 1~2 分钟**,
探测要给足,否则会把"还在加载"误判成失败
- 打桩登录(仅 dev:`fill.mp-h5.stub-enabled`,**生产必须 false**,代码里还会再判一次 profile):
`/api/v1/mp/h5/stub-login?openid=<openid>&scene=t1-k17` → 302 回 H5,token 放在 **URL fragment** 里
- `scene` 三段式:`t{tenantId}` / `t{tenantId}-k{taskId}` / `t{tenantId}-k{taskId}-o{orgId}`
(微信限 32 字符)。它**可穷举**,所以匿名接口只返回最少信息 + 按 IP 限流
- 存储默认 `provider=local`(落 `backend/data/uploads`,**只适合开发**:多副本必然丢文件);
七牛用 `provider=qiniu`,AK/SK 只放 `backend/application-local.yml` 或环境变量。
**MinIO 已删**,本地不再有 S3,别去找
- 七牛空间是**私有**的 → 下载走签名链接(有有效期):
**URL 不能入库、不能发出去让人明天再打开**,每次要用现取
- 改中文文档、或跑任何会碰到中文文本的脚本,**一律写成 `.py` 文件再执行**:
PowerShell 不支持 heredoc(`<<'PY'` 直接报错);`@"..."@` here-string 会**吃掉双引号**
(`return "bin";` 变成 `return bin;`)—— 带引号的代码用编辑工具改,别过转义层
## 六、提交前
- 不要提交:`backend/data/`、`dist/`、`application-local.yml`、`deploy/.env`
(都已在 `.gitignore`,但 `git add -A` 容易顺手带上 —— 先 `git status` 看一眼)
- `deploy/` 里**只有** `.env.example` 与 `docker-compose.yml` 被 git 跟踪
(`*.pem` 在 ignore 里,`deploy/rsa-public-key-dev.pem` 是本地开发公钥)
- 改动接口 / 库表 / FR-VAL 编号 → 重跑 `python scripts/verify-docs.py`,
并同步 `docs/00-交付计划.md` 与 `docs/交接摘要.md`