大疆(Dji)上云开源,支持大疆机场1,2,3(dock1,2,3)上云,pilot上云

0 阅读12分钟

开源地址

运营前端:github.com/guipie/dji_…

运营后端:github.com/guipie/dji_…

基于大疆官方给出了上云 API,但没有给出可用的服务端:云司空 2 闭源,而开源的 Cloud-API-Demo 已于 2025-04-10 停止维护,所以目前打算把生产上已经持续运行的两年,基于Cloud-API-Demo二开的全部重构。

为何摒弃Java,因为不喜欢。

image.png

image.png

前端

功能一览

模块路径能力
机场管理views/main/djiDock机场台账、在线状态、指令下发(开舱 / 启停)
设备管理views/main/djiDevice无人机 / 遥控器台账与子设备挂载关系
设备字典views/main/djiDeviceEnum
djiDeviceCameraEnum
机型 / 相机型号字典维护(22 种机型 + 48 种相机)
在线设备views/main/djiDeviceOnline实时在线总览
飞行区域views/main/djiFlightAreaCesium 三维绘制作业区 / 禁飞区,见下方专节
航线管理views/main/djiWayline航线文件(WPML/KMZ)台账、任务下发与进度
直播views/main/djiLive对接自建 SRS,flv.js / hls.js 拉流播放
媒体文件views/main/djiMedia机场与无人机拍摄的媒体清单与下载
HMS 告警views/main/djiHms设备健康告警分级查看
空域感知views/main/djiAirSense附近载人飞机告警
远程日志views/main/djiLog设备上报的日志文件清单与解析
OTA 升级views/main/djiOta固件升级任务下发与结果
工作空间views/main/djiWorkspace
djiWorkspaceUser
多租户空间与成员管理

示例-飞行区域绘制

views/main/djiFlightArea 是最能体现本项目价值的部分 —— 在 Cesium 三维地球上直接画出作业区与禁飞区,

并导出成 DJI 设备可以识别的格式。

能力说明
两种区域类型作业区(dfence,圈内可飞) / 禁飞区(nfz,圈外可飞)
两种几何多边形(3~255 顶点)与圆形(中心点 + 半径)
三维绘制左键逐点打个多边形,点「完成」闭合;圆形支持拖拽定半径
量算基于 @turf/turf 实时算面积 / 周长 / 顶点数
导入导出导出 DJI 标准 FeatureCollection(GeoJSON),可直接进上云流程
后端校验半径 ≥ 10m、顶点数、是否闭合、坐标范围 —— 违规逐条给出中文原因

相关实现:src/utils/cesium/flyZone.ts、src/utils/cesium/index.ts、后端

DjiFlyZoneService(提供 exportDji 接口生成设备侧格式)。

多内核切换注意:Cesium 的 viewer 在全局复用。从其他 Cesium 页面跳到飞行区时,

若复用了绑定在已销毁容器上的旧 viewer,会得到一张空白地图。

当前实现按 viewer.container === 当前容器元素 判定,不一致就先销毁再重建。


技术选型

维度选型备注
框架Vue 3.5 + TypeScript 5.9组合式 API,<script setup>
构建Vite 7含 CDN 外链、gzip、devtools 插件
UIElement Plus 2.11 + UnoCSS组件库 + 原子化 CSS
状态Pinia 3 + 持久化插件登录态、偏好设置持久化
三维地图Cesium 1.136飞行区绘制、航线预览
图表ECharts 5.4 + echarts-gl + 词云数据看板
视频flv.js / hls.js自建 SRS 拉流,低延迟用 HTTP-FLV
实时@microsoft/signalr订阅后端 publicclientmessage
加密sm-crypto-v2登录密码 SM2 国密加密
几何@turf/turf面积 / 周长量算

目录结构

dji_vue/
├── .env / .env.development / .env.production   # 环境变量(三个都已入库)
├── src/
│   ├── api-services/         # 由后端 Swagger 自动生成的 TS SDK(含少量二开改动)
│   ├── api/main/             # 业务接口封装(按模块拆分)
│   ├── views/main/           # 业务页面
│   │   └── djiFlightArea/    #   飞行区绘制(Cesium)
│   ├── utils/
│   │   └── cesium/           #   Cesium 初始化、绘制、交互、飞行区、航线工具
│   ├── stores/               # Pinia store
│   ├── layout/ 、router/ 、theme/ 、i18n/
│   └── assets/
├── demos/                    # 效果截图
└── vite.config.ts            # 含 /api 代理到 VITE_API_URL

