统一能力调度架构方案(CapabilityDispatcher)

统一能力调度架构方案(CapabilityDispatcher)

文档版本:v2.0

创建日期:2026-06-23

作者:BOSSAGENTS 团队

状态:方案确认中(已根据评审意见修正 v1.0 中的问题)


一、背景与目标

1.1 业务背景

BOSSAGENTS 数字员工平台提供 8 种基础能力(identify/repair/optimize/compare/generate/create/validate/inspect),支撑库存管家、供应链管家、质量巡检等多种业务场景。当前系统服务于三个前端渠道:

| 渠道 | 说明 | 典型场景 |

|------|------|----------|

| 网页端 | Web 管理界面 | 管理员通过对话式界面创建Part、查询库存 |

| 飞书端 | 飞书机器人 | 用户在群聊中@机器人发送"帮我查一下库存" |

| 小程序端 | 微信小程序 | 移动端用户查询轻量级数据 |

1.2 核心目标

  1. 架构统一:三条调用链路合并为单一调度入口,消除代码重复
  2. 行为一致:同一能力在任何渠道执行结果和行为完全相同
  3. 审计可追溯:所有能力执行均有完整审计日志,可追溯来源和用户
  4. 权限集中管控:能力执行权限在统一层校验
  5. 可测试可回滚:每步改动独立验证,出问题可快速回滚

二、现状详细分析

2.1 当前架构(三套并行)

