@lark-apaas/coding-steering 0.1.32-beta.0 → 0.1.32-dev.0f5e9dd

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 (82) hide show
  1. package/package.json +6 -6
  2. package/steering/design-html/skills/charts/SKILL.md +15 -4
  3. package/steering/design-html/skills/data-viz/SKILL.md +1 -1
  4. package/steering/design-html/skills/hi-fi-design/SKILL.md +1 -1
  5. package/steering/design-html/skills/interactive-prototype/SKILL.md +1 -17
  6. package/steering/design-html/skills/pptx-style-extract/SKILL.md +71 -26
  7. package/steering/design-html/skills/pptx-style-extract/font-fallback.yaml +3 -3
  8. package/steering/design-html/skills/pptx-style-extract/scripts/census.py +26 -14
  9. package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +153 -8
  10. package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +2831 -293
  11. package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +544 -22
  12. package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +19 -2
  13. package/steering/design-html/skills/pptx-style-extract/scripts/package.py +991 -165
  14. package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +22 -3
  15. package/steering/design-html/skills/pptx-style-extract/scripts/query.py +4 -9
  16. package/steering/design-html/skills/pptx-style-extract/scripts/render_pages.py +20 -12
  17. package/steering/design-html/skills/pptx-style-extract/scripts/test_asset_judgment_package.py +556 -0
  18. package/steering/design-html/skills/pptx-style-extract/scripts/test_background_composite.py +364 -0
  19. package/steering/design-html/skills/pptx-style-extract/scripts/test_color_contract.py +60 -0
  20. package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +76 -0
  21. package/steering/design-html/skills/pptx-style-extract/scripts/test_flow_layout_contract.py +528 -0
  22. package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +1814 -0
  23. package/steering/design-html/skills/pptx-style-extract/scripts/test_logo_scope.py +600 -0
  24. package/steering/design-html/skills/pptx-style-extract/scripts/test_rounded_contract.py +112 -0
  25. package/steering/design-html/skills/pptx-style-extract/scripts/test_text_role_contract.py +315 -0
  26. package/steering/design-html/skills/pptx-style-extract/scripts/verify_layout_assets.py +421 -0
  27. package/steering/design-html/skills/pptx-style-extract/scripts/verify_logo_scope.py +12 -0
  28. package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +27 -15
  29. package/steering/design-html/skills/slide-deck/SKILL.md +5 -3
  30. package/steering/design-html/skills/social-card/SKILL.md +263 -0
  31. package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +4 -0
  32. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +6 -0
  33. package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +5 -5
  34. package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +1 -1
  35. package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +38 -113
  36. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +15 -2
  37. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +13 -7
  38. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +205 -38
  39. package/steering/nestjs-react-fullstack/skills/connections-sdk/SKILL.md +202 -0
  40. package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +67 -4
  41. package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +254 -0
  42. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +75 -0
  43. package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +164 -543
  44. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +72 -15
  45. package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +30 -14
  46. package/steering/nestjs-react-fullstack/skills/raw-sql-boundary-audit/SKILL.md +63 -0
  47. package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
  48. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/SKILL.md +78 -0
  49. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/eslint.mcp-ui.config.cjs +19 -0
  50. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/assets/ui-tsconfig.json +19 -0
  51. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/client-onboarding.md +90 -0
  52. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/debugging.md +81 -0
  53. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/mcp-apps.md +197 -0
  54. package/steering/nestjs-react-fullstack/skills_common/mcp-guide/references/tool-authoring.md +248 -0
  55. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/SKILL.md +284 -12
  56. package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +3 -1
  57. package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
  58. package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +6 -0
  59. package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +49 -22
  60. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +11 -3
  61. package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +57 -14
  62. package/steering/vite-react/skills/plugin-guide/SKILL.md +4 -1
  63. package/steering/vite-react/skills/react-three-fiber/SKILL.md +5 -0
  64. package/steering/vite-react/tech.md +3 -0
  65. package/steering/design-html/skills/preflight/SKILL.md +0 -51
  66. package/steering/design-html/skills/preflight/scripts/probe.sh +0 -108
  67. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
  68. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -269
  69. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
  70. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
  71. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -311
  72. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
  73. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
  74. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -257
  75. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -104
  76. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -199
  77. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
  78. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
  79. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -165
  80. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -91
  81. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -165
  82. package/steering/nestjs-react-fullstack/skills_common/trigger-guide/references/trigger-lifecycle.md +0 -301
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: trigger-guide
3
- description: 自动化任务触发器代码开发指南,支持 cron 定时触发器、record_change 数据变更触发器和 webhook 触发器,包含 @Automation/@BindTrigger 装饰器用法、handler 入参解析和 Crontab 表达式规范。Use when 需要:(1) 为已创建的自动化任务/定时任务编写业务 handler,(2) 编写 automation 代码绑定触发器,或其他自动化任务相关开发
3
+ description: 自动化任务触发器配置与代码开发指南,支持 cron 定时触发器、record_change 数据变更触发器和 webhook 触发器,包含 @Automation/@BindTrigger 装饰器用法和 Crontab 表达式规范。Use when 需要:(1) 创建或配置自动化任务/定时任务,(2) 编写 automation 代码绑定触发器,或其他自动化任务相关开发
4
4
  steering: true
