@lark-apaas/coding-steering 0.1.6-alpha.4 → 0.1.6-alpha.5

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 (44) hide show
  1. package/README.md +11 -2
  2. package/package.json +1 -1
  3. package/steering/design-stack/skills/client-add-aily-web-chat/SKILL.md +139 -0
  4. package/steering/design-stack/skills/client-builtins-user-service/SKILL.md +628 -0
  5. package/steering/design-stack/skills/code-fix/SKILL.md +246 -0
  6. package/steering/design-stack/skills/feishu/SKILL.md +270 -0
  7. package/steering/design-stack/skills/feishu/references/approval.md +214 -0
  8. package/steering/design-stack/skills/feishu/references/attendance.md +163 -0
  9. package/steering/design-stack/skills/feishu/references/bitable.md +309 -0
  10. package/steering/design-stack/skills/feishu/references/calendar.md +190 -0
  11. package/steering/design-stack/skills/feishu/references/contacts.md +160 -0
  12. package/steering/design-stack/skills/feishu/references/doc.md +256 -0
  13. package/steering/design-stack/skills/feishu/references/drive.md +103 -0
  14. package/steering/design-stack/skills/feishu/references/events.md +198 -0
  15. package/steering/design-stack/skills/feishu/references/id-convert.md +128 -0
  16. package/steering/design-stack/skills/feishu/references/messaging.md +207 -0
  17. package/steering/design-stack/skills/feishu/references/oauth.md +164 -0
  18. package/steering/design-stack/skills/feishu/references/perm.md +90 -0
  19. package/steering/design-stack/skills/feishu/references/wiki.md +164 -0
  20. package/steering/design-stack/skills/user-identity/SKILL.md +300 -0
  21. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +122 -0
  22. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +139 -0
  23. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +628 -0
  24. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +270 -0
  25. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +214 -0
  26. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +163 -0
  27. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +309 -0
  28. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +190 -0
  29. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +160 -0
  30. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +256 -0
  31. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +103 -0
  32. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +198 -0
  33. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +128 -0
  34. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +207 -0
  35. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +164 -0
  36. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +90 -0
  37. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +164 -0
  38. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +267 -0
  39. package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +452 -0
  40. package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +300 -0
  41. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +246 -0
  42. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +707 -0
  43. package/steering/nestjs-react-fullstack/skills/.gitkeep +0 -0
  44. package/steering/nestjs-react-fullstack/tech.md +0 -21