┌──────────────────────────────────────────────────────────────────────┐
│                           当前架构(三套并行)                          │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────┐     HTTP API          ┌────────────────────────┐   │
│  │   网页端      │ ──────────────────────→│ /api/capability/*      │   │
│  │ useCapability│     (fetch)           │ server/routes/         │   │
│  │ Pipeline.js  │                       │ capability-api.js     │   │
│  └─────────────┘                        └───────────┬────────────┘   │
│                                                      │                │
│                                                      ▼                │
│                                           ┌────────────────────────┐  │
│                                           │  CapabilityRuntime      │  │
│                                           │  server/core/           │  │
│                                           └───────────┬────────────┘  │
│                                                       │                │
├───────────────────────────────────────────────────────┼────────────────┤
│                                                       │                │
│  ┌─────────────┐     直接调用          ┌──────────────▼───────────┐  │
│  │   飞书端      │ ─────────────────────→│ LiteScheduler._runWorker │  │
│  │ feishu-     │     (方法引用)        │ lite-scheduler.js:849   │  │
│  │ router.js   │                       │ 直接调用 capability[cap] │  │
│  └─────────────┘                        └──────────────────────────┘  │
│                                                                       │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ┌─────────────┐                            ┌──────────────────────┐ │
│  │   小程序端    │ ─────────────────────────→│ HTTP(已正常工作)     │ │
│  │ miniapp.js  │     HTTP API 调用         │ ai-chat/bom/agent     │ │
│  └─────────────┘                            └──────────────────────┘ │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

2.2 各端代码路径

#### 2.2.1 网页端(HTTP链路)

| 层级 | 文件 | 行号 | 说明 |

|------|------|------|------|

| 前端调用 | src/composables/useCapabilityPipeline.js | 137-195 | fetch('/api/capability/repair', ...) |

| 后端路由 | server/routes/capability-api.js | 66-89 | handleCapabilityRequest() |

| 能力执行 | server/core/capability-runtime.js | 504-538 | identify() / repair() 等方法 |

关键代码

// useCapabilityPipeline.js L137-150
async repair(ctx) {
  await fetch('/api/capability/repair', {
    method: 'POST',
    body: JSON.stringify({ item_type: ctx.item_type, data: ctx.data, ... })
  });
}

// capability-api.js L66-89
async function handleCapabilityRequest(req, res, pathname, bodyStr) {
  const capability = pathname.replace('/api/capability/', '').split('?')[0];
  req.body = JSON.parse(bodyStr || '{}');
  await handleCapability(req, res, capability);
}

#### 2.2.2 飞书端(直接调用)

| 层级 | 文件 | 行号 | 说明 |

|------|------|------|------|

| 路由入口 | server/boss-scheduler/feishu-router.js | 306-309 | runWithTimeout() |

| 调度执行 | server/boss-scheduler/lite-scheduler.js | 776-855 | _runWorker() |

| 能力调用 | server/boss-scheduler/lite-scheduler.js | 849 | capability[capName]() |

关键代码

// feishu-router.js L306-309
async function runWithTimeout(staffId, text, parameters = {}, timeoutMs = 60000) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('EXEC_TIMEOUT')), timeoutMs));
  return Promise.race([
    _scheduler.runStaffOnce(staffId, text, parameters),  // ← 直接调用,跳过HTTP
    timeout
  ]);
}

// lite-scheduler.js L849
const stepResult = await capability[capName](stepContext);  // ← 直接方法调用

#### 2.2.3 小程序端(已有 HTTP 通路)

注意:§2.2.3 与 v1.0 有差别。小程序已经有能力调用链路,并非"无能力执行"。

| 功能 | 实际调用路径 |

|------|-------------|

| 对象创建(数字员工委托) | ai-chat/index.vuePOST /api/digital-staff/run → lite-scheduler → aras-creator |

| BOM 查看 | bom-structure.vueGET /api/bom/search / POST /api/bom/tree |

| AI 对话 | ai-chat/index.vuePOST /api/ai-agent/chat → AI agent → 调度器 |

小程序通过 HTTP 已经走通了能力调用链路,与飞书端(直接方法引用)不同。


2.3 关键问题定位

#问题文件行号影响
1飞书端绕过统一层feishu-router.js308审计缺失
2调度器绕过统一层lite-scheduler.js849审计缺失
3审计日志分散各文件-无法统一查询
4权限校验缺失capability-api.js-安全性风险
5前端 API 端点不统一useCapabilityPipeline.js137后端改了前端也要改

三、问题根因分析

3.1 调用路径不统一

根本原因:系统演进时未统一规划

阶段1:只考虑网页端HTTP调用
  └─→ 设计了 /api/capability/* HTTP API

阶段2:飞书端集成
  └─→ 飞书作为"内部系统",直接调用 CapabilityRuntime
  └─→ 绕过HTTP层(性能考虑)

阶段3:调度器集成
  └─→ 同样是内部调用,直接调用 CapabilityRuntime

3.2 Bug修复成本高

假设 identify 能力发现一个bug需要修复:

需要检查的位置:
1. server/routes/capability-api.js (HTTP路由)
2. server/core/capability-runtime.js (能力实现)
3. lite-scheduler.js (_runWorker 调用路径)
4. feishu-router.js (runWithTimeout)

结果:只修HTTP层,飞书端和调度器仍有bug

3.3 无法统一审计

网页端日志格式:
{ staffId: 'admin', action: 'identify', status: 'success' }

飞书端日志格式:
{ staffId: 'DS-BOSS-001', action: 'pipeline.identify', status: 'success' }

无法做到:统一查询"所有identify操作,按渠道、按时间排序"

四、改进方案

⚠️ 术语说明:本方案中的后端 CapabilityDispatcher 与项目中已有的前端 usePipeline.js

(前端请求去重/取消/重试 composable)是两个独立的、互补的抽象层级。

  • 前端 pipeline:负责 HTTP 请求层的去重/取消/重试
  • 后端 CapabilityDispatcher:负责调度路由 + 审计 + 权限

两者的详细对比和协作方式见附录 C。

4.1 目标架构

┌─────────────────────────────────────────────────────────────────────────────┐
│                           目标架构(统一调度入口)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────────┐                                                        │
│   │     网页端        │ ─────────────────────────────────────────────────┐   │
│   │ useCapability   │                    POST /api/pipeline/execute      │   │
│   │ Pipeline.js     │                                                        │   │
│   └─────────────────┘                                                        │   │
│                                                                              │
│   ┌─────────────────┐                                                        │
│   │     飞书端        │ ─────────────────────────────────────────────────┤   │
│   │ feishu-router   │          CapabilityDispatcher.execute()             │   │
│   └─────────────────┘            (内部调用,不走HTTP)                       │   │
│                                                                              │
│   ┌─────────────────┐                                                        │
│   │     小程序端      │ ─────────────────────────────────────────────────┤   │
│   │ miniapp.js      │           POST /api/pipeline/execute               │   │
│   └─────────────────┘                                                        │   │
│                                                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│              ┌───────────────────────────────────────────────┐              │
│              │       CapabilityDispatcher (统一调度入口)       │              │
│              │       server/core/capability-dispatcher.js     │              │
│              │                                                │              │
│              │  1. 参数归一化 (normalizeParams)                │              │
│              │  2. 审计日志 (auditLog)                        │              │
│              │  3. 执行调度 (dispatch)                        │              │
│              │  4. 结果包装 (wrapResult)                       │              │
│              └──────────────────────┬────────────────────────┘              │
│                                     │                                         │
│                                     ▼                                         │
│              ┌───────────────────────────────────────────────┐              │
│              │           CapabilityRuntime (执行引擎)          │              │
│              │           server/core/capability-runtime.js     │              │
│              │                                                │              │
│              │  identify / repair / optimize / compare /      │              │
│              │  generate / create / validate / inspect        │              │
│              └────────────────────────────────────────────────┘              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 核心组件设计

#### 4.2.1 CapabilityDispatcher 职责

| 职责 | 说明 | P0 | P1 | P2 |

|------|------|----|----|----|

| 参数归一化 | 不同端传来的参数格式不同,统一转换 | ✅ | - | - |

| 审计日志 | 记录每次执行的 source/capability/userId/duration | ✅ | - | - |

| 执行调度 | 调用 CapabilityRuntime 执行能力 | ✅ | - | - |

| 结果包装 | 统一响应格式 { success, capability, source, traceId, duration, data } | ✅ | - | - |

| 权限校验 | 外部调用需校验 | - | - | ✅ |

⚠️ 重要变更:权限校验从 P0 移出至 P2。v1.0 中的 checkPermission 仅有 return true 桩代码,

在 P0-P1 阶段不会实际被测试到,属于死代码。P2 引入真正的权限系统时再一并实现。

#### 4.2.2 新增文件清单

| 文件 | 说明 | 优先级 |

|------|------|--------|

| server/core/capability-dispatcher.js | 统一调度分发器核心类 | P0 |

| server/services/audit-log.js | 审计日志服务 | P0 |

| server/services/permission-service.js | 权限服务 | P2(从 P0 移出) |

#### 4.2.3 修改文件清单

| 文件 | 修改内容 | 优先级 |

|------|----------|--------|

| server.js | 添加 POST /api/pipeline/execute 路由 | P0 |

| server/boss-scheduler/feishu-router.js | runWithTimeout 改用 CapabilityDispatcher.execute() | P0 |

| server/routes/capability-api.js | 改造为调用 CapabilityDispatcher(纯业务调用,HTTP 层留在路由) | P0 |

| src/composables/useCapabilityPipeline.js | API 端点改为 /api/pipeline/execute,参数格式对齐 | P0 |

| server/boss-scheduler/lite-scheduler.js | 外部包装层接入审计,内部逻辑不做侵入式修改 | P1 |

| bossagents-miniapp/server/miniapp-routes.js | 添加 /api/pipeline/execute 路由(复用主服务的) | P1 |

| bossagents-miniapp/src/api/miniapp.js | 添加 executeCapability(capability, params) 方法 | P1 |


五、具体实施步骤(含预估工作量)

5.1 P0 阶段:核心架构(预估:3~4 人天)

#### 步骤 1:创建 CapabilityDispatcher(纯业务层,不关心 HTTP)

文件server/core/capability-dispatcher.js(新建)

const { getRuntime } = require('./capability-runtime');
const auditLog = require('../services/audit-log');

class CapabilityDispatcher {

  /**
   * 统一能力调度入口
   *
   * @param {Object} request
   * @param {string} request.source       来源标识: web | feishu | miniapp | scheduler
   * @param {string} request.capability   能力名称
   * @param {Object} request.params       执行参数
   * @param {string} [request.userId]     用户 ID
   * @param {string} [request.traceId]    追踪 ID
   * @param {Object} [request.context]    执行上下文(仅 scheduler 使用,用于传递 prompt/协作链状态)
   * @returns {{ success: boolean, capability: string, source: string, traceId: string,
   *            executedAt: string, duration: number, data?: any, error?: string }}
   */
  static async execute(request) {
    const startTime = Date.now();
    const traceId = request.traceId
      || `trace_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;

    try {
      // step 1: 参数归一化 —— 先铺默认值,再用入参覆盖
      // 注意:...params 必须在前面,显式覆盖字段在后面,避免无意义的 undefined 覆盖默认值
      const normalized = this.normalizeParams(request);

      // step 2: 审计日志 START
      const auditId = auditLog.start({
        traceId,
        source: normalized.source,
        capability: normalized.capability,
        userId: normalized.userId,
        itemType: normalized.params.item_type,
      });

      // step 3: 能力校验
      const runtime = getRuntime();
      if (!runtime[normalized.capability]) {
        throw Object.assign(
          new Error(`能力 "${normalized.capability}" 不存在`),
          { statusCode: 404 }
        );
      }

      // step 4: 执行能力
      // scheduler 传入的 context 透传给能力函数,支撑 prompt 渲染、协作链状态等
      const result = await runtime[normalized.capability]({
        ...normalized.params,
        ...(normalized.context ? { _context: normalized.context } : {}),
      });

      // step 5: 审计日志 COMPLETE
      auditLog.complete(auditId, {
        success: true,
        duration: Date.now() - startTime,
      });

      // step 6: 统一响应
      return {
        success: true,
        capability: normalized.capability,
        source: normalized.source,
        traceId,
        executedAt: new Date().toISOString(),
        duration: Date.now() - startTime,
        data: result,
      };

    } catch (error) {
      const duration = Date.now() - startTime;
      // 错误也要有审计
      if (auditLog && traceId) {
        auditLog.complete(`audit_${startTime}`, {
          success: false,
          error: error.message,
          duration,
        });
      }
      // 将已经构建的成功响应和数据信息抛出
      throw Object.assign(
        error,
        {
          statusCode: error.statusCode || 500,
          _pipelineTraceId: traceId,
          _pipelineSource: request.source,
        }
      );
    }
  }

  /**
   * 参数归一化
   *
   * 核心原则:...spread 放在前面,显式字段放在后面覆盖,
   * 避免 undefined 值意外覆盖默认值(v1.0 的设计存在此 bug)。
   */
  static normalizeParams(request) {
    const { source, capability, params = {}, userId, traceId, context } = request;

    // step 1: 基础字段
    const result = {
      source: source || 'unknown',
      capability,
      userId: userId || null,
      traceId: traceId || null,
      context: context || null,
    };

    // step 2: 参数归一化 — 先把入参展开,再用显式处理覆盖
    const itemType = params.item_type
      || (params.itemType)
      || (params.data && params.data.item_type)
      || 'Part';

    const data = params.data || params;
    const intent = params.intent || params.text || '';

    result.params = {
      ...params,
      item_type: itemType,
      data,
      intent,
    };

    return result;
  }

  static async dispatch(runtime, request) {
    return await runtime[request.capability](request.params);
  }

  static logStart(request, traceId) {
    console.log(
      `[CapabilityDispatcher] ▶ ${request.source}/${request.capability} [${traceId}]`
    );
  }

  static logComplete(entry, { success, error, duration }) {
    const icon = success ? '✅' : '❌';
    console.log(
      `[CapabilityDispatcher] ${icon} ${entry.source}/${entry.capability} [${entry.traceId}] ${duration}ms`
    );
  }
}

module.exports = CapabilityDispatcher;

修正点说明(vs v1.0):

  1. normalizeParams 修复:...params 放在前,显式字段放在后,避免 undefined 覆盖默认值
  2. handleHttpRequest 移除:CapabilityDispatcher 是纯业务对象,不关心 res 和 HTTP 状态码
  3. context 字段:scheduler 可以通过它传递 prompt 上下文/协作链状态
  4. traceId 增加随机后缀:避免极高并发下的冲突
  5. checkPermission 完全移除:P0 不做权限,P2 再引入

#### 步骤 2:主服务添加统一路由 + HTTP 分离

文件server.js

CapabilityDispatcher 不处理 HTTP,所以路由层需要自己构建请求对象并处理响应:

// ========== POST /api/pipeline/execute ==========
// HTTP 处理在路由层,不侵入 CapabilityDispatcher
if (pathname === '/api/pipeline/execute' && req.method === 'POST') {
  const { CapabilityDispatcher } = require('./server/core/capability-dispatcher');
  const auditLog = require('./server/services/audit-log');

  collectBody(req).then(async (bodyStr) => {
    try {
      const body = JSON.parse(bodyStr || '{}');

      const result = await CapabilityDispatcher.execute({
        source: body.source || 'web',
        capability: body.capability,
        params: body.params || body,
        userId: body.userId || (req.auth && req.auth.userId),
        traceId: body.traceId,
      });

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(result));

    } catch (error) {
      const statusCode = error.statusCode || 500;
      res.writeHead(statusCode, { 'Content-Type': 'application/json' });

      // Dispatcher 抛出的错误携带了 traceId,回传前端帮助排查
      res.end(JSON.stringify({
        success: false,
        error: error.message,
        traceId: error._pipelineTraceId,
      }));
    }
  }).catch(catchHandler(req, res, 'pipeline-execute'));
  return;
}

#### 步骤 3:修改 capability-api.js

文件server/routes/capability-api.js

CapabilityDispatcher 不包含 HTTP 处理,所以路由直接构建业务对象调用 .execute(),然后自己序列化响应:

const { CapabilityDispatcher } = require('../core/capability-dispatcher');

async function handleCapabilityRequest(req, res, pathname, bodyStr) {
  const capability = pathname.replace('/api/capability/', '').split('?')[0];

  try {
    const body = JSON.parse(bodyStr || '{}');

    const result = await CapabilityDispatcher.execute({
      source: body.source || 'web',
      capability,
      params: body,
      userId: req.auth && req.auth.userId,
    });

    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(result));

  } catch (error) {
    res.writeHead(error.statusCode || 500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({
      success: false,
      error: error.message,
      traceId: error._pipelineTraceId,
    }));
  }
}

修正点说明(vs v1.0):

不再调用 PipelineDispatcher.handleHttpRequest(),而是路由层直接构造 request 对象,

调用 CapabilityDispatcher.execute(),自己控制 HTTP 写入。未来切换到 gRPC/WebSocket/消息队列时,

只需要换路由层代码,CapabilityDispatcher 无需任何改动。


#### 步骤 4:改造飞书端

文件server/boss-scheduler/feishu-router.js

const { CapabilityDispatcher } = require('../core/capability-dispatcher');

async function runWithTimeout(staffId, text, parameters = {}, timeoutMs = 60000) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('EXEC_TIMEOUT')), timeoutMs));

  const { capability = 'identify', item_type = 'Part' } = parameters;

  return Promise.race([
    CapabilityDispatcher.execute({
      source: 'feishu',
      capability,
      params: { item_type, intent: text, data: parameters },
      userId: staffId,
      traceId: `feishu_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
    }),
    timeout
  ]);
}

