开源地址
基于大疆官方给出了上云 API,但没有给出可用的服务端:云司空 2 闭源,而开源的 Cloud-API-Demo 已于 2025-04-10 停止维护,所以目前打算把生产上已经持续运行的两年,基于Cloud-API-Demo二开的全部重构。
为何摒弃Java,因为不喜欢。
前端
功能一览
| 模块 | 路径 | 能力 |
|---|---|---|
| 机场管理 | views/main/djiDock | 机场台账、在线状态、指令下发(开舱 / 启停) |
| 设备管理 | views/main/djiDevice | 无人机 / 遥控器台账与子设备挂载关系 |
| 设备字典 | views/main/djiDeviceEnumdjiDeviceCameraEnum | 机型 / 相机型号字典维护(22 种机型 + 48 种相机) |
| 在线设备 | views/main/djiDeviceOnline | 实时在线总览 |
| 飞行区域 | views/main/djiFlightArea | Cesium 三维绘制作业区 / 禁飞区,见下方专节 |
| 航线管理 | 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/djiWorkspacedjiWorkspaceUser | 多租户空间与成员管理 |
示例-飞行区域绘制
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 插件 |
| UI | Element 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.js | 18+(建议 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_OPEN | dev 时自动打开浏览器 | 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.UserInfo | GET /api/sysAuth/userInfo |
DjiFlyZoneService.ExportDji | POST /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、指令、DRC | EMQX / 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 横向扩展 backplane | Redis 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.* 命名空间,可随仓库二次修改 | |
| ORM | SqlSugar | CodeFirst,SQLite 开箱即用,可切 MySQL / PostgreSQL 等 |
| MQTT | MQTTnet | 作为 Broker 客户端,订阅大疆物模型主题 |
| 实时推送 | SignalR | /hubs/onlineUser,token 走 query string |
| 对象映射 | Mapster | 比 AutoMapper 更轻,编译期生成 |
| 认证 | JWT + SM2 国密 | 登录密码由前端先用 SM2 公钥加密再传输 |
| 缓存 | 内存 / Redis | 可切换,Redis 模式下支持 SignalR 横向扩展(backplane) |
架构
flowchart TB
subgraph DEV["端 · 边"]
DOCK["大疆机场 Dock"]
DRONE["无人机 M30 / M3D / M4D"]
RC["遥控器 RC Plus"]
end
subgraph MQTTB["MQTT Broker(EMQX / Mosquitto 等)"]
B1["thing/product/{sn}/osd<br/>state / events / requests<br/>drc/up"]
end
subgraph SERVER["dji_server(本项目)"]
direction TB
MQTTGW["MqttService + Mq*Service<br/>物模型编解码"]
DB[("SqlSugar<br/>SQLite / MySQL")]
REST["动态 API<br/>/api/{service}/{action}"]
HUB["SignalR Hub<br/>publicclientmessage"]
JOB["后台任务<br/>任务派发 / 会话清理 / 数据同步"]
end
subgraph FE["前端"]
ADMIN["dji_vue 运维端"]
CONSOLE["dji-cloud-console 指挥端"]
end
DEV -.上行 发布.-> MQTTB
MQTTB -.订阅.-> MQTTGW
MQTTGW --> DB
MQTTGW --> HUB
MQTTB <-.下行 指令.-> MQTTGW
REST --> DB
HUB -->|WebSocket| FE
REST -->|HTTPS| FE
数据流要点
- 上行:设备 → MQTT Broker → 本服务
Mq*Service解码 → 落库 + 推 SignalR → 前端 - 下行:前端 HTTP 调
/api/djiDock/execute等 → 服务端编码 → 发布到thing/product/{sn}/services→ 设备 - 前端永远不直连 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 SDK | 10.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/PasswordApp.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/kapi | Swagger 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.json | JWT 签发参数(密钥、签发方、有效期容错) |
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。