快速开始

环境要求

组件版本
Node.js18+(建议 20 LTS 或更高)
包管理器npm / pnpm / yarn 均可

安装启动

git clone https://github.com/guipie/dji_vue.git
cd dji_vue

npm install          # 或 pnpm install / yarn
npm run dev          # 默认 http://localhost:8888

打包与代码风格:

npm run build        # 产物在 dist/
npm run lint-fix     # ESLint 修复

连接后端

Vite 已配置 /api 代理,指向环境变量里的后端地址:

# .env.development
VITE_API_URL = http://localhost:5005

因此前端页面里直接请求 /api/xxx 即可,跨域由代理解决;生产环境用 Nginx 转发同样路径。

登录与 SM2 加密(重要)

后端要求登录密码先用 SM2 公钥加密再传输,公钥必须与后端一致:

# .env.development
VITE_SM2_PUBLIC_KEY = 04xxxxxxxx...(130 位十六进制)

取值来自后端 Dji.Application/Configuration/App.json → Cryptogram.PublicKey。

⚠️ 错配最难查的症状:登录恒失败,服务端只报「账号或密码错误」,

没有任何线索指向密钥不匹配。如果换了部署环境,第一时间核对这一项。

生成方法见 dji_server README 的安全须知。


环境变量

变量说明默认值
VITE_PORT开发服务器端口8888
VITE_OPENdev 时自动打开浏览器false
VITE_API_URL后端地址,被 /api 代理到此处http://localhost:5005
VITE_SM2_PUBLIC_KEY登录密码 SM2 加密公钥空(必填)
VITE_PUBLIC_PATH打包后的资源前缀空
VITE_OPEN_CDN打包是否改用 CDN 外链资源false

.env / .env.development / .env.production 有意纳入版本管理(便于开箱即用),

但不要在里面填真实密钥 —— 私密值请放 .env.local(已被 .gitignore 忽略)。


后端接口约定

对 dji_server 的三个关键约定(前端 SDK 已适配,写新接口时注意):

1. 统一响应信封不是 {code, data}

{ "code": 200, "type": "success", "message": "", "result": { "业务数据" } }

业务数据在 result,成功判据是 code === 200。

2. 路由是小驼峰动态 API

/api/{服务类名去掉Service}/{动作名},且默认是 POST:

后端方法前端路径
SysAuthService.UserInfoGET /api/sysAuth/userInfo
DjiFlyZoneService.ExportDjiPOST /api/djiFlyZone/exportDji

3. 实时走 SignalR,不是一个事件一个业务

connection.on('publicclientmessage', (raw) => {
  const env = JSON.parse(raw);
  switch (env.method) {
    case 'dockOsd':  /* 机场 OSD */
    case 'droneOsd': /* 无人机 OSD */
    case 'hms':      /* 健康告警 */
  }
});

⚠️ 服务端按 workspace 全量推送(100 台 × 0.5Hz ≈ 50 msg/s),必须做聚合节流,否则页面必卡。


后端

功能一览

模块能力
设备物模型机场 / 无人机 / 遥控器 / 负载的上下线拓扑(thing/product/{sn}/state)与 OSD 订阅
航线任务航线文件上传(WPML/KMZ)、任务下发、进度回传、任务调度(含自动派发 Job)
直播对接自建 SRS,直播启停、路数配额、会话超时回收
媒体文件机场 / 无人机拍摄的媒体清单与下载,支持 OSS 对象存储
HMS 健康告警设备健康管理系统告警的接收、落库、分级与推送
空域感知AirSense 告警(附近有载人飞机)接收与转发
飞行区 / 禁飞区作业区(dfence)与禁飞区(nfz)的增删改查,并导出大疆格式 FeatureCollection
远程日志 / OTA设备日志上报、固件升级任务下发
多租户工作空间按 workspace 隔离设备与数据,用户可归属多个空间
系统管理用户 / 角色 / 菜单 / 字典 / 任务调度 / 操作审计(Admin.NET 体系)

部署必备组件

按需部署:最小化(仅设备拓扑 + 航线任务)只要 MQTT Broker + 数据库;启用直播 / 多副本 / OSS 时再补齐其余组件。