#### 步骤 5:改造前端 useCapabilityPipeline.js

文件src/composables/useCapabilityPipeline.js

// 改前
async repair(ctx) {
  await fetch('/api/capability/repair', {
    method: 'POST',
    body: JSON.stringify({ item_type: ctx.item_type, data: ctx.data, ... })
  });
}

// 改后
async repair(ctx) {
  const res = await fetch('/api/pipeline/execute', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      source: 'web',
      capability: 'repair',
      params: { item_type: ctx.item_type, data: ctx.data, ... },
    }),
  });
  const json = await res.json();
  if (!json.success) throw new Error(json.error);
  return json.data;
}

前端使用现有的 usePipeline.js(已完成的前端 pipeline composable)包装这个调用,

自动获得去重、取消、重试能力:

> const pipeline = usePipeline();
> async function repair(ctx) {
>   return pipeline.request({
>     type: 'capability:repair',
>     handler: async (signal) => {
>       const res = await fetch('/api/pipeline/execute', { signal, ... });
>       return (await res.json()).data;
>     },
>   });
> }
> 

5.2 P1 阶段:调度器接入 + 小程序统一(预估:2~3 人天)

#### 步骤 6:lite-scheduler 外部包装(不做侵入式修改)

文件server/boss-scheduler/lite-scheduler.js