5
5
  steering-topic: trigger_guide
6
6
  match-template-name: nestjs-react-fullstack
@@ -30,7 +30,7 @@ server
30
30
  1. 每个模块只应该有一个存放自动化任务逻辑的文件,业务逻辑需要聚合到该文件中。
31
31
  2. 如果该模块只有对应的自动化任务,无需编写 Controller
32
32
 
33
- ### 触发器类型
33
+ ### 触发器类型与入参
34
34
 
35
35
  触发器类型(`triggerType`)有三种:
36
36
 
@@ -38,15 +38,151 @@ server
38
38
  - `cron`:定时触发器,**无入参**
39
39
  - `webhook`:Webhook 触发器,**有入参**
40
40
 
41
- 各触发器 handler 的入参类型定义(`TaskHandlerArgs`、`DataChangeEventInput`、`WebhookEvent`)见 [触发器入参类型与代码示例](references/trigger-lifecycle.md)。
41
+ ```typescript
42
+ // 有入参触发器的入参类型(triggerType = 'record_change' 时)
43
+ interface TaskHandlerArgs {
44
+ attributes: {
45
+ trigger: string;
46
+ triggerID?: string;
47
+ triggerType: 'record_change' | 'cron' | 'webhook';
48
+ instanceID: string;
49
+ startAt?: number;
50
+ };
51
+ content: {
52
+ input: string; // JSON 字符串,根据 triggerType 解析为对应类型
53
+ };
54
+ }
42
55
 
43
- ### 指定值限制
56
+ // record_change:input 解析后的数据结构
57
+ interface DataChangeEventInput {
58
+ id: string;
59
+ tenant_id: number;
60
+ workspace: string;
61
+ branch: string;
62
+ app: string;
63
+ table: string;
64
+ type: 'INSERT' | 'UPDATE' | 'DELETE';
65
+ timestamp: number;
66
+ before?: Record<string, unknown>; // DELETE 时有值,其他情况可能为空
67
+ after?: Record<string, unknown>; // INSERT/UPDATE 时有值,其他情况可能为空
68
+ msg_id: string;
69
+ }
44
70
 
45
- 1. Webhook 触发器不可以设置指定值,并且告知用户。
71
+ // webhook:input 解析后的数据结构
72
+ interface WebhookEvent {
73
+ method: 'GET' | 'POST';
74
+ url: string; // 完整 URL(含查询参数)
75
+ host: string; // 不含查询参数的 URL
76
+ path: string; // 路径部分
77
+ query: Record<string, string[]>; // URL 查询参数,值为字符串数组
78
+ headers: Record<string, string[]>; // 请求头,值为字符串数组
79
+ body: string; // 请求体,JSON 字符串
80
+ meta: {
81
+ timestamp: number; // 请求时间戳(秒)
82
+ traceID: string; // 追踪 ID
83
+ env: 'development' | 'online'; // 环境:开发/线上
84
+ };
85
+ }
86
+ ```
46
87
 
47
- ### 代码绑定
88
+ ### 指定值限制
89
+ 1. Webhook 触发器不可以设置指定值,并且告知用户。
48
90
 
