@oj-module/cli 0.1.21 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/devkit/README.md +1 -1
  2. package/devkit/SKILL.md +17 -3
  3. package/devkit/manual.md +77 -0
  4. package/package.json +2 -2
  5. package/shell-dist/assets/{index-BUyoDvUl.js → index-CLdhl3Qd.js} +61 -12
  6. package/shell-dist/assets/runtime.js +226 -89
  7. package/shell-dist/index.html +3 -3
  8. package/shell-dist/versions.json +3 -3
  9. package/src/build.ts +25 -0
  10. package/src/init.ts +3 -0
  11. package/templates/README.md +15 -0
  12. package/templates/api/config.yaml +14 -0
  13. package/templates/api/seed.mysql.sql +5 -0
  14. package/templates/api/seed.pg.sql +5 -0
  15. package/templates/api/src/_platform/_shared/respond.ts +18 -0
  16. package/templates/api/src/_platform/_shared/service/access.ts +99 -0
  17. package/templates/api/src/_platform/_shared/service/audit.ts +90 -0
  18. package/templates/api/src/_platform/_shared/service/contacts.ts +36 -0
  19. package/templates/api/src/_platform/_shared/service/guard.ts +30 -0
  20. package/templates/api/src/_platform/_shared/service/oplog.ts +47 -0
  21. package/templates/api/src/_platform/_shared/service/query.ts +53 -0
  22. package/templates/api/src/_platform/_shared/service/rate.ts +79 -0
  23. package/templates/api/src/_platform/_shared/service/roles.ts +137 -0
  24. package/templates/api/src/_platform/_shared/service/settings.ts +67 -0
  25. package/templates/api/src/_platform/_shared/service/solution-perms.ts +94 -0
  26. package/templates/api/src/_platform/_shared/service/users.ts +163 -0
  27. package/templates/api/src/_platform/contacts/api.ts +22 -0
  28. package/templates/api/src/_platform/contacts/detail/api.ts +18 -0
  29. package/templates/api/src/_platform/manifest.yaml +9 -1
  30. package/templates/api/src/_platform/migrations/0001__create_users.mysql.sql +9 -0
  31. package/templates/api/src/_platform/migrations/0001__create_users.pg.sql +9 -0
  32. package/templates/api/src/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +3 -0
  33. package/templates/api/src/_platform/migrations/0003__users_source_display_name.mysql.sql +5 -0
  34. package/templates/api/src/_platform/migrations/0003__users_source_display_name.sql +5 -0
  35. package/templates/api/src/_platform/migrations/0004__sys_rbac.mysql.sql +70 -0
  36. package/templates/api/src/_platform/migrations/0004__sys_rbac.pg.sql +67 -0
  37. package/templates/api/src/_platform/migrations/0004__sys_rbac.sql +67 -0
  38. package/templates/api/src/_platform/migrations/0005__users_must_change.mysql.sql +3 -0
  39. package/templates/api/src/_platform/migrations/0005__users_must_change.sql +2 -0
  40. package/templates/api/src/_platform/migrations/0006__users_is_forbidden.mysql.sql +3 -0
  41. package/templates/api/src/_platform/migrations/0006__users_is_forbidden.sql +3 -0
  42. package/templates/api/src/_platform/migrations/0007__users_email.sql +3 -0
  43. package/templates/api/src/_platform/migrations/0008__sys_refresh_token.mysql.sql +10 -0
  44. package/templates/api/src/_platform/migrations/0008__sys_refresh_token.pg.sql +10 -0
  45. package/templates/api/src/_platform/migrations/0008__sys_refresh_token.sql +11 -0
  46. package/templates/api/src/_platform/migrations/0009__sys_throttle.mysql.sql +12 -0
  47. package/templates/api/src/_platform/migrations/0009__sys_throttle.pg.sql +12 -0
  48. package/templates/api/src/_platform/migrations/0009__sys_throttle.sql +13 -0
  49. package/templates/api/src/_platform/schema.yaml +76 -1
  50. package/templates/api/src/_platform/seed.mysql.sql +8 -0
  51. package/templates/api/src/_platform/seed.pg.sql +8 -0
  52. package/templates/api/src/_platform/seed.sql +19 -2
  53. package/templates/api/src/auth/_shared/ldap-login.ts +86 -0
  54. package/templates/api/src/auth/_shared/ldap.ts +14 -0
  55. package/templates/api/src/auth/_shared/login-throttle.ts +29 -0
  56. package/templates/api/src/auth/_shared/session.ts +163 -18
  57. package/templates/api/src/auth/api.ts +54 -44
  58. package/templates/api/src/auth/ldap-login/api.ts +29 -0
  59. package/templates/api/src/system/login-methods/api.ts +19 -0
  60. package/templates/api/src/system/manifest.yaml +5 -0
  61. package/templates/api/src/system/oplog/api.ts +53 -0
  62. package/templates/api/src/system/permissions/api.ts +26 -0
  63. package/templates/api/src/system/roles/api.ts +75 -0
  64. package/templates/api/src/system/roles/bindings/api.ts +29 -0
  65. package/templates/api/src/system/settings/api.ts +60 -0
  66. package/templates/api/src/system/users/api.ts +58 -0
  67. package/templates/api/src/system/users/forbidden/api.ts +26 -0
  68. package/templates/api/src/system/users/password/api.ts +26 -0
  69. package/templates/api/src/system/users/roles/api.ts +27 -0
  70. package/templates/api/src/web/user-info/api.ts +9 -18
  71. package/templates/api/tests/auth-ldap.spec.ts +173 -0
  72. package/templates/api/tests/auth-session.spec.ts +194 -0
  73. package/templates/api/tests/auth-throttle.spec.ts +74 -0
  74. package/templates/api/tests/fixture.ts +50 -0
  75. package/templates/api/tests/guard.spec.ts +62 -0
  76. package/templates/api/tests/query-utils.spec.ts +68 -0
  77. package/templates/api/tests/rbac-audit.spec.ts +105 -0
  78. package/templates/api/tests/rbac-contacts.spec.ts +85 -0
  79. package/templates/api/tests/rbac-guard.spec.ts +192 -0
  80. package/templates/api/tests/rbac-roles.spec.ts +159 -0
  81. package/templates/api/tests/rbac-seed.spec.ts +54 -0
  82. package/templates/api/tests/rbac-settings.spec.ts +79 -0
  83. package/templates/api/tests/rbac-solution-perms.spec.ts +168 -0
  84. package/templates/api/tests/rbac-tables.spec.ts +63 -0
  85. package/templates/api/tests/rbac-users.spec.ts +164 -0
  86. package/templates/api/vitest.config.ts +12 -0
  87. package/templates/openapi.json +1060 -31
  88. package/templates/pnpm-workspace.yaml +9 -0
  89. package/templates/web/src/auth/__client/api.schemas.ts +6 -1
  90. package/templates/web/src/auth/__client/api.ts +33 -1
  91. package/templates/web/src/login/entry.ts +17 -0
  92. package/templates/web/src/login/locales/en-US.json +12 -2
  93. package/templates/web/src/login/locales/zh-CN.json +12 -2
  94. package/templates/web/src/login/pages/change-password.tsx +74 -0
  95. package/templates/web/src/system/entry.ts +90 -0
  96. package/templates/web/src/system/locales/en-US.json +98 -0
  97. package/templates/web/src/system/locales/zh-CN.json +98 -0
  98. package/templates/web/src/system/pages/errors.ts +51 -0
  99. package/templates/web/src/system/pages/forbidden.tsx +35 -0
  100. package/templates/web/src/system/pages/login-methods/index.tsx +29 -0
  101. package/templates/web/src/system/pages/oplog/index.tsx +91 -0
  102. package/templates/web/src/system/pages/roles/index.tsx +91 -0
  103. package/templates/web/src/system/pages/settings/index.tsx +106 -0
  104. package/templates/web/src/system/pages/users/index.tsx +168 -0
  105. package/templates/web/src/system/pages/users/roles.tsx +68 -0
  106. package/templates/web.config.ts +8 -0
  107. package/vendor/host-versions.json +3 -3
