@lark-apaas/coding-steering 0.1.59 → 0.1.60-alpha.20260923111703

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.
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-steering",
3
- "version": "0.1.59",
3
+ "version": "0.1.60-alpha.20260923111703",
4
4
  "description": "Stack-specific steering content for miaoda-coding templates",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "steering"
8
8
  ],
9
+ "scripts": {
10
+ "lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**' --ignore 'steering/**/skills_common/**' --ignore 'steering/**/skills_local/**'"
11
+ },
9
12
  "devDependencies": {
10
13
  "markdownlint-cli": "^0.47.0"
11
14
  },
@@ -17,8 +20,5 @@
17
20
  "miaoda",
18
21
  "coding-steering"
19
22
  ],
20
- "license": "MIT",
21
- "scripts": {
22
- "lint:md": "markdownlint 'steering/**/*.md' --ignore 'steering/**/skills/**' --ignore 'steering/**/skills_common/**' --ignore 'steering/**/skills_local/**'"
23
- }
24
- }
23
+ "license": "MIT"
24
+ }
@@ -34,17 +34,7 @@ match-template-name: nestjs-react-fullstack
34
34
 
35
35
  ### 403 统一处理(所有模式通用)
36
36
 
37
- **⛔ 403 是正常 HTTP 响应(status 200 范畴外但不会触发 axios error)**,`axiosForBackend` 不会抛异常,必须在 `.then()` / `response` 层面检查 `status`,**禁止放在 `.catch()` 或 `try-catch` 的 catch 块中**——catch 永远捕获不到 403。
38
-
39
- 在前端统一请求层拦截 403 并抛错,**禁止**在业务组件中单独处理:
40
-
41
- ```typescript
42
- const response = await axiosForBackend(config);
43
- // ⛔ 403 是正常响应,不会进 catch——必须在 response 层面检查
44
- if (response.status === 403) throw new Error('无操作权限,请联系管理员分配角色');
45
- ```
46
-
47
- (403 落 catch 会被吞成通用错误提示如'操作失败请稍后重试',掩盖真实无权限信号,导致排查方向跑偏)
37
+ 当前 `axiosForBackend` 的响应拦截器会将 rejected 403 降级为 resolved `response`;不能因此断言 403 永远进不了 catch(请求器或拦截器行为可能变化)。在统一 API 层使用模板 `client/src/api/error.ts` 的 `requireApiSuccess(response, { fallbackMessage })` 检查 resolved 非 2xx,并在 catch 用 `toApiRequestError(error, { fallbackMessage })` 处理 rejected;两条路径均抛错且保留 `status` / 原始 `cause`。页面按 `status === 403` 呈现无权限状态,不从错误字符串猜测权限原因,也不要在各业务组件重复拦截;401 登录跳转保持由 SDK 处理。
48
38
 
49
39
  ### 平台的角色面板入口
50
40
 
@@ -153,7 +143,7 @@ import { CanRole } from '@lark-apaas/fullstack-nestjs-core';
153
143
  | 自建任意平行的角色/权限存储(自建角色表 / 角色字段 / 用户名单,不限具体命名)来做鉴权判断 | @CanRole 只认平台角色,应用自建的存储不会被鉴权读取;角色一律用平台真实角色 |
154
144
  | 复杂授权就**另起炉灶搞一套平台不认的权限**(自建角色表 / 引用平台没 create 过的标识 / 写死 user_id 名单) | 角色一律用平台真实角色;消费方式不限(`@CanRole` 或读 `req.userContext.roles` 都行),复杂度用「平台角色 + 数据层行级过滤」表达 |
155
145
  | `@CanRole([X])` 的 X 凭语义臆造、与平台角色标识(bizID)不一致 | X 必须 = 平台角色 bizID;`role create --biz-id` 固定标识或 `role list` 取实际标识再写代码 |
156
- | 在 `.catch()` 或 catch 块中处理 403 | 403 不触发异常,必须在 response 层面检查 `status` |
146
+ | 断言 403 永远只进入 response 或 catch | 用 `requireApiSuccess` / `toApiRequestError` 在 API 层处理两种交付方式并保留 `status` / `cause` |
157
147
 
158
148
  ---
159
149
 
@@ -532,17 +532,17 @@ await db.select().from(users).where(eq(users.adminUser, userId));
532
532
 
533
533
  | 分层 | 目标 |
534
534
  |------|------|
535
- | service | 抛业务异常,**必须用 NestJS 内置 HttpException 子类**(`@nestjs/common`),禁止裸 `throw new Error`(会被全局 Filter 兜成 500,前端拿不到语义状态码) |
535
+ | service | 要向用户展示具体原因的可预期业务失败,抛模板 `BusinessException(ResponseCode, message, httpStatus)`;技术异常不伪装成业务文案 |
536
536
  | controller | 不处理异常,交全局 Error Filter |
537
537
 