组件等级用途推荐选型
MQTT 消息服务器必备设备上下行通道:物模型、OSD、指令、DRCEMQX / Mosquitto(任意 MQTT 3.1.1/5)
数据库必备状态、OSD、HMS、媒体清单、航线、飞行区全量落库MySQL / SQL Server / PostgreSQL / Oracle / SQLite;国产库:达梦 DM、人大金仓 KingbaseES、南大通用 GBase、华为 GaussDB 等(SqlSugar 全适配,CodeFirst 建表)
NTP 时间服务器强烈建议机场与云端时钟同步,偏差 >30s 立即任务会被拒(报错无时钟线索)内网自建 NTP;外网可直接用云厂商公共 NTP:阿里云 ntp.aliyun.com、腾讯云 time1.cloud.tencent.com、国家授时中心 ntp.ntsc.ac.cn
Redis推荐(多副本必备)缓存、JWT 黑名单、限流;SignalR 横向扩展 backplaneRedis 6+ / 哨兵集群;单副本可切 Cache.json → Memory 不用 Redis
SRS 直播服务器按需(直播)RTMP 推流 / FLV·HLS 拉流SRS 4.x / 5.x;Dji.json → Live.Enabled = false 可完全不部署
对象存储按需(媒体 / KMZ)媒体文件、KMZ 航线、固件包持久化Minio(自建) / 阿里云 OSS / 七牛云 / 腾讯云 COS / 华为云 OBS;未配置 OSS 时自动回退服务端本地存储,零依赖起步

云存储「未配置即本地」规则:Upload.json → OSSProvider 留空时走 Dji.Web.Entry/wwwroot 下的本地上传目录,无需额外部署;联调 / 生产再切到 Minio 或任意云 OSS。


技术选型

层次选型说明
运行时.NET 10全仓目标框架 net10.0
Web 框架整体更名为 Dji.* 命名空间,可随仓库二次修改
ORMSqlSugarCodeFirst,SQLite 开箱即用,可切 MySQL / PostgreSQL 等
MQTTMQTTnet作为 Broker 客户端,订阅大疆物模型主题
实时推送SignalR/hubs/onlineUser,token 走 query string
对象映射Mapster比 AutoMapper 更轻,编译期生成
认证JWT + SM2 国密登录密码由前端先用 SM2 公钥加密再传输
缓存内存 / Redis可切换,Redis 模式下支持 SignalR 横向扩展(backplane)

架构

