Files
workspace/code/g3soft-erp/CODEBUDDY.md
T
2026-10-17 17:17:39 +08:00

172 lines
10 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.
# CODEBUDDY.md
给 AI 协作者的项目约定。人看的话直接读 `README.md`(前端)与 `server/README.md`(后端)。
## 项目速览
前后端同仓、**独立构建、独立版本,唯一契约是 HTTP 接口**(前端不读后端源码,后端不管前端怎么调)。
| 目录 | 是什么 | 端口 / 入口 |
|---|---|---|
| `web/` | 前端(Vue 3 + Vite + Pinia),唯一有 `package.json` 的包 | dev server `5090`,`/api` 代理到 `127.0.0.1:8088` |
| `server/` | 后端(Spring Boot 4.1 + Java),多机构 SQL Server | `8088`,context-path `/api` |
| `sql/` | 工作流模块 SQL 脚本与演示数据 | 执行顺序见 `sql/README.md` |
**根目录没有 `package.json`**,前端命令一律在 `web/` 下跑。
## 启动
```bash
# 1) 组件库必须先有产物(@g3soft/ui 的 exports 指向 dist)
# 需要 g3soft-libs 与 g3soft-erp 平级
cd ../g3soft-libs && pnpm --filter @g3soft/tokens build && pnpm --filter @g3soft/ui build
# 2) 前端
cd ../g3soft-erp/web && pnpm dev # http://localhost:5090
# 3) 后端(连只支持 TLS 1.0 的老 SQL Server 时必须这么起,见 server/config/dbconfigs/README.md)
cd ../g3soft-erp/server
mvn "-Dspring-boot.run.jvmArguments=-Djava.security.properties=./config/legacy-tls.security" spring-boot:run
```
改完组件库要重启 dev server(构建会清空 `dist/`,dev server 会崩或白屏),详见根 `README.md`。
## 开发期免登录(省掉每次填账号 + 拖滑块)
两条互补的路径,共用 `web/.env.development` 里的同一组 `VITE_DEV_*`:
| 场景 | 机制 |
|---|---|
| 打开任意非 `/login` 页面 | `web/src/dev/auto-login.ts` 自动登录,页面直接是登录态 |
| 打开 `/login`(或点了退出) | 登录页底部的「**一键登录**」按钮,一步进去 |
登录页的按钮只在 **DEV 且 `VITE_DEV_ORG`/`VITE_DEV_ACCOUNT` 配齐**时渲染(`devCred`),
调的是同一个 `loginApi`,因此同样不需要滑块验证码。
`web/.env.development` 里配好了测试账号:
| 变量 | 值 | 说明 |
|---|---|---|
| `VITE_DEV_AUTO_LOGIN` | `1` | 设为 `0` 关闭自动登录(例如要调登录页本身),**一键登录按钮不受它影响** |
| `VITE_DEV_ORG` | `G3HD` | 机构码,需对应 `server/config/dbconfigs/{机构码}.properties` |
| `VITE_DEV_ACCOUNT` | `g3soft` | 前后端唯一超管,免查 `s_power`,能进全部页面 |
| `VITE_DEV_PASSWORD` | `g3soft` | 2026-10-14 实测有效 |
实现见 `web/src/dev/auto-login.ts`(自动登录)与 `web/src/dev/credentials.ts`(凭据读取,
两个消费方共用),**只在 `import.meta.env.DEV` 下生效**,生产构建里被 tree-shake。
滑块验证码是纯前端校验(不发请求),所以走接口登录不需要它。
覆盖面:入口是 `/login` 时不自动登录(留给「一键登录」按钮);已有 token 时不重复登录。
个人覆盖用 `web/.env.development.local`(已被 gitignore,优先级更高)。
> ⚠️ `g3soft` 是超管,且 `VITE_DEV_ACCOUNT` 已默认改成它 —— 若有人在用同一个 `g3soft`
> 跑系统,你的 dev server 会把他踢下线(反之亦然)。需要并行时用
> `.env.development.local` 换回 `demo_zhangwei`(权限窄,只够进审批页)。
命令行取 token(例如要用 curl 直接打业务接口):
```bash
node scripts/dev-login.mjs --token-only # 只输出 token 一行
node scripts/dev-login.mjs # 人看的输出 + curl 示例
```
## E2E(Playwright)
```bash
cd web
pnpm test:e2e # 全量,headless
pnpm test:e2e:ui # 带 UI 的调试模式
```
前置条件:**后端已在 8088 起来**(前端 dev server 由配置里的 `webServer` 负责,已经在跑就复用)。
后端没起会在 globalSetup 阶段直接报「连不上后端」,不会跑到用例里。
与 Vitest 的分工:`pnpm test` 跑 `tests/**`(happy-dom 单元测试),`pnpm test:e2e` 跑 `e2e/**`(真浏览器 + 真后端)。
**单元测试放哪儿**:统一放 `web/tests/`,目录**镜像 `src/` 结构**
(`src/components/biz-tree/BizTree.vue` → `tests/components/biz-tree/BizTree.spec.ts`)。
不要贴源放进组件目录;`vitest.config.ts` 的 `include` 只收 `tests/**`,src 下误放的 spec 不会被执行。
测试里引用被测文件一律用 `@/` 别名(如 `@/components/biz-tree/BizTree.vue`),别写相对路径。
`playwright.config.ts` 的 `testDir` 必须限定 `./e2e`,否则 Playwright 会把 vitest 的用例也收进来。
### 免登录是怎么做的
1. `e2e/support/global-setup.ts`:**整轮测试只登录一次**,调 `POST /api/auth/login`,
会话落到 `e2e/.auth/session.json`(已 gitignore,里面有可用 token)。
2. `e2e/support/fixtures.ts`:覆盖内置 `context` fixture,用 `addInitScript` 在应用脚本
执行**之前**把 `sessionStorage['g3erp:login']` 种好,于是守卫直接放行,用例一进站就是登录态。
**为什么不是 `storageState`**:token 存在 `sessionStorage`,而 Playwright 的 `storageState`
只序列化 cookie + localStorage,种不进去。`addInitScript` 是唯一可靠的办法。
**为什么必须只登录一次**:后端 `ActiveSessionRegistry` 按 (机构码, 账号) 只保留最新会话,
重复登录会互踢(业务码 `40101`)。并发 worker 各登录一次就会互相把对方踢下线。
> 所以:**不要在用例里提交登录表单**。要测登录流程本身,得用一个专用账号,
> 并把 `VITE_DEV_AUTO_LOGIN` 设为 `0`(否则应用自己会先把你登录了);
> 登录页那个「一键登录」按钮同样会真的登进去,测登录表单时别点它。
账号沿用 `web/.env.development` 的 `VITE_DEV_*`,可用 `E2E_ORG` / `E2E_ACCOUNT` / `E2E_PASSWORD`
覆盖;`E2E_BASE_URL` 换前端地址,`E2E_API_TARGET` 换后端地址,`E2E_CHANNEL=msedge|chrome`
改用系统已装的浏览器(Chromium 下载不便时)。
### 写用例时注意
- 落点别选 `/dashboard`:它**没有任何权限点**(`sql/20_registration/14_dashboard_menu.sql`
明确「不建权限点」),只有超管可见。默认走 `VITE_DEV_ACCOUNT=g3soft`(超管)时能进,
但**换个非超管账号(`E2E_ACCOUNT=demo_zhangwei`)就会被守卫送到 `/403`**。
`e2e/smoke.spec.ts` 用 `/approval/todo`,超管和演示账号都能进 —— 换账号也不会挂。
- 断言用 `toHaveURL(/\/approval\/todo$/)` 这种**正向**断言,别用 `not.toHaveURL(/\/login/)`:
`goto` 之后 URL 立刻就是目标值,否定断言会在重定向发生之前就通过,等于什么都没断言。
- dev server 是按需转译的,某页**第一次**访问要现编依赖图,实测 5-11s。
配置里已经把 expect 超时放宽到 15s —— 别在用例里写死更短的值。
## 测试账号
| 账号 | 密码 | 权限 | 用途 |
|---|---|---|---|
| `g3soft` | `g3soft` | 前后端唯一超管,免查 `s_power`(`permission.ts:61`、`SqlPermissionService.ADMIN_ACCOUNT`) | **默认账号**,能进全部页面 |
| `demo_zhangwei` / `demo_liqiang` / `demo_wangfang` / `demo_liumin` | 同账号名 | 只授了 `menu.approval%` 与 `action.v_b_othercompany.%`(`sql/90_mock/02_*.sql:186`) | 已实测可登录;侧栏只有「审批中心」,其他页面会被守卫拦到 `/403` |
机构码统一 `G3HD`。
**`g3soft` 的密码已确认**:2026-10-14 实测 `G3HD/g3soft/g3soft` 登录成功
(返回 `user.name = 超级管理员`);`/空密码` 返回 `code=401`。
已填进 `web/.env.development` 的 `VITE_DEV_PASSWORD` —— **不必再查库**。
> 需要**并行**时才换账号:`g3soft` 是超管且默认,你和别人共用它就会互相踢下线,
> 此时用 `web/.env.development.local` 换回 `demo_zhangwei`(记得它只有审批页权限)。
账号密码不用去查库。库里没有的账号靠 `sql/` 下的脚本建(执行顺序见 `sql/README.md`);
要给 `demo_*` 放开更多菜单,用 `sql/README.md` 里「可选:给普通账号授权」那段。
## UI 文案
**不写解释性 / 引导性文案。** 这是内部系统,用的人就是配系统的人,不需要教他下一步点哪。
- 别写「创建后会自动新增一条页签,请继续填写页签名称和 SQL 条件」这种"接下来你要干什么"的说明。
- 表单下面不挂说明段落;也别用文字解释控件本身(禁用态、必填标记、示例 placeholder 就够了)。
- 别写「改动即时生效」「留空表示不带默认条件」这类系统本该自明的话。
仍然要写的:**空态提示、校验报错、图标按钮的 `title`**——它们承载的是「页面没坏 / 哪儿错了 /
这个图标是什么」,不是教操作。
## 踩坑备忘
- **同一账号重复登录会互踢**:每次登录都刷新服务端 session(`ActiveSessionRegistry.activate`),
上一个会话的下一次请求会拿到业务码 `40101` 并被踢回登录页。所以**别和正在用 `g3soft` 的人共用一个账号**——
否则双方会在「登录 → 被踢 → 重新登录」之间来回。需要并行时用 `.env.development.local`
给 AI 换一个账号(`demo_*` 权限窄,测非审批页要另开权限)。
- **鉴权失败是 HTTP 200 + `body.code=401`**,不是 HTTP 401,判状态码会漏。
- **菜单是动态的**(读 `s_menu` 表):新增页面必须同时加前端静态路由 +`s_menu` 记录,
缺一个就表现为「点菜单进 404」。而 Vite 对任意路径都返回 HTTP 200,网络面板里看不出 404,
判据是**路由是否匹配**。
- **权限是白名单语义**:`s_power` 没授权 → 侧栏不显示该菜单 + 守卫拦到 `/403`。
- **token 存 `sessionStorage`**(key `g3erp:login`,pinia-plugin-persistedstate 写的),
所以浏览器每个新会话/新 context 都是未登录态 —— 这正是自动登录要解决的问题。
- **`docs/` 目录当前不存在**(提交 `15e0ae5b` 删掉了)。文档写进 `README.md` / `server/README.md`
或本文件,别再新增指向 `docs/` 的链接。
- **疑难问题的排查记录在 `web/TROUBLESHOOTING.md`**(按「现象 → 根因 → 修复 → 约束」四段式):
遇到"看起来坏了"的表单 / 样式类问题先按现象关键词搜它,别从零排查;解决新问题后追加条目。