538
- 场景 → 异常类型映射:
538
+ ```typescript
539
+ import { HttpStatus } from '@nestjs/common';
540
+ import { ResponseCode } from '../../common/constants/api_response_code'; // server/modules/<name>/ 下的示例路径
541
+ import { BusinessException } from '../../common/interfaces/exception.interface';
542
+ throw new BusinessException(ResponseCode.CONFLICT, '资源已被占用', HttpStatus.CONFLICT);
543
+ ```
539
544
 
540
- | 场景 | 异常 |
541
- |------|------|
542
- | 资源不存在(含 update / delete 未命中) | `NotFoundException` |
543
- | 参数 / 状态非法 | `BadRequestException` |
544
- | 并发冲突、库存 / 余额不足 | `ConflictException` |
545
- | 无权限操作他人数据 | `ForbiddenException` |
545
+ 参数 / 状态非法、权限不足或资源未命中需要展示具体原因时,分别用 `ResponseCode.BAD_REQUEST` / `FORBIDDEN` / `NOT_FOUND` 和对应 `HttpStatus`;不需展示具体原因的 `HttpException` 由 Filter 输出安全文案。模板 Filter 仅将 `BusinessException` 标记为 `error.userFacing: true`;class-validator 校验和其它普通异常均返回固定安全文案且标记为 false,不可把校验数组、`details` / `fieldErrors` / `stack` / `cause` 当作 UI 文案。
546
546
 
547
547
  ## 当前用户信息
548
548
 
@@ -668,7 +668,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
668
668
  // ✅ axiosForBackend.get('/api/users') / .post(...) 等实例方法(axiosForBackend 是 axios.create 返回的实例)
669
669
  ```
670
670
 
671
- - **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**,禁止对后端接口请求进行兜底和过度封装
671
+ - **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**;禁止用 mock / 假成功兜底真实失败或过度封装,失败仍要抛出。未知错误的安全 UI 文案回退不是伪造成功。
672
+ - **错误读取**:在 `client/src/api` 统一用 `requireApiSuccess(response, { fallbackMessage })` 检查 resolved 非 2xx,在 catch 中用 `toApiRequestError(error, { fallbackMessage })` 处理 rejected;从 `client/src/api/error.ts` 导入(按文件位置调整路径,`index.ts` 有调用示例)。共享 helper 只在 `response.data.error.userFacing === true` 时取嵌套 `error.message`;`userFacing: false`、未知结构、5xx 或非 JSON 使用安全文案,绝不读取 `data.message`、`details`、`stack`、`cause` 当默认 UI 文案。仅当已核实旧端点返回 JSON object,且其顶层 `message:string|string[]` 是用户文案时,才显式传 `allowLegacyUserMessage: true`;必须同时是已解析 object 与 JSON media type,原始字符串、畸形正文或非 JSON 响应均回退安全文案。UI 展示 `ApiRequestError.message`,调用方继续抛错并保留 `status` / 原始 `cause`;401 登录跳转由 SDK 处理。
672
673
  - **全栈项目禁止 mock 数据**:只要有服务端接口就当全栈对待,严禁 mock。纯前端项目(静态展示、无后端)可以使用 mock
673
674
  - **提交前检查 mock 残留**:禁止硬编码用户 ID、项目 ID 等 mock 数据。提交前确认无 `"user_xxx"`、`"project_1"` 等占位值
674
675
 
@@ -46,16 +46,7 @@ match-template-name: nestjs-react-fullstack
46
46
 
47
47
  ### 403 统一处理(所有模式通用)
48
48
 
49
- 403 进入 response 分支还是 reject 分支取决于当前工程 `axiosForBackend` 的 `validateStatus` 和拦截器合同,**禁止断言 catch 永远捕获不到 403**。先读取工程内真实请求封装;在统一 API 层同时按其实际合同识别 403,业务组件只消费归一化后的无权限错误。
50
-
51
- 若当前请求器会 resolve 403,则在前端统一请求层按 response 检查并抛错,**禁止**在业务组件中单独处理:
52
-
53
- ```typescript
54
- const response = await axiosForBackend(config);
55
- if (response.status === 403) throw new Error('无操作权限,请联系管理员分配角色');
56
- ```
57
-
58
- 若请求器会 reject 403,则在同一 API 层从结构化错误中读取 HTTP status 后转换,并保留原始 cause;不要在页面 catch 中用错误字符串猜测。
49
+ 当前 `axiosForBackend` 的响应拦截器会将 rejected 403 降级为 resolved `response`;不能因此断言 403 永远进不了 catch(请求器或拦截器行为可能变化)。在统一 API 层使用模板 `client/src/api/error.ts` 的 `requireApiSuccess(response, { fallbackMessage })` 检查 resolved 非 2xx,并在 catch 用 `toApiRequestError(error, { fallbackMessage })` 处理 rejected;两条路径均抛错且保留 `status` / 原始 `cause`。页面按 `status === 403` 呈现无权限状态,不从错误字符串猜测权限原因,也不要在各业务组件重复拦截;401 登录跳转保持由 SDK 处理。
59
50
 
60
51
  本地只读排查 403 时,读取 `.spark/meta.json` 和真实 handler/policy 后,依次用 `+role-list`、`+role-match-list --user-id <open_id>`、`+role-get --role-id <required_role_id>` 对比“代码要求角色”和“用户实际命中角色”。只有代码绑定链和平台结果都能对上时才下根因结论,不为排查自动创建角色、添加成员或修改可见范围。
61
52
 
@@ -166,7 +157,7 @@ import { CanRole } from '@lark-apaas/fullstack-nestjs-core';
166
157
  | 自建任意平行的角色/权限存储(自建角色表 / 角色字段 / 用户名单,不限具体命名)来做鉴权判断 | @CanRole 只认平台角色,应用自建的存储不会被鉴权读取;角色一律用平台真实角色 |
167
158
  | 复杂授权就**另起炉灶搞一套平台不认的权限**(自建角色表 / 引用平台没 create 过的标识 / 写死 user_id 名单) | 角色一律用平台真实角色;消费方式不限(`@CanRole` 或读 `req.userContext.roles` 都行),复杂度用「平台角色 + 数据层行级过滤」表达 |
168
159
  | `@CanRole([X])` 的 X 凭语义臆造、与平台真实角色标识不一致 | 用 `lark-cli apps +role-list` / `+role-get` 获取真实 `role_id`;需要稳定 ID 时创建角色显式传 `--role-id` |
169
- | 未读取请求器合同就断言 403 只会进入 response 或 catch | 按实际 `validateStatus` / 拦截器合同在统一 API 层处理两种交付方式 |
160
+ | 断言 403 永远只进入 response 或 catch | 用 `requireApiSuccess` / `toApiRequestError` 在 API 层处理两种交付方式并保留 `status` / `cause` |
170
161
 
171
162
  ---
172
163
 
@@ -391,9 +391,11 @@ await db.select().from(users).where(eq(users.adminUser, userId));
391
391
 
392
392
  | 分层 | 目标 |
393
393
  | ---------- | ------------------------------- |
394
- | service | 抛出业务异常 |
394
+ | service | 要向用户展示具体原因的可预期业务失败,抛模板 `BusinessException(ResponseCode, message, httpStatus)`;技术异常不伪装成业务文案 |
395
395
  | controller | 不处理异常,交全局 Error Filter |
396
396
 
397
+ 冲突、权限、参数等具体业务文案用 `ResponseCode.CONFLICT` / `FORBIDDEN` / `BAD_REQUEST` 及对应 `HttpStatus`;Filter 仅将 `BusinessException` 标记为 `error.userFacing: true`。class-validator 校验和其它普通异常均返回固定安全文案且标记为 false,校验数组、`details` / `fieldErrors` / `stack` / `cause` 不用于 UI。
398
+
397
399
  ## 当前用户信息
398
400
 
399
401
  - **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码
@@ -533,7 +535,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
533
535
  // ❌ axiosForBackend.get(...) / .post(...) / .put(...) / .delete(...) ← 不是 axios 实例,所有实例方法都会 TypeError
534
536
  ```
