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

142 lines
8.2 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` 里配好了测试账号,`pnpm dev` 打开页面**已是登录态**:
| 变量 | 值 | 说明 |
|---|---|---|
| `VITE_DEV_AUTO_LOGIN` | `1` | 设为 `0` 关闭(例如要调登录页本身) |
| `VITE_DEV_ORG` | `G3HD` | 机构码,需对应 `server/config/dbconfigs/{机构码}.properties` |
| `VITE_DEV_ACCOUNT` | `demo_zhangwei` | 实测可登录,但**只有审批中心权限**(见下) |
| `VITE_DEV_PASSWORD` | `demo_zhangwei` | 演示账号密码=账号名 |
实现见 `web/src/dev/auto-login.ts`:调 `POST /api/auth/login` 拿 token 写进 store,
**只在 `import.meta.env.DEV` 下生效**,生产构建里被 tree-shake。
滑块验证码是纯前端校验(不发请求),所以走接口登录不需要它。
覆盖面:入口是 `/login` 时不自动登录;已有 token 时不重复登录。
个人覆盖用 `web/.env.development.local`(已被 gitignore,优先级更高)。
命令行取 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`
明确「不建权限点」),只有超管可见;默认账号 `demo_zhangwei` 进它会被守卫送到 `/403`。
`e2e/smoke.spec.ts` 用 `/approval/todo`,演示账号和超管都能进。
- 断言用 `toHaveURL(/\/approval\/todo$/)` 这种**正向**断言,别用 `not.toHaveURL(/\/login/)`:
`goto` 之后 URL 立刻就是目标值,否定断言会在重定向发生之前就通过,等于什么都没断言。
- dev server 是按需转译的,某页**第一次**访问要现编依赖图,实测 5-11s。
配置里已经把 expect 超时放宽到 15s —— 别在用例里写死更短的值。
## 测试账号
| 账号 | 密码 | 权限 | 用途 |
|---|---|---|---|
| `demo_zhangwei` / `demo_liqiang` / `demo_wangfang` / `demo_liumin` | 同账号名 | 只授了 `menu.approval%` 与 `action.v_b_othercompany.%`(`sql/90_mock/02_*.sql:186`) | 已实测可登录;侧栏只有「审批中心」,其他页面会被守卫拦到 `/403` |
| `g3soft` | **未知** | 前后端唯一超管,免查 `s_power`(`permission.ts:61`、`SqlPermissionService.ADMIN_ACCOUNT`) | 测全部页面得用它 |
机构码统一 `G3HD`。
**`g3soft` 的密码待确认**:2026-10-14 实测 `G3HD/g3soft/空密码` 返回 `code=401`
(`AuthServiceTests` 里「超管空密码」是纯 mock,不代表 G3HD 库的真实数据)。
拿到真实密码后填进 `web/.env.development` 的 `VITE_DEV_PASSWORD` 即可——
**一次配置,不必每次查库**。另外 `g3soft` 在 `b_user` 里是否存在也需确认:
本仓库的 `sql/` 没有任何脚本创建它(只建 `demo_*`),假定它来自 G3HD 核心库。
账号密码不用去查库。库里没有的账号靠 `sql/` 下的脚本建(执行顺序见 `sql/README.md`);
要给 `demo_*` 放开更多菜单,用 `sql/README.md` 里「可选:给普通账号授权」那段。
## 踩坑备忘
- **同一账号重复登录会互踢**:每次登录都刷新服务端 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/` 的链接。