目录
1. 背景与技术挑战
共享单车的“扫码即走”表面上只需几秒,背后却要在 前端 ⇄ 后端 ⇄ 物联网设备 三点之间完成 身份校验、指令加密、实时通信、状态回写 等一系列链路。
系统需同时满足:
维度 | 典型指标 |
---|---|
⏱ 时延 | < 3 s 完成解锁 |
🔐 安全 | 防伪二维码、HTTPS、指令签名、硬件可信根 |
⚖️ 并发 | 高峰期每秒数万次扫码请求 |
🔋 低功耗 | 智能锁待机 90 天+ |
2. 整体架构鸟瞰
┌──────────┐ HTTPS ┌─────────────┐ MQTT/BLE ┌────────┐
│ App │ ─────────────▼────────────▶ │ 网关/业务层 │ ─────────────▼────────────▶ │ 智能锁 │
└──────────┘ <──JSON───▲───────────────┘ (账户&车辆) └───────────▲───────────────┘
▲ │ │
└────本地蓝牙────┘ └──状态回传───▶ 数据库
-
App:二维码扫描、UI 反馈、蓝牙通道(可选)
-
网关/业务层:统一鉴权、风控、指令下发、MQTT Broker
-
智能锁:蜂窝 / BLE / NB-IoT,多协议支持,控制舵机解锁
3. Step-by-Step 流程深拆
3.1 扫码阶段
sequenceDiagram
User->>App: 扫描二维码
App->>App: 解析 QR 数据 (bike_id, signature…)
-
容错点:二维码污损时,App 可回退到输入车牌号。
3.2 鉴权阶段
App->>Server: POST /api/v1/unlock
Server->>Redis: 查询用户余额 & 信用
Server->>DB: 检查车辆状态
alt 通过
Server-->>App: 200 OK + unlockToken
else 拒绝
Server-->>App: 403 Forbidden
end
3.3 指令下发 & 通信策略
策略 1:蜂窝直连
Server-->>MQTT Broker: PUBLISH /lock/123456/cmd {open:true}
MQTT Broker-->>BikeLock: Message
策略 2:蓝牙中转
Server-->>App: unlockToken & signedCmd
App-->>BikeLock: BLE writeCharacteristic(openCmd)
3.4 解锁执行
-
MCU 驱动电机旋转 720 ms → 卡榫滑动 → 机械锁体打开。
-
完成后把 status=opened 上报。
3.5 状态回传
BikeLock-->>Server: MQTT /lock/123456/status {opened: true, ts: 1719202020}
Server-->>App: WebSocket push "解锁成功"
4. 二维码信息设计
4.1 数据字段
{
"bike_id": "B123456",
"platform": "mobike",
"api": "https://api.mobike.com/unlock",
"ts": 1719201471,
"type": "e-bike",
"sig": "E4821B47..."
}
4.2 签名算法示例(Python)
import hmac, hashlib, base64, time, json
SECRET = b"QR_SIGN_SECRET"
payload = {
"bike_id": "B123456",
"ts": int(time.time())
}
msg = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode()
sig = base64.urlsafe_b64encode(hmac.new(SECRET, msg, hashlib.sha256).digest()).decode()
payload["sig"] = sig
print(payload)
5. 两种通信模式对比
🛰️ 蜂窝直连 | 🔵 蓝牙中转 | |
---|---|---|
用户距离 | 远程可解锁 | 必须贴近车辆 |
设备成本 | ⭐⭐⭐(SIM+射频) | ⭐(BLE 芯片) |
信号依赖 | 基站覆盖 | 手机 + BLE |
典型场景 | 一线城市、电单车 | 停车棚、信号盲区 |
6. 后端核心接口示例(Node.js + MQTT)
6.1 解锁请求
// unlock.controller.js
export async function unlock(req, res) {
const { bikeId } = req.body;
const userId = req.user.id;
// 1. 鉴权
if (!await checkBalance(userId)) return res.status(402).json({ msg: '余额不足' });
// 2. 车辆状态
const bike = await Bike.findById(bikeId);
if (bike.status !== 'idle') return res.status(423).json({ msg: '车辆已被占用' });
// 3. 指令下发 (蜂窝模式示例)
const cmd = { open: true, nonce: Date.now() };
mqttClient.publish(`/lock/${bikeId}/cmd`, JSON.stringify(cmd));
// 4. 记录日志
await RideLog.create({ userId, bikeId, action: 'unlock', cmd });
res.json({ code: 0, msg: '指令已发送' });
}
6.2 MQTT 监听
mqttClient.on('message', async (topic, payload) => {
const [ , bikeId, type ] = topic.split('/'); // /lock/123456/status
if (type === 'status') {
const status = JSON.parse(payload);
await Bike.updateOne({ _id: bikeId }, { status: status.opened ? 'in_use' : 'idle' });
io.to(userSocketMap[bikeId]).emit('unlock-result', status);
}
});
7. 智能锁固件示例片段(Arduino-C)
#include <TinyGsmClient.h>
#include <PubSubClient.h>
#include <Servo.h>
TinyGsm modem(Serial1);
PubSubClient mqtt(modem);
Servo lockServo;
void callback(char* topic, byte* payload, unsigned int len) {
if (strcmp(topic, "/lock/123456/cmd") == 0) {
if (payload[0] == '1') { // open
lockServo.write(180);
delay(700);
lockServo.write(0);
mqtt.publish("/lock/123456/status", "{\"opened\":true}");
}
}
}
void setup() {
modem.restart();
mqtt.setServer("broker.xxx.com", 1883);
mqtt.setCallback(callback);
lockServo.attach(5);
}
8. 客户端 BLE 解锁示例(Flutter)
// 使用 flutter_blue_plus
await flutterBlue.startScan(withServices: [guidLockService]);
flutterBlue.scanResults.listen((results) async {
final r = results.firstWhere((r) => r.device.id.id == targetMac);
await r.device.connect();
var service = await r.device.discoverServices()
.then((s) => s.firstWhere((s) => s.uuid == guidLockService));
var char = service.characteristics
.firstWhere((c) => c.uuid == guidLockCharacteristic);
await char.write(utf8.encode(unlockToken)); // 写入解锁指令
});
9. 安全与高可用实践
-
二维码加密 + 动态盐:二维码被拍照后可被伪造,务必加入过期时间戳和 HMAC。
-
双向 TLS:设备与 MQTT Broker 建立 mTLS,防止中间人。
-
重放攻击防护:指令加入
nonce
与服务器侧 Redis-setNX。 -
灰度发布:智能锁 OTA 分批推送,避免全部离线。
-
多活架构:北京、上海双 IDC + Auto-Route,99.95% SLA。
10. 结语
本文从 二维码设计 → 后端鉴权 → 指令下发 → 设备执行 全链路拆解共享单车扫码解锁技术细节,并提供了 Node.js、Arduino、Flutter 等多端代码示例。希望对正在研发 IoT + 共享出行项目的你有所启发。
🚀 如果文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论交流!
延伸阅读
-
《物联网设备安全白皮书》— 360 攻防实验室
-
《MQTT Essentials》— HiveMQ Blog
-
Designing Data-Intensive Applications — Martin Kleppmann