flowchart TB
    subgraph DEV[&#34;端 · 边&#34;]
        DOCK[&#34;大疆机场 Dock&#34;]
        DRONE[&#34;无人机 M30 / M3D / M4D&#34;]
        RC[&#34;遥控器 RC Plus&#34;]
    end

    subgraph MQTTB[&#34;MQTT Broker(EMQX / Mosquitto 等)&#34;]
        B1[&#34;thing/product/{sn}/osd<br/>state / events / requests<br/>drc/up&#34;]
    end

    subgraph SERVER[&#34;dji_server(本项目)&#34;]
        direction TB
        MQTTGW[&#34;MqttService + Mq*Service<br/>物模型编解码&#34;]
        DB[(&#34;SqlSugar<br/>SQLite / MySQL&#34;)]
        REST[&#34;动态 API<br/>/api/{service}/{action}&#34;]
        HUB[&#34;SignalR Hub<br/>publicclientmessage&#34;]
        JOB[&#34;后台任务<br/>任务派发 / 会话清理 / 数据同步&#34;]
    end

    subgraph FE[&#34;前端&#34;]
        ADMIN[&#34;dji_vue 运维端&#34;]
        CONSOLE[&#34;dji-cloud-console 指挥端&#34;]
    end

    DEV -.上行 发布.-> MQTTB
    MQTTB -.订阅.-> MQTTGW
    MQTTGW --> DB
    MQTTGW --> HUB
    MQTTB <-.下行 指令.-> MQTTGW
    REST --> DB
    HUB -->|WebSocket| FE
    REST -->|HTTPS| FE

数据流要点

  1. 上行:设备 → MQTT Broker → 本服务 Mq*Service 解码 → 落库 + 推 SignalR → 前端
  2. 下行:前端 HTTP 调 /api/djiDock/execute 等 → 服务端编码 → 发布到 thing/product/{sn}/services → 设备
  3. 前端永远不直连 MQTT:Broker 凭证、AppKey / AppLicense 都只在服务端

目录结构

dji_server/
├── Dji.Server.sln
├── Dji.Web.Entry/          # 启动入口(Program.cs、wwwroot、db、Dockerfile)
├── Dji.Web.Core/           # Web 层(Startup、中间件、Swagger、鉴权)
├── Dji.Application/        # 业务层
│   ├── Cloud/              #   大疆物模型:Mq*Service(按 Topic 域拆分)
│   ├── Service/            #   业务 Service(自动暴露为动态 API)
│   ├── Configuration/      #   全部配置项(17 个 json)
│   ├── SeedData/           #   种子数据
│   └── Job/                #   后台定时任务
├── Dji.Core/               # 核心层(实体、仓储、授权、SignalR、工具类)
│   ├── Entity/DjiEntity/   #   21 张业务表
│   └── Util/GM/            #   SM2 国密加解密
└── Plugins/
    ├── Dji.Pure/                               # Pure 源码
    ├── Dji.Pure.Extras.DependencyModel.CodeAnalysis/
    ├── Dji.Extras.Authentication.JwtBearer/
    ├── Dji.Extras.ObjectMapper.Mapster/
    ├── Dji.Plugin.Elsa/                        # 工作流
    └── Dji.Plugin.GoView/                      # 大屏设计

快速开始

环境要求

组件版本备注
.NET SDK10.0+dotnet --list-sdks 确认
数据库SQLite(默认)/ MySQL 5.7+SQLite 零配置,CodeFirst 自动建表
MQTT Broker任意支持 MQTT 3.1.1/5 的EMQX / Mosquitto;联调真机才需要
SRS 媒体服务器4.x / 5.x只有需要直播才要

1. 准备配置

Dji.Application/Configuration/ 目录已被 .gitignore 排除(避免真实凭证入库), 所以 clone 下来是空的。请从模板复制:

cp docs/config-template/*.json Dji.Application/Configuration/

然后至少填写这几项(详见下方「配置详解」):

  • Dji.json → AppId / AppKey / AppLicense(大疆开发者凭证)
  • Dji.json → Mqtt.Server / Username / Password
  • App.json → Cryptogram.PublicKey / PrivateKey(必须重新生成,见安全须知)
  • JWT.json → IssuerSigningKey(必须替换)

2. 启动

dotnet build Dji.Server.sln
dotnet run --project Dji.Web.Entry

首次启动会自动 CodeFirst 建表并写入种子数据。看到下面这行即启动成功:

Now listening on: http://0.0.0.0:5005

删库重来前先确认种子开关。 Database.json 主库的 SeedSettings.EnableInitSeed 为 false 时 只 CodeFirst 建表、不写种子数据,全新库会得到一个没有菜单、没有空间的空壳系统。 需要重建基础数据(账号、菜单、设备 / 载荷型号、默认工作空间及其用户归属)时,把它改成 true 跑一次, 数据落库后可以改回 false。 种子是「按主键 insert-or-update」:保持 true 的好处是缺失的行每次启动都会被自动补回来, 代价是手工改过的菜单标题 / 排序也会被拉回种子里的值。

3. 验证

端点预期
http://localhost:5005/kapiSwagger UI(注意是 kapi,不是 swagger)
GET /api/sysAuth/captcha返回信封 { "code": 200, ..., "result": { "id": ..., "img": ... } }

4. 登录

种子数据内置了系统账号(密码统一为 123456,生产环境务必首次登录后立即修改):

账号角色
superadmin超级管理员
admin系统管理员

登录时密码必须先用 SM2 公钥加密后再 POST,不能传明文。两个前端都已内置该逻辑。

5. 默认工作空间

初始化会建一个名为「默认空间」的工作空间(WorkspaceId = default),种子账号全部归属它,且被设为各自的默认空间。

后续新建的账号不必手工去「空间成员」里挂:首次拉取自己的空间列表时(/api/djiWorkspaceUser/My) 若发现该用户没有任何空间归属,会自动补进默认空间。原因是设备 / 航线 / 飞行区表上的 WorkspaceId 是 NOT NULL, 业务侧保存时用「当前用户的默认空间」兜底,用户不属于任何空间时这些接口只会抛错。

Docker

Dji.Web.Entry/Dockerfile 只有运行阶段,需要先在宿主机 publish:

dotnet publish Dji.Web.Entry/Dji.Web.Entry.csproj -c Release -o ./publish
docker build -f Dji.Web.Entry/Dockerfile -t dji-server ./publish
docker run -p 5005:5005 dji-server

配置详解

配置位于 Dji.Application/Configuration/,均为 带注释 JSON。

Dji.json — 大疆上云与媒体

{
  "Dji": {
    // 以下三项通过 MQTT 的 config 应答下发给机场
    "AppId": "",        // 大疆开发者 App ID
    "AppKey": "",       // 大疆开发者 App Key
    "AppLicense": "",   // 大疆开发者 App License
    // ⚠️ 地址必须是【机场侧可达】的地址,不能写 localhost
    "FileBaseUrl": "",  // KMZ 对外访问前缀
    "NtpServerHost": "", // 强烈建议配:机场与云端时钟偏差过大会让立即任务被拒(30s 容差)
    "Live": {
      "Enabled": true,
      "RtmpPushBaseUrl": "rtmp://<SRS_HOST>:1935/live",  // 机场推流(不能被复写浏览器)
      "FlvPlayBaseUrl":  "http://<SRS_HOST>:8080/live",  // 浏览器拉流,低延迟首选
      "HlsPlayBaseUrl":  "http://<SRS_HOST>:8080/live",  // 兼容性兜底
      "MaxSessionMinutes": 120
    }
  },
  "Mqtt": {
    "Server": "<BROKER_HOST>",
    "Port": 1883,
    "SubscribedTopics": [
      "sys/product/+/status",
      "thing/product/+/osd",
      "thing/product/+/state",
      "thing/product/+/events",
      "thing/product/+/drc/up",
      "..."
    ]
  }
}

NTP 是第一个要填的坑:机场与云端时钟偏差超过 30 秒,立即任务会被直接拒绝, 且报错信息看不出是时钟问题。

App.json — 动态 API 与国密

{
  "Urls": "http://0.0.0.0:5005",
  "DynamicApiControllerSettings": {
    "AsLowerCamelCase": true,   // 决定 /api/sysAuth/userInfo 这种小驼峰路由
    "KeepVerb": false
  },
  "Cryptogram": {
    "CryptoType": "SM2",        // 登录密码加密算法
    "PublicKey":  "<130位hex>", // 与前端共用:前端加密、服务端解密
    "PrivateKey": "<64位hex>"   // ⛔ 绝对不能进版本库
  }
}

其余配置

文件用途
Database.json主库 + 日志库连接串,支持 SQLite / MySQL / PostgreSQL / Oracle 等
JWT.jsonJWT 签发参数(密钥、签发方、有效期容错)
Cache.json缓存类型(Memory / Redis)、Redis 连接串、SignalR backplane
Swagger.json文档分组与访问策略
Upload.json本地上传限制与 OSS(Minio / 阿里云 / 七牛 / 腾讯云 / 华为云)
Email.json / Sms.json / Wechat.json / OAuth.json消息与第三方登录
Captcha.json / Limit.json / Logging.json / CodeGen.json / Enum.json验证码、限流、日志、代码生成、枚举

MQTT 物模型

订阅主题遵循大疆标准,单设备序列号用 + 通配:

主题方向说明
sys/product/{sn}/status上行设备拓扑 / 子设备挂载关系变化
thing/product/{sn}/osd上行高频 OSD(机场 + 无人机)
thing/product/{sn}/state上行设备上下线拓扑、固件版本、能力集
thing/product/{sn}/events上行HMS 告警、AirSense、飞行任务事件
thing/product/{sn}/requests上行设备主动请求(如请求云端下发设备能力配置)
thing/product/{sn}/services_reply上行指令应答(含 tid / bid 用于关联)
thing/product/{sn}/drc/up上行DRC 模式下的链路状态
thing/product/{sn}/services下行云端下发指令
thing/product/{sn}/drc/down下行DRC 杆量与控制指令

对应的处理服务在 Dji.Application/Cloud/: MqDeviceService / MqDockControlService / MqLiveService / MqWaylineService / MqMediaService / MqHmsService / MqAirSenseService / MqOtaService / MqLogService / MqOrgService。