1. 前后端协作
插件化手册
  • FH2 插件开发手册
    • 产品简介
    • 典型场景
    • 发版说明
    • 快速开始
      • 快速开始总览
      • 第一个插件
      • 后端插件
      • 全栈闭环
    • AI辅助开发
      • AI 开发 Skill
      • 安装与复验
      • 第一个工程
      • 真实需求示例:现场问题上报
      • 改造已有工程
      • 调试与交付
    • 开发指南
      • 开发指南
      • 基础概念
        • 基础概念总览
        • 什么是插件开发
        • 平台架构
        • 前端宿主、扩展点与能力
        • 后端运行时、入口与权限
        • 开发流程
        • 安全与隔离
        • 支持范围与问题反馈
      • 开发指南
        • 开发指南总览
        • 前端插件
          • 前端插件深度指南
          • 前端工程结构
          • 插件清单(fh2-plugin.json)
          • 入口与生命周期
          • 界面与状态
          • 多宿主插件
          • 跨宿主通信服务
          • HTTP、WebSocket 与事件
          • 存储、Blob 与定时器
          • 工具链与本地调试页面
        • 后端插件
          • 后端开发深度指南
          • 后端插件的结构
          • 后端源清单 fh2-plugin.json
          • HTTP 接口处理函数
          • 消息订阅与事件处理函数
          • ctx 与能力注入
          • 数据存储与缓存
          • 流程编排
          • 触发方式
          • 连接外部系统
          • 流程编排练习(DAG)
        • 前后端协作
          • 前后端协作总览
          • 前后端共享契约
          • 端到端幂等
          • 事件、通知与状态
          • 统一错误模型
          • 链路标识与诊断日志
        • 完整练习
          • 环境准备
          • 第一个前端插件
          • 第一个后端插件
          • 第一个全栈插件
      • 能力总览
        • 能力地图
        • 能力可用性矩阵
        • 前端能力
          • 平台通用能力
            • 平台通用能力总览
            • 基础运行时:HTTP、事件、WebSocket 与存储
            • 导航、位置与通知
            • 弹窗、音频与剪贴板
            • 地图能力
            • 项目设备能力
            • WPMZ 与航线预览
            • 受控对象上传
          • 项目宿主
            • 项目宿主总览
            • 项目上下文
            • 项目侧边栏入口
            • 项目插件路由
            • 项目工作区
          • 消息宿主
            • 消息宿主总览
            • 消息告警弹层
            • 消息导航与通知
          • 计划库宿主
            • 计划库宿主总览
            • 计划库表单上下文
            • 计划库表单扩展
            • 校验、提交与 `biz_ext`
          • 驾驶舱
            • 驾驶舱插件能力总览
            • 驾驶舱扩展点
            • 上下文与遥测
            • 相机、检测与 AI
            • 场景几何与直播指针
            • 飞控、云台与任务
            • 驾驶舱平台事件
        • 后端能力
          • 后端能力地图
          • 后端能力总览
          • 配置、受管出网与集成连接
          • 事件与流程
          • 飞行任务与违停巡逻
          • 数据存储
          • 媒体与前端通知
          • 资源租约
          • 作业、目标轨迹与视频处理
      • 调试与测试
        • 调试与测试总览
        • 快速入门排错
        • 本地开发
        • Web IDE:打开工程与保存改动
        • 工作流编辑器:节点、接线与交付
        • 知识与 MCP:先判断需要哪一层事实
        • 前端本地调试
        • 后端运行时与流程调试
        • 模拟数据与测试数据
        • 集成测试与 E2E
        • 日志与链路追踪
        • 常见问题排查
      • 完整示例
        • 示例与案例总览
        • 项目侧边栏与工作区
        • 消息告警卡片
        • 计划表单任务清单
        • 驾驶舱动作与左侧面板
        • 最小后端插件
        • 最小全栈插件
        • 多宿主插件实战
        • 事故快处
        • 交通抓拍
        • 违停巡逻完整案例
      • 构建发布与安全
        • 构建、发布与安全
        • 质量门禁
        • 行业包构建与打包
        • 能力版本发布
        • 安装、升级与回滚
        • 安全基线
      • API参考
        • API 参考总览
        • 前端
          • 前端 API 参考
          • 前端插件清单结构
          • 前端 SDK API
          • 前端扩展点目录
          • 前端能力目录
          • 前端平台事件目录
          • 跨宿主通信服务 API
          • 工具链命令行参考(`fh2-plugin`)
        • 后端
          • 后端 API 参考
          • 后端插件源清单
          • 后端 HTTP 接口参考
          • 后端事件消费参考
          • 后端脚本上下文(`ctx`)
          • 后端数据存储
          • 流程编排语言
          • 流程触发器参考
          • 后端能力目录
          • 后端 HTTP API
        • 共享契约
          • 共享契约参考
          • 类型与契约目录
          • 错误模型
          • 版本兼容性
      • 附录
        • 错误码
        • 术语表
        • 常见问题
  1. 前后端协作