49
- 你需要根据触发器创建后确定的自动化任务名字(应用内唯一),编写并绑定到对应的方法上:`@BindTrigger('<任务名字>')` 中的名字必须与创建触发器时确定的名字逐字相同,不能用 trigger ID 或方法名代替。`@Automation()` 标记的类需注册为对应 `<module>.module.ts` 的 provider,且该 module 必须被 `server/app.module.ts` 直接或传递 import,否则装饰器不会生效。完整代码示例见 [触发器入参类型与代码示例](references/trigger-lifecycle.md)。
91
+ ### 代码示例
92
+
93
+ 你需要根据 `automation_trigger_manager` 工具返回的自动化任务名字,编写并绑定到对应的方法上。具体代码示例如下:
94
+
95
+ ```typescript
96
+ // 文件名:demo.automation.ts
97
+ import { Logger } from '@nestjs/common';
98
+ // 必须导入
99
+ import { Automation, BindTrigger } from '@lark-apaas/fullstack-nestjs-core';
100
+
101
+ /**
102
+ * 示例自动化任务服务
103
+ * 使用 @Automation 装饰器标记 class,@BindTrigger 绑定具体 function
104
+ */
105
+ @Automation()
106
+ export class DemoAutomationTasksService {
107
+ // 务必使用 logger 打印日志
108
+ private readonly logger = new Logger(DemoAutomationTasksService.name);
109
+
110
+ @BindTrigger('triggerName1')
111
+ // 任务对应具体的实现
112
+ async helloWorld() {
113
+ this.logger.log('执行 Hello World 任务');
114
+ // do logic
115
+ // return logic result or throw Error
116
+ }
117
+
118
+ @BindTrigger('triggerName2')
119
+ // 任务对应具体的实现
120
+ async sendNotification() {
121
+ this.logger.log('开始发送通知');
122
+ // do logic
123
+ this.logger.log('通知发送完成');
124
+ // return logic result or throw Error
125
+ }
126
+
127
+ @BindTrigger('recordChangeTrigger')
128
+ // 记录变更任务(triggerType = 'record_change')
129
+ async handleDataChange(event: TaskHandlerArgs) {
130
+ // 1. 校验并解析 input
131
+ const input = event.content.input;
132
+ if (typeof input !== 'string') {
133
+ this.logger.error('input 类型错误');
134
+ return;
135
+ }
136
+
137
+ let eventData: DataChangeEventInput;
138
+ try {
139
+ eventData = JSON.parse(input);
140
+ } catch (error) {
141
+ this.logger.error('JSON 解析失败', error);
142
+ return;
143
+ }
144
+
145
+ // 2. 根据操作类型获取数据:INSERT/UPDATE 用 after,DELETE 用 before
146
+ const record = eventData.after || eventData.before;
147
+ if (!record) {
148
+ this.logger.error('记录数据为空');
149
+ return;
150
+ }
151
+ this.logger.log(`处理 ${eventData.type} 事件,记录ID: ${record.id}`);
152
+ // do logic
153
+ // return logic result or throw Error
154
+ }
155
+
156
+ @BindTrigger('webhookTrigger')
157
+ // Webhook 任务(triggerType = 'webhook')
158
+ async handleWebhook(event: TaskHandlerArgs) {
159
+ // 1. 校验并解析 input
160
+ const input = event.content.input;
161
+ if (typeof input !== 'string') {
162
+ this.logger.error('input 类型错误');
163
+ return;
164
+ }
165
+
166
+ let webhookEvent: WebhookEvent;
167
+ try {
168
+ webhookEvent = JSON.parse(input);
169
+ } catch (error) {
170
+ this.logger.error('JSON 解析失败', error);
171
+ return;
172
+ }
173
+
174
+ // 2. 获取请求信息
175
+ const { method, path, query, headers, body } = webhookEvent;
176
+ this.logger.log(`处理 Webhook 请求:${method} ${path}`);
177
+
178
+ // 3. 按需解析 body(body 本身也是 JSON 字符串)
179
+ // const bodyData = JSON.parse(body);
180
+
181
+ // do logic
182
+ // return logic result or throw Error
183
+ }
184
+ }
185
+ ```
50
186
 
51
187
  ### 任务代码实现约束
52
188
 
@@ -62,12 +198,148 @@ server
62
198
 
63
199
  ### 技术实现路径参考
64
200
 