535
537
 
536
- - **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**,禁止对后端接口请求进行兜底和过度封装
538
+ - **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**;禁止用 mock / 假成功兜底真实失败或过度封装,失败仍要抛出。未知错误的安全 UI 文案回退不是伪造成功。
539
+ - **错误读取**:在 `client/src/api` 统一用 `requireApiSuccess(response, { fallbackMessage })` 检查 resolved 非 2xx,在 catch 中用 `toApiRequestError(error, { fallbackMessage })` 处理 rejected;从 `client/src/api/error.ts` 导入(按文件位置调整路径,`index.ts` 有调用示例)。共享 helper 只在 `response.data.error.userFacing === true` 时取嵌套 `error.message`;`userFacing: false`、未知结构、5xx 或非 JSON 使用安全文案,绝不读取 `data.message`、`details`、`stack`、`cause` 当默认 UI 文案。仅当已核实旧端点返回 JSON object,且其顶层 `message:string|string[]` 是用户文案时,才显式传 `allowLegacyUserMessage: true`;必须同时是已解析 object 与 JSON media type,原始字符串、畸形正文或非 JSON 响应均回退安全文案。UI 展示 `ApiRequestError.message`,调用方继续抛错并保留 `status` / 原始 `cause`;401 登录跳转由 SDK 处理。
537
540
  - **全栈项目禁止 mock 数据**:只要有服务端接口就当全栈对待,严禁 mock。纯前端项目(静态展示、无后端)可以使用 mock
538
541
  - **提交前检查 mock 残留**:禁止硬编码用户 ID、项目 ID 等 mock 数据。提交前确认无 `"user_xxx"`、`"project_1"` 等占位值
539
542