统一错误模型

接口出错时,平台按你抛出的 code 把错误分成三类,决定 HTTP 状态码、响应码和"该不该重试"。这一页讲三分类规则、前后端各自的写法,读完你能让前端正确区分"参数写错了""记录不存在""系统暂时不可用"。

抛错怎么写#

后端脚本里用 throw 带 code 和 message:
throw { code: 'INVALID_ARGUMENT', message: 'title 不能为空' }
throw { code: 'NOT_FOUND', message: '记录不存在' }
平台按 code 分类:
类别错误码(code)平台行为你该怎么用
契约错误INVALID_ARGUMENT / UNKNOWN_FIELD / UNKNOWN_ENTITY / INVALID_FILTER / EMPTY_FILTER / TYPE_MISMATCH / LIMIT_EXCEEDED / PERMISSION_DENIEDHTTP 400 + code=40006参数缺失、格式不对;调用方改参数就能好,不要重试
业务结果NOT_FOUND / DUPLICATE / CONFLICTHTTP 200 + code=20001查无此记录、重复提交、状态冲突;这是正常业务结果,不要重试
系统错误(默认档)TIMEOUT / UNAVAILABLE / DEADLOCK / STORAGE_ERROR,以及一切认不出的 codeHTTP 503 + code=50301网络抖动、下游暂时不可用;调用方可以退避重试
注意:拼错 code(比如写成 NOTFOUND)不会自动变成契约错误,而是落进默认档(可重试)。写完记得核对拼写。

一个完整的接口处理函数示例#

// handlers/capture/get-event.ts
function requireText (input, key) {
  const value = String(input[key] == null ? '' : input[key]).trim()
  if (!value) {
    throw { code: 'INVALID_ARGUMENT', message: key + ' 不能为空' }
  }
  return value
}

export function handle (input: RecordInput, ctx: HandlerCtx) {
  const eventId = requireText(input, 'event_id')

  const result = store.query('capture_event', { id: { eq: eventId } }, { limit: 1 })
  if (!result.ok) {
    throw { code: result.error.code, message: '查询抓拍失败: ' + result.error.message }
  }
  if (result.data.list.length === 0) {
    throw { code: 'NOT_FOUND', message: '抓拍事件不存在' }
  }

  const row = result.data.list[0]
  return { event_id: row.id, status: row.status || '' }
}
不要这样写:
// ❌ 平台会把返回值当成成功,前端拿到 code=0
return { ok: false, error: '数据库失败' }
HTTP 接口处理函数和流程脚本不自动提升返回的失败信封,需要调用失败时必须显式 throw result.error。store 方法返回的 { ok: false, error } 是底层约定,接口处理函数检查到后要转成 throw。事件入口顶层的失败信封则会按 error.code 分类消息处置,不能把事件规则套用到另外两路;三种入口都必须返回对象,无业务输出通常为 {}。

HTTP 信封对照表#

HTTP响应码(code)含义前端动作
2000成功读 data
20020001插件业务结果(NOT_FOUND/DUPLICATE/CONFLICT 等)显示业务提示,不自动重试
40040001JSON 非法或 invoke 顶层字段多余检查请求体
40040006插件契约错误修正参数,不重试
40140101鉴权失败重新登录
40340301流程未声明 api 触发器检查流程 trigger 配置
40440401 / 40402流程 / 流程实例不存在检查流程名和实例 ID
40940901~40903流程信号时机、作用域或重复冲突先刷新状态再决定
50350301插件系统错误先核对错误及写入结果,安全重试须复用原幂等键
50450401信号投递超时,结果未知先查实例状态,再决定是否重发同一信号

前端:结构化处理错误#

ctx.apiCall 在网关业务码非零时直接抛 PluginError(稳定形状:code、message,网关业务码在 detail.gatewayCode)。用 SDK 的 isPluginError 做结构判断,不要依赖 instanceof:
fh2-plugin-sdk 为文档使用的 SDK 模块名;实际路径使用生成工程配置。保留生成工程中的实际 import,不按文档模块名修改依赖或工具链别名。
// src/shared/invoke.ts
import { isPluginError } from 'fh2-plugin-sdk'
import type { JsonValue, PluginContext } from 'fh2-plugin-sdk'

interface GatewayDetail {
  readonly gatewayCode?: number
  readonly httpStatus?: number
  readonly gatewayMessage?: string
}

