@lark-apaas/coding-steering 0.1.5 → 0.1.6-alpha.0
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 +8 -3
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +122 -0
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +139 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +628 -0
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +246 -0
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +707 -0
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +270 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +214 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +163 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +309 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +190 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +160 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +256 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +103 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +198 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +128 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +207 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +164 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +90 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +164 -0
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +267 -0
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +452 -0
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +300 -0
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/coding-steering",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6-alpha.0",
|
|
4
4
|
"description": "Stack-specific steering content for miaoda-coding templates",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"files": [
|
|
6
|
+
"files": [
|
|
7
|
+
"steering"
|
|
8
|
+
],
|
|
7
9
|
"scripts": {
|
|
8
10
|
"lint:md": "markdownlint 'steering/**/*.md'"
|
|
9
11
|
},
|
|
@@ -14,6 +16,9 @@
|
|
|
14
16
|
"access": "public",
|
|
15
17
|
"registry": "https://registry.npmjs.org/"
|
|
16
18
|
},
|
|
17
|
-
"keywords": [
|
|
19
|
+
"keywords": [
|
|
20
|
+
"miaoda",
|
|
21
|
+
"coding-steering"
|
|
22
|
+
],
|
|
18
23
|
"license": "MIT"
|
|
19
24
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authn-guide
|
|
3
|
+
description: "Use when implementing authentication features: @NeedLogin decorator (AuthN is opt-in, public interfaces need no decorator), AuthNPaasGuard flow, or handling 401 errors. 触发词:认证, authn, 身份验证, 登录检查, NeedLogin, Public, 公开接口, AuthNPaasGuard, x-login-url, 401, 未授权"
|
|
4
|
+
steering: true
|
|
5
|
+
steering-topic: authn_guide
|
|
6
|
+
match-template-name: nestjs-react-fullstack
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 身份认证(AuthN)编码指南
|
|
10
|
+
|
|
11
|
+
本 skill 专注于**接口认证**:装饰器、守卫流程、401 处理。
|
|
12
|
+
|
|
13
|
+
**用户身份**(`req.userContext` 字段定义、`useCurrentUserProfile`、`AuthNPaasService`、`lark_user_id`、妙搭 ↔ 飞书 ID 转换)请使用 [`user-identity`](../user-identity/SKILL.md) skill。本文也会在认证流程里出现 `req.userContext`,那是守卫语义,**字段含义**仍以 user-identity 为准。
|
|
14
|
+
|
|
15
|
+
## 一、功能决策树
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
用户需求
|
|
19
|
+
│
|
|
20
|
+
├─ 需要标记某些接口必须登录才能访问?
|
|
21
|
+
│ └─ 是 ──→ 使用 @NeedLogin() 装饰器(第二节)
|
|
22
|
+
│
|
|
23
|
+
├─ 需要让某些接口公开访问?
|
|
24
|
+
│ └─ 不需要任何装饰器 ──→ AuthNPaasGuard 默认放行未标 @NeedLogin() 的接口
|
|
25
|
+
│ (`@Public()` 是历史遗留 no-op,不要使用,详见第二节)
|
|
26
|
+
│
|
|
27
|
+
├─ 接口返回 401 / 登录跳转不对?
|
|
28
|
+
│ └─ 是 ──→ 第四节 · 401 未授权
|
|
29
|
+
│
|
|
30
|
+
└─ 想读取当前用户 ID / 角色 / 飞书 ID?
|
|
31
|
+
└─ 这属于"用户身份"范畴 ──→ 跳到 `user-identity` skill
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> **相关技能**:用户身份/ID 转换/userContext 字段参见 `user-identity`;登录/登出/获取用户信息的 Dataloom SDK 操作参见 `client-builtins-user-service`;权限点位鉴权参见 `authz-guide`。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 二、认证装饰器
|
|
39
|
+
|
|
40
|
+
### 模块注册
|
|
41
|
+
|
|
42
|
+
`AuthNPaasModule` 已通过 `PlatformModule.forRoot()` 自动注册,**无需手动导入**。模块全局生效,自动注册 `AuthNPaasGuard` 守卫。
|
|
43
|
+
|
|
44
|
+
### @NeedLogin()
|
|
45
|
+
|
|
46
|
+
标记接口需要登录。未登录用户访问时,守卫设置 `x-login-url` 响应头并返回 401。
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { Controller, Get } from '@nestjs/common';
|
|
50
|
+
import { NeedLogin } from '@lark-apaas/fullstack-nestjs-core';
|
|
51
|
+
|
|
52
|
+
// 控制器级别 — 所有路由都需要登录
|
|
53
|
+
@Controller('api/dashboard')
|
|
54
|
+
@NeedLogin()
|
|
55
|
+
export class DashboardController {
|
|
56
|
+
@Get()
|
|
57
|
+
getDashboard() { return '仪表板'; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 方法级别 — 仅特定路由需要登录
|
|
61
|
+
@Controller('api')
|
|
62
|
+
export class ApiController {
|
|
63
|
+
@Get('public-data')
|
|
64
|
+
getPublicData() { return '公开数据'; }
|
|
65
|
+
|
|
66
|
+
@Get('private-data')
|
|
67
|
+
@NeedLogin()
|
|
68
|
+
getPrivateData() { return '私密数据'; }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### ⚠️ @Public() 是 no-op,不要使用
|
|
73
|
+
|
|
74
|
+
`@lark-apaas/fullstack-nestjs-core` 仍然 export 了 `@Public()`,但 `AuthNPaasGuard` / `AuthZPaasGuard` 都**不读** `IS_PUBLIC_KEY`,整段是历史遗留死代码。Guard 默认 opt-in:**没标 `@NeedLogin()` 的接口直接放行**——这意味着公开接口本身就不需要任何装饰器。如果项目里有现存的 `@Public()` 用法,可以删掉。
|
|
75
|
+
|
|
76
|
+
### 认证流程
|
|
77
|
+
|
|
78
|
+
`AuthNPaasGuard` 是 **opt-in 模式**:未标记 `@NeedLogin()` 的接口默认放行,**公开接口无需任何装饰器**。
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
请求 → Gateway 注入 x-larkgw-suda-webuser 头
|
|
82
|
+
→ UserContextMiddleware 解析 → req.userContext { userId, tenantId, appId, loginUrl, ... }
|
|
83
|
+
→ AuthNPaasGuard 检查:
|
|
84
|
+
有 @NeedLogin() 且无 userId → 设置 x-login-url + 401
|
|
85
|
+
其他(含未标记的接口)→ 放行
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> `req.userContext` 的完整字段表(`userId` / `tenantId` / `roles` / `userName` 等)见 `user-identity` skill 第二节。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 三、禁止行为清单
|
|
93
|
+
|
|
94
|
+
| 禁止行为 | 正确做法 |
|
|
95
|
+
|---------|---------|
|
|
96
|
+
| 单独注册 `AuthNPaasModule.forRoot()` | 已通过 `PlatformModule.forRoot()` 自动注册,无需手动导入 |
|
|
97
|
+
| 手动实例化 `AuthNPaasGuard` | 模块自动注册为全局守卫,无需手动 `@UseGuards()` |
|
|
98
|
+
| 用 `@Public()` 标记公开接口 | `@Public()` 是 no-op;公开接口不需要任何装饰器(opt-in 模式默认放行未标 `@NeedLogin()` 的接口) |
|
|
99
|
+
| 在未标 `@NeedLogin()` 的接口里依赖 `req.userContext.userId` 做业务判断 | 该接口默认放行未登录请求,`userId` 可能为 `undefined`,必须先判空 |
|
|
100
|
+
| 业务层自行返回 401 实现"未登录跳转" | 用 `@NeedLogin()` 让守卫统一处理,确保 `x-login-url` 头被正确写入 |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 四、常见问题
|
|
105
|
+
|
|
106
|
+
### 401 未授权
|
|
107
|
+
|
|
108
|
+
1. 确认接口是否标记了 `@NeedLogin()`
|
|
109
|
+
2. 检查请求是否携带了正确的 Cookie(`credentials: 'include'`)
|
|
110
|
+
3. 确认 Gateway 是否正确注入了 `x-larkgw-suda-webuser` 头
|
|
111
|
+
4. 确认响应头是否包含 `x-login-url`,前端拦截器需读取此头执行跳转
|
|
112
|
+
|
|
113
|
+
### 登录跳转 URL 错误
|
|
114
|
+
|
|
115
|
+
1. 检查 `req.userContext.loginUrl` 是否被中间件正确解析
|
|
116
|
+
2. 确认应用部署环境(preview / online)对应的网关配置
|
|
117
|
+
|
|
118
|
+
### 公开接口被错误地要求登录
|
|
119
|
+
|
|
120
|
+
1. 检查 Controller 或父级是否打了 `@NeedLogin()`(继承到本方法);按 opt-in 默认行为,**移除装饰器**接口就会公开
|
|
121
|
+
2. **不要**用 `@Public()` 试图取消登录要求——它是 no-op,对 guard 无影响
|
|
122
|
+
3. 检查是否有自定义 Guard 比 `AuthNPaasGuard` 更早执行并主动拦了未登录请求
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: client-add-aily-web-chat
|
|
3
|
+
description: Use when integrating Aily AI chat panel into a web app using @lark-apaas/client-toolkit/aily-chat. 触发词:Aily 聊天, Aily 对话面板, Aily chat, aily-chat SDK, AI 对话组件集成
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 添加 Aily Web 对话面板
|
|
7
|
+
|
|
8
|
+
使用 `@lark-apaas/client-toolkit/aily-chat` 将 Aily 对话面板集成到 Web 应用。SDK 与框架无关(纯 DOM 操作)。
|
|
9
|
+
|
|
10
|
+
## Quick Reference
|
|
11
|
+
|
|
12
|
+
| 操作 | 方法 |
|
|
13
|
+
|------|------|
|
|
14
|
+
| 初始化 | `await initAilyChat(container, config)` |
|
|
15
|
+
| 发送消息 | `chatPanel.sendMessage({ content, skillID? })` |
|
|
16
|
+
| 清空记录 | `chatPanel.clear()` |
|
|
17
|
+
| 清空并停止 | `chatPanel.clearAndStop()` |
|
|
18
|
+
| 更新配置 | `chatPanel.updateConfig(partialConfig)` |
|
|
19
|
+
| 销毁 | `chatPanel.destroy()` |
|
|
20
|
+
|
|
21
|
+
## 核心流程
|
|
22
|
+
|
|
23
|
+
### 1. 向用户获取 appKey
|
|
24
|
+
|
|
25
|
+
实现前必须先询问用户:
|
|
26
|
+
|
|
27
|
+
> "请提供你的 Aily `appKey` 以配置对话组件。你可以在 [Aily 平台](https://apaas.feishu.cn/ai/projects) 中找到对应应用的 appKey。"
|
|
28
|
+
|
|
29
|
+
### 2. 集成代码
|
|
30
|
+
|
|
31
|
+
React 组件示例(vanilla JS 同理,对 DOM 元素调用 `initAilyChat` 即可):
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { useEffect, useRef } from "react";
|
|
35
|
+
import { initAilyChat, type ChatPanel } from "@lark-apaas/client-toolkit/aily-chat";
|
|
36
|
+
|
|
37
|
+
interface AilyChatProps {
|
|
38
|
+
appKey: string;
|
|
39
|
+
anonymous?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const containerStyle = { width: "100%", height: "500px" };
|
|
43
|
+
|
|
44
|
+
const AilyChat = ({ appKey, anonymous }: AilyChatProps) => {
|
|
45
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
46
|
+
const chatPanelRef = useRef<ChatPanel | null>(null);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!containerRef.current) return;
|
|
50
|
+
let destroyed = false;
|
|
51
|
+
|
|
52
|
+
initAilyChat(containerRef.current, {
|
|
53
|
+
appKey,
|
|
54
|
+
anonymous,
|
|
55
|
+
conversion: { needAvatar: true },
|
|
56
|
+
events: {
|
|
57
|
+
onReady: () => console.log("对话就绪"),
|
|
58
|
+
onError: (err) => console.error("对话错误", err),
|
|
59
|
+
},
|
|
60
|
+
}).then((panel) => {
|
|
61
|
+
if (destroyed) panel.destroy();
|
|
62
|
+
else chatPanelRef.current = panel;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
destroyed = true;
|
|
67
|
+
chatPanelRef.current?.destroy();
|
|
68
|
+
chatPanelRef.current = null;
|
|
69
|
+
};
|
|
70
|
+
}, [appKey, anonymous]);
|
|
71
|
+
|
|
72
|
+
return <div ref={containerRef} style={containerStyle} />;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default AilyChat;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. 告知用户 UI 定制选项
|
|
79
|
+
|
|
80
|
+
代码集成完成后,**必须**向用户展示以下可定制选项:
|
|
81
|
+
|
|
82
|
+
- 修改组件标题/顶部标题
|
|
83
|
+
- 关闭/开启组件标题栏
|
|
84
|
+
- 显示/隐藏关闭按钮
|
|
85
|
+
- 隐藏/展示用户头像、隐藏/展示系统头像
|
|
86
|
+
- 修改用户头像或系统头像,并发送新头像图片
|
|
87
|
+
- 隐藏/展示组件欢迎语
|
|
88
|
+
|
|
89
|
+
## 配置参考
|
|
90
|
+
|
|
91
|
+
### WebSDKConfig(顶层)
|
|
92
|
+
|
|
93
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
94
|
+
|------|------|------|------|
|
|
95
|
+
| `appKey` | string | 是 | 应用标识 |
|
|
96
|
+
| `uuid` | string | 否 | 用户标识,启用 `needCache` 时必填 |
|
|
97
|
+
| `anonymous` | boolean | 否 | `true` 开启匿名模式,跳过登录。**须在 Aily 应用「渠道管理」中将 SDK 模式开启匿名访问,否则会报错「匿名SDK没有开启支持使用应用身份调用API和SDK」** |
|
|
98
|
+
| `conversion` | object | 否 | 对话配置(头像、缓存、欢迎语) |
|
|
99
|
+
| `editor` | object | 否 | 顶部栏配置(标题、关闭按钮) |
|
|
100
|
+
| `events` | object | 否 | 事件回调 |
|
|
101
|
+
|
|
102
|
+
### conversion
|
|
103
|
+
|
|
104
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
105
|
+
|------|------|--------|------|
|
|
106
|
+
| `needCache` | boolean | `false` | 启用消息缓存(需同时提供 `uuid`) |
|
|
107
|
+
| `storageType` | string | `"memory"` | `memory` / `sessionStorage` / `localStorage` / `indexDB` / `server` |
|
|
108
|
+
|
|
109
|
+
> 头像和欢迎语相关字段见上方「UI 定制选项」表。
|
|
110
|
+
|
|
111
|
+
### editor
|
|
112
|
+
|
|
113
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
114
|
+
|------|------|--------|------|
|
|
115
|
+
| `display` | boolean | `true` | 是否显示编辑器 |
|
|
116
|
+
| `needHeader` | boolean | `true` | 是否显示顶部 header(含标题和新建对话按钮),仅 V2 生效 |
|
|
117
|
+
| `headerTitle` | string | 应用名称 | 对话标题,显示在 header 中 |
|
|
118
|
+
| `needCloseButton` | boolean | `false` | 是否显示关闭按钮。配合 `events.onClose` 监听点击 |
|
|
119
|
+
|
|
120
|
+
### events
|
|
121
|
+
|
|
122
|
+
| 事件 | 签名 | 说明 |
|
|
123
|
+
|------|------|------|
|
|
124
|
+
| `onReady` | `() => void` | 对话就绪 |
|
|
125
|
+
| `onError` | `(error: Error) => void` | 发生错误 |
|
|
126
|
+
| `onMessage` | `(message: unknown) => void` | 收到消息 |
|
|
127
|
+
| `onInited` | `(data: { sessionId, conversationId, handler }) => void` | 会话已创建 |
|
|
128
|
+
| `onInitedWithWelcome` | `() => void` | 欢迎语已创建 |
|
|
129
|
+
| `onClose` | `() => void` | 用户点击关闭按钮时触发(需配合 `editor.needCloseButton: true`) |
|
|
130
|
+
|
|
131
|
+
## Common Mistakes
|
|
132
|
+
|
|
133
|
+
| 错误 | 正确做法 |
|
|
134
|
+
|------|----------|
|
|
135
|
+
| 忘记在 useEffect 清理时调用 `destroy()` | 必须在 cleanup 中销毁 ChatPanel,否则内存泄漏 |
|
|
136
|
+
| 启用 `needCache` 但未传 `uuid` | 缓存依赖 `uuid` 识别用户,缺失则无法恢复历史消息 |
|
|
137
|
+
| 容器元素无固定宽高 | 容器必须有明确尺寸,否则面板不可见 |
|
|
138
|
+
| 在 React 严格模式下未处理重复初始化 | 用 `destroyed` 标志防止 StrictMode 双重 mount 导致重复面板 |
|
|
139
|
+
| 设置 `anonymous: true` 但未在 Aily 平台开启匿名访问 | 须前往 Aily 应用「渠道管理」将 SDK 模式开启匿名访问,否则报错「匿名SDK没有开启支持使用应用身份调用API和SDK」 |
|