package/devkit/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  | 文件 | 用途 |
16
16
  |---|---|
17
17
  | `SKILL.md` | Claude Code 等 agent 的 skill 入口:工作流、红线、新模块 checklist、陷阱速查,按章节号引用手册 |
18
- | `manual.md` | 模块开发手册(10 章):工程结构、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令) |
18
+ | `manual.md` | 模块开发手册(10 章):工程结构(含 §1.7 handler 四红线——oj 运行时实测钉出,后端必读)、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令) |
19
19
  | `framework-dev.md` | 框架贡献者路线(**仅框架仓库**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
20
20
 
21
21
  ## 安装(业务工程)
package/devkit/SKILL.md CHANGED
@@ -54,12 +54,20 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
54
54
  仅平级声明 `.response`。不要手写 `content-type`(运行时按 boundary 自动补)。
55
55
  - **前端模块的 import 只有三类**:`@oj-module/runtime`、宿主 importmap 提供的共享依赖、
56
56
  自身相对路径。共享依赖不进工程 `dependencies`(宿主提供单例,模块不得打包副本)。
57
+ **拿用户/角色等信息:读走 `useUserStore` / `useAuthStore` / `useAccess`(`AccessControl` 门禁),
58
+ 写走契约 client(`ojm api` 生成模块自有 `__client`,或复用 runtime 导出的 `api/rbac` / `api/user`),
59
+ 绝不 import 框架 `#src/*`**——设计动机与红线见手册 §2.8。
57
60
  - **后端方法名是 `del`,不是 `delete`**(写成 `delete` 返回 405)。
58
61
  - **加一张新表是三件套,不是只写 `schema.yaml`**:`migrations/{seq:04}__*.sql`(DDL 的真正定义(实际 SQL),
59
62
  DDL 只进 migrations)+ `schema.yaml`(声明,与 migration 逐字段一致)+ `manifest.yaml` 的 `tables:`
60
63
  (与 schema 双向一致)。少一个就建不出表或启动失败。见手册 §1.5。
61
64
  - **`seed.sql` 每次启动都重放**,必须幂等(`INSERT OR IGNORE`);它按 `;` 朴素切分,
62
65
  注释里不许出现分号字面量。
66
+ - **handler 四红线(manual §1.7,oj 运行时实测)**:① 未捕获异常恒 500 不读
67
+ `err.status`——业务错误必须经 `respond()` 收口 `json.fail`;② 相对 import 按
68
+ **目录**解析(多一层 `../` 就 route error);③ 模块级 `let`/`Map` 不跨请求存活
69
+ (oj 每请求重建模块)——跨请求状态必须落库;④ 模块级禁止触碰运行时全局
70
+ (`jwt`/`axis`/`bcrypt`),`oj build` introspect 空沙箱会炸。
63
71
 
64
72
  ## 重启边界(最容易卡住新人)
65
73
 
@@ -78,6 +86,7 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
78
86
  - [ ] 菜单标题用命名空间语法 `"<模块名>:menu.<key>"`,且 `locales/<lang>.json` 里有这个 key
79
87
  - [ ] 有接口 → handler 动词挂了 `.schema`(入参)+ 平级 `.response`(出参,oj v0.1.72+)→ `oj openapi` + `ojm api` 跑过 → onInit 里 `create<Module>Client(ctx)`
80
88
  - [ ] 接管了某功能 → provider 方法数与类型定义**一个不差**
89
+ - [ ] 有后端 → handler 四红线过一遍(manual §1.7:respond 收口 / import 深度 / 无模块级状态 / 无模块级运行时全局)
81
90
  - [ ] `pnpm dev` 起来后按 §8 五步排查全过
82
91
 
83
92
  ## 旧工程迁移(contract.ts / defineApi → 新流水线)
@@ -126,10 +135,15 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
126
135
  | 后端 `import '@oj-module/runtime/...'` 报 `escapes project root` | oj 解析裸 specifier 以 `api/` 为项目根并钳制不越界,`@oj-module/runtime` 符号链接在 `packages/runtime`(项目根外) | 后端原语/类型放 `api/src` 内相对导入;跨模块共享类型仍以 runtime 为单一真源,两端形状手动同步 |
127
136
  | 跨用户删/读他人通知不报错却像没生效 | 原语对不存在/非本人的行 `UPDATE 0` 行返回 `ok`,没区分归属 | 原语(`delNotice`/`markRead`)返回 `boolean`/`count`,端点据 `false`/`0` 映射 `json.fail(403, "forbidden")` |
128
137
  | handler 写了动词子目录(`read/api.ts` 这类) | 旧形态(oj v0.1.64 前) | 按 manual §3.9「DDD 判据速查」reshape:同聚合多动词收模块根嵌套组,`del` 保留键例外 |
138
+ | 业务错误恒 `{"code":500}`(401/403/409 全变 500) | handler 抛异常被 oj 一律抹 500,不读 `err.status` | service 抛 `Error & {status}`,handler 经 `respond()` 收口(manual §1.7-1) |
139
+ | dev 日志 route error(Cannot find module)/ 端点 404 | handler 相对 import 多一层——按**目录**解析 | 按 manual §1.7 深度表改 |
140
+ | 限速/节流/计数真实请求下永不累积(测试全绿) | 模块级 `Map`/`let` 不跨请求存活(每请求重建模块) | 状态落库(模板迁移 0009 先例)或 oj 持久面 |
141
+ | `ojm build` 报 `introspect api.ts: jwt not configured` | 模块级语句碰运行时全局,introspect 空沙箱炸 | 移进 handler 函数体内(manual §1.7-4) |
129
142
  | 页签怎么点都关不掉、无报错提示 | `beforeClose` 守卫 fail-safe 否决(守卫抛错按取消处理) | 先查控制台 `[tabs] beforeClose` 报错;确认弹窗逻辑与 `resolve(true/false)` 收口(manual §6.4) |
130
143
 
131
144
  ## 手册
132
145
 
133
- `manual.md`(同目录)共 10 章:1 工程结构 / 2 写前端模块 / 3 契约与代码生成 /
134
- 4 接管框架内置功能(provider)/ 5 布局与插槽 / 6 i18n 与样式 / 7 构建与发布 / 8 排查 /
135
- 9 参考 / 10 CLI 子命令参考(ojm 原生命令 vs oj 透传命令的差异区分)。
146
+ `manual.md`(同目录)共 10 章:1 工程结构(含 **§1.7 handler 四红线**——oj 运行时实测钉出,
147
+ 后端 handler 必读)/ 2 写前端模块 / 3 契约与代码生成 / 4 接管框架内置功能(provider)/
148
+ 5 布局与插槽 / 6 i18n 与样式 / 7 构建与发布 / 8 排查 / 9 参考 / 10 CLI 子命令参考
149
+ (ojm 原生命令 vs oj 透传命令的差异区分)。
package/devkit/manual.md CHANGED
@@ -92,6 +92,43 @@ api/src/<模块>/<路径...>/api.ts → /api/<模块>/<路径...>
92
92
 
93
93
  > `seed.sql` 按 `;` 朴素切分,**注释里不许出现分号字面量**,否则语法错。
94
94
 
95
+ ### 1.7 handler 四红线(oj 运行时实测钉出,2026-10-16 RBAC 阶段)
96
+
97
+ 以下四条都是真机冒烟(`ojm init` + `ojm dev`/`build`)实测钉出的 oj 运行时行为,
98
+ **dev 模式体感正常、一到构建/并发就炸**——vitest 直引 service 的测试结构性覆盖不到,
99
+ 评审 checklist 必查。逐条展开:
100
+
101
+ 1. **未捕获异常恒 500,不读 `err.status`。** handler 抛出任何异常,oj 运行时一律回
102
+ `{"code":500,"msg":"Error: ..."}`——你在 service 层精心设计的 401/403/409 全被抹平。
103
+ **红线**:handler 业务体必须收口成 `json.fail(status, msg, extra?)`;模板统一约定
104
+ `_platform/_shared/respond.ts` 的 `respond(fn)`——service 抛 `Error & {status?, ...extra}`,
105
+ handler 写 `await respond(async () => { ...; return data; })` 即自动映射
106
+ (extra = error 的 own 可枚举属性除去 `status`/`message`,进信封 `data`)。
107
+ 2. **相对 import 按「目录」解析,不是按文件层级。** 路由加载器以**含 `api.ts` 的目录**
108
+ 为基准解析相对路径——多写一层 `../` 在 dev 日志里是 route error(模块像没注册),
109
+ vitest 直引 service 永远发现不了。正确深度:
110
+
111
+ | 文件位置 | 到 `_platform/_shared` 的写法 |
112
+ |---|---|
113
+ | `api/src/system/<x>/api.ts` | `../../_platform/_shared/...` |
114
+ | `api/src/system/<x>/<y>/api.ts` | `../../../_platform/_shared/...` |
115
+ | `api/src/auth/api.ts` | `../_platform/_shared/...` |
116
+ | `api/src/auth/<z>/api.ts` | `../../_platform/_shared/...` |
117
+ | `api/src/_platform/<x>/api.ts` | `../_shared/...` |
118
+ | `api/src/_platform/<x>/<y>/api.ts` | `../../_shared/...` |
119
+ 3. **模块级可变状态不跨请求存活。** oj **每请求重建 handler 模块实例**(实测:模块级
120
+ 计数器连打三次恒为初值)——`let` / `Map` / `Set` 级状态一律视为**单请求作用域**。
121
+ 需要跨请求的状态(限速、节流、去重窗口、判权缓存)**必须落库**或走 oj 持久面。
122
+ 模板先例:迁移 `0009__sys_throttle`(`sys_login_throttle` 连败节流 +
123
+ `sys_rate_window` 滑动窗口,三方言)。别想着「先内存后 Redis」——内存方案在 oj 下
124
+ 连单实例都跑不对。
125
+ 4. **`oj build` 的 introspect 在无运行时配置的空沙箱里评估模块。** 构建期会加载
126
+ handler 模块提取 `.schema`,此时 `jwt` / `axis` 等运行时全局**不存在**——模块级
127
+ 任何触碰运行时全局的语句(`createSession()`、`axis("ldap")`、`bcrypt.xxx`)
128
+ 直接炸构建:`introspect api.ts: Error: jwt not configured`。**红线**:handler 模块
129
+ 顶部只许 import 与纯常量声明;一切运行时全局的取用移进 handler 函数体内按需执行
130
+ (无状态构造是纯闭包,每请求新建零成本)。
131
+
95
132
  ---
96
133
 
97
134
  ## §2 写前端模块
@@ -283,6 +320,41 @@ importmap 键,构建期报错——需要新深路径请联系框架方在共
283
320
 
284
321
  ---
285
322
 
323
+ ### 2.8 模块如何获取用户/角色等信息(读与写的边界)
324
+
325
+ 模块常要读当前用户、角色、权限,甚至写改密/改角色。**正确姿势是走框架的「公共出口 + 契约客户端」,而不是 import 框架源码**——后者会把 runtime 内部实现打进模块 bundle,造成编译夹带、双份 store / 双 React、版本不一致。本节讲清楚边界与红线。
326
+
327
+ **读(用户/角色/权限)→ 用 `@oj-module/runtime` 导出的公共 hook/store**
328
+
329
+ 这些是框架写入的同一份 zustand 单例,模块订阅即拿到实时状态,无需自己再拉一份:
330
+
331
+ - `useUserStore`:当前用户画像(`username` / `roles` / `permissions` / `email` …);
332
+ - `useAuthStore`:`token`、登录/登出(`login` / `logout`);
333
+ - `useAccess` + `AccessControl` 组件:基于 `roles` / `permissions` 的按钮/菜单门禁,别自己拿 `roles` 数组手算;
334
+ - 其它:`usePreferences`、`useTabsStore`、`useSlotNodes`(§5.5)等。
335
+
336
+ ```ts
337
+ import { useUserStore, useAccess, AccessControl } from "@oj-module/runtime";
338
+
339
+ const roles = useUserStore(s => s.roles);
340
+ // 或按权限门禁:<AccessControl permission="system:users:read"><Button>用户管理</Button></AccessControl>
341
+ ```
342
+
343
+ **写(改密/改角色/改设置)→ 走契约 API client,不碰框架内部**
344
+
345
+ - 框架已把 `auth/*`、`system/*`、`web/*` 等端点经 OpenAPI 契约定义,并从 `@oj-module/runtime` 导出类型化 fetch(如 `api/rbac` 的 `fetchChangePassword`、`fetchRbacUpdateUserRoles`,`api/user` 的 `fetchUserInfo`)。要调框架内置写接口,直接复用这些导出。**但更推荐模块 own 自己的契约**:在 `api/src/<模块>/contract.ts` 声明,跑 `ojm api` 生成 `web/src/<模块>/__client/api.ts`,模块经自己的 client 调框架端点——接口由契约(versioned OpenAPI)对齐,而非把框架源文件打进来。
346
+ - 底层都走同一个 `request` 单例 + 契约 URL;模块侧请求被 `defineModule` 的 `ctx` 收敛到登记过的 `apiPrefix`,越界 / `../` 穿越 / 逐请求 prefix 覆盖均被拒(请求边界见 §2.6 + `tests/runtime/scoped-request.test.ts`)。
347
+
348
+ **为什么不会「版本不一致」**
349
+
350
+ 宿主(cli 内置 `shell-dist`)把 `react` / `antd` / `runtime` 等共享依赖预打包、经 importmap 提供**单例**——宿主与所有模块命中同一份实例。所以模块的 `useUserStore` 和框架的是**同一个 store**,antd `ConfigProvider` 也只有一个:不会出现两份 React / 两份 store 状态不同步。共享依赖版本以 `catalog:` 单一真相钉死(见 §2.7),模块只在 `devDependencies` 对齐版本供 `tsc` 用。
351
+
352
+ **红线**
353
+
354
+ - **绝不 `import … from "#src/*"`**:构建期直接拦(§2.6)。模块只 import 三类(§2.6)。
355
+ - **`@oj-module/runtime` 的出口是冻结契约**:有 `tests/runtime/runtime-exports.test.ts` 守护,加/删导出即红,属破坏性变更。要用的东西若在出口里没有,**提需求让它进公共出口**,别改成 import 框架内部——否则一发版就碎。
356
+ - 需要框架没暴露的能力(如接管某内置功能),走 §4 的 provider 接管,而非源码 import。
357
+
286
358
  ## §3 契约与代码生成
287
359
 
288
360
  ### 3.1 一句话
@@ -1274,6 +1346,10 @@ export default async (path: string, html: string) => {
1274
1346
  | curl 访问 SPA 路由返回 404 | SPA history 回落要求 `Accept: text/html` | 加 `-H 'Accept: text/html'`(浏览器自带,不是 bug) |
1275
1347
  | `oj serve` 启动即退,报 `config uses removed server.* site keys (v0.1.58): api_prefix`(或 `app_path` / `app_prefix` / `static_sites` / `app_spa_fallback`) | 用了 oj v0.1.58 起删除的旧站点键 | 改 `server.api_prefix: /api` 为顶层 `mounts: [{prefix: "/api", api: "src"}]`(一行=一个 URL 前缀+一个目录,`api`/`web` 恰好其一);迁移完整 YAML 由报错信息给出;详见后端 devkit「mounts 迁移」 |
1276
1348
  | 改完 `mounts:` 仍报 `no route matched`(/api/* 全 404) | `api:` 指向的目录不是 oj 启动目录下的 `src`(相对 config 所在目录) | `mounts[].api` 写 `"src"`,且 `oj serve` 的 `-c` 指向 `api/config.yaml`(ojm dev/preview 已内置正确路径) |
1349
+ | 业务错误恒返回 `{"code":500}`(401/403/409 全变 500) | handler 抛异常被 oj 一律抹成 500,不读 `err.status` | service 抛 `Error & {status}`,handler 经 `respond()` 收口(§1.7-1) |
1350
+ | dev 起不来,日志一堆 route error(Cannot find module) | handler 相对 import 多写了一层——按**目录**解析不是按文件(§1.7-2) | 按 §1.7 深度表改 |
1351
+ | 限速/节流/去重窗口在真实请求下永不累积(测试却全绿) | 模块级 `Map`/`let` 不跨请求存活——oj 每请求重建模块(§1.7-3) | 状态落库(模板 0009 先例)或走 oj 持久面 |
1352
+ | `ojm build` 报 `introspect api.ts: Error: jwt not configured`(dev 模式正常) | 模块级语句触碰运行时全局,introspect 空沙箱里炸(§1.7-4) | 移进 handler 函数体内按需执行 |
1277
1353
 
1278
1354
  ---
1279
1355
 
@@ -1289,6 +1365,7 @@ export default async (path: string, html: string) => {
1289
1365
  | 无页面模块(`routes: []`) | 同 `web/src/notification/entry.ts` |
1290
1366
  | handler `.schema` 入参 + 平级 `.response` 出参写法(GET + POST、参数段) | `api/src/notification/api.ts`、模板 `packages/cli/templates/api/src/notification/api.ts` |
1291
1367
  | handler 写法(嵌套组列表 / 写入 / 403 归属) | `api/src/notification/api.ts`(模块根单文件,`read`/`read-all`/`clear` 子组) |
1368
+ | 统一异常出口 `respond()`(service 抛 `Error & {status}` → 信封 `json.fail`) | 模板 `api/src/_platform/_shared/respond.ts` + 各 handler 用法(§1.7-1) |
1292
1369
  | 生成物长什么样(含工厂) | `web/src/notification/__client/api.ts` |
1293
1370
  | 建表三件套 | `api/src/notification/{schema.yaml,manifest.yaml,migrations/}` |
1294
1371
  | 全局类型(`window.$message` 等) | 工程根 `env.d.ts` |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oj-module/cli",
3
3
  "type": "module",
4
- "version": "0.1.21",
4
+ "version": "0.2.1",
5
5
  "description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@faker-js/faker": "^10.4.0",
52
- "@oj-module/runtime": "0.1.21",
52
+ "@oj-module/runtime": "0.2.1",
53
53
  "@redocly/cli": "^2.51.0",
54
54
  "@tailwindcss/vite": "^4.3.0",
55
55
  "esbuild": "^0.28.2",
@@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
4
  import { ConfigProvider, theme } from "antd";
5
5
  import { useEffect, useState } from "react";
6
6
  import { createRoot } from "react-dom/client";
7
+ import { useTranslation } from "react-i18next";
7
8
  import { Navigate, Outlet, RouterProvider, createBrowserRouter } from "react-router";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
9
10
  //#region \0vite/modulepreload-polyfill.js
@@ -141,15 +142,55 @@ var DEMO_USER = {
141
142
  description: "manager",
142
143
  roles: ["admin"]
143
144
  };
144
- useUserStore.setState(DEMO_USER);
145
145
  /**
146
- * 登录/登出都会经 auth store reset() 清空 user store;宿主链无 AuthGuard
147
- * 重新拉取 userInfo,播种丢失后头部用户名/角色恒为空。这里在 token 变化
148
- * 时重播种演示用户(模块路由的重登记由 access store reset() 快照机制负责)。
146
+ * 定角色:宿主链**没有 AuthGuard**(免登录演示能力绕开 LayoutRoot),没有任何
147
+ * 机制拉 `/web/user-info`,于是 user store 的 roles 恒为演示值或空——而模块路由
148
+ * 的角色过滤(B16,`config.requiredRoles`)与 superadmin 类前端判断都读它。
149
+ * 后果实测:seed 的 admin 账号(roles=["superadmin"])被 `["admin"]` 挡在
150
+ * /system 路由外(404),成员资格入口也判不出来。
151
+ *
152
+ * - 有 token = 真实登录链 → 拉真实角色(must_change 悬挂期该端点仍返回 200);
153
+ * 失败则清空,不让演示角色冒充真实权限(401 由请求层兜底跳登录);
154
+ * - 无 token = 演示态 → 播种 DEMO_USER(与 App 链 fake 数据一致,两链显示同构)。
155
+ */
156
+ async function hydrateUser() {
157
+ if (!useAuthStore.getState().token) {
158
+ useUserStore.setState(DEMO_USER);
159
+ return;
160
+ }
161
+ try {
162
+ await useUserStore.getState().getUserInfo();
163
+ } catch (e) {
164
+ console.warn("[shell] /web/user-info 拉取失败,角色置空", e);
165
+ useUserStore.getState().reset();
166
+ }
167
+ }
168
+ /**
169
+ * 登录/登出都会经 auth store reset() 清空 user store(宿主链无 AuthGuard 重新
170
+ * 拉取),token 变化即重新定角色。
171
+ *
172
+ * 已知偏差:角色变化只影响渲染期判断,侧栏菜单是 Boot 期按当时角色登记的,
173
+ * 不登录直接进站再登录的场景菜单需刷新一次才对(后端 403 硬兜底,不是权限泄漏)。
149
174
  */
150
175
  useAuthStore.subscribe((state, prev) => {
151
- if (state.token !== prev.token) useUserStore.setState(DEMO_USER);
176
+ if (state.token !== prev.token) hydrateUser();
152
177
  });
178
+ /**
179
+ * 直链兜底页(catch-all 路由元素)。无匹配路由时渲染框架 `PageError`,
180
+ * 把 title 设为框架 404 文案(`exception.notFoundTitle` = "404"),
181
+ * 观感与无权限面板同构,替代 React Router 默认的「Unexpected Application Error!」。
182
+ * 仅作观感兜底,权限判定仍由后端 403 硬执行。
183
+ */
184
+ function CatchAll() {
185
+ const { t } = useTranslation();
186
+ return /* @__PURE__ */ jsx(PageError, {
187
+ error: {
188
+ status: 404,
189
+ message: t("exception.notFoundTitle")
190
+ },
191
+ resetErrorBoundary: () => {}
192
+ });
193
+ }
153
194
  var i18nReady = false;
154
195
  function ensureI18n() {
155
196
  if (!i18nReady) {
@@ -193,6 +234,7 @@ function Boot() {
193
234
  document.head.appendChild(link);
194
235
  }
195
236
  const manifest = toLoaderManifest(list, runtimeVersion);
237
+ await hydrateUser();
196
238
  await loadAll(manifest);
197
239
  if (cancelled) return;
198
240
  setRouter(createBrowserRouter([{
@@ -201,13 +243,20 @@ function Boot() {
201
243
  FallbackComponent: PageError,
202
244
  children: [/* @__PURE__ */ jsx(LayoutEffects, {}), /* @__PURE__ */ jsx(Outlet, {})]
203
245
  }),
204
- children: [{
205
- index: true,
206
- element: /* @__PURE__ */ jsx(Navigate, {
207
- to: getRoutes()[0]?.path ?? "/",
208
- replace: true
209
- })
210
- }, ...getRoutes()]
246
+ children: [
247
+ {
248
+ index: true,
249
+ element: /* @__PURE__ */ jsx(Navigate, {
250
+ to: getRoutes()[0]?.path ?? "/",
251
+ replace: true
252
+ })
253
+ },
254
+ ...getRoutes(),
255
+ {
256
+ path: "*",
257
+ element: /* @__PURE__ */ jsx(CatchAll, {})
258
+ }
259
+ ]
211
260
  }]));
212
261
  } catch (e) {
213
262
  if (!cancelled) setError(e instanceof Error ? e.message : String(e));