# 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** —— `frontend-pc/AGENTS.md` 里"开发默认 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=&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`