@@ -0,0 +1,300 @@
1
+ ---
2
+ name: user-identity
3
+ description: "Use when getting current user info/profile, displaying user name/avatar/email, converting miaoda userId to lark_user_id, or reading req.userContext fields (userId/roles/tenantId): useCurrentUserProfile, AuthNPaasService, FeishuID conversion. 触发词:用户身份, 用户信息, 用户资料, 当前用户, userProfile, useCurrentUserProfile, 飞书ID, FeishuID, 飞书用户ID, lark_user_id, 用户ID转换, AuthNPaasService, 用户上下文, userContext, userContext.roles, 用户角色, 当前用户角色, 获取请求者角色, 展示用户, 显示用户, 用户面板, 我是谁, 获取用户"
4
+ steering: true
5
+ steering-topic: user_identity
6
+ match-template-name: nestjs-react-fullstack
7
+ ---
8
+
9
+ # 用户身份与上下文
10
+
11
+ 本 skill 专注于**用户身份**:ID 体系、`req.userContext` 字段、`useCurrentUserProfile`、妙搭 ↔ 飞书 ID 转换。
12
+
13
+ **接口认证**(`@NeedLogin()` 装饰器、`AuthNPaasGuard` opt-in 模式、公开接口处理、401)请使用 [`authn-guide`](../authn-guide/SKILL.md) skill。
14
+
15
+ ## 零、ID 体系警告(CRITICAL)
16
+
17
+ ⚠️ "user_id" 这个字面在本项目里指**三个不同的东西**,必须先区分清楚再写代码:
18
+
19
+ **作为身份 ID 字段名(指代具体某个用户的 ID 值):**
20
+
21
+ | 出现位置 | 实际含义 | 体系 |
22
+ |---|---|---|
23
+ | `useCurrentUserProfile().user_id`、`req.userContext.userId` | 妙搭用户 ID(纯数字字符串) | 妙搭 |
24
+ | `useCurrentUserProfile().lark_user_id`、`AuthNPaasService.getCurrentUserLarkUserId()` 返回值 | 飞书 user_id(== `employee_id`,企业内身份,**无固定前缀**) | 飞书 |
25
+
26
+ **作为 API 参数的取值(不是一个 ID,而是告诉接口"我传入哪种类型的 ID"):**
27
+
28
+ | 出现位置 | 实际含义 |
29
+ |---|---|
30
+ | 飞书 OpenAPI 参数 `user_id_type: 'user_id'`(也可取 `'open_id'` / `'union_id'`) | 字符串字面,与上方的飞书 user_id 字段是一致体系但用途不同 |
31
+
32
+ **严禁**:把 `useCurrentUserProfile().user_id`(妙搭 ID)直接当飞书 ID 传给飞书 API。飞书 ID 必须通过 `lark_user_id` 字段或 `AuthNPaasService` 获取。
33
+
34
+ `useCurrentUserProfile()` 返回的字段中涉及**两套完全独立的 ID 体系**,严禁混用或互相回退:
35
+
36
+ | 字段 | 体系 | 格式 | 说明 |
37
+ |------|------|------|------|
38
+ | `user_id` | 妙搭 | 纯数字字符串 | 妙搭平台用户 ID |
39
+ | `lark_user_id` | 飞书 | 字符串 | 飞书 user_id(== employee_id),通过额外异步请求获取 |
40
+
41
+ > **严禁**:`useCurrentUserProfile()` 返回值里**没有** `open_id`、`feishu_id`、`openId` 字段——飞书 ID 在这个 Hook 里**只通过 `lark_user_id`** 暴露。如果在本 Hook 的消费代码里看到 `userInfo.open_id` 等写法,属于历史错误,必须改成 `lark_user_id`。
42
+ >
43
+ > (范围限定:上一条只约束 `useCurrentUserProfile()` 的消费代码。**项目其他场景** —— 例如调 spark `id_convert`、调飞书通讯录 API —— 出现 `open_id` / `union_id` 是正常且必要的,不在禁止之列。)
44
+ >
45
+ > **严禁**:当 `lark_user_id` 为空时回退到 `user_id` 展示。两套 ID 含义完全不同,回退会误导用户。正确做法是条件渲染:有值则展示,无值则不展示或展示"未关联飞书账号"。
46
+
47
+ ---
48
+
49
+ ## 一、功能决策树
50
+
51
+ ```text
52
+ 用户需求
53
+
54
+ ├─ 接口认证(@NeedLogin / 公开接口 / 401 / 守卫流程)?
55
+ │ └─ 跳到 `authn-guide` skill
56
+
57
+ ├─ 需要在服务端读取当前用户 ID / 租户 / 角色等?
58
+ │ └─ 是 ──→ req.userContext(第二节)
59
+
60
+ ├─ 需要在前端展示当前用户信息(名称/头像/邮箱)?
61
+ │ └─ 是 ──→ useCurrentUserProfile()(第三节)
62
+
63
+ ├─ 需要获取当前用户的飞书 user_id(即 employee_id,企业内身份)?
64
+ │ ├─ 后端 ──→ AuthNPaasService.getCurrentUserLarkUserId()(第三节)
65
+ │ └─ 前端 ──→ useCurrentUserProfile().lark_user_id(第三节)
66
+
67
+ ├─ 需要批量把 妙搭 userId 转成 飞书 **user_id (employee_id,无前缀)** —— 不是 open_id (`ou_`)、不是 union_id (`on_`)?
68
+ │ └─ 是 ──→ AuthNPaasService.getBatchLarkUserIds()(第三节)
69
+
70
+ ├─ 需要 飞书 open_id(`ou_` 开头)/ union_id(`on_` 开头)?
71
+ │ └─ ⚠️ **AuthNPaasService 不产出 open_id/union_id**,只能 user_id
72
+ │ └─ 跳到 `feishu` skill 的 `references/id-convert.md`(spark id_convert type 10/11)
73
+
74
+ ├─ 需要把 飞书 open_id / union_id 反查成 妙搭 userId?
75
+ │ └─ 跳到 `feishu` skill 的 `references/id-convert.md`(spark id_convert type 20/21)
76
+
77
+ ├─ 需要把 飞书 user_id(employee_id)反查成 妙搭 userId?
78
+ │ └─ 无单步方案 ──→ `feishu` skill `references/id-convert.md` "反向两步走"
79
+
80
+ └─ 需要自定义飞书 ID 转换接口?
81
+ └─ 是 ──→ 注入 AuthNPaasService 编写 Controller(第四节,仅适用于 user_id)
82
+ ```
83
+
84
+ > **相关技能**:接口认证/守卫/401 参见 `authn-guide`;登录/登出/获取用户信息等 Dataloom SDK 操作参见 `client-builtins-user-service`;**"用户能做什么"**(角色/权限点位鉴权)参见 `authz-guide`。本 skill 只解决**"用户是谁"**。
85
+
86
+ ---
87
+
88
+ ## 二、用户上下文(req.userContext)
89
+
90
+ `UserContextMiddleware` 解析 Gateway 注入的 `x-larkgw-suda-webuser` 头,把当前用户上下文挂到 `req.userContext`,所有 Controller 均可通过 `@Req() req: Request` 读取。
91
+
92
+ > 完整的解析→守卫流程见 `authn-guide` skill 第二节"认证流程"。
93
+
94
+ ### 字段表
95
+
96
+ | 字段 | 类型 | 说明 |
97
+ |------|------|------|
98
+ | `userId` | `string` | 妙搭用户 ID |
99
+ | `tenantId` | `number` | 租户 ID |
100
+ | `appId` | `string` | 应用 ID |
101
+ | `loginUrl` | `string` | 登录跳转 URL |
102
+ | `userType` | `string` | 用户类型(如 `_employee`) |
103
+ | `env` | `string` | 环境(如 `preview`、`online`) |
104
+ | `userName` | `string` | 用户名 |
105
+ | `userNameI18n` | `{ zh_cn, en_us, ja_jp }` | 多语言用户名 |
106
+ | `isSystemAccount` | `boolean` | 是否系统账号 |
107
+ | `roles` | `string[] \| null` | 用户角色列表。**未开启权限服务或用户无角色时为 `null`** |
108
+ | `baseUrl` | `string` | 网关内部地址 |
109
+
110
+ ### 服务端读取角色示例
111
+
112
+ `roles` 字段记录当前用户在应用中的角色列表,可在 Controller 业务逻辑中消费(如根据角色返回不同数据):
113
+
114
+ ```typescript
115
+ import { Controller, Get, Req } from '@nestjs/common';
116
+ import { Request } from 'express'; // ⚠️ Request 类型必须来自 'express',不要 import 自 'http' / 'undici' 或漏掉 import
117
+ import { TaskService } from './task.service'; // 按项目实际路径 import 业务 Service
118
+
119
+ // req.userContext 的类型由 @lark-apaas/fullstack-nestjs-core 通过 declare module 'express' 自动注入到 Request,无需手动声明
120
+ @Controller('api/tasks')
121
+ export class TasksController {
122
+ constructor(private readonly taskService: TaskService) {}
123
+
124
+ @Get()
125
+ async listTasks(@Req() req: Request) {
126
+ const roles: string[] | null = req.userContext?.roles ?? null;
127
+ // roles 示例:['text_editor', 'visitor', 'admin']
128
+ // ⚠️ 未开启权限服务或用户无角色时为 null
129
+ if (roles?.includes('admin')) {
130
+ return this.taskService.findAll();
131
+ }
132
+ return this.taskService.findByUser(req.userContext?.userId);
133
+ }
134
+ }
135
+ ```
136
+
137
+ > **注意**:`roles` 在未开启权限服务或用户无角色时为 `null`,使用前必须做空值处理。
138
+
139
+ ---
140
+
141
+ ## 三、飞书 ID 转换(FeishuID Converter)
142
+
143
+ ### 核心概念
144
+
145
+ 妙搭平台的用户 ID(`userId`)与飞书用户 ID 是两套独立体系。调用飞书 OpenAPI 时需要传入某种飞书侧 ID(`open_id` / `union_id` / `user_id` 任选其一,具体通过哪个参数指定取决于 API:消息 API 用 `receive_id_type`,多数其他 API 用 `user_id_type`,文档协作者用 `member_id_type`)。
146
+
147
+ `AuthNPaasService` 暴露的飞书 ID 是 **`user_id`**(即 `employee_id`,飞书企业内的用户标识)这一种,且**只支持 妙搭 userId → 飞书 user_id 单向转换**。
148
+
149
+ > 如果需要 `open_id` / `union_id`,或者需要"飞书 ID → 妙搭 userId"反向,请改用飞书开放平台 `spark id_convert` 接口,参见 `feishu` skill 的 `references/id-convert.md`。
150
+
151
+ ### 后端 API
152
+
153
+ ```typescript
154
+ import { Injectable } from '@nestjs/common';
155
+ import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core';
156
+
157
+ @Injectable()
158
+ export class MyService {
159
+ constructor(private readonly authnService: AuthNPaasService) {}
160
+
161
+ async example() {
162
+ // 获取当前登录用户的飞书 user_id
163
+ const larkUserId = await this.authnService.getCurrentUserLarkUserId();
164
+ // => '<飞书 user_id>' | null
165
+
166
+ // 批量转换(最多 100 个)
167
+ const larkUserIds = await this.authnService.getBatchLarkUserIds(['uid1', 'uid2']);
168
+ // => ['<飞书 user_id>', null] 顺序与输入对应,失败项为 null
169
+ }
170
+ }
171
+ ```
172
+
173
+ | 方法 | 签名 | 说明 |
174
+ |------|------|------|
175
+ | `getCurrentUserLarkUserId` | `() → Promise<string \| null>` | 从请求上下文获取当前用户的飞书 ID |
176
+ | `getBatchLarkUserIds` | `(userIds: string[]) → Promise<(string \| null)[]>` | 批量转换,最多 100 个,与输入顺序一一对应 |
177
+
178
+ ### 内置接口
179
+
180
+ 模块自动注册了 `GET /api/authnpaas/lark-user-id`,返回当前登录用户的飞书 ID:
181
+
182
+ ```json
183
+ { "lark_user_id": "<飞书 user_id>" }
184
+ ```
185
+
186
+ ### 前端获取
187
+
188
+ `useCurrentUserProfile()` Hook 已自动调用上述内置接口,返回值中包含 `lark_user_id` 字段:
189
+
190
+ > 本节示例只涉及 ID 相关字段(`user_id` / `lark_user_id`)和顺手的 `name`。完整返回值(`name` / `avatar` / `email` / `tenantId` 等)见 `client-builtins-user-service` skill。
191
+
192
+ ```typescript
193
+ import { useCurrentUserProfile } from '@lark-apaas/client-toolkit/hooks/useCurrentUserProfile';
194
+
195
+ const UserInfoPanel = () => {
196
+ const userInfo = useCurrentUserProfile();
197
+
198
+ if (!userInfo?.user_id) return <div>加载中...</div>;
199
+
200
+ return (
201
+ <div>
202
+ <p>用户名: {userInfo.name}</p>
203
+ <p>用户 ID: {userInfo.user_id}</p>
204
+ {userInfo.lark_user_id && <p>飞书用户 ID: {userInfo.lark_user_id}</p>}
205
+ </div>
206
+ );
207
+ };
208
+ ```
209
+
210
+ **注意**:
211
+ - `lark_user_id` 通过额外异步请求获取,可能晚于 `user_id` 等基础字段就绪
212
+ - 请求失败或用户无对应飞书账号时值为 `undefined`,**必须用条件渲染**
213
+ - `useCurrentUserProfile()` 的返回值里**不存在** `open_id`、`feishu_id`、`openId` 字段——在这个 Hook 的消费代码里飞书 ID 唯一字段名是 `lark_user_id`(仅约束本 Hook;项目其他场景调 spark `id_convert` / 通讯录 API 出现 `open_id` 是正常的)
214
+
215
+ ---
216
+
217
+ ## 四、自定义飞书 ID 转换接口
218
+
219
+ 当内置接口不满足需求(如需要批量转换),可注入 `AuthNPaasService` 编写自定义 Controller:
220
+
221
+ ```typescript
222
+ import { Controller, Get, Post, Body, HttpCode } from '@nestjs/common';
223
+ import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core';
224
+
225
+ // 生产环境建议加 class-validator 装饰器(如 @IsArray() @IsString({ each: true })
226
+ // @ArrayMaxSize(100))并启用全局 ValidationPipe;本示例聚焦 AuthNPaas 用法,省略校验
227
+ class BatchConvertDto {
228
+ userIds!: string[];
229
+ }
230
+
231
+ @Controller('api/feishu-id')
232
+ export class FeishuIdController {
233
+ constructor(private readonly authnService: AuthNPaasService) {}
234
+
235
+ @Get('current')
236
+ async getCurrent() {
237
+ const larkUserId = await this.authnService.getCurrentUserLarkUserId();
238
+ return { larkUserId };
239
+ }
240
+
241
+ @Post('batch')
242
+ @HttpCode(200)
243
+ async batchConvert(@Body() dto: BatchConvertDto) {
244
+ const larkUserIds = await this.authnService.getBatchLarkUserIds(dto.userIds);
245
+ return { larkUserIds };
246
+ }
247
+ }
248
+ ```
249
+
250
+ 前端调用示例:
251
+
252
+ ```typescript
253
+ // 获取当前用户飞书 ID
254
+ const { larkUserId } = await request<{ larkUserId: string | null }>({
255
+ url: '/api/feishu-id/current',
256
+ method: 'GET',
257
+ });
258
+
259
+ // 批量转换
260
+ const { larkUserIds } = await request<{ larkUserIds: (string | null)[] }>({
261
+ url: '/api/feishu-id/batch',
262
+ method: 'POST',
263
+ data: { userIds: ['uid1', 'uid2', 'uid3'] },
264
+ });
265
+ ```
266
+
267
+ ---
268
+
269
+ ## 五、禁止行为清单
270
+
271
+ | 禁止行为 | 正确做法 |
272
+ |---------|---------|
273
+ | 在 `useCurrentUserProfile()` 消费代码里使用 `open_id`、`feishu_id`、`openId` 等字段名 | `useCurrentUserProfile()` 中飞书 ID 的**唯一字段名**是 `lark_user_id`(限本 Hook;调 spark `id_convert` / 通讯录 API 出现 `open_id` 正常) |
274
+ | `lark_user_id` 为空时回退到 `user_id` 展示 | 两套 ID 体系完全不同,严禁互相回退。无值时不展示或展示"未关联飞书账号" |
275
+ | 前端直接展示 `lark_user_id` 而不处理空值 | `lark_user_id` 可能为 `undefined`(加载中/获取失败/无飞书账号),必须用条件渲染 |
276
+ | 用 `if (!userInfo)` 判断加载状态 | 初始值为空对象(truthy),必须用 `if (!userInfo?.user_id)` |
277
+ | 手动实例化 `AuthNPaasService` | 通过 NestJS 依赖注入获取:`constructor(private readonly authnService: AuthNPaasService)` |
278
+ | 单独注册 `AuthNPaasModule.forRoot()` | 已通过 `PlatformModule.forRoot()` 自动注册,无需手动导入 |
279
+ | 自行调用平台 `/v1/app/{appId}/account/user/convert` 接口 | 必须使用 `AuthNPaasService` 的方法,内置错误处理和可观测性 |
280
+ | `getBatchLarkUserIds` 传入超过 100 个 ID | 分批调用,每批最多 100 个 |
281
+ | 在前端自行封装飞书 ID 转换请求 | 使用 `useCurrentUserProfile()` 获取当前用户飞书 ID;批量转换通过后端接口 |
282
+
283
+ ---
284
+
285
+ ## 六、常见问题
286
+
287
+ > 接口 401 / 登录跳转相关问题见 `authn-guide` skill 第四节"常见问题"。
288
+
289
+ ### 飞书 ID 返回 null
290
+
291
+ 1. 确认用户已登录(`req.userContext.userId` 存在)
292
+ 2. 确认 `appId` 在请求上下文中存在
293
+ 3. 检查平台转换接口是否正常(查看后端日志中 `[batchConvertUserIds]` 相关输出)
294
+ 4. 部分用户可能无对应飞书账号,此时转换结果为 null 是预期行为
295
+
296
+ ### 前端 lark_user_id 一直为 undefined
297
+
298
+ 1. 确认后端 `AuthNPaasModule` 已注册(内置接口 `/api/authnpaas/lark-user-id` 可访问)
299
+ 2. 检查浏览器开发者工具中该接口的请求和响应
300
+ 3. `lark_user_id` 异步加载,确保组件正确处理了加载态
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: code-fix
3
+ description: Use when encountering code errors such as import failures, TypeScript/Dto type mismatches, JSX syntax issues, API call exceptions (traceid troubleshooting), production log troubleshooting that should route through miaoda-cli, route 404 errors, or **lucide-react icon not found / duplicate identifier / barrel-export naming conflicts**. 触发词:导入错误, 模块解析失败, 类型错误, Dto不匹配, JSX语法, API异常, traceid, 线上日志, 线上日志查询, 查询线上日志, 路由404, code fix, debugging, lucide-react import error, icon not found, 图标不存在, Cannot find name, 标识符重复, no-redeclare, export 冲突, 桶导出冲突, dual export, "请修复错误" 通用排错
4
+ steering: true
5
+ steering-topic: code_fix
6
+ match-template-name: nestjs-react-fullstack
7
+ ---
8
+
9
+
10
+ # 代码问题诊断与修复指南
11
+
12
+ ## 概述
13
+
14
+ 本文档提供开发过程中常见问题的诊断与修复流程,涵盖导入错误、语法问题、API 调用异常、路由配置等。规范性内容请参考 `coding-guide`,React Hook 相关问题请参考 `react-hook-best-practices`。
15
+
16
+ ## 导入和模块错误
17
+
18
+ ### 缺少导入声明
19
+
20
+ **问题描述**:使用组件或图标时没有正确导入
21
+
22
+ **解决方案**:
23
+
24
+ - 使用前务必验证技术栈中组件的可用性
25
+ - 添加正确的导入语句
26
+ - 检查组件是否在当前技术栈中可用
27
+
28
+ **代码示例**:
29
+
30
+ ```typescript
31
+ // 正确的导入方式
32
+ import { Button } from '@client/src/components/ui/button';
33
+ import { Input } from "@client/src/components/ui/input"
34
+ import { ListPlus, Cake, Home, Building, Twitter } from "lucide-react";
35
+ ```
36
+
37
+ ### 导入路径错误诊断
38
+
39
+ **问题描述**:错误的导入路径导致模块解析失败
40
+
41
+ **诊断步骤**:
42
+
43
+ 1. 检查是否使用了项目别名(`@client/`、`@server/`、`@shared/`),而非相对路径 `../`
44
+ 2. 确认别名对应的实际路径是否正确(如 `@client/` 对应 `client/`)
45
+ 3. 验证目标文件是否存在于指定路径
46
+
47
+ > 路径别名的完整定义请参考 `coding-guide` 的"全局编码约定"部分。
48
+
49
+ ### 组件可用性验证
50
+
51
+ **问题描述**:使用不存在的组件或错误的导入路径
52
+
53
+ **解决方案**:
54
+
55
+ - 严格遵循技术栈文档中的可用组件列表
56
+ - 验证组件是否存在于指定路径
57
+ - 检查组件参数是否匹配
58
+
59
+ **检查清单**:
60
+
61
+ - [ ] 导入路径是否正确
62
+ - [ ] 组件是否存在
63
+ - [ ] 组件参数是否匹配
64
+
65
+ ### lucide-react 图标存在性核查
66
+
67
+ **问题描述**:从 `lucide-react` 导入的图标名拼错或臆造,渲染时得到 `undefined`,触发 React "type is invalid" 或 LSP "Cannot find name"。
68
+
69
+ **预防规则(写 import 时主动核查,而非事后救援)**:
70
+
71
+ 1. **不确定就查**:图标名不在你已知列表里 → 先 `Read packages/client/lucide-react/iconMappings.json`(或 lucide-react 包的 d.ts 导出列表)确认存在,再写 import
72
+ 2. **替换图标必须连同 import 列表一起检查**:把旧图标替换成新图标时,**必须先 grep 当前文件 import 列表**确认新名未已 import,避免触发 LSP "标识符重复" / `no-redeclare`
73
+ 3. **LSP 警告是硬约束**:multi_edit / 写代码后看到 LSP 任意 "Cannot find name" / "标识符重复" → **必须先修完警告才能 commit**,禁止带 LSP 错误跑 `commit_task`
74
+
75
+ ### 同名 export 重复(修复 SOP)
76
+
77
+ **症状**:LSP 报 "标识符 X 重复" / `no-redeclare`。
78
+
79
+ **修复步骤**:
80
+
81
+ 1. 整个项目 `grep "export.*\bX\b"` 找所有同名 export 一并处理,**不能只改单点**——首次出 bug 时同类常已在多处复制粘贴
82
+ 2. 预防规则(跨子组件常量前缀化、行内/桶导出二选一)见 `coding-guide` 的「TypeScript 规范 · 命名约定」与「文件命名约定 · 导入导出」
83
+
84
+ ### 前端 Dto 类型使用错误
85
+ **问题描述**:代码中使用的 Dto 类型属性与实际定义不一致,导致 TypeScript 类型检查报错
86
+
87
+ **核心原则**:遇到类型错误,先查 `@client/src/api/gen/types.gen.ts` 确认定义,再修改代码
88
+
89
+ **错误示例**:
90
+ - 示例1: 赋值时缺失必需属性
91
+ ```
92
+ Property 'dueDate' is missing in type {...} but required in type 'CreateBorrowRecordDto'
93
+ ```
94
+
95
+ - 示例2:访问不存在的属性
96
+ ```
97
+ Property 'avatar' does not exist on type 'LotteryParticipantResponseDto'
98
+ ```
99
+
100
+ - 示例3:导入不存在的类型
101
+ ```
102
+ Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponseDto'
103
+ ```
104
+ **解决步骤**:
105
+ 1. 在 `@client/src/api/gen/types.gen.ts` 中搜索目标Dto实际定义
106
+ 2. 确认类型名称是否正确,明确完整定义
107
+
108
+ **检查清单**:
109
+ - [ ] 已查看 `@client/src/api/gen/types.gen.ts` 中的类型定义
110
+ - [ ] 已对比代码使用与实际定义的差异
111
+ - [ ] 已根据实际定义修正代码
112
+ - [ ] 已验证修改后类型使用正确
113
+
114
+ ## JSX 和语法错误
115
+
116
+ ### 特殊字符转义处理
117
+
118
+ **问题描述**:JSX 中未转义的特殊字符导致渲染错误
119
+
120
+ **常见字符**:`<`, `>`, `{`, `}`, `&`, `"`, `` ` ``
121
+
122
+ **解决方案**:
123
+
124
+ | 字符 | HTML 实体 | 使用场景 |
125
+ |------|-----------|----------|
126
+ | `<` | `<` | 显示小于号 |
127
+ | `>` | `>` | 显示大于号 |
128
+ | `&` | `&` | 显示与符号 |
129
+ | `"` | `"` | 显示双引号 |
130
+ | `'` | `'` | 显示单引号 |
131
+
132
+ **代码示例**:
133
+
134
+ ```jsx
135
+ // ✅ 正确的特殊字符处理
136
+ <div>
137
+ <p>价格: &lt; 100元</p>
138
+ <p>公司: A &amp; B 科技</p>
139
+ <p>标题: &quot;Hello World&quot;</p>
140
+ </div>
141
+
142
+ // ❌ 错误的写法
143
+ <div>
144
+ <p>价格: < 100元</p> {/* 会被解析为标签 */}
145
+ <p>公司: A & B 科技</p> {/* 可能导致解析错误 */}
146
+ </div>
147
+ ```
148
+
149
+ ## API 生成错误
150
+
151
+ ### 调用 API 客户端时参数与预期不符
152
+
153
+ **问题描述**:调用 API 客户端时发现传参与后端定义不相同
154
+
155
+ **解决方案**:检查后端 Swagger 注解中是否对 DTO 对象正确做了注解
156
+
157
+ ## 调用生成的 API 异常
158
+
159
+ ### 需要查询线上日志时使用 miaoda-cli
160
+
161
+ **适用场景**:仅当用户提供 traceid、logid、线上报错、发布错误日志、线上运行日志、线上链路追踪,或排查必须依赖线上前端/后端日志。
162
+
163
+ **处理要求**:引导并使用 `miaoda-cli` skill 查询线上日志。优先通过 `miaoda observability log/trace` 查询线上运行日志与链路追踪;排查发布错误时使用 `miaoda deploy error-log`。本地开发日志、构建日志、测试日志、浏览器控制台日志不在此范围内,按当前技能或对应工具排查。拿到线上日志后再回到本技能继续定位与修复代码问题。
164
+
165
+ ### 用户提供了 traceid,排查错误
166
+
167
+ **解决方案**:使用 `miaoda-cli` skill 读取线上前端与后端日志,获取详细错误
168
+
169
+ **排查示例**:
170
+
171
+ 用户输入:8ae6724e-277e-4d51-afbf-b524b654f27f 看看这个 trace 为什么报错了
172
+ 排查路径:
173
+ 1. 使用 `miaoda-cli` skill 查询线上服务端与 trace 日志
174
+ 2. 根据服务端日志中对应的日志内容修复对应代码逻辑
175
+
176
+ ### 调用 API 客户端时后端返回异常
177
+
178
+ **解决方案**:检查并修复后端的实现,请勿修改 API 客户端相关代码
179
+
180
+ **排查示例**:
181
+
182
+ 异常报错如下
183
+
184
+ ```json
185
+ [ERROR]{"type":"HTTP Response","url":"/spark/p/app_4hnezxn4uy49c/api/hello/config","method":"GET","status":500,"statusText":"Internal Server Error","message":"Request failed with status code 500","responseData":{"code":"INTERNAL_ERROR","message":"服务器内部错误","success":false,"data":null,"timestamp":1761048452289,"httpStatus":500,"error":{"code":"INTERNAL_ERROR","message":"服务器内部错误","stack":"Error: 这是测试异常:HelloController.getConfig方法故意抛出的错误\n at HelloController.getConfig (/home/gem/workspace/dist/server/modules/hello/hello.controller.js:17:15)\n at /home/gem/workspace/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)"}},"responseTime":283}
186
+ ```
187
+
188
+ 排查步骤:
189
+
190
+ 1. 分析错误信息
191
+
192
+ 根据 ERROR 可以得知如下关键信息:
193
+ 请求的 URL:/spark/p/app_4hnezxn4uy49c/api/hello/config
194
+ 请求 METHOD:GET
195
+ 错误信息:服务内部错误
196
+ 错误堆栈(可选):Error: 这是测试异常:HelloController.getConfig方法故意抛出的错误\n at HelloController.getConfig (/home/gem/workspace/dist/server/modules/hello/hello.controller.js:17:15)\n at /home/gem/workspace/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
197
+
198
+ 2. 如果错误堆栈存在,优先按照错误堆栈中的相关文件与行列号找到对应文件,读取内容并启发式分析
199
+ 3. 如果错误堆栈不存在,按照请求的 URL 找到对应的 controller,检查其中的逻辑,启发式的分析依赖。如发现问题可以直接处理。若仍未发现问题,可以在 controller 抛出的错误对象上增加 `message` 属性,改变 message 内容方便 debug。你可以在增加完之后让用户重新请求触发问题。
200
+
201
+ ```typescript
202
+ try {
203
+ // some logic
204
+ } catch (err) {
205
+ // 后端异常必须在后端打印日志
206
+ this.logger.error('...')
207
+ // 后端异常同时需要抛出到前端,方便修复
208
+ // 构造为 http-errors compatible 的对象
209
+ err.statusCode = 500;
210
+ err.message = err.stack; // 抛出 stack 信息,保障有足够的错误内容透出
211
+ throw err;
212
+ }
213
+ ```
214
+
215
+ 注意:优先让错误信息中包含堆栈信息,以更精确的定位错误发生位置。
216
+
217
+ ## 路由和导航问题
218
+
219
+ ### 路由 404 错误诊断
220
+
221
+ **问题描述**:新页面无法访问或出现 404 错误
222
+
223
+ **诊断步骤**:
224
+
225
+ 1. 确认页面组件已在 `client/src/app.tsx` 中注册路由
226
+ 2. 验证路由路径拼写正确(注意大小写敏感)
227
+ 3. 检查导航链接路径与路由定义是否完全匹配
228
+ 4. 确认动态路由参数正确传递
229
+
230
+ > 路由配置规范和导航开发详见 `coding-guide` 的"页面与路由开发规范"部分。
231
+
232
+ ## 性能优化
233
+
234
+ ### React Hook 相关问题
235
+
236
+ useEffect 无限循环、依赖数组管理、useMemo/useCallback 记忆化等问题,请参考 `react-hook-best-practices` 技能,该技能涵盖 React 19 新特性、派生状态、事件 vs Effect 等完整内容。
237
+
238
+ ## 错误处理快速参考
239
+
240
+ | 场景 | 做法 | 注意事项 |
241
+ |------|------|----------|
242
+ | 用户反馈 | 使用 `toast` (sonner) 显示友好消息 | 消息简洁、可操作,避免暴露技术细节 |
243
+ | 前端日志 | 使用 `logger` (`@lark-apaas/client-toolkit/logger`) | 禁止 console;参数为 string,对象需 `JSON.stringify` |
244
+ | 后端日志 | 使用 `@nestjs/common` 的 Logger | 禁止 console;参数为 string,对象需 `JSON.stringify` |
245
+ | 业务错误 | 区分预期错误与意外错误 | 预期错误用 `logger.warn`,意外错误用 `logger.error` |
246
+ | 异常处理 | 禁止静默处理异常 | 必须显示明确的错误信息,参考 `coding-guide` 相关规范 |