export async function invoke<T> (
  ctx: Pick<PluginContext, 'apiCall'>,
  pluginId: string,
  handler: string,
  input: Record<string, JsonValue>,
  idempotencyKey?: string,
): Promise<T> {
  try {
    const res = await ctx.apiCall<{ code: number; message: string; data: JsonValue }>('/plugin/invoke', {
      method: 'POST',
      body: { pluginId, handler, input },
      ...(idempotencyKey ? { idempotencyKey } : {}),
    })
    // 只有 code === 0 才会走到这里
    return res.data as T
  } catch (error) {
    if (isPluginError(error) && error.code === 'PLUGIN_GATEWAY_ERROR') {
      const detail = (error.detail ?? {}) as GatewayDetail
      // gatewayCode 对应上方对照表:40006 契约、20001 业务、50301 系统
      const gatewayCode = detail.gatewayCode ?? 0
      const retryable = gatewayCode === 50301 || gatewayCode === 50401
      const business = gatewayCode === 20001
      void retryable
      void business
      throw new Error(`[${gatewayCode}] ${detail.gatewayMessage ?? error.message}`)
    }
    throw error
  }
}
传输信封的 data 和请求 input 按 JsonValue 约束,不能把未约束的 T 直接传给 apiCall。封装返回的 T 是插件的业务契约,as T 不替代运行时字段校验;只需要 apiCall 的上下文即可调用。完整工程写法见第一个全栈插件的调用封装。
数字码对照表描述已部署网关。当前 1.0.0 本地模拟网关已把后端错误转换为 40006 / 20001 / 50301 数字信封,原脚本错误码保留在错误文案中,不应再按字符串 code 处理。不过本地 HTTP 状态仍可能不同,例如业务 NOT_FOUND 的本地 HTTP 状态可以是 404;数字码分类不能证明目标网关全部行为一致,正式调用与前端展示仍须在目标环境验证。
重试原则:
HTTP 失败后平台不会自动重发请求。gatewayCode 为 50301 / 50401 时,先查权威业务或流程状态;确认可以安全重试后,复用原来的 Idempotency-Key。稳定键还须配合后端原子去重,不能单凭请求头保证没有重复副作用。
40006、业务 20001、409xx 不要盲目自动重试;409xx 先刷新业务/流程状态再决定。
对最终用户展示时,系统错误的消息不一定友好,显示稳定的"稍后重试"提示并记录 gatewayCode 即可。

数据存储的错误#

store 方法不抛错,返回稳定的错误对象:
{
  "ok": false,
  "error": { "code": "INVALID_FILTER", "message": "筛选条件非法" }
}
表不存在、列不存在、缺唯一索引等数据库问题,对脚本统一收敛为 STORAGE_ERROR(可重试档);细节只在服务日志里。
空查询结果不是 NOT_FOUND,返回 { list: [], total: 0 }。要不要翻成业务 NOT_FOUND 由接口处理函数决定。

事件与流程里的处置差异#

同一个 NOT_FOUND,在不同链路里的处置不同:
链路处置
HTTP 调用返回 200/20001,由前端展示
流程步骤业务结果让步骤失败,不重试;系统错误按该动作和步骤的有效策略处理
事件消费确认消费,不重投
错误"类别"是稳定的,但各链路对它的"处置"不同。写事件脚本时要清楚这一点:把业务结果误抛成系统错误,会导致消息被反复重投。
最大尝试次数包含首次执行:默认 5 次尝试最多产生 4 次重试,不是重试 5 次。非幂等受控动作保持单次尝试,run_script 重试会重新执行整段脚本;动作退避、显式调整和导出差异统一见重试边界。事件则按每次投递的部署配置尝试,不套用流程默认值。

常见问题#

现象原因与处理
前端拿到 503 反复重试接口处理函数抛了认不出的 code(如拼写错误);核对三分类表
DUPLICATE 被当故障弹窗它是业务结果(200/20001);重复本该正常的链路改用 createIfAbsent
接口处理函数返回 {ok:false} 前端却显示成功HTTP 接口处理函数不提升失败信封,需要失败时显式 throw result.error
空列表报"记录不存在"空结果要自己判断后转 NOT_FOUND,平台不会替你转
错误信息里出现敏感数据错误消息会到客户端和日志;只放字段名和原因,不放请求体、token、SQL

延伸阅读#

HTTP 接口:接口处理函数的错误写法与超时。
数据存储:store 错误码全表。
端到端幂等:重试为什么要复用幂等键。
错误码参考:完整错误码列表。
修改于 2026-10-22 03:39:53
上一页
事件、通知与状态
下一页
链路标识与诊断日志
Built with