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 全局对象里(平台数据库)。这一页讲六方法签名、过滤写法、建表方式和设计限制。权限:data:read 给 query/count,data:write 给 insert/createIfAbsent/update/delete,三条代码路线(HTTP/事件/流程脚本)都可用。

六个方法,没有 put/get#

store.insert(entity, record)              -> {ok, data} | {ok:false, error}
store.createIfAbsent(entity, record, uniqueBy)  -> {ok, data} | {ok:false, error}
store.update(entity, filter, patch)       -> {ok, data} | {ok:false, error}
store.delete(entity, filter)              -> {ok, data} | {ok:false, error}
store.query(entity, filter, options)      -> {ok, data} | {ok:false, error}
store.count(entity, filter)               -> {ok, data} | {ok:false, error}
返回值语义:
方法返回数据(data)
insert新记录(平铺 id、createdAt、updatedAt 与业务字段)
createIfAbsent{ created: boolean, record }——原子地"有则取、无则建"
update{ affected: number }——实际改了几行
delete{ deleted: number }
query{ list, total, limit, offset };每条记录含公开平台字段与业务字段
count{ count: number }
失败不抛异常,统一返回 { ok: false, error: { code, message } }。标准处理:
function must<T>(result: { ok: true, data: T } | { ok: false, error: { code: string, message: string } }): T {
  if (!result.ok) throw { code: result.error.code, message: result.error.message }
  return result.data
}

查询与过滤#

const page = must(store.query(
  "lane_change_event",
  {
    dock_sn: { eq: input.dockSn },
    crossed_at_ms: { gte: input.fromMs, lte: input.toMs },
    post_status: { in: ["pending_video", "processing"] }
  },
  { limit: 50, offset: 0, orderBy: [{ field: "crossed_at_ms", dir: "desc" }] }
))
过滤算子:eq、neq、gt、gte、lt、lte、in、nin、contains、startsWith、endsWith、isNull、isNotNull;多个条件 AND 组合。
orderBy 必须是数组,最多 3 个字段。
update 和 delete 强制要求非空筛选条件(防止误操作全表)。
主要上限:查询默认 100 条、最多 1000 条;offset 最大 100000;in/nin 最多 1000 项;单字符串 64 KiB;单记录 512 KiB。超限返回 LIMIT_EXCEEDED,不会静默截断。
以上是后端运行时的契约。JS 读取、筛选和排序平台时间时用 createdAt / updatedAt,不要使用 SQL 物理列名 created_at / updated_at。本地模拟器的对象排序等支持有差异,保持正式查询声明,并在已部署测试环境验证,见已知限制。

示例:幂等去重 + 条件抢占#

// handlers/lane-change/create-event.ts
export function createEvent(input: RecordInput, ctx: HandlerCtx) {
  // 1. 原子去重:同一 event_key 只建一条
  const created = must(store.createIfAbsent(
    "lane_change_event",
    {
      id: ctx.stableId("event:" + input.eventKey),
      event_key: input.eventKey,
      post_status: "pending_video",
      crossed_at_ms: String(input.crossedAtMs).padStart(13, "0")
    },
    ["event_key"]
  ))
  if (!created.created) {
    return { id: created.record.id, duplicate: true }
  }

  // 2. 条件抢占:affected=1 才说明抢到了
  const claimed = must(store.update(
    "lane_change_event",
    { id: { eq: created.record.id }, post_status: { eq: "pending_video" } },
    { post_status: "processing" }
  ))
  return { id: created.record.id, claimed: claimed.affected === 1 }
}

实体与建表#

实体名:^[a-z][a-z0-9_]*$;业务字段名建议统一用下划线命名,业务查询、写入和排序使用同一键名;平台时间字段使用公开名 createdAt / updatedAt。
物理表:plugin_db_<实体名>(如实体 lane_change_event → 表 plugin_db_lane_change_event),由部署方在发布流程中执行包里的 schema.sql 建表——后端运行时不会自动建表,漏跑建表脚本首次访问会报 STORAGE_ERROR。
SQL 平台列:id、tenant_id、created_at、updated_at;业务字段从 JS 写入时传字符串。JS store 的公开平台字段为 id、createdAt、updatedAt,tenant_id 不作为脚本可读写字段。
insert 可以指定 id;创建/更新时间由平台维护,不写入业务记录,平台字段不能用于更新内容。业务响应数据结构若声明 created_at,从 record.createdAt 映射,不改变响应数据结构或 SQL 建表语句,见共享契约。
毫秒时间戳存定宽 13 位字符串(如 padStart(13,"0")),否则文本列上的范围比较和排序会乱。
存储契约由 schema.sql 单点定义(≤32 实体、≤64 字段/实体):本地 dev 按派生声明在内存建表,构建时投影进运行时清单,不要在源清单手写 entities。

错误码#

错误码(code)含义
UNKNOWN_ENTITY / UNKNOWN_FIELD实体/字段名形状非法(表或列不存在通常是 STORAGE_ERROR)
INVALID_FILTER算子、值、排序或分页参数非法
EMPTY_FILTER写操作缺过滤器,或 uniqueBy 为空
TYPE_MISMATCH参数形状或值类型不符
DUPLICATEinsert 撞唯一约束
LIMIT_EXCEEDED超出各类上限
STORAGE_ERROR未装配、表/列不存在、超时等底层错误

设计限制(写代码前必读)#

没有事务、没有关联查询、没有 OR、没有聚合。设计约束是"一张表一个唯一键"。
并发互斥判据只有两个:update 的 affected 行数、createIfAbsent 的 created。
delete 是物理删除,没有软删除。
"先 query 再 update"判断抢占是错的——两次调用之间有竞争窗口,必须用条件 update 的 affected。

相关能力:cache#

需要高速读写的临时状态用 cache(权限 cache:read/cache:write),方法有 get/set/del/expire/incr/setnx/hset/zadd/rpush 等一组,底层缓存服务由部署方配置。返回值和错误通道请逐方法查生成的 types/,不要套用 store 的统一信封。注意:
缓存键是共享空间,平台不加插件前缀。store 的物理表名同样全局统一为 plugin_db_<entity>,跨插件同名实体在装载 / 合并时拒绝;不要理解成运行时自动按插件加表前缀。缓存键要自带业务前缀(如 myplugin:task:42),避免和其他插件撞名。
cache 适合计数器、去重窗口、临时锁标记;业务事实数据请落 store。

常见问题#

现象原因与处理
store is not defined清单没申报 data:read/data:write
首次访问报 STORAGE_ERRORschema.sql 没被执行,表不存在
时间范围查询结果不对毫秒时间戳没存成定宽 13 位字符串
想用 store.put / store.get没有这两个方法;写用 insert/update,读用 query/count
并发下出现两条"唯一"记录uniqueBy 字段缺真实唯一索引,或用了 insert 而不是 createIfAbsent
EMPTY_FILTERupdate/delete 的筛选条件为空对象;至少带一个条件
方法签名速查见数据存储 API。
修改于 2026-10-26 07:15:37
上一页
飞行任务与违停巡逻
下一页
媒体与前端通知
Built with