Files
MyAiDesk/nomifun-tauri/docs/feishu-integration-plan.md
T
freedak f7a720204a Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
2026-07-04 19:20:46 +08:00

1178 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# NomiFun × 飞书 — 企业工作台集成落地方案
> 文档版本:v3.4
> 日期:2026-07-02
> 状态:方案评估(基于代码库深度验证 + 飞书 API 文档验证)
> 变更:v3.0 — 审批流程改为人工执行(AI 仅辅助查询,人在工作台 UI 操作);新增企业经营看板模块
> 变更:v3.1 — 落地模式优化:审批 UI 采用混合模式(原生列表/详情 + 飞书 JS SDK 表单);经营看板独立页面优先(打开即看)+ AI 对话深度查询;分阶段交付,首个可用版本提前至 10-16 天
> 变更:v3.2 — 代码库二次验证修正:recharts 尚未安装(需新增依赖);前端路径修正为 `ui/src/renderer/pages/`;新增 Tauri 深链配置、飞书 JS SDK SSO 流程、数据表 Schema、错误降级策略、测试策略;澄清卡片按钮机制与 CallerCtx 角色映射链路;修正飞书卡片 chart 工作量评估
> 变更:v3.3 — **关键可行性修正**:飞书 H5 JS SDK 仅在飞书客户端内可用,Tauri webview 不可用;审批写操作是服务端 REST API 非 JS SDK 方法;OAuth redirect_uri 改为本地 HTTP 回调方案;审批 UI 回归原生模式(React + Arco Form 动态渲染);工作量相应调整
> 变更:v3.4 — **审批方案简化**:改用飞书 AppLink 跳转模式,NomiFun 只做查询/展示,审批写操作跳转飞书客户端完成。消除 OAuth/动态表单/后端写代理三大复杂度,Phase 1b 从 10-15 天降至 5-7 天。新增 `tauri-plugin-opener` 依赖
---
## 一、方案总览
### 1.1 核心命题
组织架构、审批流程、文档知识库全部托管在飞书平台,企业经营数据由数据中台加工完成。NomiFun 作为统一工作台,通过以下能力整合调用:
- **消息通道**:飞书 Bot 作为 NomiFun 的统一交互入口(自然语言 → AI 理解 → 工具调用)
- **MCP 工具总线**:将飞书通讯录、审批查询、数据中台 API 封装为标准 MCP 工具,AI Agent 自动编排调用
- **审批辅助(非执行)**:AI 仅查询审批状态、辅助填表预览;**审批的创建、同意、拒绝通过 AppLink 跳转飞书客户端由人完成,AI 不执行任何审批动作**
- **审批 UI AppLink 跳转模式**:列表/详情用原生 React 组件(轻量、UX 一致),写操作通过 AppLink 跳转飞书客户端完成(复用飞书原生表单引擎,零维护)
- **知识库同步**:飞书 Wiki 空间文档增量同步为本地 Markdown 快照,供 AI 检索
- **经营看板**:从数据中台获取已加工的经营指标,按用户角色/部门展示差异化看板。**独立看板页面**(打开即看)+ **AI 自然语言对话**(深度查询/下钻/对比)双模式
- **飞书卡片即看板**:经营数据查询结果也可通过飞书卡片 chart 组件渲染,在聊天中直接查看
- **Webhook 通知**:任务完成/审批状态变更通过飞书自定义机器人卡片推送到群/个人
### 1.2 架构全景图
```
┌──────────────────────────────────────────────────────────────┐
│ 飞书 (Lark) │
│ 通讯录 │ 审批流程 │ Wiki 文档 │ 消息/卡片 │ 事件订阅 │
└────┬─────────┬──────────┬──────────┬──────────┬───────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ NomiFun Rust Backend │
│ │
│ ┌────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Lark │ │ Feishu │ │ MCP 工具总线 │ │
│ │ Channel │ │ Connector │ │ (Registry) │ │
│ │ Plugin │ │ (知识库) │ │ │ │
│ │ │ │ │ │ 已有 132+ 工具 │ │
│ │ WebSocket │ │ 增量同步 │ │ + 新增飞书域: │ │
│ │ 事件接收 │ │ 快照→MD │ │ - feishu_org_* │ │
│ │ 消息发送 │ │ │ │ - feishu_appr_* │ │
│ │ 卡片交互 │ │ │ │ - dashboard_* │ │
│ └─────┬──────┘ └──────┬──────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ┌─────▼──────────────────────────────────▼─────────────┐ │
│ │ AI Agent Engine (nomi) │ │
│ │ 自然语言理解 → 工具选择 → 执行 → 结果整合 │ │
│ │ 工具审批门控 (DangerTier × Surface) │ │
│ │ ⚠ 审批操作不经过 AI — 仅查询/辅助,人在飞书客户端操作 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 数据中台对接 (caps_dashboard.rs) │ │
│ │ 外部 API ← 已加工经营指标 · 角色化数据过滤 │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Webhook 通知服务 │ │
│ │ 飞书自定义机器人卡片 + HMAC-SHA256 签名 │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 审批工作台 UI (AppLink 跳转模式) │ │
│ │ 原生: 列表/详情/时间线 (React + Arco) │ │
│ │ 写操作: AppLink 跳转飞书客户端 (tauri-opener) │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 经营看板前端 (React + Arco Design + recharts) │ │
│ │ 角色化看板页面 · 指标卡片 · 趋势图表 │ │
│ │ ⚠ recharts 需新增依赖(当前未安装) │ │
│ │ 也通过飞书 Bot 卡片 chart 组件投递 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
---
## 二、现有基础设施评估(代码级验证)
### 2.1 ✅ 飞书消息通道 — 已完整实现
**文件**: `crates/backend/nomifun-channel/src/plugins/lark/plugin.rs`
| 能力 | 状态 | 实现位置 | 说明 |
|------|------|----------|------|
| WebSocket 长连接 | ✅ 已实现 | `start()` 方法 | 通过飞书 SDK 获取 WS endpoint,自动重连 |
| 消息接收 | ✅ 已实现 | `handle_message_event()` | 解析 `im.message.receive_v1` 事件,转换为 `UnifiedIncomingMessage` |
| 事件去重 | ✅ 已实现 | `event_id` 去重窗口 | 防止飞书重推导致重复处理 |
| 文本/富文本发送 | ✅ 已实现 | `send_message()` | 支持纯文本和富文本 |
| 交互卡片发送 | ✅ 已实现 | `send_card()` | 调用 `api.rs` 的卡片消息接口 |
| 卡片更新 | ✅ 已实现 | `update_card()` | 用于审批状态变更后刷新卡片 |
| 卡片按钮回调 | ✅ 已实现 | `card_action_trigger` 事件处理 | 用户点击卡片按钮 → 回调路由 |
| Bot 菜单事件 | ✅ 已实现 | `bot_menu_event` 处理 | 飞书机器人菜单点击 |
| 用户配对授权 | ✅ 已实现 | `ActionExecutor::handle_incoming_message()` | 飞书 open_id → 内部 user_id 映射 |
**消息路由链路(已验证)**:
```
飞书 WebSocket 事件
→ LarkPlugin::handle_message_event() // 解析为 UnifiedIncomingMessage
→ mpsc::Sender → ChannelManager forwarder // 加盖 channel_id
→ ChannelOrchestrator::handle_message() // 异步 spawn
→ ActionExecutor::handle_incoming_message() // 授权检查 + 路由
→ MessageResult::Dispatched // 文本消息 → AI 会话
→ MessageResult::Action // 按钮回调 → 动作执行
→ ChannelMessageService → AI Agent 引擎 // 进入 nomi agent 工具调用循环
```
**关键发现**: 消息从飞书到 AI Agent 的完整链路已经打通,无需新增代码。配置飞书自建应用即可使用。
### 2.2 ✅ 飞书知识库 Connector — 已完整实现
**文件**: `crates/backend/nomifun-knowledge/src/connector_feishu.rs`
| 能力 | 状态 | 实现细节 |
|------|------|----------|
| Token 管理 | ✅ | `tenant_access_token` 获取 + 30min 缓存 + 401 自动刷新 |
| 限流 | ✅ | 4 QPS (250ms 间隔) + 429 重试 (`x-ogw-ratelimit-reset`) |
| Wiki 空间节点列表 | ✅ | `/open-apis/wiki/v2/spaces/{space_id}/nodes` 分页拉取 |
| 增量同步 | ✅ | `SyncCursor.last_sync_at` 游标,跳过未修改文档 |
| 文档块 → Markdown | ✅ | `feishu_md::blocks_to_markdown()` 转换 |
| 快照持久化 | ✅ | 写入 `snapshots/` 目录,YAML frontmatter 含 `source_url` |
| 凭证验证 | ✅ | `validate_credentials()` → 获取 token 验证有效性 |
| 测试覆盖 | ✅ | wiremock 集成测试 8 个用例(token 缓存/分页/增量/错误码) |
**Connector trait 架构**:
```rust
// crates/backend/nomifun-knowledge/src/connector.rs
trait KnowledgeConnector {
fn kind(&self) -> &'static str; // "feishu"
async fn validate_credentials(...) -> ConnectorIdentity;
async fn list_documents(...) -> SyncPage; // 分页 + 增量
async fn fetch_document(...) -> FetchedConnectorDoc; // blocks → markdown
async fn subscribe_webhook(...) -> Option<...>; // 预留(当前 poll-only
async fn push_document(...) -> Result<...>; // 预留(未来双向同步)
}
```
**同步编排(已验证)**: `KnowledgeService::sync_connector_source()` 执行两阶段同步:
1. **Phase 1**: 分页 `list_documents`,收集文档引用 + 墓碑(删除标记)
2. **Phase 2**: 逐个 `fetch_document` → 压缩 → 写入快照文件
**限制**: 当前仅同步 `docx` 类型文档(`obj_type == "docx"` 过滤),不支持 Sheet/Bitable/Mindnote。
### 2.3 ✅ MCP 工具总线 — 已完整实现
**文件**: `crates/backend/nomifun-gateway/src/registry/`
**工具注册体系**:
```
Registry::build() → 逐域 register()
├── caps_memory (记忆)
├── caps_conversation (会话管理)
├── caps_requirement (需求管理)
├── caps_autowork (自动化工作)
├── caps_knowledge (知识库搜索)
├── caps_channel (IM 通道管理)
├── caps_agent (Agent 委托)
├── caps_terminal (终端)
├── caps_files (文件)
├── caps_mcp (MCP 管理)
├── ... 共 18 个域, 132+ 个工具
├── caps_feishu_org (待新增) ← 飞书组织架构 (Read)
├── caps_feishu_approval (待新增) ← 飞书审批查询 (Read only, 人在飞书客户端操作)
└── caps_dashboard (待新增) ← 经营看板 (Read, 对接数据中台)
```
**权限矩阵(已验证,关键约束)**:
| Surface | Read | Write | Destructive | Sensitive |
|---------|------|-------|-------------|-----------|
| Desktop | Allow | Allow | Confirm | Confirm |
| **Channel** | **Allow** | **Allow** | **Deny** | **Deny** |
| Remote | Allow | Allow | Confirm | Deny |
**⚠️ 关键发现**: 飞书消息触发的 AI 会话走 `Surface::Channel`。在该 Surface 下:
- `Read` 级别工具 **自动放行** — 适合审批查询、组织架构查询、经营看板查询
- `Write` 级别工具 **自动放行** — 但审批操作不封装为工具,避免 AI 越权
- `Destructive``Sensitive` 级别工具 **硬拒绝** — 即使 confirm=true 也不行
- 可通过 `deny_on` / `confirm_on` 覆盖特定 Surface 行为
**工具定义模式(已验证)**:
```rust
// 新增工具只需:
// 1. 创建 caps_feishu.rs,定义参数 struct + handler
// 2. 在 lib.rs 添加 mod caps_feishu;
// 3. 在 Registry::build() 添加 crate::caps_feishu::register(&mut caps);
Capability::new(
CapabilityMeta::new(
"nomi_feishu_org_search", // 工具名,≤42 字符
"feishu", // 域标签
"Search employees in Feishu contacts", // LLM 可见描述
DangerTier::Read, // 读操作 → Channel Surface 放行
),
|deps, ctx, params| async move {
// handler 逻辑
json!({ "results": [...] })
},
)
```
### 2.4 ✅ Webhook 通知 — 已完整实现
**文件**: `crates/backend/nomifun-webhook/src/sender.rs`
| 能力 | 状态 | 说明 |
|------|------|------|
| 飞书自定义机器人卡片 | ✅ | 支持 interactive card JSON payload |
| HMAC-SHA256 签名 | ✅ | 按飞书规范签名,timestamp + key |
| Slack 格式 | ✅ | 兼容 |
| 通用 HTTP JSON | ✅ | 兜底 |
| Webhook CRUD 管理 | ✅ | `service.rs` 完整 CRUD + 标签绑定 |
| 事件绑定 | ✅ | tag-based event bindingAutoWork 可触发 |
### 2.5 ✅ AI Agent 工具调用链路 — 已完整实现
**完整调用链路(已验证)**:
```
用户在飞书发送消息
→ LarkPlugin → ChannelOrchestrator → ActionExecutor
→ ChannelMessageService → AI Agent (nomi)
→ LLM 识别意图,返回 tool_use (ContentBlock::ToolUse)
→ orchestration.rs::execute_tool_calls_with_approval()
→ ToolConfirmer / ToolApprovalManager 审批门控
→ registry.get(name).execute(input) → McpToolProxy
→ McpManager::call_tool(server_name, tool_name, input)
→ stdio bridge → GatewayMcpServer (HTTP)
→ Registry::global().dispatch_opt(deps, ctx, name, args)
→ Capability.handler(deps, ctx, params) → 实际执行
→ 结果返回 → LLM 整合 → 飞书消息回复
```
**审批门控(已验证)**:
- `ToolCategory::Info`(readOnly)→ 自动批准,可并发执行
- `ToolCategory::Exec`(mutating)→ 需要用户批准(除非 auto_approve 或 allow_list
- 飞书 Channel Surface 额外受 DangerTier 矩阵约束
---
## 三、新增工作:飞书 OA 工具域 + 经营看板
### 3.1 模块划分
| 模块 | 文件 | 工具数 | DangerTier | 工作量 |
|------|------|--------|------------|--------|
| 组织架构 | `caps_feishu_org.rs` | 3 | Read | 3-5 天 |
| 审批查询(仅读) | `caps_feishu_approval.rs` | 3 | Read | 3-4 天 |
| 审批列表/详情 UI(原生) | `ui/src/renderer/pages/approval/` | - | - | 3-4 天 |
| 审批写操作(AppLink 跳转) | `OpenInFeishuButton.tsx` + `tauri-plugin-opener` | - | - | 1 天 |
| 经营看板 MCP 工具 | `caps_dashboard.rs` | 4 | Read | 5-8 天 |
| 看板前端页面 | `ui/src/renderer/pages/dashboard/` | - | - | 6-10 天 |
| 飞书卡片 chart 投递 | 扩展 `sender.rs` | - | - | 2-3 天 |
| 卡片交互路由 | 扩展 `action.rs` | - | - | 1-2 天 |
| Tauri 深链(已有)+ Opener 插件 | `tauri.conf.json`(已有)+ `Cargo.toml` 新增 | - | - | 1 天 |
| 数据表 migration | `nomifun-db/migrations/` | - | - | 1 天 |
| 定时同步调度 | 扩展 `services.rs` | - | - | 1-2 天 |
| 权限感知搜索 | 扩展 `knowledge_tools.rs` | - | - | 3-5 天 |
> **💡 v3.4 方案变更**: v3.2 的「飞书 JS SDK 混合模式」不可行(H5 SDK 仅在飞书客户端内可用);v3.3 的原生模式虽可行但复杂度高(动态表单 + OAuth + 后端写代理 = 10-15 天)。v3.4 改用 **AppLink 跳转模式**:NomiFun 负责查询和展示,审批写操作通过 AppLink 跳转飞书客户端完成。**无需 OAuth、无需动态表单、无需后端写代理**,工作量降至 3-5 天,且保留增量升级路径。
> **⚠️ 审批流程设计原则**: AI 仅提供查询和辅助能力(查待办、查详情、辅助填表预览),**审批的创建、同意、拒绝全部通过 AppLink 跳转飞书客户端由人完成**。AI 不执行任何审批动作,确保审批责任链清晰,AI 不承担审批决策风险。
### 3.2 组织架构工具 (`caps_feishu_org.rs`)
**飞书 API 对接**:
| 工具名 | 飞书 API | DangerTier | 说明 |
|--------|----------|------------|------|
| `nomi_feishu_org_search` | `/open-apis/contact/v3/users/search` | Read | 按姓名/部门搜索员工 |
| `nomi_feishu_org_dept_tree` | `/open-apis/contact/v3/departments` | Read | 获取部门树 |
| `nomi_feishu_org_user_detail` | `/open-apis/contact/v3/users/{user_id}` | Read | 员工详情(职位、上级、邮箱) |
**实现要点**:
1. **复用 FeishuConnector 的 token 管理**: 将 `connector_feishu.rs` 中的 `token_cache` / `rate_limit` / `authed_get` 提取为公共 trait 或共享模块,避免重复实现
2. **本地缓存**: 飞书通讯录 API 有严格限流(10 QPS),需在 SQLite 中维护 `org_cache` 表,TTL 15 分钟
3. **PII 脱敏**: 手机号/邮箱在返回给 LLM 前脱敏(`138****1234`),仅在用户明确请求时解敏
4. **权限边界**: 飞书 app 的通讯录可见范围 = NomiFun 可访问的数据边界
**参数 struct 示例**:
```rust
#[derive(Deserialize, JsonSchema)]
struct OrgSearchParams {
/// 搜索关键词(姓名、拼音、工号)
query: String,
/// 限制返回数量,默认 10,最大 50
#[serde(default = "default_page_size")]
page_size: u32,
}
```
### 3.3 审批查询工具 (`caps_feishu_approval.rs`) — 仅查询,人在飞书客户端操作
**设计原则**: 审批流程全部通过 AppLink 跳转飞书客户端由人完成。AI 的角色是:
1. **查询待办**: "我有哪些待审批?"
2. **查询详情**: "这个审批的具体内容是什么?"
3. **辅助填表**: AI 根据用户描述生成表单预览,用户在飞书客户端中确认并提交
4. **状态提醒**: 审批状态变更时通过 Webhook 推送通知
**飞书 API 对接(仅 Read**:
| 工具名 | 飞书 API | DangerTier | 说明 |
|--------|----------|------------|------|
| `nomi_feishu_appr_list` | `/open-apis/approval/v4/tasks` | Read | 查询待审批列表(按用户过滤) |
| `nomi_feishu_appr_get` | `/open-apis/approval/v4/instances/{instance_id}` | Read | 查询审批详情(表单内容、审批节点、状态) |
| `nomi_feishu_appr_templates` | `/open-apis/approval/v4/approvals` | Read | 查询可用审批模板及表单结构定义 |
**❌ 不封装为 MCP 工具的操作(人在飞书客户端执行)**:
| 操作 | 原因 | AppLink 跳转方式 |
|------|------|----------------|
| 创建审批实例 | 审批发起需人工确认 | AppLink 打开飞书发起审批页 → 填写表单 → 提交 |
| 同意审批 | 审批决策必须由人做出 | AppLink 打开飞书审批详情页 → 点击「同意」 |
| 拒绝审批 | 同上 | AppLink 打开飞书审批详情页 → 点击「拒绝」 |
**AI 辅助填表流程**:
```
用户: "帮我发起请假,7月5号到7月7号,事假"
① AI 调用 nomi_feishu_appr_templates → 获取「请假申请」模板表单结构
② AI 根据用户描述填充表单字段 → 生成预览卡片
③ 飞书回复卡片: 表单预览 + 「在飞书中发起」按钮(AppLink URL
④ 用户点击 → AppLink 跳转飞书客户端审批发起页
⑤ 用户在飞书客户端中确认并提交审批
```
**审批模板管理**: 在 NomiFun 中维护 `feishu_approval_templates` 表:
- `approval_code` ↔ 飞书审批定义 code
- `form_schema` — JSON Schema 描述表单字段,供 AI 理解如何填充
- `display_name` — 人类可读名称("请假申请"、"报销单"等)
- `applink_path` — 飞书 AppLink 路径模板(如 `pc/pages/create-form/index?id={approval_code}`
### 3.4 工作台审批 UI 页面 — AppLink 跳转模式
**设计原则**: NomiFun 负责**查询和展示**(审批列表、详情预览、AI 辅助),审批写操作(创建/同意/拒绝)通过**飞书 AppLink 跳转飞书客户端**完成。AI 不参与审批操作。
> **⚠️ v3.4 方案变更**: v3.3 的原生模式(动态表单 + OAuth + 后端写代理 = 10-15 天)虽可行但复杂度高。经飞书官方文档验证,飞书 AppLink 协议支持直接打开审批页面(发起/详情/审批中心),用户在飞书客户端中操作无需 OAuth。v3.4 改为 AppLink 跳转模式,大幅降低复杂度(3-5 天),且保留升级路径。
**AppLink 跳转模式架构**:
```
┌─────────────────────────────────────────────────────┐
│ NomiFun 工作台 │
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ │
│ │ 审批列表/详情 │ │ AI 辅助填表预览 │ │
│ │ (原生 React) │ │ (预填数据 → AppLink 传递) │ │
│ │ 轻量·快速·一致 │ │ 跳转飞书完成实际提交 │ │
│ └──────┬───────┘ └───────────┬───────────────┘ │
│ │ │ │
│ │ MCP 查询工具 │ AppLink URL │
│ │ (AI 辅助) │ (tauri-plugin-opener)│
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 飞书审批 OpenAPI (v4) + AppLink │ │
│ │ Read: 查询列表/详情/模板 → MCP 工具 │ │
│ │ Write: 创建/同意/拒绝 → AppLink 跳飞书 │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
**为什么 AppLink 跳转模式(v3.4**:
| 维度 | 原生模式 (v3.3) | AppLink 跳转 (v3.4) |
|------|----------------|---------------------|
| OAuth 流程 | 需要(本地 HTTP 回调 + token 管理) | **不需要**(用户已在飞书登录) |
| 动态表单 | 需要 Arco Form 动态渲染 | **不需要**(飞书原生表单引擎) |
| 后端写代理 | 需要 REST API 代理 | **不需要**(飞书客户端直接调 API |
| user_access_token | 需要获取和管理 | **不需要** |
| 工作量 | 10-15 天 | **3-5 天** |
| UX | 全程不离开 NomiFun | 写操作需跳转飞书(一次跳转) |
| 升级路径 | - | 可增量添加 OAuth + 写代理 |
**飞书 AppLink 审批页面 URL**:
```
// 打开审批详情页(同意/拒绝在飞书内完成)
https://applink.feishu.cn/client/mini_program/open
?appId=cli_9cb844403dbb9108
&path=pages%2Fdetail%2Findex%3FinstanceId%3D${instanceId}
// 打开发起审批页
https://applink.feishu.cn/client/mini_program/open
?appId=cli_9cb844403dbb9108
&path=pc%2Fpages%2Fcreate-form%2Findex%3Fid%3D${approvalCode}
// 打开审批中心(待办列表)
https://applink.feishu.cn/client/mini_program/open
?mode=appCenter
&appId=cli_9cb844403dbb9108
&path=pc%2Fpages%2Fin-process%2Findex
```
> **来源**: 飞书官方文档 [打开审批页面](https://open.feishu.cn/document/applink-protocol/supported-protocol/open-an-approval-page)。`cli_9cb844403dbb9108` 是飞书审批应用的固定 appId。
**前端页面架构**:
```
ui/src/renderer/pages/approval/
├── ApprovalListPage.tsx // 审批待办列表(原生 React + Arco Table
├── ApprovalDetailPage.tsx // 审批详情预览(原生:表单内容 + 时间线 + 「在飞书中处理」按钮)
├── components/
│ ├── ApprovalTimeline.tsx // 审批流程时间线(原生)
│ └── OpenInFeishuButton.tsx // AppLink 跳转按钮(调用 tauri-plugin-opener
└── hooks/
└── useApprovalQuery.ts // 调用 MCP 查询工具获取列表/详情
```
> **对比 v3.3**: 无需 `DynamicForm.tsx`、`useFeishuAuth.ts`、`ApprovalCreatePage.tsx`——审批表单填写和提交在飞书客户端完成。
**后端审批查询 API(MCP 工具,AI 和前端共用)**:
| 端点 | 方法 | 说明 | MCP 工具 |
|------|------|------|----------|
| `/api/approval/list` | GET | 获取当前用户待审批列表 | `nomi_feishu_appr_list` |
| `/api/approval/:id` | GET | 获取审批详情 | `nomi_feishu_appr_get` |
| `/api/approval/templates` | GET | 获取可用审批模板 | `nomi_feishu_appr_templates` |
**审批写操作(AppLink 跳转飞书客户端)**:
| 操作 | 方式 | 认证 |
|------|------|------|
| 创建审批 | AppLink 打开飞书发起审批页 | 飞书客户端登录态 |
| 同意审批 | AppLink 打开飞书审批详情页 | 飞书客户端登录态 |
| 拒绝审批 | AppLink 打开飞书审批详情页 | 飞书客户端登录态 |
> **关键设计**: 审批写操作通过 AppLink 跳转飞书客户端,用户在飞书中完成操作。**不需要 OAuth**、**不需要 user_access_token**、**不需要后端写代理**。AI Agent 无法触达审批写操作。
> **审批列表也可嵌入对话侧边栏**: 复用 `ChatSlider` 的 `extraTabs` 模式(参考 `NomiSessionMetricsPanel`),在对话侧边栏中嵌入审批待办列表,实现「边聊天边查看审批」。
> **升级路径**: 若后续需要「全程不离开 NomiFun」,可增量添加:① OAuth(2-3 天)→ ② 后端写代理(3-5 天)→ ③ 动态表单(3-4 天)。每步独立,无需推翻重来。
### 3.4.1 Tauri 深链配置(`nomifun://` 协议)
**现状**: 项目已配置 Tauri v2 深链插件,`nomifun` scheme 已注册(见 `tauri.conf.json:38-42``Cargo.toml:33`)。
```json
// apps/desktop/tauri.conf.json (已有配置)
"plugins": {
"deep-link": {
"desktop": {
"schemes": ["nomifun"]
}
}
}
```
```toml
# apps/desktop/Cargo.toml (已有依赖)
tauri-plugin-deep-link = "2"
tauri-plugin-single-instance = { version = "2", features = ["deep-link"] }
```
> **注意**: Windows/Linux 上深链通过 argv 传递给第二个进程,`single-instance` 插件拦截并转发给主进程。macOS 通过 Apple Events 处理。已有配置已正确处理跨平台。
**需新增**: 前端路由监听深链事件并导航到对应页面。
```typescript
// ui/src/renderer/main.tsx 或 Router.tsx 中注册
import { listen } from '@tauri-apps/api/event';
listen('deep-link', (event) => {
const url = event.payload as string; // "nomifun://approval/detail?id=xxx"
const path = url.replace('nomifun://', '/');
navigate(path);
});
```
**支持的深链路径**:
| 深链 | 目标页面 | 场景 |
|------|----------|------|
| `nomifun://approval/list` | 审批待办列表 | 查看待审批 |
| `nomifun://approval/detail?id=xxx` | 审批详情 | 查看具体审批 |
| `nomifun://dashboard` | 经营看板 | 查看经营数据 |
### 3.4.2 Tauri Opener 插件(打开外部 URL
**问题**: AppLink 跳转需要从 Tauri 应用中打开外部 URL(`https://applink.feishu.cn/...`),需使用 Tauri 官方 opener 插件。
**需新增依赖**:
```toml
# apps/desktop/Cargo.toml 新增
tauri-plugin-opener = "2"
```
```typescript
// 前端调用
import { openUrl } from '@tauri-apps/plugin-opener';
// 打开飞书审批详情页
await openUrl(
`https://applink.feishu.cn/client/mini_program/open?appId=cli_9cb844403dbb9108&path=${encodeURIComponent(`pages/detail/index?instanceId=${instanceId}`)}`
);
```
**Capability 配置** (`src-tauri/capabilities/default.json`):
```json
{
"permissions": {
"opener:allow-open-url": {
"scope": {
"allow": [{ "url": "https://applink.feishu.cn/**" }]
}
}
}
}
```
> **安全约束**: opener scope 限制为 `applink.feishu.cn` 域名,防止任意 URL 跳转。
> **飞书卡片中也支持 AppLink**: 飞书卡片 button 组件的 `default_url` 字段可直接配置 AppLink URL,用户在飞书聊天中点击卡片按钮即可跳转审批页面,无需经过 NomiFun。参见[飞书卡片交互文档](https://open.feishu.cn/document/common-capabilities/message-card/add-card-interaction/interaction-module)。
### 3.5 卡片交互闭环(跳转工作台)
**现有能力**: Lark Plugin 已处理 `card.action.trigger` 事件,将按钮回调转为 `UnifiedAction`(见 `handle_card_action()` in `plugin.rs`)。回调解析通过 `parse_lark_callback()``"category:action:k=v,k=v"` 格式拆分,当前仅支持 `platform`/`system`/`chat` 三种 category。
**两种卡片按钮模式**:
| 模式 | 飞书卡片按钮类型 | 行为 | 适用场景 |
|------|-----------------|------|----------|
| **URL 直跳** | `{ "tag": "button", "text": "...", "url": "nomifun://..." }` | 飞书客户端直接打开 URL → OS 层面唤起 NomiFun → Tauri 深链路由 | 简单跳转(前往审批页、前往看板) |
| **Action 回调** | `{ "tag": "button", "text": "...", "value": { "action": "feishu:approval_preview:code=xxx" } }` | 触发 `card.action.trigger``handle_card_action()``parse_lark_callback()``route_action()` | 需要后端处理后再回复的场景(如刷新状态) |
**审批辅助场景使用 URL 直跳模式**(简单可靠,无需后端介入):
```json
{
"type": "template",
"data": {
"template_id": "approval_preview",
"template_variable": {
"title": "请假申请预览",
"applicant": "张三",
"duration": "7月5日 - 7月7日",
"type": "事假",
"actions": [
{ "tag": "button", "text": { "tag": "plain_text", "content": "在飞书中发起" }, "url": "https://applink.feishu.cn/client/mini_program/open?appId=cli_9cb844403dbb9108&path=pc%2Fpages%2Fcreate-form%2Findex%3Fid%3Dleave_v1", "type": "default" }
]
}
}
}
```
> **注意**: 卡片中不包含「同意/拒绝」按钮 — 审批操作必须在飞书客户端中由人完成。URL 直跳模式不需要扩展 `parse_lark_callback()`,飞书客户端原生处理 URL 打开。
**可选扩展(Action 回调模式)**: 若需要在卡片中实现「刷新审批状态」等交互,需扩展 `parse_lark_callback()` 的 category 白名单,增加 `"feishu"` 类别,并在 `route_action()` 中新增对应路由。工作量 1-2 天。
### 3.6 企业经营看板 (`caps_dashboard.rs` + 前端页面)
**数据来源**: 外部数据中台(已完成 ETL 加工的指标数据),通过 REST API 提供。
**设计原则**:
- 数据中台负责数据加工(聚合、清洗、建模),NomiFun 只做展示和查询
- **独立看板页面优先**: 用户打开工作台即可看到角色化看板,无需对话即可获取核心指标
- **AI 对话为补充**: 用户需深度查询(下钻/对比/自定义时间范围)时,通过自然语言对话与 AI 交互
- **角色化数据过滤**: 不同用户看到不同层级的指标(总部/部门/个人)
- 权限由后端强制执行,不信任前端传入的 scope 参数
**双模式交互**:
```
模式一:独立看板页面(打开即看)
用户打开工作台 → 看板页面自动加载角色化指标
→ 指标卡片 + 趋势图表 + KPI 进度条
→ 手动刷新 / 切换时间范围
模式二:AI 自然语言对话(深度查询)
用户在飞书或工作台中提问:
"本月华东区销售额对比上月怎么样?"
→ AI 调用 nomi_dashboard_trend + nomi_dashboard_breakdown
→ 返回图表卡片 + 文字分析
→ 可继续追问 "按产品线拆分呢?"
```
**MCP 工具定义**:
| 工具名 | DangerTier | 说明 |
|--------|------------|------|
| `nomi_dashboard_summary` | Read | 获取当前用户可见的经营概览指标 |
| `nomi_dashboard_trend` | Read | 获取指定指标的趋势数据(时间序列) |
| `nomi_dashboard_breakdown` | Read | 按维度下钻指标(部门/产品/区域) |
| `nomi_dashboard_compare` | Read | 多指标对比分析 |
**角色化数据过滤机制**:
```rust
#[derive(Deserialize, JsonSchema)]
struct DashboardSummaryParams {
/// 指标分类(如 "sales", "hr", "finance"),可选
#[serde(default)]
category: Option<String>,
/// 时间范围(如 "today", "this_week", "this_month"
#[serde(default = "default_time_range")]
time_range: String,
// 注意:scope/department/user_id 不接受前端传入
// 由 handler 从 CallerCtx.user_id 解析用户角色后注入
}
```
**角色权限映射**:
| 角色 | 可见数据范围 | 示例指标 |
|------|-------------|----------|
| CEO/高管 | 全公司 | 总营收、总利润、人效、增长率 |
| 部门负责人 | 本部门 + 下属部门 | 部门 KPI、部门人均产出、部门预算执行率 |
| 团队主管 | 本团队 | 团队任务完成率、团队工时分布 |
| 普通员工 | 个人 | 个人 KPI、个人任务进度、个人考勤 |
**实现要点**:
1. **用户角色获取链路**: `CallerCtx.user_id` → 查询 `user_roles` 表 → 确定数据可见范围
- **Channel Surface 链路**: 飞书消息 → `ActionExecutor::handle_incoming_message()``pairing.get_internal_user_id(open_id, "lark")``internal_user_id` → 传入 `ChannelMessageService` → AI Agent → `CallerCtx.user_id = internal_user_id`
- **Desktop Surface 链路**: 工作台直接调用 → `CallerCtx.user_id = 当前登录用户 ID`
- **角色解析**: `user_roles` 表中 `user_id` 关联 `internal_user_id``role` 字段决定数据 scope
- **scope 注入**: handler 内部根据 `role` 计算 `scope`(如 `role=ceo``scope=all``role=dept_head``scope=dept:{dept_id}`),传递给数据中台 API
2. **数据中台 API 对接**: 在 `GatewayDeps` 中新增 `data_platform_client` 字段(需在 `deps.rs` 添加字段 + `routes.rs` 注入)
3. **缓存策略**: 高频指标在 NomiFun 侧缓存 5 分钟,减少数据中台压力
4. **前端看板页面**: 基于 Arco Design + recharts 构建可视化看板(**需先安装 recharts 依赖**
5. **飞书卡片投递**: AI 也可通过飞书 Bot 以卡片形式回复经营数据查询
**前端看板页面架构**:
```
ui/src/renderer/pages/dashboard/
├── DashboardPage.tsx // 看板主页面(角色化路由)
├── components/
│ ├── MetricCard.tsx // 指标卡片组件
│ ├── TrendChart.tsx // 趋势图表(recharts
│ ├── BreakdownTable.tsx // 下钻数据表格
│ └── KPIProgress.tsx // KPI 进度条
├── hooks/
│ └── useDashboardData.ts // 数据获取 hook(调用后端 API)
└── config/
└── roleWidgets.ts // 角色 → 看板组件配置映射
```
**数据中台 API 约定**:
```
GET /api/v1/metrics/summary?scope={scope}&category={category}&range={range}
→ { metrics: [{ name, value, unit, change_pct, target, target_pct }] }
GET /api/v1/metrics/trend?metric={metric}&range={range}&granularity={day|week|month}
→ { points: [{ timestamp, value }] }
GET /api/v1/metrics/breakdown?metric={metric}&dimension={department|product|region}
→ { groups: [{ label, value, share_pct }] }
```
> NomiFun 后端在调用时注入 `scope`(根据用户角色计算),数据中台只返回该 scope 范围内的数据。
**飞书卡片 chart 投递技术说明**:
现有 `build_lark_card()` 仅支持 `div` + `lark_md` 文本元素(见 `sender.rs:66-87`)。经营看板需要在飞书卡片中展示图表,需扩展为飞书卡片 JSON 模板的 `chart` 组件:
```rust
// 新增:在 build_lark_card 中支持 chart 类型 element
fn build_lark_chart_element(data: &ChartSpec) -> Value {
json!({
"tag": "chart",
"chart_spec": data.spec, // 飞书卡片 chart JSON spec
"mode": "transparent"
})
}
```
飞书卡片 chart 组件接受 Vega-Lite 风格的 JSON spec,需将数据中台返回的指标数据转换为飞书 chart spec 格式。工作量 2-3 天(含 spec 转换 + 测试)。
### 3.7 定时增量同步
**现状**: 知识库同步通过 HTTP API 手动触发(`POST /api/knowledge/:id/sync`)。
**增强**: 在 `AppServices` 中添加 tokio cron 调度器:
```rust
// 每 60 分钟触发一次飞书 Wiki 增量同步
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(3600));
loop {
interval.tick().await;
for kb in knowledge_service.list_connector_sources("feishu").await {
let _ = knowledge_service.sync_connector_source(&kb.id).await;
}
}
});
```
### 3.8 数据表 Schema 定义
**新增 SQLite 表**(通过 migration 脚本创建):
#### `feishu_approval_templates` — 审批模板映射
```sql
CREATE TABLE feishu_approval_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
approval_code TEXT NOT NULL UNIQUE, -- 飞书审批定义 code
display_name TEXT NOT NULL, -- 人类可读名称("请假申请")
form_schema TEXT NOT NULL, -- JSON Schema 描述表单字段
applink_path TEXT NOT NULL, -- 飞书 AppLink 路径模板
enabled BOOLEAN DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
```
#### `user_roles` — 用户角色映射(经营看板权限)
```sql
CREATE TABLE user_roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL, -- internal_user_id
role TEXT NOT NULL, -- ceo | dept_head | team_lead | employee
dept_id TEXT, -- 飞书部门 IDdept_head/team_lead 必填)
scope_override TEXT, -- 可选:手动覆盖数据可见范围
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(user_id)
);
```
#### `org_cache` — 飞书通讯录本地缓存
```sql
CREATE TABLE org_cache (
open_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
en_name TEXT,
department_ids TEXT, -- JSON array of dept IDs
job_title TEXT,
mobile TEXT, -- 加密存储
email TEXT, -- 加密存储
leader_open_id TEXT,
raw_json TEXT, -- 完整飞书响应(加密)
cached_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL -- cached_at + TTL(15min)
);
```
#### `audit_logs` — 审计日志
```sql
CREATE TABLE audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_by TEXT NOT NULL, -- user_id
action TEXT NOT NULL, -- 如 "feishu_appr_list", "dashboard_summary"
target TEXT, -- 操作目标(如审批 instance_id
result TEXT, -- success | error:xxx
surface TEXT, -- channel | desktop | remote
created_at INTEGER NOT NULL
);
```
> **Migration 方式**: 在 `nomifun-db/src/migrations/` 中新增 `xxxx_feishu_integration.sql`,遵循现有 migration 命名约定。
---
## 四、数据流详解
### 4.1 场景一:AI 辅助审批填表(人在飞书客户端提交)
```
用户在飞书发送: "帮我发起请假,7月5号到7月7号,事假"
① 飞书 WebSocket → LarkPlugin::handle_message_event()
→ UnifiedIncomingMessage { text: "帮我发起请假...", user: { id: open_id } }
② ChannelOrchestrator → ActionExecutor::handle_incoming_message()
→ pairing.get_internal_user_id(open_id, "lark") → internal_user_id
→ session_mgr.get_or_create_session() → session
→ MessageResult::Dispatched { session_id, conversation_id }
③ ChannelMessageService → AI Agent (nomi)
→ LLM 接收消息 + 工具列表(含 nomi_feishu_*
→ LLM 返回 tool_use: nomi_feishu_appr_templates (获取请假模板结构)
④ 工具执行 (仅查询):
→ McpToolProxy.execute() → stdio bridge → GatewayMcpServer
→ Registry::dispatch_opt() → Capability.handler()
→ 调用飞书审批 API 查询模板定义 → 返回表单结构
⑤ AI Agent 收到模板结构 → LLM 填充表单字段
→ 生成审批预览卡片 JSON + AppLink 跳转链接
→ ChannelMessageService → LarkPlugin::send_card()
→ 飞书渲染预览卡片(含「在飞书中发起」按钮)
⑥ 用户点击「在飞书中发起」
→ AppLink 跳转飞书客户端审批发起页
→ 用户在飞书客户端中确认并提交审批
→ ❌ AI 不参与审批提交操作
⑦ (异步) 审批状态变更
→ 飞书事件订阅 → 或 NomiFun 轮询
→ Webhook 自定义机器人卡片推送到用户/群
```
### 4.2 场景二:文档知识问答
```
用户在飞书发送: "我们的差旅报销标准是什么?"
①-② 同上,消息路由到 AI Agent
③ AI Agent 调用 knowledge_search MCP 工具
→ KnowledgeMcpServer 检索已同步的飞书 Wiki 快照
→ 返回匹配文档片段 + 飞书原文链接 (source_url)
④ AI 整合为自然语言回答
→ "根据《差旅报销管理办法》,国内出差住宿标准为..."
→ 附带飞书文档链接: https://open.feishu.cn/docx/xxx
⑤ LarkPlugin::send_message() → 飞书回复
```
### 4.3 场景三:组织架构查询
```
用户在飞书发送: "产品部有哪些人?"
①-② 同上
③ AI Agent 调用 nomi_feishu_org_dept_tree → 获取部门树
AI Agent 调用 nomi_feishu_org_search → 按部门搜索员工
④ AI 整合结果:
→ "产品部共 12 人:\n- 张三(产品总监)\n- 李四(产品经理)\n..."
→ 手机号脱敏: 138****1234
⑤ LarkPlugin::send_message() → 飞书回复
```
### 4.4 场景四:经营看板查询
```
用户在飞书发送: "这个月销售情况怎么样?"
①-② 同上,消息路由到 AI Agent
③ AI Agent 调用 nomi_dashboard_summary (category="sales", range="this_month")
→ handler 从 CallerCtx.user_id 解析用户角色 → 确定数据可见范围
→ 调用数据中台 API (注入 scope) → 返回已加工指标
④ AI 整合结果:
→ "本月销售概览:\n- 总销售额:¥1,250,000(环比 +12%\n- 新增客户:85 家(环比 +8%)\n- 客单价:¥14,705(环比 +3.5%"
→ 附带趋势图卡片(如用户为高管,含部门对比数据)
⑤ LarkPlugin::send_card() → 飞书回复指标卡片
⑥ 用户也可在 NomiFun 桌面端打开「经营看板」页面
→ DashboardPage 根据用户角色渲染差异化看板
→ 指标卡片 + 趋势图表 + 下钻表格
```
---
## 五、飞书应用配置清单
### 5.1 自建应用创建
1. 登录 [飞书开放平台](https://open.feishu.cn/) → 创建企业自建应用
2. 获取 `App ID` + `App Secret` — 用于 NomiFun connector credential
3. 配置应用名称、头像、描述
### 5.2 权限申请
| 权限范围 | 权限标识 | 用途 |
|----------|----------|------|
| 通讯录 | `contact:user.base:readonly` | 搜索/查询员工基本信息 |
| 通讯录 | `contact:user.phone:readonly` | 查询手机号(需审批) |
| 通讯录 | `contact:user.email:readonly` | 查询邮箱(需审批) |
| 通讯录 | `contact:department.base:readonly` | 获取部门树 |
| 审批 | `approval:approval:read` | 查询审批列表/详情/模板(仅读) |
| ~~审批~~ | ~~`approval:approval:write`~~ | ~~不申请~~ — 审批通过 AppLink 跳转飞书客户端由人执行 |
| 文档 | `wiki:wiki:readonly` | 读取 Wiki 空间节点 |
| 文档 | `docx:document:readonly` | 读取文档内容(blocks |
| 消息 | `im:message` | 接收消息 |
| 消息 | `im:message:send_as_bot` | 以机器人身份发送消息 |
| 消息 | `im:chat:readonly` | 获取群信息 |
### 5.3 事件订阅
- **模式**: WebSocketNomiFun 已支持,无需公网回调地址)
- **订阅事件**:
- `im.message.receive_v1` — 接收消息
- `card.action.trigger` — 卡片按钮回调
- `application.bot.menu_v6` — 机器人菜单点击
### 5.4 机器人配置
- 启用机器人能力
- 配置指令菜单(可选):`查审批``查文档``找人``看板`
- 配置欢迎语
### 5.5 审批应用配置
- 在飞书管理后台 → 审批管理中确认审批模板
- 获取每个模板的 `approval_code`
- 在 NomiFun 中配置 `feishu_approval_templates`
- **不申请审批写入权限** — 审批操作通过 AppLink 跳转飞书客户端由人完成
### 5.6 数据中台对接配置
- 确认数据中台 API 地址、认证方式(API Key / OAuth
- 在 NomiFun 中配置 `data_platform` 连接信息(加密存储)
- 定义角色 → 数据 scope 映射规则(`user_roles` 表)
- 确认数据中台已加工好所需指标(NomiFun 不做 ETL
---
## 六、安全与合规评估
### 6.1 密钥管理
| 密钥 | 存储方式 | 风险评估 |
|------|----------|----------|
| `app_id` / `app_secret` | NomiFun connector credential 加密存储 | ✅ 不进 Git,仅 `.env.example` |
| `tenant_access_token` | 内存缓存,30min 过期 | ✅ 不持久化 |
| Webhook 签名密钥 | webhook 配置加密存储 | ✅ HMAC-SHA256 签名验证 |
| 数据中台 API Key | NomiFun 加密存储 | ✅ 不进 Git,仅 `.env.example` |
### 6.2 权限边界
- **飞书 App 可见范围** = NomiFun 可访问的数据边界
- **后端独立校验**: `ActionExecutor``pairing.get_internal_user_id()` 强制做 open_id → internal_user_id 映射,不信任请求体中的 user_id
- **Surface 权限矩阵**: `Destructive`/`Sensitive` 操作在 Channel Surface 硬拒绝
- **审批不封装写入工具**: 审批创建/同意/拒绝不作为 MCP 工具,AI 无法越权执行
- **看板角色过滤**: `scope` 由后端从 `CallerCtx.user_id` 解析注入,不信任前端传入
### 6.3 PII 脱敏
- 手机号: `138****1234`
- 邮箱: `z***@company.com`
- 身份证: `110***********1234`
- 脱敏在 MCP 工具 handler 层执行,确保不进入 LLM context
### 6.4 审计日志
- 所有审批查询操作记录到 `audit_logs` 表(审批执行在飞书侧已有审计)
- 看板数据查询记录到 `audit_logs`
- 字段: `created_by` / `action` / `target` / `result` / `created_at`
- 保留 ≥ 6 个月
### 6.5 注入防御
- AI 辅助填表预览仅生成 AppLink 链接,不直接提交审批 → 飞书客户端校验表单有效性
- 用户输入与系统 prompt 分离 role,防 prompt injection
- LLM 输出的 tool_use 参数经 `Capability::new()` 的 typed deserialization 校验
- 看板数据中台 API Key 不暴露给 LLM context
---
## 七、实现路线图
### Phase 0: 即用即有(0 天)
| 能力 | 说明 |
|------|------|
| 飞书 Bot 消息收发 | 配置自建应用 WebSocket 即可 |
| 飞书 Wiki 文档同步 | 配置 connector credential + space_id |
| AI 知识问答 | 文档同步后自动可用 |
| Webhook 群通知 | 配置飞书自定义机器人 webhook URL |
### Phase 1a: MCP 查询工具 + 经营看板页面 + 深链基础设施(11-18 天)
| 任务 | 工作量 | 依赖 |
|------|--------|------|
| 提取 FeishuConnector 公共能力(token/限流/authed_get | 1 天 | - |
| `caps_feishu_org.rs` — 3 个组织架构工具 | 3-5 天 | 公共能力 |
| `caps_feishu_approval.rs` — 3 个审批查询工具(仅读) | 3-4 天 | 公共能力 |
| `caps_dashboard.rs` — 4 个经营看板工具 | 5-8 天 | 数据中台 API |
| 安装 recharts 依赖 + 看板前端页面 (DashboardPage + 组件) | 6-10 天 | 看板工具 |
| 飞书卡片 chart 组件投递(扩展 `build_lark_card` | 2-3 天 | 看板工具 |
| 数据表 migration + Tauri opener 插件 | 1 天 | - |
| 集成测试 | 2-3 天 | 全部 |
> **Phase 1a 交付价值**:
> - **经营看板页面**: 用户打开工作台即可看到角色化看板(指标卡片 + 趋势图 + KPI 进度)
> - **AI 聊天查询**: 飞书聊天中可查组织架构、审批待办/详情、经营指标(含卡片 chart 可视化)
> - **深度对话分析**: 用户可通过自然语言追问下钻/对比,AI 调用 dashboard 工具返回图表 + 分析
> - **深链 + Opener 基础设施**: Tauri 深链已就绪 + opener 插件就绪,为 Phase 1b 审批跳转铺路
### Phase 1b: 审批工作台 UI5-7 天)
| 任务 | 工作量 | 依赖 |
|------|--------|------|
| 审批列表/详情 UI(原生 React + Arco | 3-4 天 | Phase 1a 查询工具 |
| AppLink 跳转按钮 + Tauri opener 集成 | 1 天 | Phase 1a opener |
| Tauri 深链前端路由监听 | 0.5 天 | - |
| 审批侧边栏集成(ChatSlider extraTabs | 0.5 天 | 审批列表 UI |
| 飞书审批模板管理表 | 0.5 天 | 审批工具 |
| 集成测试 | 0.5-1 天 | 全部 |
> **Phase 1b 交付价值**: 工作台可视化审批列表/详情,用户点击「在飞书中处理」跳转飞书客户端完成审批操作。
### Phase 2: 交互增强(2-4 天)
| 任务 | 工作量 | 依赖 |
|------|--------|------|
| 卡片交互路由扩展 (`action.rs`) | 1-2 天 | Phase 1a |
| 审批预览卡片模板 | 1 天 | 卡片路由 |
| 审批状态变更推送 | 1 天 | Webhook 已有 |
### Phase 3: 自动化与精细化(4-7 天)
| 任务 | 工作量 | 依赖 |
|------|--------|------|
| 定时增量同步调度 | 1-2 天 | Phase 0 |
| 权限感知搜索(按用户过滤文档) | 3-5 天 | Phase 1a 组织架构 |
### 总工作量
| 阶段 | 天数 | 累计 | 交付物 |
|------|------|------|--------|
| Phase 0 | 0 | 0 | 飞书 Bot + Wiki 同步 + Webhook |
| Phase 1a | 11-18 | 11-18 | MCP 查询工具 + 经营看板页面 + 深链/opener 基础设施 + AI 聊天查询 |
| Phase 1b | 5-7 | 16-25 | 审批工作台 UI(列表/详情 + AppLink 跳转 + 深链) |
| Phase 2 | 2-4 | 18-29 | 卡片交互增强 |
| Phase 3 | 4-7 | 22-36 | 自动化调度 + 权限搜索 |
> **对比 v3.0**: 首个可用版本 **11-18 天**(含看板页面 + 深链);总工作量 **22-36 天**。v3.4 通过 AppLink 跳转模式消除 OAuth/动态表单/后端写代理,比 v3.3 的 28-46 天减少 6-10 天。
---
## 八、风险评估与缓解
### 8.1 技术风险
| 风险 | 级别 | 缓解措施 |
|------|------|----------|
| 飞书 API 限流导致同步失败 | 中 | Connector 已实现 4 QPS 限流 + 429 重试;通讯录加本地缓存 |
| LLM 误填审批表单 | 低 | 表单在飞书客户端中填写 + 飞书原生校验;AI 仅生成预填数据,人工确认 |
| 飞书 WebSocket 断连 | 低 | Plugin 已有自动重连逻辑 |
| 文档类型限制(仅 docx) | 低 | Phase 1 不影响;后续可扩展 Sheet/Mindnote 支持 |
| 数据中台 API 不可用 | 中 | NomiFun 侧 5min 缓存 + 降级提示「数据暂不可用」 |
| 数据中台指标口径变更 | 中 | 版本化 API 约定 + 季度对齐检查 |
| ~~飞书 JS SDK 兼容性~~ | ~~中~~ | **已消除** — v3.4 不使用 H5 JS SDKAppLink 跳转飞书原生审批 UI |
| ~~飞书 OAuth redirect_uri~~ | ~~中~~ | **已消除** — v3.4 不需要 OAuth,AppLink 跳转利用飞书客户端登录态 |
| ~~动态表单字段类型覆盖不全~~ | ~~中~~ | **已消除** — v3.4 不渲染表单,飞书原生表单引擎处理 |
| ~~user_access_token 过期~~ | ~~低~~ | **已消除** — v3.4 不使用 user_access_token |
| Tauri 深链未注册 | 低 | 项目已配置 `tauri-plugin-deep-link``nomifun` scheme 已注册 |
| AppLink 跳转失败(未安装飞书) | 低 | AppLink 网页会提示下载飞书;NomiFun 可检测并提示用户 |
| tauri-plugin-opener 权限配置 | 低 | scope 限制为 `applink.feishu.cn` 域名,Tauri 官方插件 |
### 8.2 业务风险
| 风险 | 级别 | 缓解措施 |
|------|------|----------|
| ~~审批权限过大(AI 可代替人审批)~~ | ~~高~~ | **已消除** — 审批不封装为 MCP 工具,AI 无法执行审批操作 |
| 组织架构数据泄露 | 中 | 飞书 App 可见范围控制;PII 脱敏;后端独立校验 |
| 用户绑定错误(open_id 映射到错误账号) | 高 | pairing 服务强制验证;首次使用需配对确认 |
### 8.3 错误降级策略
| 场景 | 降级行为 | 用户感知 |
|------|----------|----------|
| 数据中台 API 不可用 | 返回缓存数据(如有)+ 提示「数据可能非最新」 | 看板显示缓存指标 + 警告标识 |
| 数据中台 API 超时 | 5s 超时 → 返回缓存或空状态 | 看板显示「数据加载中」骨架屏 |
| 飞书审批 API 不可用 | MCP 工具返回错误 JSON → AI 告知用户 | AI 回复「审批服务暂时不可用」 |
| 飞书通讯录 API 限流 | 返回 `org_cache` 缓存数据(TTL 15min) | AI 回复缓存数据(可能略旧) |
| 飞书知识库同步失败 | 保留上次成功快照 | AI 仍可检索旧快照 |
| AppLink 跳转失败 | 检测飞书是否安装;未安装时提示下载链接 | 用户可手动打开飞书审批中心 |
| Tauri 深链未注册 | 卡片 URL 改为 HTTPS 网页版降级链接 | 用户在浏览器中查看审批 |
| LLM 工具调用超时 | 30s 超时 → AI 回复「查询超时,请稍后重试」 | 用户可重新提问 |
### 8.4 体验风险
| 风险 | 级别 | 缓解措施 |
|------|------|----------|
| AI 回复延迟(多工具调用链路长) | 中 | streaming dispatch 已支持;卡片预览减少往返 |
| 看板数据刷新延迟 | 低 | 5min 缓存 + 手动刷新按钮;飞书侧 AI 查询实时获取 |
| 飞书卡片兼容性 | 低 | 使用飞书标准 template API;测试不同端(PC/iOS/Android |
| 群聊 @bot 误触发 | 低 | `require_mention` 配置项已有 |
| 深链跳转后页面状态丢失 | 中 | 深链参数写入 URL query,页面加载时从 query 恢复预填数据 |
| AppLink 跳转后用户需手动返回 NomiFun | 低 | 审批完成后 Webhook 推送状态变更通知;用户可点击通知返回 |
---
## 九、测试策略
### 9.1 后端单元测试
| 模块 | 测试方式 | 覆盖点 |
|------|----------|--------|
| `caps_feishu_org.rs` | wiremock 模拟飞书 API | 搜索/部门树/用户详情 + 限流 + 401 刷新 + PII 脱敏 |
| `caps_feishu_approval.rs` | wiremock 模拟飞书审批 API | 列表/详情/模板查询 + 空结果 + 分页 |
| `caps_dashboard.rs` | wiremock 模拟数据中台 API | 概览/趋势/下钻/对比 + 角色过滤 + 缓存命中 |
| 通讯录缓存 | SQLite in-memory | TTL 过期 + 缓存命中 + 缓存未命中 |
| 角色权限映射 | SQLite in-memory | 各角色 scope 计算 + scope_override |
> **参考**: `connector_feishu.rs` 已有 8 个 wiremock 测试用例,新模块遵循相同模式。
### 9.2 前端组件测试
| 组件 | 测试方式 | 覆盖点 |
|------|----------|--------|
| `ApprovalListPage` | React Testing Library | 列表渲染 + 空状态 + 加载状态 |
| `ApprovalDetailPage` | React Testing Library | 详情展示 + 时间线 + 操作按钮 |
| `DashboardPage` | React Testing Library | 角色化渲染 + 指标卡片 + 图表 |
| `OpenInFeishuButton` | Mock opener plugin | AppLink URL 构造 + 跳转触发 |
| 深链路由 | Mock Tauri event | URL 解析 + 路由导航 + 参数传递 |
### 9.3 集成测试
| 场景 | 测试方式 | 验证点 |
|------|----------|--------|
| 飞书消息 → AI → 工具调用 → 回复 | 端到端 mock | 完整链路无断裂 |
| 审批辅助填表 → 卡片 → 深链跳转 | 手动 + Playwright | 预填数据正确传递到工作台 |
| 看板角色化数据 | 多用户 mock | 不同角色看到不同数据范围 |
| AppLink 跳转流程 | 手动 + Playwright | AppLink URL 正确性 + 飞书客户端唤起 + 审批页面打开 |
| 审批状态变更推送 | Mock webhook | 审批同意/拒绝后 Webhook 通知正确送达 |
### 9.4 回归测试
- **Registry 自测**: 新增工具注册后,`all_caps_modules_are_mod_declared_and_registered` CI 测试自动验证(见 `registry/mod.rs` 注释)
- **权限矩阵回归**: 新增 `Read` 级工具在 Channel Surface 自动放行,无需额外配置
- **工具名长度**: Registry 自测强制工具名 ≤ 42 字符
---
## 十、与企微(WeCom)的未来扩展
NomiFun 的架构设计天然支持多平台扩展:
1. **Channel Plugin trait**: `crates/backend/nomifun-channel/src/plugin.rs` 定义了 `ChannelPlugin` trait,新增企微只需实现该 trait
2. **Connector trait**: `KnowledgeConnector` trait 可新增 `WeComConnector` 实现企微文档同步
3. **MCP 工具域**: 飞书工具命名为 `nomi_feishu_*`,企微工具命名为 `nomi_wecom_*`,共享 domain 标签或独立
4. **Webhook**: `sender.rs` 已支持多平台格式,新增企微格式即可
5. **权限矩阵**: 企微消息同样走 `Surface::Channel`,复用现有安全策略
**预估额外工作量**: 10-15 天(主要是企微 API 对接 + Channel Plugin 实现)
---
## 十一、结论
### 可行性评估:✅ 高度可行
NomiFun 现有架构与飞书集成高度契合:
| 基础设施 | 就绪度 | 说明 |
|----------|--------|------|
| 飞书消息通道 | 100% | WebSocket、消息收发、卡片交互全部已实现 |
| 飞书知识库同步 | 100% | Connector 完整实现,含增量同步、限流、测试 |
| MCP 工具总线 | 100% | 132+ 工具已注册,新增工具只需 3 步 |
| AI Agent 工具调用 | 100% | 完整链路从消息到工具执行到回复已打通 |
| Webhook 通知 | 100% | 飞书卡片 + HMAC 签名已实现 |
| 权限与安全 | 100% | Surface × DangerTier 矩阵 + 配对授权 + PII 脱敏 |
| 前端基础设施 | 75% | Arco Design 已有;recharts 需新增依赖;看板/审批页面需新建 |
| 数据中台对接 | 0% | 需新建 caps_dashboard 域 + 数据中台 API client |
### 核心结论
- **Phase 0 零代码**: 配置飞书自建应用即可启用消息通道和文档同步
- **Phase 1a 首个可用版本 ~11-18 天**: MCP 查询工具 + 经营看板页面 + 深链/opener 基础设施上线,用户打开工作台即可看角色化看板,AI 聊天中可深度查询
- **Phase 1b 审批工作台 UI ~5-7 天**: 审批列表/详情 + AppLink 跳转飞书客户端 + Tauri 深链
- **总工作量 ~22-36 天**: 含交互增强、自动化调度、Tauri 深链、opener 插件
- **审批安全**: AI 仅查询/辅助填表,审批操作全部由人在飞书客户端中完成(AppLink 跳转 + 飞书原生表单 + 飞书登录态)
- **AppLink 跳转模式**: v3.4 消除了 v3.3 的 OAuth/动态表单/后端写代理三大复杂度,工作量减少 6-10 天,且保留增量升级路径
- **看板角色化**: 后端强制角色过滤,不同用户看到不同层级指标
- **安全合规**: 权限矩阵、审计日志、PII 脱敏全覆盖
- **可扩展**: 架构天然支持企微等未来平台扩展
- **错误降级**: 每个外部依赖均有降级策略,确保单点故障不影响整体可用性
- **测试覆盖**: 后端 wiremock + 前端 RTL + 端到端集成测试,遵循现有测试模式