Files
xiaotutongxue/AGENTS.md
T
lovife a9b2dd2a16 docs: 登记 F-125~F-130 并同步脚本数 37→38
三个 P0 本轮只测不改、先记账:F-125 学生被驳回后改不了任何东西(改字段/传材料/
删材料全 40030、撤回 40001,而重新提交是允许的,四处信号都说他能改)、
F-128 扩展字段存得进读不出、必填扩展字段会让整个任务永久无法提交(内置表单一个
扩展字段都没有,所以从来没有数据走过这条路)、F-130 小程序端登录断成两截
(前端从没调过 /mp/login,后端 scene 带 taskId 必然 500)。

README 补 docs/11 的入口,AGENTS.md 补小程序端现状与 38 个脚本的新口径,
问题清单补 F-126/F-127/F-129 三条与 F-125/F-128/F-130 的修法方向。
2026-10-14 15:40:18 +08:00

191 lines
14 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` 是**唯一状态源** —— 动手前先读它最近的两节(**§一·三十五、§一·三十六**)
与 `## 二、已锁定的铁律`;那里面大量决定"读代码看不出来、但都有理由"。
> ⚠️ **2026-10 需求收敛(见 §一·三十六)**:只做 **4 个角色**(平台运营 / 学校管理员 / 班主任 / 学生)、
> **10 个 PC 菜单**、**不设二期**。删掉的东西不要再"顺手加回来":副班主任 / 审核员两个角色、
> 历史整理器、加入申请(含学生端自助申请入口)、`collect_task_org.deputy_user_ids`。
## 一、四个可运行部分
| 目录 | 是什么 | 入口 |
|---|---|---|
| `backend/` | Java 21 + Spring Boot 4.1.1 + MyBatis-Plus + PG16 + Redis7 | `xt-server/`(`xt-common/` 放响应体/错误码/加解密) |
| `frontend-pc/` | 管理端 **纯 Vue 3 + Naive UI 2.42**(2026-10 从 Gi Admin Pro + Arco 重搭) | `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`。
**不设二期** —— 与「二期」有关的任何设计(社会收集、社会评价、专项职业能力、数据范围可配等)都**不做**,
别顺手实现;OCR 与材料识别是「缓做,等供应商选型」。
## 二、常用命令(备注里是容易猜错的地方)
### 基础设施
```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 已到 **V19**;迁移文件发布后不许改,只能新加)
- 本地私有配置机制是 `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` 都已删除)→ 后端不起着就没有数据
- 接口模块都在 `src/apis/xt/`(按业务域分文件);**没有 auto-import**,组件与 API 全显式 `import`
- ⚠️ **2026-10 已从 Gi Admin Pro + Arco 重搭为纯 Vue 3 + Naive UI**,因此:
- **`pnpm lint:style`(Stylelint)已移除**,只剩 `pnpm lint`(ESLint);`pnpm typecheck` 现在 **0 错**
- 权限判断统一写 `userStore.has('码')`(**没有 `v-hasPerm` 指令**),字符串必须与后端 `PermissionDef` **逐字一致**
- `n-tag` 的 `type` 必须过 **`@/utils/tag.ts`** 转换:`apis/xt/*.ts` 里的展示映射仍是 Arco 色词
(`green`/`arcoblue`/`gold`/`gray`),直接透传会**静默变灰**且不报错
- 图标:`scripts/extract-pc-icons.py` 生成 `src/icons/icon-data.ts`(37 个);
`AppIcon` 对缺失图标是**空占位、不报错** → **图标集升级后要重跑该脚本**
- **PC 前端有独立的规范来源:`frontend-pc/AGENTS.md` + `frontend-pc/.agents/`**(已按 Naive UI 重写)。
改那里的代码前先读它,不要另起一套写法
### 学生端(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` 重复导出),不是新引入的。
⚠️ PC 端不同:重搭后 `pnpm typecheck` 是 **0 错** —— 那边一有报错就是新引入的
- 当前节奏:**先做 H5**(可自动化测试、不依赖小程序资质);小程序端代码同步维护
- ⚠️ **小程序端目前学生用不了(F-130,P0)**:前端**从来没有调用过 `/mp/login`**(token 永远是空 → 门禁 40101),
而后端那条路也不通 —— `scene` 带 taskId 时 `/mp/login` 必然 500(`MpLoginService.java:127` 匿名查租户表被租户拦截器拒)。
实测细节见 `docs/11` §5.4 / §6。**除登录外整条链路(落地页/门禁/表单/压缩上传/红黄校验/提交)都是好的**
—— 用开发者工具自动化(`wmp_*` 工具族)可以跑,`wx.login()` 在页面上下文里能拿到**真 code**
## 三、怎么验证(本仓库最不显然的一点)
**后端的单元测试很少**(`backend/*/src/test` 只有 12 条纯函数断言,`mvn test` 跑)。
主验证手段是 `scripts/e2e-*.py`:打真实接口 + `docker exec` 进 PG 查库断言。
```bash
python scripts/e2e-m3-file.py # 单跑一个;前提是后端 8080 在跑、xt-pg 容器在跑
```
- 全量:把 `scripts/e2e-*.py` 依次跑一遍(**38 个脚本 / 1712 条断言**,以最近一次
`verify-all --with-e2e` 的输出为准;2026-10 需求收敛删掉了 `e2e-m2-join.py` 与 `e2e-m7-cleanup.py`,
之后又补了 `e2e-m3-flow.py` —— 填报链路的覆盖缺口,见 `docs/11-填报流程端到端测试计划.md`),
或一条命令 `python scripts/verify-all.py --with-e2e`
- 脚本**自己造 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`(**默认 dry-run**;
`--only-orphan-files --apply` 只清磁盘孤儿文件 —— 压测跑几轮就能攒下几百 MB;
`--apply` 清 e2e 租户与测试组织,**会先算「仍被存活任务引用的组织」并保留**)、
`python scripts/dev-clean-test-users.py`、`python scripts/dev-clean-qiniu.py`
(清真实桶里 `xiaotutongxue/` 前缀的测试对象,**默认 dry-run**,删前先核实库中零引用)
- 文档一致性:`python scripts/verify-docs.py`
- 接线检查:`python scripts/check-wiring.py`(**"声明了但没接上"** ——
权限码有没有地方校验、错误码有没有被引用、站内信模板有没有发送点、
`bizType` 有没有被用过、菜单键 ↔ PC 端 `XtMenu` 是否对得上、
**PC 端 `user.has('码')` 引用的权限码 ↔ `PermissionDef`**(第 ⑦ 项)、
**模板里的 `<n-*>` 组件有没有漏 import**(第 ⑧ 项 —— 漏了会被当未知元素渲染,样式整块丢、
编译与 lint 都不红)、
**`.svg` 资产是不是合法 XML**(第 ⑨ 项 —— SVG 注释里出现连续两个减号就 not well-formed,
图标变裂图而控制台一句话都不打;2026-10 换品牌标时连踩两次)、
**后端会写的日志动作码 ↔ PC 端 `OPERATION_ACTIONS` 的文案**(第 ⑩ 项 —— 缺一个文案,
日志页会**原样回显英文码**;做这一项时才发现 `AUTO_CLOSE` 是字符串字面量写的,
所以"提成常量"是让这个检查真正生效的前提)。
⚠️ 这一族错**编译 / lint / review 都不会红**(类在、表在、常量在、注释还写得很清楚),
只有"从入口走一遍"才暴露 —— 所以把它固化成命令;有意不接线的写在脚本上方的例外表里(带理由)。
⚠️ **检查器自己也会失效**:PC 端重搭后权限码的写法从 `permissions.includes` / `v-hasPerm`
改成了 `user.has`,第 ⑦ 项曾因此"一个码都扫不到却判为通过"。
加/改这类检查时**必须反向验证一次**(故意写错,它得红)
- 一条命令跑完上面这些:`python scripts/verify-all.py --with-e2e`
(9 项;**跳过项会如实列出来**,不是静默跳过)
## 四、接口与数据契约(不显然、容易做错)
- 响应恒为 `{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`