@lark-apaas/coding-steering 0.1.32-beta.0 → 0.1.32-dev.5abff3b
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 +6 -6
- package/steering/design-html/skills/charts/SKILL.md +4 -0
- package/steering/design-html/skills/pptx-style-extract/SKILL.md +64 -26
- package/steering/design-html/skills/pptx-style-extract/font-fallback.yaml +3 -3
- package/steering/design-html/skills/pptx-style-extract/scripts/census.py +18 -12
- package/steering/design-html/skills/pptx-style-extract/scripts/check_v2.py +153 -8
- package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +2735 -288
- package/steering/design-html/skills/pptx-style-extract/scripts/extract.py +325 -22
- package/steering/design-html/skills/pptx-style-extract/scripts/ooxml.py +19 -2
- package/steering/design-html/skills/pptx-style-extract/scripts/package.py +991 -165
- package/steering/design-html/skills/pptx-style-extract/scripts/parts.py +6 -3
- package/steering/design-html/skills/pptx-style-extract/scripts/query.py +4 -9
- package/steering/design-html/skills/pptx-style-extract/scripts/render_pages.py +16 -10
- package/steering/design-html/skills/pptx-style-extract/scripts/test_asset_judgment_package.py +443 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_background_composite.py +57 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_color_contract.py +60 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_design_consumer_contract.py +63 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_flow_layout_contract.py +528 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +1513 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_rounded_contract.py +112 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_text_role_contract.py +315 -0
- package/steering/design-html/skills/pptx-style-extract/v2-format-spec.md +27 -15
- package/steering/design-html/skills/preflight/scripts/probe.sh +0 -0
- package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +1 -0
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +6 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +5 -5
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +1 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +37 -113
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +13 -2
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +7 -7
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +149 -24
- package/steering/nestjs-react-fullstack/skills/connections-sdk/SKILL.md +202 -0
- package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +255 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +158 -543
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +15 -1
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +30 -14
- package/steering/nestjs-react-fullstack/skills/raw-sql-boundary-audit/SKILL.md +63 -0
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills_common/trigger-guide/SKILL.md +284 -12
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +4 -0
- package/steering/vite-react/skills/plugin-guide/SKILL.md +3 -1
- package/steering/vite-react/skills/react-three-fiber/SKILL.md +4 -0
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -269
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -311
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -257
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -104
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -199
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -165
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -91
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -165
- package/steering/nestjs-react-fullstack/skills_common/trigger-guide/references/trigger-lifecycle.md +0 -301
|
@@ -9,6 +9,25 @@ unavailable-agents:
|
|
|
9
9
|
- AppInit
|
|
10
10
|
- SpecDoc
|
|
11
11
|
---
|
|
12
|
+
|
|
13
|
+
# 平台关键文件红线(CRITICAL — 任何修改 / 排查前先读)
|
|
14
|
+
|
|
15
|
+
下列文件 / 目录由平台模板与 SDK 预置,是应用运行的地基,**不建议改 / 非必要不改**——尤其禁止删除或重写(删坏会直接导致白屏 / 启动失败 / 鉴权链断):
|
|
16
|
+
|
|
17
|
+
- `server/main.ts`
|
|
18
|
+
- `client/src/index.tsx`
|
|
19
|
+
- `client/index.html`
|
|
20
|
+
- `client/src/app.tsx`(加路由 / Provider 可改,但**禁止删除其中的平台根组件 / Provider 包裹**,禁止用 `eslint-disable` 关掉平台校验规则)
|
|
21
|
+
- `client/src/components/business-ui/*`
|
|
22
|
+
- `server/modules/view/view.controller.ts`
|
|
23
|
+
- `vite.config.ts`
|
|
24
|
+
|
|
25
|
+
**修改 / 排查上列文件时**:
|
|
26
|
+
|
|
27
|
+
1. **禁删、禁重写**;类型与平台代码不兼容时,在**调用点写 adapter / 包一层**消化差异,禁止改平台文件去迁就(更禁止 `as any`)。
|
|
28
|
+
2. 文件疑似被**清空 / 损坏**时,**从模板恢复原版**(对照同模板初始版本还原),禁止凭记忆从零重写——空文件不是「可以自由发挥」的信号。
|
|
29
|
+
3. 同一症状已尝试 **3 种以上修法仍未解决** → 停手回溯根因,不要再叠第 4 种 hack(3+ 次失败通常是把平台关键文件改坏了)。
|
|
30
|
+
|
|
12
31
|
# 项目结构
|
|
13
32
|
|
|
14
33
|
## 根目录组织
|
|
@@ -28,7 +47,7 @@ unavailable-agents:
|
|
|
28
47
|
|
|
29
48
|
```
|
|
30
49
|
server/ # 符合 NestJS 项目基本规范
|
|
31
|
-
├── main.ts #
|
|
50
|
+
├── main.ts # 应用程序入口点,平台关键文件,禁删禁重写(见顶部红线)
|
|
32
51
|
├── app.module.ts # 根模块,模块需要在该文件中导入
|
|
33
52
|
├── config/ # 配置文件
|
|
34
53
|
│ └── app.config.ts # 主要应用配置
|
|
@@ -38,7 +57,7 @@ server/ # 符合 NestJS 项目基本规范
|
|
|
38
57
|
│ ├── hello.controller.ts # controller 示例
|
|
39
58
|
│ ├── hello.module.ts # module 示例
|
|
40
59
|
│ └── hello.service.ts # service 示例(可选)
|
|
41
|
-
├── database/ # Drizzle ORM 数据库相关。应用仅能进行 DML 操作。若用户需求设计表结构变更,DDL
|
|
60
|
+
├── database/ # Drizzle ORM 数据库相关。应用仅能进行 DML 操作。若用户需求设计表结构变更,DDL 相关操作先加载数据库操作 skill(按「应用数据库 / 建表 / 改表 / SQL」召回),按其中的执行通道与建表规范操作。
|
|
42
61
|
│ ├── schema.ts # Drizzle ORM 数据库 Schema 定义。会在执行完 DDL 操作后自动生成,必须从该文件导入数据库类型,禁止自行编写 schema 文件。如有需要可调用 CodeGen 工具手动生成。
|
|
43
62
|
└── common/ # 共享工具和接口
|
|
44
63
|
│ ├── filters/ # 通用错误处理。
|
|
@@ -55,7 +74,7 @@ client/
|
|
|
55
74
|
├── index.html # HTML 模板
|
|
56
75
|
├── public/ # 静态资源
|
|
57
76
|
├── src/
|
|
58
|
-
│ ├── index.tsx # React
|
|
77
|
+
│ ├── index.tsx # React 应用入口点,平台关键文件,禁删禁重写(见顶部红线)
|
|
59
78
|
│ ├── index.css # 全局样式
|
|
60
79
|
│ ├── tailwind-theme.css # tailwindcss 全局css主题变量定制
|
|
61
80
|
│ ├── app.tsx # 主应用组件(包含路由定义)
|
|
@@ -165,7 +184,7 @@ shared/ # 前后端共享的目录
|
|
|
165
184
|
通用提交前检查(代码检查 / 接口测试 / 读日志确认无错误)见系统任务流程约束;本栈具体落点:
|
|
166
185
|
|
|
167
186
|
- **日志源**:服务端读 `server-devserver` / `server` 日志,客户端读 `client-devserver` 日志
|
|
168
|
-
- **dev 服务无响应**:`pkill -f "
|
|
187
|
+
- **dev 服务无响应**:`pkill -f "npm run dev"` 触发重启
|
|
169
188
|
|
|
170
189
|
---
|
|
171
190
|
|
|
@@ -236,7 +255,7 @@ export class TestService {
|
|
|
236
255
|
```
|
|
237
256
|
|
|
238
257
|
- **每次编写或修改数据库操作代码前,必须重新读取 `server/database/schema.ts`**——该文件由系统在 DDL 执行后自动重新生成,内容随时可能变化。禁止凭记忆或之前读取的版本编写字段名和类型,否则会引用不存在的字段导致 TS2339 批量报错
|
|
239
|
-
-
|
|
258
|
+
- **事务**:多条写操作需要整体成功/失败时(如扣库存 + 写审批流水),用 `db.transaction` 包裹;单条语句能完成的操作不要套事务
|
|
240
259
|
- 条件查询用三元分支,禁止 `let query` 重赋值(类型不兼容):
|
|
241
260
|
|
|
242
261
|
```typescript
|
|
@@ -282,10 +301,17 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
282
301
|
| 写(insert/update) | `ROW(${userId})::user_profile`(整体赋值) | 只更新复合类型单字段 |
|
|
283
302
|
| 过滤(where) | `WHERE (col).user_id = ${userId}` | `col::text = ${id}` / 裸 `col = ${id}` / `col = ROW(${id})::user_profile` 比较 |
|
|
284
303
|
|
|
285
|
-
>
|
|
304
|
+
> 权威来源是数据库操作 skill(按「应用数据库 / 建表 / 改表 / SQL」召回)的「`user_id` / `user_profile`」小节。原生 SQL 现场遇到 user_profile 列直接套用本表,不要在 `ROW()::user_profile` / `::text` / 裸值之间反复试错。
|
|
286
305
|
|
|
287
306
|
### 使用侧边界陷阱
|
|
288
307
|
|
|
308
|
+
- **Postgres 不做 MySQL 式隐式转换**:where 的参数类型要和 `schema.ts` 的列类型一致,否则报 `42883 operator does not exist`。
|
|
309
|
+
|
|
310
|
+
| 场景 | 写法 |
|
|
311
|
+
| --- | --- |
|
|
312
|
+
| 按月 / 按天筛 date、timestamptz | 半开区间 `and(gte(col, "2026-08-01"), lt(col, "2026-09-01"))`;日期列不能用 `like(col, "2026-08%")` |
|
|
313
|
+
| 数字列过滤(`@Query` 取到的是 string) | 先 `Number(q)` 再进 `eq` |
|
|
314
|
+
|
|
289
315
|
- **`count()` 返回 string**(PostgreSQL bigint)。
|
|
290
316
|
|
|
291
317
|
- **UUID 多值过滤不要直接插值数组到 `ANY(...::uuid[])`**:在 sql 模板中直接插入 ids 这类 JS 数组,可能被展开成 `($1, $2, ...)::uuid[]`,运行时报 `42846: cannot cast type record to uuid[]`。
|
|
@@ -306,12 +332,90 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
306
332
|
|
|
307
333
|
多 ID 数据库接口(如按班级查课程/考试/成绩统计)写完后必须实际调用验证,避免隐藏 500。
|
|
308
334
|
|
|
309
|
-
-
|
|
335
|
+
- **Drizzle raw `sql` 参数不走列 encoder**:绑定 schema 列的 helper 会编码;raw `sql` 的 `${...}` 只是 driver 参数。Date / custom type 进 raw SQL 前先转 driver-safe 标量(时间用 ISO string)或改回 helper;`user_profile` 仍按上一小节专表处理。
|
|
336
|
+
- raw SQL 聚合、`filter (...)`、`CASE WHEN`、窗口函数或复杂 where 涉及 Date / custom type 时,按 `raw-sql-boundary-audit` 审计,并实际调用对应 API 验证 HTTP 200,避免隐藏 500。
|
|
337
|
+
|
|
338
|
+
- **时间列跨网络后都是 string**:`date` 列 `$inferSelect` 就是 `string`(原样透传,禁止 `new Date()` 包装);`timestamp` / `timestamptz` 是 `Date`,**必须在 service 出口 `.toISOString()`**。两类在 `shared/api.interface.ts` 中统一声明为 `string`,前端需要 Date 时手动 `new Date(value)`。
|
|
339
|
+
|
|
340
|
+
- **禁止删类型注解消除报错**(`as unknown as T` 同禁):典型症状是「改完 service,错误跑到 controller 了」——错误没有转移,是校验点被往外推了一层;推到最外层删完,报错归零而契约失守。
|
|
341
|
+
|
|
342
|
+
### PostgreSQL SQLSTATE / Drizzle 异常处理(CRITICAL)
|
|
343
|
+
|
|
344
|
+
- 可预期幂等 / 冲突优先用数据库原子语义(`ON CONFLICT`、`UPDATE ... WHERE ... RETURNING`),不要靠异常流兜底。
|
|
345
|
+
- 必须 catch 数据库异常时,**禁止只读顶层 `error.code`**:Drizzle 可能把原始 PostgreSQL error 放在 `cause` 链里。判断 `23505` 等 SQLSTATE 时用 cause-aware helper:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
function extractPostgresErrorCode(error: unknown): string | undefined {
|
|
349
|
+
let current: unknown = error;
|
|
350
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth += 1) {
|
|
351
|
+
const { code, cause } = current as { code?: unknown; cause?: unknown };
|
|
352
|
+
if (typeof code === "string") return code;
|
|
353
|
+
current = cause;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- 只处理明确 SQLSTATE:`23505`(unique violation)可转业务态或 `ConflictException`;`23503` 外键、`42501` 权限 / RLS 按业务语义失败;未知错误继续抛出,禁止吞成成功。
|
|
360
|
+
- 依赖约束 / 权限分支的写接口,至少验证正常写入 + 冲突 / 重复写入,确认不是 500 且无重复 / 部分写入。
|
|
361
|
+
|
|
362
|
+
### 写操作与批量读(CRITICAL)
|
|
363
|
+
|
|
364
|
+
- **PATCH/UPDATE 更新契约**:`PATCH` 和增量 `update` 接口只写入请求 DTO 中明确提供的可选字段,禁止用 `dto.field ?? 默认值`、空数组、空字符串等默认值覆盖未提供字段;用户明确清空时由 DTO 传入清空语义值(如空数组、空字符串或 `null`)并按业务规则写入。业务字段发生更新且接口/页面展示最后修改时间或最后修改人时,不能依赖 `DEFAULT CURRENT_TIMESTAMP` 等建表默认值刷新 UPDATE;必须在同一次 Drizzle update 的 `set(patch)` 调用中维护 `schema.ts` 暴露的更新时间和更新人字段,更新人从 `req.userContext.userId` 传入 service,禁止从前端传递。
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const patch: Partial<typeof table.$inferInsert> = {};
|
|
368
|
+
if (dto.title !== undefined) patch.title = dto.title;
|
|
369
|
+
if (dto.labels !== undefined) patch.labels = JSON.stringify(dto.labels);
|
|
370
|
+
if (Object.keys(patch).length === 0) throw new BadRequestException('未提供可更新字段');
|
|
371
|
+
|
|
372
|
+
patch.updatedAt = new Date();
|
|
373
|
+
patch.updatedBy = userId;
|
|
374
|
+
|
|
375
|
+
const updated = await this.db.update(table)
|
|
376
|
+
.set(patch)
|
|
377
|
+
.where(eq(table.id, id))
|
|
378
|
+
.returning({ id: table.id });
|
|
379
|
+
if (updated.length === 0) throw new NotFoundException('记录不存在');
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- **`Partial<$inferInsert>` 仅用于 update 的 `set()`**:insert 的 `.values()` 要求必填字段齐全,套 `Partial` 会把必填字段变可选、报缺属性错误;create 直接构造完整对象(类型用 `typeof table.$inferInsert`),禁止把上面的 patch 写法复制到 create
|
|
383
|
+
|
|
384
|
+
- **禁止 N+1 查询**:列表接口禁止在循环 / `Promise.all` 里逐条查子记录。先收集主记录 ids,用 `inArray` 一次查回,内存中按外键 `Map` 分组回填:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// ❌ N+1:每条 order 单独查 items
|
|
388
|
+
for (const order of orders) {
|
|
389
|
+
order.items = await this.db.select().from(items).where(eq(items.orderId, order.id));
|
|
390
|
+
}
|
|
391
|
+
// ✅ 批量查 + Map 分组
|
|
392
|
+
const allItems: Item[] = await this.db.select().from(items)
|
|
393
|
+
.where(inArray(items.orderId, orders.map((o: Order) => o.id)));
|
|
394
|
+
const byOrder = new Map<string, Item[]>();
|
|
395
|
+
for (const it of allItems) {
|
|
396
|
+
byOrder.set(it.orderId, [...(byOrder.get(it.orderId) ?? []), it]);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
- **写操作必须校验生效行数**:update / delete 必须带 `.returning({ id })`,返回空数组 = 目标不存在或条件不满足 → 抛对应异常(见「异常处理」),禁止盲发 delete 后直接返回成功
|
|
401
|
+
- **禁止 SELECT→计算→UPDATE 写数值**:库存 / 余额 / 计数的增减必须用单条原子 UPDATE,把业务校验放进 WHERE。注意:仅用 `db.transaction` 包裹 SELECT+UPDATE **不能**防止并发超卖(两个事务可同时读到旧值),除非 SELECT 加 `FOR UPDATE` 行锁——优先用原子 UPDATE,简单且无锁等待:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// ❌ 竞态:先 select 库存,JS 里算完再 update(两请求并发会超卖)
|
|
405
|
+
// ✅ 原子条件更新 + 生效行数校验(同时满足上一条规则)
|
|
406
|
+
const updated = await this.db.update(products)
|
|
407
|
+
.set({ stock: sql`${products.stock} - ${qty}` })
|
|
408
|
+
.where(and(eq(products.id, id), gte(products.stock, qty)))
|
|
409
|
+
.returning({ id: products.id });
|
|
410
|
+
if (updated.length === 0) throw new ConflictException("库存不足或商品不存在");
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
- 原子 UPDATE 需要与其他写操作保持一致时(如同时写审批流水),把原子 UPDATE 放进 `db.transaction`,生效行数为 0 时抛异常令事务回滚
|
|
310
414
|
|
|
311
415
|
### 数据库使用强约束
|
|
312
416
|
|
|
313
417
|
- 优先通过 schema.ts 暴露的客户端和类型读写;**被迫手写 SQL 时 user_profile 列必须按上表三侧写法**,禁止临时 `::text` 转换或裸值比较
|
|
314
|
-
- **变更流程(CRITICAL)**:DDL
|
|
418
|
+
- **变更流程(CRITICAL)**:DDL 先加载数据库操作 skill(按「应用数据库 / 建表 / 改表 / SQL」召回),按其中的 SQL 执行通道执行(建表 / 改表规则、具体命令与必填参数一律以该 skill 为准,不要凭记忆拼命令,也不要裸连数据库)→ 系统自动 codegen → **立即重新读取 schema.ts** → 再编写业务代码。跳过重新读取直接编码是 TS2339 批量错误的主要根因
|
|
315
419
|
- 连接失败/SSL 错误:立即停机并提示用户联系技术支持
|
|
316
420
|
|
|
317
421
|
## API 规范
|
|
@@ -329,12 +433,17 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
329
433
|
- 编写前端 API 时必须先读取对应 Controller 确认 method 和路径
|
|
330
434
|
5. **路由注册验证**:创建新 Controller 后必须用接口测试工具验证路由是否生效。**遇到 404 排查路径**:① 检查 `@Controller(...)` 是否以 `api/` 或 `openapi/` 开头 ② 检查 Module 是否在 `app.module.ts` 注册 ③ 检查静态路由是否在动态路由 `/:id` 之前
|
|
331
435
|
6. **@Query/@Param 类型转换**:默认 string,必须在 controller 层手动转换(如 `parseInt(limit, 10)`)
|
|
332
|
-
7.
|
|
436
|
+
7. **写接口鉴权**:POST/PUT/PATCH/DELETE 不一刀切。个人/后台/依赖当前用户的写入加 `@NeedLogin()`;未登录访客表单/报名/反馈可公开,但每张被写入的 RLS 表都要有 `FOR INSERT TO anon WITH CHECK (true)` 或等价 Drizzle `withCheck`,并用未登录 HTTP POST 验证。`FOR SELECT TO anon` 只读、`FOR INSERT TO authenticated` 只登录态;公开只读页的增删改仍按登录态处理。
|
|
333
437
|
```typescript
|
|
334
438
|
import { NeedLogin } from "@lark-apaas/fullstack-nestjs-core";
|
|
439
|
+
|
|
335
440
|
@NeedLogin()
|
|
336
441
|
@Post()
|
|
337
442
|
async createItem(@Req() req, @Body() dto) { ... }
|
|
443
|
+
|
|
444
|
+
// 公开匿名写:不加 @NeedLogin(),DDL 需 anon INSERT WITH CHECK
|
|
445
|
+
@Post("public-submit")
|
|
446
|
+
async submitPublic(@Body() dto) { ... }
|
|
338
447
|
```
|
|
339
448
|
8. **OpenAPI 文档同步**:改动 `*.openapi.controller.ts` 或其引用的 interface / service 返回值 / schema 字段时,加载 `openapi-guide` skill,同步更新 `docs/openapi.json`
|
|
340
449
|
|
|
@@ -342,9 +451,18 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
342
451
|
|
|
343
452
|
| 分层 | 目标 |
|
|
344
453
|
|------|------|
|
|
345
|
-
| service |
|
|
454
|
+
| service | 抛业务异常,**必须用 NestJS 内置 HttpException 子类**(`@nestjs/common`),禁止裸 `throw new Error`(会被全局 Filter 兜成 500,前端拿不到语义状态码) |
|
|
346
455
|
| controller | 不处理异常,交全局 Error Filter |
|
|
347
456
|
|
|
457
|
+
场景 → 异常类型映射:
|
|
458
|
+
|
|
459
|
+
| 场景 | 异常 |
|
|
460
|
+
|------|------|
|
|
461
|
+
| 资源不存在(含 update / delete 未命中) | `NotFoundException` |
|
|
462
|
+
| 参数 / 状态非法 | `BadRequestException` |
|
|
463
|
+
| 并发冲突、库存 / 余额不足 | `ConflictException` |
|
|
464
|
+
| 无权限操作他人数据 | `ForbiddenException` |
|
|
465
|
+
|
|
348
466
|
## 当前用户信息
|
|
349
467
|
|
|
350
468
|
- **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码(`const { userId } = req.userContext`)
|
|
@@ -395,15 +513,17 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
395
513
|
| 语音识别、音频转文字、STT | AI语音转文字 |
|
|
396
514
|
| 插件实例、PluginInstance、Capability | 通用插件调用 |
|
|
397
515
|
|
|
516
|
+
> ⚠️ **语义检索 / 相似推荐场景例外**:若需求是对**应用自有数据**做语义检索、向量检索、相似推荐、相关推荐、按内容找相似,**不要**用上表的「AI搜索总结 / AI文本转JSON / AI智能生文」等通用插件替代,必须召回 `/semantic-search`。上表的 AI 搜索/文本插件用于公网搜索、文本生成、结构化抽取,**不具备对自有数据库的向量检索能力**。
|
|
517
|
+
|
|
398
518
|
### 关键区分:多维表格 vs 数据库表
|
|
399
519
|
|
|
400
520
|
| 场景 | 判断 | 操作 |
|
|
401
521
|
|-----|------|-----|
|
|
402
|
-
| 明确提到"多维表格/飞书/Base/bitable" | 飞书平台外部服务 |
|
|
403
|
-
| "建表/DDL/schema变更" | 应用内数据库结构 |
|
|
522
|
+
| 明确提到"多维表格/飞书/Base/bitable" | 飞书平台外部服务 | 加载 `plugin-guide` skill |
|
|
523
|
+
| "建表/DDL/schema变更" | 应用内数据库结构 | 加载数据库操作 skill(按「应用数据库 / 建表 / 改表 / SQL」召回),按其执行通道操作 |
|
|
404
524
|
| "往XX表插入数据"(无明确来源) | 检查 `schema.ts` | 有定义→Drizzle ORM;无定义→询问用户确认 |
|
|
405
525
|
|
|
406
|
-
> 插件实例的复用/创建、`get_plugin_ai_json`、调用签名(`capabilityClient.load().call/callStream`)、配置完整性(`CapabilityNotFoundError`)、多维表格数据架构选择等**详细开发指南,见 `
|
|
526
|
+
> 插件实例的复用/创建、`get_plugin_ai_json`、调用签名(`capabilityClient.load().call/callStream`)、配置完整性(`CapabilityNotFoundError`)、多维表格数据架构选择等**详细开发指南,见 `plugin-guide` skill**(按上表关键词召回)。
|
|
407
527
|
|
|
408
528
|
## 自动化任务
|
|
409
529
|
|
|
@@ -437,7 +557,7 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
437
557
|
|
|
438
558
|
- **框架**: React 19 + TypeScript
|
|
439
559
|
- **路由**: React Router DOM v6
|
|
440
|
-
- **样式**:
|
|
560
|
+
- **样式**: tailwindcss(语义化 token)
|
|
441
561
|
- **UI 组件库**: shadcn/ui — Use components for functionality, heavily style them
|
|
442
562
|
- **图表**: ReactECharts,**开发前必须调用 `/charts-skill`**
|
|
443
563
|
- **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
|
|
@@ -452,12 +572,12 @@ async createArticle(@Req() req: Request, @Body() dto: CreateArticleDto) {
|
|
|
452
572
|
|
|
453
573
|
## API 请求
|
|
454
574
|
|
|
455
|
-
**禁止** `fetch`,必须使用 `axiosForBackend
|
|
575
|
+
**禁止** `fetch`,必须使用 `axiosForBackend`(不用会报 `Tenant not found`):
|
|
456
576
|
|
|
457
577
|
```typescript
|
|
458
578
|
import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBackend';
|
|
459
579
|
// ✅ axiosForBackend({ url: '/api/users', method: 'GET' })
|
|
460
|
-
//
|
|
580
|
+
// ✅ axiosForBackend.get('/api/users') / .post(...) 等实例方法(axiosForBackend 是 axios.create 返回的实例)
|
|
461
581
|
```
|
|
462
582
|
|
|
463
583
|
- **前后端联调**:编写前端 API 对接代码前**必须先读取后端接口定义**,禁止对后端接口请求进行兜底和过度封装
|
|
@@ -497,6 +617,14 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
497
617
|
|
|
498
618
|
禁止未调用 Skill 直接编写表单/图表/表格代码。
|
|
499
619
|
|
|
620
|
+
### 检索 Skill 召回规则(强制执行)
|
|
621
|
+
|
|
622
|
+
| 场景 | Skill | 说明 |
|
|
623
|
+
|------|-------|------|
|
|
624
|
+
| 语义检索、向量检索、相似推荐、相关推荐、相似内容、按内容找相似、猜你喜欢 | `/semantic-search` | 对应用自有数据库的数据做向量检索 / 相似召回 |
|
|
625
|
+
|
|
626
|
+
禁止未调用 Skill 直接用「AI搜索总结 / AI文本转JSON / AI智能生文」等通用插件实现对自有数据的语义检索。
|
|
627
|
+
|
|
500
628
|
### 组件使用规范
|
|
501
629
|
|
|
502
630
|
- 优先使用 `client/src/components` 下已有组件(Card/Button/Badge 等)
|
|
@@ -509,7 +637,9 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
509
637
|
|
|
510
638
|
## @lark-apaas/client-toolkit
|
|
511
639
|
|
|
512
|
-
**零假设原则**:绝不基于假设使用任何子模块。使用任何 `@lark-apaas/client-toolkit` 子模块前**必须**:① 查询 Skills 或 `steering_doc_search` → ② 等待响应获取文档 → ③ 严格按文档编码。**禁止直接调用 `@lark-apaas/client-toolkit`
|
|
640
|
+
**零假设原则**:绝不基于假设使用任何子模块。使用任何 `@lark-apaas/client-toolkit` 子模块前**必须**:① 查询 Skills 或 `steering_doc_search` → ② 等待响应获取文档 → ③ 严格按文档编码。**禁止直接调用 `@lark-apaas/client-toolkit` 任何函数/方法**(不基于查询到的文档)。该库是**前端/浏览器侧 SDK**,仅可在 `client/**`、React 组件、hooks、前端工具函数中使用;**禁止在 `server/**`、NestJS controller/service/module、Node.js runtime、migration、script 中 import 或调用**。
|
|
641
|
+
|
|
642
|
+
**服务端替代原则**:后端需要当前用户身份时用 `req.userContext`;需要飞书 user_id 转换时用 `AuthNPaasService`(见 `user-identity`);没有明确 server-side API 时只保留/透传用户 ID,不要为了展示姓名头像在服务端引入前端 SDK。
|
|
513
643
|
|
|
514
644
|
| 功能 | 导入路径 |
|
|
515
645
|
|------|----------|
|
|
@@ -531,8 +661,8 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
531
661
|
|
|
532
662
|
```
|
|
533
663
|
需要写样式?
|
|
534
|
-
├─
|
|
535
|
-
├─
|
|
664
|
+
├─ 基础布局/间距/颜色/动画/伪元素 → Tailwind ✅
|
|
665
|
+
├─ 全局/复杂 CSS → index.css / tailwind-theme.css ✅
|
|
536
666
|
└─ JS动态计算值 → 行内 style ✅
|
|
537
667
|
```
|
|
538
668
|
|
|
@@ -545,11 +675,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
545
675
|
- **arbitrary values 中空格用下划线**:`from-[hsl(215_60%_18%)]` 非 `from-[hsl(215 60% 18%)]`
|
|
546
676
|
- `tailwind-theme.css` 自定义属性用 `hsl(H, S%, L%)` 格式(非 `23 10% 23%`)
|
|
547
677
|
|
|
548
|
-
### styled-jsx 规范
|
|
549
|
-
|
|
550
|
-
- **技术栈一致性**:仅在已配置 styled-jsx 插件的项目中使用。`package.json` 无 `styled-jsx` 依赖则**禁用**,否则运行时 SyntaxError
|
|
551
|
-
- **禁止动态插值**:`<style jsx>` 内禁止 `${...}` 等表达式(会卡死)。动态值放 CSS 变量,用 `var(--xxx)` 引用
|
|
552
|
-
|
|
553
678
|
### 布局/排版
|
|
554
679
|
|
|
555
680
|
- Spacing 保持一致(small/medium/large 三级),Panel 风格统一
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: connections-sdk
|
|
3
|
+
description: "用于 Miaoda fullstack app 的 NestJS 服务端代码读取用户在妙搭开发态配置的三方集成凭证(代码概念:Miaoda Connection;读取参数:connectionName),并通过 @lark-apaas/miaoda-connections-sdk、ConnectionsService 或 MiaodaConnectionsModule 使用。不要用于前端代码、非妙搭凭证配置、妙搭自身数据库/密钥配置或创建凭证配置。触发词:妙搭凭证, 三方集成凭证, Connection 凭证, 凭证 ID, connectionName, ConnectionsService, MiaodaConnectionsModule, @lark-apaas/miaoda-connections-sdk"
|
|
4
|
+
steering: true
|
|
5
|
+
steering-topic: connections_sdk
|
|
6
|
+
match-template-name: nestjs-react-fullstack
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Miaoda Connections SDK
|
|
10
|
+
|
|
11
|
+
在 NestJS 服务端代码中使用 `@lark-apaas/miaoda-connections-sdk` 读取妙搭凭证。
|
|
12
|
+
|
|
13
|
+
## 妙搭凭证是什么
|
|
14
|
+
|
|
15
|
+
妙搭凭证是用户在妙搭开发态为三方系统配置的授权或认证资源。产品概念里也会叫三方集成凭证、凭证 ID 或 SDK 消费 key;代码里对应 `Miaoda Connection` / `Connection`,业务代码通过 `connectionName` 读取运行态凭证值。
|
|
16
|
+
|
|
17
|
+
概念和代码标识对照:
|
|
18
|
+
|
|
19
|
+
| 中文概念 | 英文/代码标识 | 说明 |
|
|
20
|
+
|----------|---------------|------|
|
|
21
|
+
| 妙搭凭证、三方集成凭证 | `Miaoda Connection` / `Connection` | 用户在妙搭开发态配置并托管的三方系统凭证资源 |
|
|
22
|
+
| 凭证 ID、凭证名、SDK 消费 key | `connectionName` | 业务代码读取凭证时传入的名称 |
|
|
23
|
+
| 运行态凭证值 | `connection.value` / `ConnectionValue` | SDK 返回的类型化凭证数据 |
|
|
24
|
+
| 原始认证输入 | `authInput` | 创建凭证时的认证输入配置;可能包含敏感字段 |
|
|
25
|
+
| 自定义凭证数据 | `authData` | `custom` 或未知类型里的运行态字段 |
|
|
26
|
+
|
|
27
|
+
SDK 支持读取这些三方认证类型:
|
|
28
|
+
|
|
29
|
+
| 三方认证类型 | `connection.value.type` | 说明 |
|
|
30
|
+
|--------------|-------------------------|------|
|
|
31
|
+
| OAuth2 三方授权 | `oauth2` | 三方系统的 OAuth token,例如 OAuth Code 或 Client Credentials 建立后的访问凭证 |
|
|
32
|
+
| 三方接口密钥 | `api_key` | 三方 API 的 API Key 值;请求头名或 query 参数名来自三方 API 文档或 `authInput` 配置 |
|
|
33
|
+
| HTTP Bearer Token | `bearer_token` | 三方 HTTP API 的 Bearer token |
|
|
34
|
+
| Basic Auth | `basic` | 三方系统的用户名和密码 |
|
|
35
|
+
| 外部关系型数据库凭证 | `rdb` | 外部数据库的连接凭证,不是妙搭应用自身默认数据库 |
|
|
36
|
+
| 自定义或未知类型 | `custom` | SDK 未内置识别的类型会归到 `custom`,业务按实际字段读取 `authData` |
|
|
37
|
+
|
|
38
|
+
妙搭凭证不是:
|
|
39
|
+
|
|
40
|
+
- 妙搭应用自身的数据表或默认数据库连接。
|
|
41
|
+
- 需要写入 `.env` 的环境变量配置。
|
|
42
|
+
- 业务代码里临时声明的 token、api key、password。
|
|
43
|
+
- Authentication、ConnectionPolicy 或授权流程本身。
|
|
44
|
+
|
|
45
|
+
## 使用边界
|
|
46
|
+
|
|
47
|
+
| 场景 | 做法 |
|
|
48
|
+
|------|------|
|
|
49
|
+
| 服务端调用三方 API 或外部数据库,凭证来自用户已配置的妙搭凭证 | 使用本 SDK |
|
|
50
|
+
| 前端页面需要调用三方 API | 通过后端接口转发,前端不得读取妙搭凭证 |
|
|
51
|
+
| 用户还没创建妙搭凭证 | 提醒用户先在妙搭开发态配置凭证 |
|
|
52
|
+
| 需要创建 Authentication 或授权流程 | 不在业务代码里实现,交给妙搭开发态配置能力 |
|
|
53
|
+
| 只是配置妙搭应用自己的数据库、环境变量或专家模式密钥 | 不使用本 SDK |
|
|
54
|
+
|
|
55
|
+
禁止把 access token、bearer token、api key、password 写入 `.env`、源码、日志或聊天回复。
|
|
56
|
+
|
|
57
|
+
## 服务端接入
|
|
58
|
+
|
|
59
|
+
在应用模块中注册 `MiaodaConnectionsModule`,然后在业务 Service 中注入 `ConnectionsService` 使用。
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Injectable, Module } from '@nestjs/common';
|
|
63
|
+
import {
|
|
64
|
+
ConnectionsService,
|
|
65
|
+
MiaodaConnectionsModule,
|
|
66
|
+
} from '@lark-apaas/miaoda-connections-sdk';
|
|
67
|
+
|
|
68
|
+
@Module({
|
|
69
|
+
imports: [MiaodaConnectionsModule.forRoot()],
|
|
70
|
+
})
|
|
71
|
+
export class AppModule {}
|
|
72
|
+
|
|
73
|
+
@Injectable()
|
|
74
|
+
export class GithubService {
|
|
75
|
+
constructor(private readonly connections: ConnectionsService) {}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 获取妙搭凭证
|
|
80
|
+
|
|
81
|
+
使用用户在妙搭开发态配置好的凭证 ID / 凭证名作为 `connectionName`。不要在代码中创建或修改凭证配置。
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const connection = await this.connections.getConnection({
|
|
85
|
+
connectionName: 'github-main',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (connection.state !== 'connected') {
|
|
89
|
+
throw new Error(`Connection github-main requires ${connection.recoveryHint}`);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
必须先判断 `connection.state === 'connected'`,再读取 `connection.value`。
|
|
94
|
+
|
|
95
|
+
## 按凭证类型处理
|
|
96
|
+
|
|
97
|
+
不要假设所有妙搭凭证都是 OAuth2。必须按 `connection.value.type` 分支处理。SDK 内置识别 `oauth2`、`api_key`、`bearer_token`、`basic`、`rdb`、`custom`;服务端新增但 SDK 尚未内置的类型会归一化为 `custom`。所有类型都有 `authInput`,表示原始认证输入;只能按业务需要读取,不要写入日志或错误信息。
|
|
98
|
+
|
|
99
|
+
### OAuth2 三方授权
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
if (connection.value.type !== 'oauth2') {
|
|
103
|
+
throw new Error('github-main must use oauth2 connection');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const response = await fetch('https://api.github.com/user/repos', {
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `Bearer ${connection.value.accessToken}`,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 三方接口密钥
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
if (connection.value.type !== 'api_key') {
|
|
117
|
+
throw new Error('service-main must use api_key connection');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const response = await fetch('https://api.example.com/resources', {
|
|
121
|
+
headers: {
|
|
122
|
+
'x-api-key': connection.value.apiKey,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`api_key` 只保证返回三方接口密钥值 `apiKey`。header 名、query 参数名等组装方式来自三方 API 文档或业务约定;如后端在 `authInput` 中透传了相关配置,也要先按具体字段断言后再使用。这里的三方接口密钥不是妙搭应用自身的环境变量、专家模式密钥或平台访问密钥。
|
|
128
|
+
|
|
129
|
+
### HTTP Bearer Token
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
if (connection.value.type !== 'bearer_token') {
|
|
133
|
+
throw new Error('partner-main must use bearer_token connection');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const response = await fetch('https://partner.example.com/resources', {
|
|
137
|
+
headers: {
|
|
138
|
+
Authorization: `Bearer ${connection.value.token}`,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Basic Auth
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
if (connection.value.type !== 'basic') {
|
|
147
|
+
throw new Error('erp-basic must use basic connection');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const authorization = Buffer.from(
|
|
151
|
+
`${connection.value.userID}:${connection.value.password}`
|
|
152
|
+
).toString('base64');
|
|
153
|
+
|
|
154
|
+
const response = await fetch('https://erp.example.com/orders', {
|
|
155
|
+
headers: {
|
|
156
|
+
Authorization: `Basic ${authorization}`,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 外部关系型数据库凭证
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
if (connection.value.type !== 'rdb') {
|
|
165
|
+
throw new Error('sales-db must use rdb connection');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const config = {
|
|
169
|
+
host: connection.value.host,
|
|
170
|
+
port: connection.value.port,
|
|
171
|
+
database: connection.value.database,
|
|
172
|
+
user: connection.value.username,
|
|
173
|
+
password: connection.value.password,
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
RDB 表示外部关系型数据库的连接凭证,不是妙搭应用自身默认数据库连接。RDB 的 `advanced` 用于特殊数据库类型的额外连接参数,例如 Oracle 的 `serviceName` 和驱动侧识别用的 `driverType`。Oracle SID 复用已有 `database` 字段,不要额外读取或生成 `advanced.sid`。
|
|
178
|
+
|
|
179
|
+
### Custom / 未知类型
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
if (connection.value.type === 'custom') {
|
|
183
|
+
const authData = connection.value.authData as {
|
|
184
|
+
server: string;
|
|
185
|
+
username: string;
|
|
186
|
+
password: string;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return createMailClient(authData);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
当服务端返回 SDK 尚不认识的 `ConnectionValue.type` 时,SDK 会把它归类为 `custom`:`authData` 保留原始 value 中除 `type` 和 `authInput` 外的运行态字段,`authInput` 保留在顶层。生成代码时可以基于 `authData` 的实际字段分支,但不要在日志或错误里输出 `authData` 或 `authInput` 明文。
|
|
194
|
+
|
|
195
|
+
## 不要做的事
|
|
196
|
+
|
|
197
|
+
- 不要在前端代码里调用 Connections SDK。
|
|
198
|
+
- 不要让用户把 token、api key、password 填到 `.env`。
|
|
199
|
+
- 不要在日志或错误里输出明文凭证值、authInput 或 authData。
|
|
200
|
+
- 不要手写平台内部 API 的鉴权 header。
|
|
201
|
+
- 不要在业务代码里直接请求平台内部接口或手写 SDK transport。
|
|
202
|
+
- 首版不要传 `user` 参数;等 end user policy 支持后再扩展用户态 Connection 示例。
|