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. 后端插件

数据存储与缓存

插件需要持久化业务数据(航线、会话、事件记录……)时,用平台提供的两个存储:
store(数据存储)cache(缓存)
存什么权威业务数据临时状态、热点数据
会丢吗不会(平台数据库)会(有 TTL,过期即删)
怎么查按条件查、排序、分页只能按键精确取
隔离物理表名全局统一:plugin_db_<entity>;装载期拒绝跨插件同名实体所有插件共享一个键空间
权限data:read / data:writecache:read / cache:write
一句话选型:这数据丢了要不要紧、要不要长期精确查?要 → store;丢了无所谓、就是为了快 → cache。 特别注意"去重":窗口期内去重(如 1 小时内别重复处理)用 cache 的 setnx;永久去重必须用 store 的唯一索引(createIfAbsent)——缓存会过期,过期后就会重复处理。

数据存储(store)#

建表:schema.sql 是唯一事实源#

数据表由 schema.sql 单点定义。你只写这一份:
1.
本地 dev:工具链从 schema.sql 派生存储声明,本地模拟器在内存建表。
2.
构建:派生的实体投影进运行时清单(dist/plugin.json),宿主装载时按它校验 store 调用——不要在源清单里手写 entities[]。
3.
生产:部署方在发布流程中执行 schema.sql 建真实表(平台不会自动建表)。
规则:
物理表名 = plugin_db_<实体名>,前缀平台拼。
SQL 平台列为 id / created_at / updated_at,另保留 tenant_id(当前不自动写)。JS store 返回及筛选/排序使用 id / createdAt / updatedAt;tenant_id 不是脚本可直接读写的平台字段。
insert 可指定 id,不指定则由平台生成;创建/更新时间由平台维护,不能写入业务记录,平台字段也不能用于更新内容。业务响应数据结构可以保留自己的 created_at,从 record.createdAt 映射,见共享契约。
业务字段从 JS 写入时一律传字符串(布尔用 '1'/'0')。毫秒时间戳建议用 TEXT 存定宽 13 位字符串(如 "1788861600000");TEXT 上的范围筛选和排序按字典序,不补零会出错。
业务字段名统一用下划线命名(和物理业务列名一致);平台时间字段使用上述公开名,不要将物理列名当作脚本字段。
漏跑 DDL 的表现:包能装载,首次访问数据报 STORAGE_ERROR。

六个方法#

注意:没有 get 和 put。按主键查一条用 query + limit: 1,空列表自己翻成 NOT_FOUND;删除按条件删,不是按 id 删。

查询写法#

算子全集:eq neq gt gte lt lte in nin contains startsWith endsWith isNull isNotNull。顶层字段之间是 AND(不支持 OR,需要 OR 就拆多次查询在脚本里合并)。
分页与排序:limit 缺省 100、最大 1000;offset 最大 100000;排序字段最多 3 个,末尾自动补 id ASC 保证分页稳定。
这些是后端运行时的查询约束;本地模拟器对对象形式 orderBy 和分页的支持不等价,不能据此确认"最新一条"或任意历史列表。保持正式查询形状,在已部署测试环境验证排序与分页,见已知限制。

幂等与并发:两条铁律#

"有则取、无则建"用 createIfAbsent,不要"先查再写"。 并发下"先查再插"会插重:
"抢占状态"用带条件的 update,看 affected 行数。 不要 update 后再查一次判断自己抢到没有:
前提:uniqueBy 的字段在数据库里有对应 UNIQUE 索引(schema.sql 里建),否则 createIfAbsent 报 STORAGE_ERROR,不会退化成普通插入。

错误码#

UNKNOWN_ENTITY(实体没声明/表没建)、UNKNOWN_FIELD(列名错)、INVALID_FILTER(筛选写错)、EMPTY_FILTER(update/delete 用了空筛选,被拒绝)、TYPE_MISMATCH、DUPLICATE、LIMIT_EXCEEDED、PERMISSION_DENIED、STORAGE_ERROR(库异常,可重试)。

当前不支持的#

事务(多表原子性)、关联查询、OR、聚合(计数/求和/分组)、批量写、裸 SQL、跨插件访问。需要跨表原子性的业务,要么设计成"一张表一个唯一键 + 状态机",要么等平台事务能力,不要用补偿代码假装原子。

缓存(cache)#

cache 是平台提供的缓存能力,底层服务由部署方配置。适合:事件去重(窗口期)、外部配置缓存几分钟、限流计数、粗粒度锁。cache.get/set/setnx 返回 {ok,data} 或 {ok:false,error};connector.fetch 不返回这个信封,而是返回 {status,headers,body},网络或出网限制错误以异常抛出。不能因为 store 使用信封就假设所有能力都同形。
下面是一个流程脚本里的热点配置片段。前提是:清单连接声明了 traffic.connector.cascade,部署方已配置 baseUrl,流程步骤用 inputs.connectorId 指向它;脚本有效 allowlist 包含 cache:read、cache:write、config:get、egress:fetch,且目标主机已获出网白名单放行。只缓存业务展示值,不缓存连接凭据或上游返回的敏感字段。
关键事实:
平台不为缓存键加前缀,所有插件共享一个缓存键空间。要私有的键自己带插件名前缀(myplugin:dedup:...);两个插件要协作共享数据,约定一个公共前缀即可——这是受支持的用法。
set 的 ttl 必填,范围 1 秒 ~ 24 小时,超限报 TTL_INVALID。要存超过 24 小时的状态,说明它该进 store。
值自动 JSON 序列化/反序列化:存什么类型取回什么类型。
限额:键 ≤ 256 字节、值 ≤ 1 MB、单次脚本最多 256 次缓存调用。

常见坑#

现象原因
首次访问数据报 STORAGE_ERRORschema.sql 没执行,表不存在
时间范围筛选查不到数据时间戳没存定宽字符串,字符串比较乱了
createIfAbsent 报 STORAGE_ERRORuniqueBy 字段没有 UNIQUE 索引
并发下出现重复数据用了"先查再插";改用 createIfAbsent
缓存过期后重复处理把永久去重放在了 cache 里;挪到 store 唯一索引
两个插件互相读不到对方的缓存一方自带了前缀另一方没有;约定统一前缀
修改于 2026-10-26 07:15:37
上一页
ctx 与能力注入
下一页
流程编排
Built with