65
- 以下常见需求的推荐实现路径,帮助你在平台能力限制下找到合理的技术方案;完整代码见 [触发器入参类型与代码示例](references/trigger-lifecycle.md)。
201
+ 以下是一些常见需求的推荐实现路径,帮助你在平台能力限制下找到合理的技术方案。
202
+
203
+ #### 场景一:用户需要管理页面控制定时任务的启停
204
+
205
+ 平台侧不支持通过 API 动态启停触发器。推荐方案:**平台定时触发器始终保持开启,在任务执行时查询数据库中的开关状态,决定是否真正执行业务逻辑。**
206
+
207
+ 实现步骤:
208
+
209
+ 1. 在数据库中建一张配置表(或复用已有配置表),存储任务开关状态
210
+ 2. 前端管理页面提供开关操作,修改数据库中的状态
211
+ 3. 定时任务触发时,先查询开关状态,关闭则直接跳过
212
+
213
+ ```typescript
214
+ @Automation()
215
+ export class ReportAutomationService {
216
+ private readonly logger = new Logger(ReportAutomationService.name);
217
+
218
+ constructor(private readonly configService: ConfigService) {}
219
+
220
+ @BindTrigger('dailyReportTrigger')
221
+ async generateDailyReport() {
222
+ // 1. 先查询任务开关状态
223
+ const config = await this.configService.getTaskConfig('dailyReport');
224
+ if (!config?.enabled) {
225
+ this.logger.log('每日报告任务已被管理员关闭,跳过执行');
226
+ return;
227
+ }
228
+
229
+ // 2. 开关开启,执行实际业务逻辑
230
+ this.logger.log('开始生成每日报告');
231
+ // do logic
232
+ }
233
+ }
234
+ ```
235
+
236
+ #### 场景二:定时任务需要将结果通知给特定用户
237
+
238
+ 自动化任务执行时无法获取当前用户上下文。推荐方案:**在数据库中预存需要通知的用户 ID,任务执行时从数据库查询目标用户,再调用飞书插件发送通知。**
239
+
240
+ ```typescript
241
+ @Automation()
242
+ export class NotifyAutomationService {
243
+ private readonly logger = new Logger(NotifyAutomationService.name);
244
+
245
+ constructor(
246
+ private readonly userConfigService: UserConfigService,
247
+ ) {}
248
+
249
+ @BindTrigger('weeklyDigestTrigger')
250
+ async sendWeeklyDigest() {
251
+ // 1. 从数据库查询订阅了周报的用户列表
252
+ const subscribers = await this.userConfigService.getSubscribers('weeklyDigest');
253
+ if (!subscribers.length) {
254
+ this.logger.log('无订阅用户,跳过发送');
255
+ return;
256
+ }
257
+
258
+ // 2. 生成周报内容
259
+ const reportContent = await this.buildWeeklyReport();
260
+
261
+ // 3. 逐个发送通知
262
+ for (const user of subscribers) {
263
+ // 调用插件发送飞书消息
264
+ this.logger.log(`已发送周报给用户: ${user.userId}`);
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ #### 场景三:记录变更触发器需要做防抖/去重
271
+
272
+ 高频数据变更场景下,同一条记录可能短时间内触发多次。推荐方案:**利用数据库记录最近一次处理时间戳,对比 event 时间戳进行去重。**
273
+
274
+ ```typescript
275
+ @BindTrigger('orderStatusChange')
276
+ async handleOrderChange(event: TaskHandlerArgs) {
277
+ const eventData: DataChangeEventInput = JSON.parse(event.content.input);
278
+ const record = eventData.after;
279
+ if (!record) return;
280
+
281
+ const orderId = record.id as string;
282
+
283
+ // 查询上次处理时间,跳过短时间内的重复事件
284
+ const lastProcessed = await this.orderService.getLastProcessedTime(orderId);
285
+ if (lastProcessed && eventData.timestamp - lastProcessed < 5000) {
286
+ this.logger.log(`订单 ${orderId} 短时间内重复触发,跳过`);
287
+ return;
288
+ }
289
+
290
+ // 记录本次处理时间并执行业务逻辑
291
+ await this.orderService.updateLastProcessedTime(orderId, eventData.timestamp);
292
+ this.logger.log(`处理订单状态变更: ${orderId}`);
293
+ // do logic
294
+ }
295
+ ```
296
+
297
+ #### 场景四:用户需要自定义定时任务的触发时间
298
+
299
+ 平台侧的 cron 表达式在触发器创建后无法由用户动态修改。推荐方案:**平台设置一个固定的高频定时器(如每 30 分钟执行一次),在任务执行时从数据库读取用户配置的触发时间,判断当前是否命中再决定是否执行。**
300
+
301
+ 实现步骤:
302
+
303
+ 1. 平台侧创建一个每 30 分钟执行的 cron 触发器(最小粒度)
304
+ 2. 数据库中存储用户配置的期望执行时间(如 `"09:00"`、`"每周一 14:00"` 等)
305
+ 3. 前端管理页面提供时间配置界面,用户可随时修改
306
+ 4. 每次触发时,读取配置并判断当前时间是否匹配,不匹配则跳过
307
+
308
+ ```typescript
309
+ @Automation()
310
+ export class ScheduleAutomationService {
311
+ private readonly logger = new Logger(ScheduleAutomationService.name);
312
+
313
+ constructor(private readonly scheduleConfigService: ScheduleConfigService) {}
314
+
315
+ @BindTrigger('fixedIntervalTrigger') // 平台侧固定每 30 分钟触发
316
+ async checkAndExecuteTasks() {
317
+ // 1. 查询所有用户配置的定时任务
318
+ const tasks = await this.scheduleConfigService.getAllActiveTasks();
319
+
320
+ const now = new Date();
321
+ for (const task of tasks) {
322
+ // 2. 判断当前时间是否命中用户配置的执行时间
323
+ if (!this.isTimeMatched(now, task.scheduledTime)) {
324
+ continue;
325
+ }
326
+
327
+ // 3. 命中则执行对应业务逻辑
328
+ this.logger.log(`执行任务: ${task.name}, 配置时间: ${task.scheduledTime}`);
329
+ await this.executeTask(task);
330
+ }
331
+ }
332
+
333
+ private isTimeMatched(now: Date, scheduledTime: string): boolean {
334
+ // 将当前时间取到半小时精度,与用户配置的时间比较
335
+ // 例如 scheduledTime = "09:00",当前 08:46~09:15 之间的某次触发即命中
336
+ const [hour, minute] = scheduledTime.split(':').map(Number);
337
+ return now.getHours() === hour && now.getMinutes() === minute;
338
+ }
339
+ }
340
+ ```
66
341
 
67
- - **场景一:管理页面控制定时任务启停** —— 平台侧不支持通过 API 动态启停触发器;定时触发器始终保持开启,在任务执行时查询数据库中的开关状态决定是否执行。
68
- - **场景二:定时任务通知特定用户** —— 任务执行时无法获取用户上下文;在数据库预存目标用户 ID,执行时查询再调用飞书插件发送。
69
- - **场景三:记录变更触发器防抖/去重** —— 利用数据库记录最近一次处理时间戳,对比 event 时间戳进行去重。
70
- - **场景四:自定义定时任务触发时间** —— cron 创建后不可动态改;平台设固定高频定时器(如每 30 分钟),执行时读数据库配置判断是否命中。
342
+ > 注意:由于平台最小调度间隔为 30 分钟,用户可配置的时间精度也应限制为 30 分钟的整数倍(如 `09:00`、`09:30`),前端做好校验提示。
71
343
 
72
344
  ## Crontab 表达式规范
73
345
 
@@ -93,12 +93,14 @@ match-template-name: nestjs-react-fullstack
93
93
 
94
94
  > 完整的解析→守卫流程见 `authn-guide` skill 第二节"认证流程"。
95
95
 
96
+ 类型使用规则:Controller 直接用 `@Req() req: Request` 读取 SDK 增强的 `req.userContext`。测试、mock 或局部业务需要窄类型时,定义独立接口,只声明实际读取字段;不要继承 Express `Request`,也不要重声明平台增强的 `userContext`。未读取的 `tenantId` 省略;必须保留时用 `unknown` 或 SDK 兼容类型。
97
+
96
98
  ### 字段表
97
99
 
98
100
  | 字段 | 类型 | 说明 |
99
101
  |------|------|------|
100
102
  | `userId` | `string` | 妙搭用户 ID |
101
- | `tenantId` | `number` | 租户 ID |
103
+ | `tenantId` | `number \| undefined` | 租户 ID;按 SDK Express `Request.userContext` 增强:可选 number |
102
104
  | `appId` | `string` | 应用 ID |
103
105
  | `loginUrl` | `string` | 登录跳转 URL |
104
106
  | `userType` | `string` | 用户类型(如 `_employee`) |
@@ -14,6 +14,8 @@ match-template-name: nestjs-react-fullstack
14
14
 
15
15
  > **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
16
16
  >
17
+ > **本地 dev 限制**:本 skill 的能力底层打 `/app/<appId>/__runtime__/api/v1/account/*`。这类端点只有在 `npm run dev:local` 成功 `env pull` 到 `MIAODA_DEV_PLATFORM_BASE`(或 `SANDBOX_PUBLIC_URL`)时才由 dev-proxy 反代到远端沙箱;**env pull 失败或未装 lark-cli 时该反代不挂**(启动脚本只 warn 不中断),表现为 POST 404 / GET 返回 200 + index.html。`useCurrentUserProfile()` 打的是 `account/login/user`;UserSelect / DepartmentSelect / UserDisplay 打 `search_user` / `list_users` / `user_profile` / `search_department`。先确认 `.env.local` 里有 `MIAODA_DEV_PLATFORM_BASE`;确实缺失时禁止改 `business-ui` 组件或换自研控件绕过,改由服务端出数据(`req.userContext` / `AuthNPaasService`)。
18
+ >
17
19
  > **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
18
20
 
19
21
  ## 怎么选(决策指引)
@@ -116,6 +116,12 @@ Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponse
116
116
  - [ ] 已根据实际定义修正代码
117
117
  - [ ] 已验证修改后类型使用正确
118
118
 
119
+ ## 业务日期错位
120
+
121
+ **问题描述**:本地凌晨、周一或月初附近,日期落到前一天、上周或上月。
122
+
123
+ **解决步骤**:按 `coding-guide` 的「业务日期」一节改——自然日统一走业务时区 helper;精确时刻不动。
124
+
119
125
  ## JSX 和语法错误
120
126
 
121
127
  ### 特殊字符转义处理
@@ -129,7 +129,7 @@ shared/ # 前后端共享的目录
129
129
 
130
130
  1. 优先使用项目已有依赖,仅在无法实现时安装新依赖
131
131
  2. 使用前先查看 `package.json` 确保依赖已存在
132
- 3. **子包完整性检查**:部分库有多个子包(如 `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities`),添加 import 后必须确认 package.json 中包含所有需要的子包
132
+ 3. **子包完整性检查**:部分库有多个子包(如 `@radix-ui/react-*` 系列每个组件都是独立子包),添加 import 后必须确认 package.json 中包含所有需要的子包
133
133
  4. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
134
134
 
135
135
  ## 文件命名约定
@@ -147,6 +147,39 @@ shared/ # 前后端共享的目录
147
147
  - `/api` 和 `/openapi` 代理已配置,前端 TS 严格模式已禁用,后端已启用
148
148
  - Rspack 用于构建,已配置好,**禁止修改**
149
149
 
150
+ ### 平台 runtime 接口(`__runtime__`)在本地要靠 env pull 才通
151
+
152
+ dev server 自己只代理 `/api`、`/openapi`、`/__innerapi__`。`/app/<appId>/__runtime__/*` 这一整类平台 runtime 接口走的是另一条路:`npm run dev:local` 会先跑 `lark-cli apps +env-pull` 把沙箱身份/凭证拉进 `.env.local`,其中的 `MIAODA_DEV_PLATFORM_BASE`(或 legacy `SANDBOX_PUBLIC_URL`)才让 dev-proxy 把 `__runtime__` 反代到远端沙箱。
153
+
154
+ **env pull 失败、未装 lark-cli、或 `.spark/meta.json` 缺 app_id 时,启动脚本只 warn 不中断**,反代就不会挂上。此时这些端点的症状是:**POST 返回 404;GET 落到 Vite 的 SPA fallback,返回 200 + `index.html`**(业务侧表现为「返回一坨 HTML,JSON 解析失败」)。
155
+
156
+ 常踩到的端点:
157
+
158
+ | 端点 | 方法 | 谁会打到它 |
159
+ |------|------|-----------|
160
+ | `api/v1/permissions/roles` | GET | `AppContainer` 每次加载应用自动打 |
161
+ | `api/v1/observability/{logs,traces,metrics}/collect`、`current_server_timestamp` | POST/GET | 平台埋点上报,自动打 |
162
+ | `api/v1/account/login/user` | POST | `useCurrentUserProfile()` → `authClient.session.getUserInfo()` |
163
+ | `api/v1/account/search_user`、`list_users`、`user_profile`、`search_department`、`convert_lark_user` | POST / `user_profile` 是 GET | `business-ui` 的 UserSelect / DepartmentSelect / UserDisplay |
164
+ | `api/v1/account/search`、`api/v1/account/chat/list_chats` | POST | ChatSelect 选群组件 |
165
+ | `api/v1/studio/user/profile` | GET | `getUserProfile()` |
166
+ | `api/v1/storage/object/<bucket>/pre_upload` | POST | `dataloom.storage.uploadFile()` 前端上传 |
167
+ | `api/v1/studio/plugins/tmp_files/acquire_upload_url`、`acquire_download_url` | POST | `capabilityClient` 的文件类入参 |
168
+
169
+ > `useCurrentUserProfile()` 打的是 `account/login/user`,**不是** `user_profile`/`search_user` 那一排;它另外还打一个 `GET /api/authnpaas/lark-user-id`,那条走 `/api`、被代理、正常。
170
+
171
+ 排查顺序:先看 `.env.local` 里有没有 `MIAODA_DEV_PLATFORM_BASE`,没有就重跑 `env pull`。确认拉不到时:
172
+
173
+ - **禁止**改 `client/src/components/business-ui/**`(平台保护文件)、**禁止**给组件加兜底请求或改用自研选人控件绕过——发布后这些端点是正常的,绕过写法反而是错的
174
+ - 需要验证「按当前用户过滤」「展示用户姓名」这类逻辑:改由服务端出数据。服务端的 `req.userContext` 和 `AuthNPaasService` 由网关注入,不受此限制(见 `user-identity` / `contacts-service` skill)。`useCurrentUserProfile()` 长期停在加载态就是这个原因
175
+ - 需要验证上传链路:改用接口测试直接打后端接口,或复用库里已有的文件 URL
176
+
177
+ 另外,`UserSelect` 弹层是**搜索驱动**的:不输关键词时列表为空属预期行为,不是接口挂了。
178
+
179
+ ### 服务端启动即崩、`/api` 全部 502
180
+
181
+ 报错形如 `UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?)... argument Function at index [0]` 时,先查构造函数有没有空参 `@Inject()`(见 plugin-guide),不要先去改 Module 的 `imports` / `providers`。
182
+
150
183
  ## 质量保障流程
151
184
 
152
185
  修改代码后:
@@ -286,6 +319,12 @@ await db.select().from(users).where(eq(users.adminUser, userId));
286
319
 
287
320
  - **`customTimestamptz` 跨网络后是 string**:service 内查询返回 `Date`,但 API 响应经 JSON 序列化后前端拿到 ISO string。`shared/api.interface.ts` 中 timestamptz 字段声明为 `string`,前端需要 Date 时手动 `new Date(value)`。
288
321
 
322
+ ### 业务日期
323
+
324
+ 需求里的日期是**自然日**(哪一天 / 哪一周 / 哪一月)而不是精确时刻时,按业务时区计算,未指定时默认 `Asia/Shanghai`。
325
+
326
+ 项目里放一个 helper 统一产出 `day` / `month` / `weekStart` / `shiftDay`;服务端写入、统计聚合、前端展示都比较它的返回值。
327
+
289
328
  ### 数据库使用强约束
290
329
 
291
330
  - 仅通过 schema.ts 暴露的客户端和类型读写,禁止手写 SQL/临时类型
@@ -329,10 +368,13 @@ await db.select().from(users).where(eq(users.adminUser, userId));
329
368
 
330
369
  - **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码
331
370
  - 依赖用户信息的接口不要用 API 测试工具测试,用 `think` 工具思考
371
+ - 后端按 `user-identity` 和 `@lark-apaas/fullstack-nestjs-core` 的 Express `Request` 增强读取 `req.userContext`;`tenantId` 为可选 number,不能套用前端 `window.tenantId` / `viewContext` 字符串语义。
372
+ - Controller 直接用 `@Req() req: Request`;测试、mock 或业务窄类型用独立接口,只声明实际读取字段。不要继承 Express `Request` 或重声明 `userContext`;未读 `tenantId` 就省略,必须保留时用 `unknown` 或 SDK 兼容类型。
332
373
 
333
374
  ```typescript
334
- // req.userContext 字段(由身份认证中间件挂载):
335
- // - userId/tenantId/appId: string
375
+ // req.userContext 字段(由身份认证中间件挂载,完整合同见 user-identity):
376
+ // - userId/appId: string
377
+ // - tenantId: SDK Request.userContext 的可选 number
336
378
  // - env: 'preview' | 'runtime' (preview=预览态, runtime=发布运行态)
337
379
  // - userName/userNameEn: string
338
380
  // - userNameI18n: string (多语名字, 如 {zh_cn: '用户', en_us: 'user'})
@@ -443,14 +485,13 @@ NestJS 自己不读 env。直连 NestJS 端口 → header 缺失 → `req.userCo
443
485
 
444
486
  - **框架**: React 19 + TypeScript
445
487
  - **路由**: React Router DOM v6
446
- - **样式**: styled-jsx + tailwindcss(语义化 token)。styled-jsx 使用前提见下方"样式开发"
488
+ - **样式**: tailwindcss(语义化 token)
447
489
  - **UI 组件库**: shadcn/ui — Use components for functionality, heavily style them
448
490
  - **图表**: ReactECharts,**开发前必须调用 `/charts-skill`**
449
491
  - **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
450
492
  - **表格/表单/图表**: 见下方"组件 Skill 召回规则",开发前必须先调用对应 Skill
451
493
  - **用户**: 用户信息展示/选择必须用 `business-ui` 组件(阅读 README.md),禁止直接展示 userId
452
- - **富文本**: `business-ui/tiptap-editor`(阅读 README.md)
453
- - **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
494
+ - **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
454
495
 
455
496
  ## API 请求
456
497
 
@@ -483,8 +524,7 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
483
524
  | --------------------------------------------------- | --------------------------------------- | --------------------------------- |
484
525
  | Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
485
526
  | UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
486
- | TiptapEditorComplete | `business-ui/tiptap-editor` | 富文本编辑器 |
487
- | Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
527
+ | Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
488
528
 
489
529
  ### 组件 Skill 召回规则(强制执行)
490
530
 
@@ -535,7 +575,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
535
575
  ```
536
576
  需要写样式?
537
577
  ├─ 基础布局/间距/颜色 → Tailwind ✅
538
- ├─ 复杂动画/伪元素/高级CSS → styled-jsx ✅
539
578
  └─ JS动态计算值 → 行内 style ✅
540
579
  ```
541
580
 
@@ -548,11 +587,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
548
587
  - **arbitrary values 中空格用下划线**:`from-[hsl(215_60%_18%)]` 非 `from-[hsl(215 60% 18%)]`
549
588
  - `tailwind-theme.css` 自定义属性用 `hsl(H, S%, L%)` 格式(非 `23 10% 23%`)
550
589
 
551
- ### styled-jsx 规范
552
-
553
- - **技术栈一致性**:仅在已配置 styled-jsx 插件的项目中使用。`package.json` 无 `styled-jsx` 依赖则**禁用**,否则运行时 SyntaxError
554
- - **禁止动态插值**:`<style jsx>` 内禁止 `${...}` 等表达式(会卡死)。动态值放 CSS 变量,用 `var(--xxx)` 引用
555
-
556
590
  ### 布局/排版
557
591
 
558
592
  - Spacing 保持一致(small/medium/large 三级),Panel 风格统一
@@ -635,15 +669,8 @@ return <h1>{data?.title || '未知标题'}</h1>;
635
669
  | 验证 | zod |
636
670
  | 工具函数 | lodash |
637
671
  | 样式 | clsx |
638
- | Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
639
- | PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
640
- | 文件上传 | react-dropzone |
641
- | 二维码 | qrcode.react |
642
672
  | 用户反馈 | sonner |
643
- | 拖拽 | @dnd-kit/core |
644
- | 数字动画 | react-countup |
645
- | Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
646
- | 3D 场景 | cobe |
673
+
647
674
 
648
675
  ## 滚动分页最佳实践
649
676
 
@@ -8,6 +8,8 @@ steering-topic: plugin_guide
8
8
  match-template-name: nestjs-react-fullstack
9
9
  ---
10
10
 
11
+ {% raw %}
12
+
11
13
  # Plugin 集成指南(本地开发)
12
14
 
13
15
  AI 插件集成规范,使用 lark-cli 命令管理插件包与实例,通过 capabilityClient / CapabilityService 生成调用代码。
@@ -33,9 +35,10 @@ lark-cli apps --help 2>&1 | grep -q '+plugin-install' && echo "READY" || echo "M
33
35
  | Client 侧非流式调用 | `capabilityClient.load(id).call(actionKey, input)` |
34
36
  | Client 侧流式调用 | `capabilityClient.load(id).callStream(actionKey, input)` |
35
37
  | Server 侧调用(仅兜底) | `capabilityService.load(id).call(actionKey, input)` |
38
+ | 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
36
39
  | capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
37
40
  | CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
38
- | CapabilityService 注入 | `@Inject() private readonly capabilityService: CapabilityService` |
41
+ | CapabilityService 注入 | `@Inject(CapabilityService) private readonly capabilityService: CapabilityService`(**禁止空参 `@Inject()`**,会导致启动崩溃 502;业务 Module 无需 imports) |
39
42
  | 插件实例配置位置 | `server/capabilities/<instance_id>.json`(默认全栈应用)。见「配置目录」段 |
40
43
 
41
44
  > **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
@@ -514,7 +517,9 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
514
517
  2. 结果供后续功能消费
515
518
  3. 用户再次访问时需要看到结果
516
519
 
517
- **推荐**:Server 侧 Service 调用插件 + 同一方法落库。**备选**:Client 侧调用 → 流式结束后调已有 CRUD 接口保存。
520
+ **推荐**:能由前端承接时,Client 侧 `callStream` 渐进展示,流式结束后调已有 CRUD 接口保存。**必要时**:敏感凭证、触发器、强事务或必须由后端保证落库一致性时,Server 侧 Service 调用插件并落库。
521
+
522
+ **高耗时 AI capability 闸门**:若插件输出定义或功能设计显示输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,禁止把完整生成放进一个同步 HTTP 请求等待;能由前端承接时优先 `callStream` 渐进展示,需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时后端创建任务记录后快速返回、由前端短轮询状态/结果,或拆成多个独立小调用。
518
523
 
519
524
  ### 生成代码
520
525
 
@@ -533,7 +538,8 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
533
538
 
534
539
  1. **禁止静默吞异常**:每个 `catch` 块必须向用户展示错误或触发补偿
535
540
  2. **异步操作必须有终态**:DB 中维护状态(pending → success / failed)
536
- 3. **插件失败必须有补偿**:至少记录到待处理列表或提示用户重试
541
+ 3. **长耗时 AI 生成不得同步等完**:大体量、多字段、多份、多语言、文件/多模态串联或需要持久化的 AI capability 调用,触发接口必须快速返回任务状态或采用前端流式展示;若接口请求、浏览器操作或页面请求出现超时/连接断开,不能只凭服务端最终日志或数据库最终写入宣告成功,必须证明用户侧存在可读取的终态结果
542
+ 4. **插件失败必须有补偿**:至少记录到待处理列表或提示用户重试
537
543
 
538
544
  ## 缓存与幂等性
539
545
 
@@ -578,3 +584,5 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
578
584
  5. **禁止用 `npm install` 安装插件包** — 插件包和 npm 包是两套独立机制。
579
585
  6. **禁止 Mock** — 必须走真实插件实例调用链路。
580
586
  7. **formValue 禁止 Handlebars 控制语法** — 仅允许 `{{input.xxx}}`。
587
+
588
+ {% endraw %}