重要:v1.0 中直接将 _runWorker 的内部 capability[capName]() 替换为

PipelineDispatcher.execute(),这会丢失 prompt 渲染、协作链状态、变量注入等执行上下文。

本版改为在外部(runStaffOnce 层)加审计拦截,内部执行逻辑保持不变

class LiteScheduler {
  async runOnce(staffId, intent, parameters) {
    const staff = this._resolveStaff(staffId);
    if (!staff) throw new Error('员工不存在');

    // ★ 提取 _skipAudit 标志并清理,避免泄露到 _runWorker
    const skipAudit = !!(parameters && parameters._skipAudit);
    if (parameters) delete parameters._skipAudit;

    // ★ 审计 START(Dispatcher 调用的跳过,避免重复审计)
    let auditId = null;
    const traceId = `scheduler_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
    if (!skipAudit) {
      auditId = auditLog.start({
        traceId, source: 'scheduler',
        capability: (parameters && parameters.capability) || staff.capability || 'identify',
        userId: staffId,
        itemType: (parameters && parameters.item_type) || staff.itemType || 'Part',
      });
    }

    return this.executionContext.withRunContext(staff.id, 'manual', async () => {
      const { skipped, result } = await this._withStaffMutex(staff.id, () =>
        this._runWorker(staff, intent, parameters)
      );

      // ★ 审计 COMPLETE
      if (!skipAudit && !skipped) {
        auditLog.complete(auditId, {
          success: result && !(result && result.type === 'confirm'),
          duration: 0,
        });
      }
    });
  }
}

// Dispatcher 调用时传入 _skipAudit,避免 scheduler 重复审计:
//   CapabilityDispatcher.execute({
//     source: 'feishu',
//     ...
//     params: { ...parameters, _skipAudit: true },
//   });
//   → scheduler.runStaffOnce(...) → runOnce() 跳过 auditLog.start()
//   → 审计由 Dispatcher 统一完成

为什么不在 _runWorker 内部替换能力调用:

| 能力上下文 | 来源 | 替换后是否保留 |

|-----------|------|---------------|

| Prompt 模板渲染 | _runWorkerbuildPrompt() | ❌ 丢失 |

| 变量注入 | _runWorkerresolveVariables() | ❌ 丢失 |

| Collaboration chain 状态 | _runWorker_getChainContext() | ❌ 丢失 |

| Worker 运行状态标记(isRunning) | _runWorker_markRunning() | ❌ 丢失 |

| Step 级别错误重试 | CapabilityRuntime 内部 | ✅ 保留 |

P1 暂不侵入 _runWorker,P2 再考虑是否将 CapabilityRuntime 调用也统一收口到 CapabilityDispatcher。


#### 步骤 7:小程序端接入

文件bossagents-miniapp/src/api/miniapp.js(新增方法)

export async function executeCapability(capability, params = {}) {
  const { request } = require('./request');
  return request.post('/api/pipeline/execute', {
    source: 'miniapp',
    capability,
    params,
  });
}

小程序走 HTTP 调用 POST /api/pipeline/execute,和网页端走同一个路由。不另外开独立服务器。

文件src/pages/ai-chat/index.vue(改造示例)

- const res = await fetch('/api/digital-staff/run', { ... });
+ const res = await executeCapability('create', { item_type: 'Part', data });

旧 API 端点保留直到 P2 迁移完成。


5.3 P2 阶段:权限 + 全量迁移(预估:2~3 人天)

#### 步骤 8:创建权限服务(P2 才启用)

文件server/services/permission-service.js(新建)

class PermissionService {

  static CAPABILITY_ROLES = {
    'identify':  ['user', 'admin', 'staff'],
    'create':    ['admin', 'staff'],
    'repair':    ['admin', 'staff'],
    'optimize':  ['admin'],
    'compare':   ['user', 'admin', 'staff'],
    'generate':  ['user', 'admin', 'staff'],
    'validate':  ['admin', 'staff'],
    'inspect':   ['admin', 'staff'],
  };

  async check(userId, capability) {
    if (!userId) return false;
    const role = await this.getUserRole(userId);
    const allowed = PermissionService.CAPABILITY_ROLES[capability] || [];
    return allowed.includes(role);
  }

  async getUserRole(userId) {
    // TODO: 从数据库/SSO 获取
    return 'admin';
  }
}

module.exports = new PermissionService();

在 CapabilityDispatcher.execute() 中加入权限拦截:

if (!['scheduler', 'feishu'].includes(normalized.source)) {
  const permitted = await permissionService.check(normalized.userId, normalized.capability);
  if (!permitted) {
    throw Object.assign(new Error('权限不足'), { statusCode: 403 });
  }
}

#### 步骤 9:清理旧 API 端点

P2 阶段确认迁移稳定后,逐步废弃旧端点:

| 旧端点 | 新端点 | 废弃策略 |

|--------|--------|----------|

| POST /api/capability/:name | POST /api/pipeline/execute | 保留兼容头,日志警告 |

| POST /api/digital-staff/run | POST /api/pipeline/execute | 保留兼容头,日志警告 |

| POST /api/ai-agent/chat | POST /api/pipeline/execute | 保留兼容头,日志警告 |


六、预期效果

6.1 架构指标对比

指标改造前改造后
调用路径数3条独立1条统一
能力实现位置1处1处
审计日志位置分散统一服务
Bug修复位置需改多处只改 Runtime
新增能力修改各端都要改只改 Runtime + routing 表

6.2 响应格式统一

{
  "success": true,
  "capability": "identify",
  "source": "web",
  "traceId": "trace_20260623_abc123",
  "executedAt": "2026-06-23T10:30:00.000Z",
  "duration": 1234,
  "data": { ... }
}

6.3 审计日志格式

{"id":"audit_xxx","traceId":"trace_xxx","source":"web","capability":"identify","event":"START","timestamp":"..."}
{"id":"audit_xxx","event":"COMPLETE","success":true,"duration":1234,"timestamp":"..."}
{"id":"audit_yyy","traceId":"feishu_yyy","source":"feishu","capability":"repair","event":"START","timestamp":"..."}
{"id":"audit_yyy","event":"COMPLETE","success":false,"error":"目标不存在","duration":500,"timestamp":"..."}

七、风险与回滚

7.1 风险识别

| 风险 | 概率 | 影响 | 缓解 |

|------|------|------|------|

| Dispatcher 有 bug | 中 | 高 | Feature Flag 降级 |

| 性能下降 | 低 | 低 | 内部调用不走 HTTP |

| 审计日志写入失败 | 低 | 中 | 降级到 console.log |

| _runWorker 上下文丢失 | 高 | 高 | P0-P1 不做侵入式修改,只在外部加钩子 |

| 前端用例遗漏 | 中 | 中 | 逐步迁移,旧端点保留兼容 |

7.2 回滚方案

环境变量控制

# .env
ENABLE_CAPABILITY_DISPATCHER=false   # 设为 false 时降级到直接调用
ENABLE_PIPELINE_AUDIT=false          # 单独关闭审计(性能原因)

降级逻辑

static async execute(request) {
  if (process.env.ENABLE_CAPABILITY_DISPATCHER === 'false') {
    // 降级:直接调用 CapabilityRuntime,不回滚到旧 API
    const runtime = getRuntime();
    const result = await runtime[request.capability](request.params);
    return { success: true, data: result, degraded: true };
  }
  // 正常流程
}

7.3 灰度发布

| 阶段 | 范围 | 观察期 |

|------|------|--------|

| 1 | 飞书端 10%(审计 + 调度经过 Dispatcher) | 7 天 |

| 2 | 网页端 50% | 7 天 |

| 3 | 调度器 + 小程序 | 7 天 |

| 4 | 全量 | 30 天 |


八、评审清单

  • [ ] 架构设计是否合理?
  • [ ] CapabilityDispatcher 职责是否清晰?(不含 HTTP、不含权限)
  • [ ] 权限模型是否满足需求?(P2 再做)
  • [ ] 审计日志格式是否够用?
  • [ ] 回滚方案是否可行?
  • [ ] 灰度发布策略是否合适?
  • [ ] 是否遗漏了 _runWorker 执行上下文的保护?(P0-P1 不侵入)
  • [ ] 性能影响是否可接受?(Dispatcher 仅做参数归一化和日志写入)

九、附录

A. 术语表

术语说明
CapabilityRuntime能力执行引擎,提供 8 种基础能力
CapabilityDispatcher统一调度分发器,所有能力调用经过此层
source来源标识,区分 web/feishu/miniapp/scheduler
traceId追踪 ID,用于关联一次完整执行的所有日志
usePipeline.js前端 composable,处理 HTTP 请求去重/取消/重试
useCapabilityPipeline.js前端 composable,封装能力调用的前端逻辑

B. 相关文件路径

server/
├── core/
│   ├── capability-runtime.js         # 能力执行引擎(不变)
│   └── capability-dispatcher.js      # 统一调度分发器(新建)
├── routes/
│   └── capability-api.js             # HTTP API 路由(改造为调用 Dispatcher)
├── boss-scheduler/
│   ├── lite-scheduler.js             # 调度器(P1 加外部审计钩子)
│   └── feishu-router.js              # 飞书路由(P0 改)
└── services/
    ├── audit-log.js                  # 审计日志(新建)
    └── permission-service.js         # 权限服务(P2 新建)

src/composables/
├── usePipeline.js                    # 前端 pipeline(已有,不变)
└── useCapabilityPipeline.js          # 前端能力调用(P0 改 API 端点)

bossagents-miniapp/src/api/
└── miniapp.js                        # 小程序 API 层(P1 加方法)

C. 前端 pipeline vs 后端 CapabilityDispatcher 对比

这是项目中容易混淆的两个概念,放在一起说明:

| 维度 | 前端 usePipeline.js | 后端 CapabilityDispatcher |

|------|----------------------|---------------------------|

| 层次 | 浏览器端 composable | 服务器端 Node.js 类 |

| 职责 | HTTP 请求去重、取消、重试 | 参数归一化、审计日志、结果统一包装 |

| 解决什么问题 | 用户多次点击、页面快速切换、网络抖动 | 调用路径不统一、审计缺失、响应格式不一致 |

| 影响范围 | 组件级别 | 全系统(所有渠道) |

| 改写为 CapabilityRuntime 调用 | 不涉及 | 是核心职责 |

| 与对方的关系 | 调用方:经过 pipeline 的 HTTP 请求最终到达后端的 Dispatcher | 被调用方:接收来自前端 + 其他端的调用 |

用户操作
    │
    ▼
前端 usePipeline.js  ──HTTP POST──→  /api/pipeline/execute
(去重/取消/重试)                           │
                                           ▼
                                    CapabilityDispatcher.execute()
                                    (参数归一化 + 审计 + 调度)
                                           │
                                           ▼
                                    CapabilityRuntime
                                    (identify / repair / ...)

两者不重叠,前端 pipeline 是对外的"栅栏",后端 Dispatcher 是对内的"枢纽"。

D. 实施总览(时间线)

阶段内容预估人天关键交付物
P0CapabilityDispatcher + 审计服务 + 飞书/网页端3~4 天capability-dispatcher.js, audit-log.js
P1调度器接入 + 小程序端2~3 天外部审计钩子, executeCapability 方法
P2权限系统 + 全量迁移2~3 天permission-service.js, 旧 API 废弃公告
合计7~10 天

E. 版本变更记录

版本日期变更
v1.02026-06-23初版(已评审)
v2.02026-06-23修正 v1.0 问题:修复 normalizeParams bug;分离 HTTP 层;重命名避免命名冲突;权限从 P0 移出;scheduler 不做侵入式修改;增加前端改造计划;增加前端/后端 pipeline 对比说明;增加工作量预估和版本变更记录
← 返回案例列表
分享:
🤖 Try Now →
🤖
🎁