# 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 binding,AutoWork 可触发 | ### 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, /// 时间范围(如 "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, -- 飞书部门 ID(dept_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 事件订阅 - **模式**: WebSocket(NomiFun 已支持,无需公网回调地址) - **订阅事件**: - `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: 审批工作台 UI(5-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 SDK,AppLink 跳转飞书原生审批 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 + 端到端集成测试,遵循现有测试模式