# jyt-device-api **Repository Path**: wei/jyt-device-api ## Basic Information - **Project Name**: jyt-device-api - **Description**: 捷易通门禁设备通迅协议包装成http接口 - **Primary Language**: Rust - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-12 - **Last Updated**: 2026-08-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jyt-device-api ## 介绍 捷易通门禁设备通迅协议包装成http接口 ![snapshort](snapshort.png) ## 软件架构 本项目基于rust的Poem web框架,最终编译成可执行文件,作为独立进程运行,连接mqtt broker,把捷易通门禁设备mqtt通迅协议转成RESTful API ```mermaid flowchart LR subgraph 设备端 D1[门禁设备 1] D2[门禁设备 2] D3[门禁设备 N] end subgraph 服务端 API[device-api\nRESTful API 服务] end subgraph 应用端 APP[移动端 APP] WEB[管理系统 Web] end MQTT((MQTT Broker)) D1 -->|MQTT 协议| MQTT D2 -->|MQTT 协议| MQTT D3 -->|MQTT 协议| MQTT MQTT -->|MQTT 协议| D1 MQTT -->|MQTT 协议| D2 MQTT -->|MQTT 协议| D3 API <-->|MQTT 协议| MQTT APP -->|HTTP/HTTPS| API WEB -->|HTTP/HTTPS| API ``` ## 安装教程 ### Windows安装 - 从右边`发行版`下载最新的压缩包,解压后用文本编辑器打开`config.toml`,修改mqtt连接设置,连接到已经安装好的mqtt broker(推荐emqx) - 直接双击运行device-api.exe, 如果窗口一闪而过可能程序出错或端口被占用了,可以进入cmd命令行cd到当前目录,然后运行`.\device-api.exe`可以看到更多信息,如果端口被占用修改`config.tom`的`server-port`配置即可 - 接口服务运行起来后,可以通过浏览器访问`http://localhost:8080/docs`查看接口文档(8080是默认端口,如果你修改成自己的端口要做相应修改) ### Docker Compose安装 - 确保已安装 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/) - 创建工作目录并进入 ```bash mkdir -p /opt/device-api && cd /opt/device-api ``` - 新建 `docker-compose.yml` ```yaml services: device-api: image: crpi-m0mj5odq3iiszp9j.cn-shenzhen.personal.cr.aliyuncs.com/jyd-access/device-api:latest container_name: device-api restart: always ports: - "8080:8080" volumes: - ./config.toml:/app/data/config.toml - ./data:/app/data ``` - 在同目录下新建 `config.toml`,参考以下内容修改(重点是 `[mqtt]` 段连接到你自己的 MQTT Broker): ```toml [server] host = "0.0.0.0" port = 8080 [mqtt] host = "你的MQTT Broker地址" port = 1883 username = "你的用户名" password = "你的密码" keep_alive = 60 [device] default_timeout = 6 qr_url = "" ``` - 启动服务 ```bash docker compose up -d ``` - 验证运行 ```bash # 查看日志,确认启动正常 docker compose logs -f # 访问接口文档 curl http://localhost:8080/docs ``` - 常用命令 ```bash docker compose down # 停止并删除容器 docker compose restart # 重启服务 docker compose pull # 拉取最新镜像后重新 up -d ``` ## 使用说明 ### 配置设备连接 开发时可以这样配置设备连接:双击设备屏幕,输入管理密码(默认`123456`),进入`通迅设置-服务器地址`,输入设备接口服务地址:`http://你的ip地址:端口(默认8080)/mqtt-info` ### 给设备绑定管理员 绑定管理员是设备首次使用的必要步骤,绑定后该账号即为设备的超级管理员(user_type=0),后续所有设备操作(开门、用户管理等)都需要传入此账号作为 `admin_acct` 参数 - **前置条件**:设备需要先进入绑定模式。在设备屏幕上操作:双击屏幕 → 输入管理密码(默认`123456`)→ 进入绑定/配网界面 - **调用接口**:`POST /devices/{mac}/wifi` ```json { "user_acct": "admin001", "door_name": "大门", "ssid": "", "password": "", "app_id": "", "timeout": 10 } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `user_acct` | string | 是 | 管理员账号,绑定后作为所有操作的 admin_acct | | `door_name` | string | 是 | 门名称,显示在设备上 | | `ssid` | string | 否 | 设备连接的 WiFi 名称,不传时传空字符串 | | `password` | string | 否 | WiFi 密码,不传时传空字符串 | | `app_id` | string | 否 | 应用ID | | `timeout` | number | 否 | 设备通讯超时(秒),不传则使用配置文件默认值 | - **示例请求**: ```bash curl -X POST http://localhost:8080/devices/AABBCCDDEEFF/wifi \ -H "Content-Type: application/json" \ -d '{ "user_acct": "admin001", "door_name": "大门" }' ``` - **注意事项**: - 超级管理员(user_type=0)只能通过此绑定接口产生,不能通过添加用户接口创建 - 绑定成功后设备会自动重启并连接 WiFi 和 MQTT Broker - 绑定管理员之前设备必须进入绑定模式 ### 设备参数设置 **读取参数**:`GET /devices/{mac}/params?admin_acct=xxx` 示例: ```bash curl "http://localhost:8080/devices/AABBCCDDEEFF/params?admin_acct=admin001" ``` 返回示例(`time_open` 和 `normal_open` 已从设备内部的十六进制自动转为结构化对象): ```json { "door_name": "大门", "door_ip": "192.168.1.23", "door_mac": "8001F4300230", "face_enable": "1", "finger_enable": "0", "iccard_enable": "1", "allow_remote_open": "6", "allow_qr_open": "1", "open_delay": "5", "similarity": "82", "version": "6.9.2-MJ1-00-21-10-20260815", "time_open": [ { "enable": true, "start_date": "2026-08-17", "end_date": "2038-01-01", "time1_start": "08:00", "time1_end": "12:00", "time2_start": "14:00", "time2_end": "18:00", "time3_start": "00:00", "time3_end": "00:00", "weeks": [1, 2, 3, 4, 5] } ], "normal_open": { "enable": true, "start_date": "2026-08-17", "end_date": "2038-01-01", "time1_start": "08:00", "time1_end": "12:00", "time2_start": "14:00", "time2_end": "18:00", "time3_start": "00:00", "time3_end": "00:00", "weeks": [1, 2, 3, 4, 5], "floors": [] } } ``` **参数说明:** | 字段 | 类型 | 取值/范围 | 说明 | |------|------|-----------|------| | `door_name` | string | 任意字符串 | 门名称 | | `door_type` | string | 位标志整数 | 门类型,bit3=1 时为梯控设备 | | `door_status` | string | `1`=启用,`2`=冻结 | 门状态 | | `door_open_mode` | string | `0`=任一有效即可开门,`1`=有效期优先 | 时间组/有效期开门模式 | | `door_qr_code` | string | 最长30字符 | 门二维码 | | `face_enable` | string | `0`=关闭,`1`=开启 | 人脸识别 | | `finger_enable` | string | `0`=关闭,`1`=开启 | 指纹识别 | | `iccard_enable` | string | `0`=关闭,`1`=开启 | IC卡识别 | | `allow_qr_open` | string | `0`=关闭,`1`=开启 | 二维码开门 | | `allow_remote_open` | string | 位标志整数,见下方说明 | 远程开门权限 | | `enable_stranger_pass` | string | `0`=关闭,`1`=开启 | 陌生人通行 | | `enable_stranger_snapshot` | string | `0`=关闭,`1`=开启 | 陌生人抓拍 | | `common_psw` | string | 任意字符串 | 公共密码 | | `common_psw_enable` | string | `0`=关闭,`1`=开启 | 是否启用公共密码 | | `open_delay` | string | 整数,最小 `1` | 开门延时(秒) | | `similarity` | string | 整数,百分比(如 `70`) | 人脸相似度阈值 | | `liveness_detection` | string | 整数,百分比(如 `60`) | 活体检测阈值 | | `wiegan_type` | string | `1`=WG26,`2`=WG34 | 韦根输出类型 | | `timezone_offset` | string | 整数(秒),如 `28800` = UTC+8 | 时区偏移,正值表示比UTC快 | | `time_open` | array | 10组时间段对象 | 开门时间组,见下方说明 | | `normal_open` | object | 时间段对象 | 常开时间段配置,见下方说明 | | `door_ip` | string | - | 设备IP地址(只读) | | `door_mac` | string | - | 设备MAC地址(只读) | | `door_port` | string | - | 设备端口(只读) | | `channel` | string | - | 通道号(只读) | | `sim_ccid` | string | - | SIM卡CCID(只读) | | `version` | string | - | 固件版本号(只读) | **allow_remote_open 位标志说明:** 该字段是一个整数,用位运算表示不同的远程开门权限: | 位 | 值 | 含义 | |----|------|------| | bit1 | 2 | 远程开门权限1 | | bit2 | 4 | 远程开门权限2 | 例如:`6`(二进制 `110`)= 同时开启权限1和权限2;`0` = 全部关闭。 **time_open 时间段说明(10组,每组):** | 字段 | 类型 | 说明 | |------|------|------| | `enable` | bool | 是否启用该时间段 | | `start_date` | string | 开始日期,格式 `YYYY-MM-DD` | | `end_date` | string | 结束日期,格式 `YYYY-MM-DD` | | `time1_start` / `time1_end` | string | 时间段1,格式 `HH:MM` | | `time2_start` / `time2_end` | string | 时间段2,格式 `HH:MM` | | `time3_start` / `time3_end` | string | 时间段3,格式 `HH:MM` | | `weeks` | array | 生效星期,1~7 表示周一~周日,如 `[1,2,3,4,5]` 为工作日 | **normal_open 常开配置说明:** 与 `time_open` 字段相同,额外多一个 `floors`(楼层数组,1~64,仅梯控设备使用)。 **设置参数**:`PUT /devices/{mac}/params?admin_acct=xxx` 设备要求所有可设置字段都必须传入。推荐的使用流程:先调用 GET 读取当前参数,修改需要变更的字段,然后将完整的对象传回。`time_open` 和 `normal_open` 直接传结构化对象,接口会自动转为设备所需的十六进制格式: ```bash curl -X PUT "http://localhost:8080/devices/AABBCCDDEEFF/params?admin_acct=admin001" \ -H "Content-Type: application/json" \ -d '{ "door_name": "公司大门", "door_type": "6", "door_status": "0", "door_open_mode": "0", "door_qr_code": "8001F4300230", "face_enable": "1", "finger_enable": "0", "iccard_enable": "1", "allow_qr_open": "1", "allow_remote_open": "6", "enable_stranger_pass": "0", "enable_stranger_snapshot": "1", "common_psw": "7890", "common_psw_enable": "1", "open_delay": "5", "similarity": "82", "liveness_detection": "45", "wiegan_type": "0", "timezone_offset": "28800", "time_open": [ { "enable": true, "start_date": "2026-08-17", "end_date": "2038-01-01", "time1_start": "08:00", "time1_end": "12:00", "time2_start": "14:00", "time2_end": "18:00", "time3_start": "00:00", "time3_end": "00:00", "weeks": [1, 2, 3, 4, 5] } ], "normal_open": { "enable": true, "start_date": "2026-08-17", "end_date": "2038-01-01", "time1_start": "08:00", "time1_end": "12:00", "time2_start": "14:00", "time2_end": "18:00", "time3_start": "00:00", "time3_end": "00:00", "weeks": [1, 2, 3, 4, 5], "floors": [] } }' ``` > `door_id` 由接口内部硬编码为 `"0"`,无需传入。 ### 用户特征现场录入 现场录入是指由后台或小程序发起指令,用户直接在设备上完成人脸、指纹或 IC 卡的采集。由于采集过程需要用户在设备上操作(如多次按压指纹),前端需要轮询状态以获取实时进度。 **流程概览:** ``` 1. 调用 POST /devices/{mac}/site-feature 发起录入 → 返回 { code, serialno } 2. 用 serialno 轮询 GET /devices/{mac}/site-feature/status 获取进度 3. code=0 时录入完成,停止轮询 ``` **第一步:发起录入** `POST /devices/{mac}/site-feature?admin_acct=xxx` ```json { "user_id": 5, "feature_type": 4 } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `user_id` | number | 是 | 设备内用户ID | | `feature_type` | number | 是 | 特征类型:`1`=IC卡,`2`=指纹,`4`=人脸 | 返回: ```json { "code": "0", "serialno": "abc123xyz" } ``` | 字段 | 说明 | |------|------| | `code` | 首次响应码,`0` 表示设备已就绪,`300` 表示设备正在等待用户操作 | | `serialno` | 本次录入的序列号,用于后续轮询状态 | **第二步:轮询录入状态** `GET /devices/{mac}/site-feature/status?serialno=xxx` 建议轮询间隔 1~2 秒,超时上限 15 秒无新状态则停止。返回示例: ```json { "code": "300", "action": "site_finger" } ``` ```json { "code": "301", "action": "site_finger", "idx": 2, "count": 3 } ``` ```json { "code": "0", "action": "site_face" } ``` **设备状态码说明:** | code | 含义 | 处理方式 | |------|------|----------| | `0` | 录入完成 | 停止轮询,刷新用户信息 | | `300` | 设备正在等待用户操作 | 继续轮询,提示用户在设备上操作 | | `301` | 需要再次采集(仅指纹) | 继续轮询,提示用户再按一次(`idx`/`count` 表示第几次/共几次) | | `302` | 录入超时 | 停止轮询,提示用户超时 | | `303` | 特征已存在,录入完成 | 停止轮询,提示特征已被覆盖 | **完整示例:** ```bash # 1. 发起人脸录入 curl -X POST "http://localhost:8080/devices/AABBCCDDEEFF/site-feature?admin_acct=admin001" \ -H "Content-Type: application/json" \ -d '{ "user_id": 5, "feature_type": 4 }' # 返回: { "code": "0", "serialno": "V1StGX" } # 2. 轮询状态(每隔1~2秒调用一次,直到 code=0) curl "http://localhost:8080/devices/AABBCCDDEEFF/site-feature/status?serialno=V1StGX" # 返回: { "code": "300", "action": "site_face" } → 继续轮询 # 返回: { "code": "0", "action": "site_face" } → 录入完成,停止轮询 ``` > 状态缓存有效期为 600 秒,过期后轮询接口返回 `null`。 ### 读取开门记录 读取设备上的开门记录,支持按日期范围、用户名、用户ID筛选和分页。 **读取记录** `GET /devices/{mac}/records?admin_acct=xxx&start_date=xxx&end_date=xxx` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `start_date` | string | 是 | 起始日期,格式 `YYYY-MM-DD` | | `end_date` | string | 是 | 结束日期,格式 `YYYY-MM-DD` | | `user_name` | string | 否 | 按用户名模糊筛选 | | `user_id` | number | 否 | 按用户ID精确筛选 | | `page` | number | 否 | 页码,默认 `1` | | `size` | number | 否 | 每页条数,默认 `10` | 返回示例: ```json { "code": "0", "data": [ { "id": "12345", "user_id": 5, "user_name": "张三", "user_card": "AABBCCDD", "acess_type": "4", "authority_type": "0", "record_time": "2026-08-17 09:30:00", "has_snapshot": "0" }, { "id": "12346", "user_id": 0, "user_name": "", "user_card": "", "acess_type": "4", "authority_type": "1", "record_time": "2026-08-17 09:35:00", "has_snapshot": "1" } ] } ``` **记录字段说明:** | 字段 | 类型 | 说明 | |------|------|------| | `id` | string | 记录ID,用于获取抓拍图片 | | `user_id` | number | 设备内用户ID,`0` 表示陌生人 | | `user_name` | string | 用户姓名,陌生人为空 | | `user_card` | string | 卡号 | | `acess_type` | string | 开门类型,见下表 | | `authority_type` | string | 开门状态:`0`=成功,`1`=失败 | | `record_time` | string | 开门时间 | | `has_snapshot` | string | 是否有人脸抓拍:`1`=有,其他=无 | **acess_type 开门类型:** | 值 | 含义 | 参与考勤 | |----|------|----------| | `0` | 刷卡开门 | 是 | | `1` | 手机远程 | - | | `2` | 手机临时密码 | - | | `3` | 手机通用密码 | - | | `4` | 人脸开门 | 是 | | `5` | 临时密码键盘开门 | - | | `6` | 通用密码键盘开门 | - | | `7` | 临时钥匙 | - | | `8` | 后台远程 | - | | `9` | 指纹开门 | 是 | | `11` | 近距离开门 | 是 | | `12` | 蓝牙开门 | 是 | > "参与考勤"表示该开门类型会被纳入考勤统计(如上下班打卡)。 **人脸抓拍** 当记录的 `has_snapshot` 为 `"1"` 时,说明设备保存了该次开门的人脸抓拍图片。设备需要先开启人脸抓拍功能(设备参数 `enable_stranger_snapshot` 设为 `"1"`),否则不会产生抓拍。 获取抓拍图片: `GET /devices/{mac}/snapshots/{record_id}?admin_acct=xxx` 返回示例: ```json { "code": "0", "snapshot_base64": "/9j/4AAQSkZJRgABAQEASABIAAD..." } ``` | 字段 | 说明 | |------|------| | `code` | `0` 表示成功 | | `snapshot_base64` | JPEG 图片的 Base64 编码,可直接用于 `` 的 `src` 属性 | **完整示例:** ```bash # 1. 读取开门记录 curl "http://localhost:8080/devices/AABBCCDDEEFF/records?admin_acct=admin001&start_date=2026-08-01&end_date=2026-08-17&page=1&size=20" # 2. 获取人脸抓拍图片(当 has_snapshot 为 "1" 时) curl "http://localhost:8080/devices/AABBCCDDEEFF/snapshots/12346?admin_acct=admin001" # 返回的 snapshot_base64 可直接用于前端显示: # ``` ### 固件升级 固件升级分为三个步骤:获取设备信息、查询可用固件、发送升级指令。 **第一步:获取设备状态** `GET /devices/{mac}/status?admin_acct=xxx` 返回设备当前的状态信息,其中 `dev_type` 和 `factory` 用于查询可用固件列表: ```json { "dev_type": "otajytJSD-X50.05.0-6.9.2-MJ1-00-21-10-20260815", "factory": "BDDDD2D7CDA8C3C5BDFB", "version": "6.9.2-MJ1-00-21-10-20260815", "user_counts": "6", "card_counts": "0", "face_counts": "0", "finger_counts": "0", "password_counts": "0", "record_counts": "12", "door_sta": "0", "csq": "99", "iccid": "", "ts": "1786969570" } ``` | 字段 | 说明 | |------|------| | `dev_type` | 设备类型标识,用于查询固件 | | `factory` | 设备厂商标识,用于查询固件 | | `version` | 当前固件版本号 | | `user_counts` | 用户数量 | | `card_counts` | 卡数量 | | `face_counts` | 人脸数量 | | `finger_counts` | 指纹数量 | | `record_counts` | 开门记录数量 | | `door_sta` | 门状态 | | `csq` | 信号强度 | **第二步:查询可用固件** 使用上一步获取的 `dev_type` 和 `factory`,调用外部固件服务接口: `GET https://access.yefiot.com/api/ota/qry?dev_type=xxx&factory=xxx` 返回示例: ```json { "is_success": true, "result": { "has_next": false, "total": 1, "page": 1, "pages": 1, "items": [ { "id": 30, "key": "X50-0-0-MJ1-1", "name": "ota.JSD-X50.05.0-6.1.0-MJ1-00-21-10-20260415", "md5": "789e5258e9b0533335ba9befb4479d7b", "version": "v6.1.0-20260415", "url": "https://yitoofile.oss-cn-hangzhou.aliyuncs.com/ota/X50-0-0-MJ1-1", "remark": "小哈锁5寸ui0", "create_at": "2026-06-02 08:59:32" } ] } } ``` | 字段 | 说明 | |------|------| | `items[].url` | 固件下载地址 | | `items[].md5` | 固件文件 MD5 校验值 | | `items[].version` | 固件版本号 | | `items[].remark` | 固件备注说明 | **第三步:发送升级指令** `POST /devices/{mac}/ota?admin_acct=xxx` ```json { "url": "https://yitoofile.oss-cn-hangzhou.aliyuncs.com/ota/X50-0-0-MJ1-1", "md5": "789e5258e9b0533335ba9befb4479d7b", "version": "v6.1.0-20260415" } ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `url` | string | 是 | 固件下载地址(从固件列表获取) | | `md5` | string | 是 | 固件 MD5 校验值(从固件列表获取) | | `version` | string | 是 | 固件版本号(从固件列表获取) | 返回: ```json { "code": 200, "message": "固件升级指令已发送" } ``` > 升级指令发送后,设备会从指定 URL 下载固件并自动重启完成升级。 **完整示例:** ```bash # 1. 获取设备状态 curl "http://localhost:8080/devices/AABBCCDDEEFF/status?admin_acct=admin001" # 返回: { "dev_type": "otajytJSD-X50...", "factory": "BDDDD2D7..." } # 2. 查询可用固件 curl "https://access.yefiot.com/api/ota/qry?dev_type=otajytJSD-X50.05.0-6.9.2-MJ1-00-21-10-20260815&factory=BDDDD2D7CDA8C3C5BDFB" # 返回: { "is_success": true, "result": { "items": [{ "url": "...", "md5": "...", "version": "..." }] } } # 3. 发送升级指令 curl -X POST "http://localhost:8080/devices/AABBCCDDEEFF/ota?admin_acct=admin001" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yitoofile.oss-cn-hangzhou.aliyuncs.com/ota/X50-0-0-MJ1-1", "md5": "789e5258e9b0533335ba9befb4479d7b", "version": "v6.1.0-20260415" }' # 返回: { "code": 200, "message": "固件升级指令已发送" } ``` ### 获取设备在线状态 device-api 服务不提供设备在线状态接口,因为不同 MQTT Broker 的查询方式不同。如果使用 **EMQX** 作为 MQTT Broker,可以通过 EMQX 的 HTTP API 查询设备是否在线。 **EMQX 查询接口** `GET {emqx_host}/api/{version}/clients/{client_id}` 其中 `client_id` 是设备连接 MQTT 时使用的客户端 ID,通常为设备的 MAC 地址。 **认证方式**:HTTP Basic Auth(用户名:密码 进行 Base64 编码) ``` Authorization: Basic base64(username:password) ``` **EMQX v4 返回示例:** ```json { "data": [ { "clientid": "AABBCCDDEEFF", "connected": true, "ip_address": "192.168.1.100", "connected_at": 1786969570 } ] } ``` - 在线状态:`data[0].connected`,`true` 表示在线,`false` 表示离线 **EMQX v5 返回示例:** ```json { "clientid": "AABBCCDDEEFF", "connected": true, "ip_address": "192.168.1.100", "connected_at": 1786969570 } ``` - 在线状态:`connected`,`true` 表示在线,`false` 表示离线 **curl 示例:** ```bash # EMQX v5 查询设备在线状态 curl -u "admin:public123" \ "http://emqx.example.com:18083/api/v5/clients/AABBCCDDEEFF" # 返回: { "clientid": "AABBCCDDEEFF", "connected": true, ... } ``` **批量查询建议:** 如果需要批量查询多个设备的在线状态,建议使用并发请求以避免串行调用导致响应过慢。Python 示例: ```python import httpx import asyncio import base64 async def get_device_online(emqx_host, version, username, password, mac, timeout=5): """查询单个设备在线状态""" credentials = base64.b64encode(f"{username}:{password}".encode()).decode() headers = {"Authorization": f"Basic {credentials}"} async with httpx.AsyncClient() as client: response = await client.get( f"{emqx_host}/api/{version}/clients/{mac}", headers=headers, timeout=timeout ) response.raise_for_status() data = response.json() if version == 'v4': items = data.get('data', []) return items[0].get('connected', False) if items else False elif version == 'v5': return data.get('connected', False) async def batch_check(devices, emqx_host, version, username, password): """批量并发查询设备在线状态""" tasks = [get_device_online(emqx_host, version, username, password, d['mac']) for d in devices] results = await asyncio.gather(*tasks, return_exceptions=True) for d, online in zip(devices, results): d['online'] = online if isinstance(online, bool) else False return devices ``` ### 配置文件说明(config.toml) 配置文件位于项目根目录 `config.toml`,支持环境变量覆盖(格式:`MQTT_HOST`、`SERVER_PORT` 等)。 ```toml [server] host = "0.0.0.0" # HTTP 服务监听地址 port = 8080 # HTTP 服务监听端口 [mqtt] host = "192.168.1.253" # MQTT Broker 地址(内网/服务端连接用) port = 1883 # MQTT Broker 端口 username = "admin" # MQTT 用户名 password = "password" # MQTT 密码 keep_alive = 60 # 心跳间隔(秒) # public_host = "" # /mqtt-info 返回给设备的外网地址,不设置则使用 host # public_port = 1883 # /mqtt-info 返回给设备的外网端口,不设置则使用 port [device] default_timeout = 6 # 设备通讯默认超时时间(秒) qr_url = "http://example.com?mac={mac}" # 设备上显示的二维码内容 ``` **[server]** | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `host` | string | `"0.0.0.0"` | HTTP 服务监听地址 | | `port` | number | `8080` | HTTP 服务监听端口 | **[mqtt]** | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `host` | string | - | MQTT Broker 内网地址,用于 device-api 连接 | | `port` | number | `1883` | MQTT Broker 端口 | | `username` | string | - | MQTT 用户名 | | `password` | string | - | MQTT 密码 | | `keep_alive` | number | `60` | 心跳间隔(秒) | | `public_host` | string | `""` | 设备端外网访问地址,`/mqtt-info` 接口优先返回此值 | | `public_port` | number | - | 设备端外网访问端口,`/mqtt-info` 接口优先返回此值 | **public_host / public_port 使用场景:** 当 device-api 部署在内网,而设备需要从外网连接 MQTT 时,需要配置外网地址: ```toml [mqtt] host = "192.168.1.100" # device-api 连接 MQTT 用内网地址 port = 1883 public_host = "mqtt.example.com" # 设备从外网连接 MQTT 用外网地址 public_port = 8883 # 外网端口可能与内网不同 ``` 此时调用 `/mqtt-info` 接口会返回: ```json { "code": "0", "mqtt_addr": "mqtt.example.com", "mqtt_port": 8883 } ``` 如果不设置 `public_host`,则 `/mqtt-info` 返回 `host` 的值。 **[device]** | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `default_timeout` | number | `6` | 设备通讯默认超时时间(秒) | | `qr_url` | string | `""` | 设备上显示的二维码内容 | **qr_url 使用场景:** 当设备需要显示二维码供用户扫码时,此配置指定二维码的内容。通常设置为小程序页面地址或 H5 页面地址: ```toml [device] qr_url = "https://example.com/access?mac={mac}" ``` 其中 `{mac}` 会在设备端替换为实际的设备 MAC 地址。 ## 错误代码 接口调用失败时,返回统一的错误响应体: ```json { "code": 464, "message": "操作失败", "device_code": "1" } ``` | 字段 | 说明 | |------|------| | `code` | HTTP 状态码或自定义错误码 | | `message` | 错误描述信息 | | `device_code` | 设备原始错误码(仅设备返回错误时有值) | ### 设备错误码 设备错误码由设备固件返回,`device_code` 字段包含原始错误码。 **通用错误** | 错误码 | 说明 | |--------|------| | `0` | 操作成功 | | `1` | 操作失败 | | `2` | 缺少参数 | | `3` | 记录不存在 | | `4` | 记录已存在 | | `5` | 查询无数据(分页没有下一页) | | `7` | 设备不支持该操作 | | `8` | 厂家编号不匹配 | **权限相关** | 错误码 | 说明 | |--------|------| | `200` | 无操作权限 | | `201` | admin_acct 不存在 | | `202` | 操作密码错误 | | `203` | 账号没有管理权限 | | `204` | 用户 user_id 不存在 | | `205` | 卡、指纹、人脸权限未开放 | | `206` | 管理员名额已满 | | `207` | 用户已失效 | | `208` | 操作不在有效期(如用户开门时不在时间组允许的时间段内) | | `209` | 密码错误 | **设备交互** | 错误码 | 说明 | |--------|------| | `300` | 设备进入状态(如现场录入卡、人脸、指纹时,设备进入录入等待状态) | | `301` | 请再录一次(如录入指纹时需要重复采集多次) | | `302` | 录入超时 | | `303` | 录入的指纹、人脸、卡已存在 | | `401` | 人脸识别失败 | ### API 错误码 API 错误码由 device-api 服务返回,表示服务端或通信层错误。 | HTTP 状态码 | 说明 | |-------------|------| | `400` | 请求参数错误 | | `404` | 设备未注册 | | `408` | 设备通讯超时 | | `464` | 设备返回错误(`device_code` 包含设备原始错误码) | | `475` | 数据解析错误 | | `500` | 系统内部错误 | ## 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request