初始化代码

This commit is contained in:
lpd
2026-01-19 18:54:03 +08:00
commit 423975df96
4386 changed files with 375663 additions and 0 deletions

View File

@@ -0,0 +1,888 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>MQTT WebSocket 测试客户端</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 28px;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px;
margin: 20px;
border-radius: 5px;
}
.info-box h3 {
color: #667eea;
margin-bottom: 10px;
font-size: 16px;
}
.info-box ul {
margin-left: 20px;
color: #666;
font-size: 14px;
line-height: 1.6;
}
.info-box code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
color: #d63384;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
padding: 30px;
}
.panel {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
}
.panel h2 {
color: #667eea;
margin-bottom: 20px;
font-size: 20px;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #333;
font-weight: 500;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
font-family: monospace;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.btn-warning {
background: #ffc107;
color: #333;
}
.btn-warning:hover {
background: #e0a800;
}
.btn-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
.status {
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
font-weight: 500;
}
.status.disconnected {
background: #f8d7da;
color: #721c24;
}
.status.connected {
background: #d4edda;
color: #155724;
}
.status.connecting {
background: #fff3cd;
color: #856404;
}
.log-area {
background: #1e1e1e;
color: #d4d4d4;
padding: 15px;
border-radius: 4px;
height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.6;
}
.log-entry {
margin-bottom: 5px;
}
.log-entry.info {
color: #4ec9b0;
}
.log-entry.success {
color: #6a9955;
}
.log-entry.error {
color: #f48771;
}
.log-entry.warning {
color: #dcdcaa;
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-top: 15px;
}
.stat-item {
background: white;
padding: 15px;
border-radius: 4px;
text-align: center;
}
.stat-item .value {
font-size: 24px;
font-weight: bold;
color: #667eea;
}
.stat-item .label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
@media (max-width: 768px) {
.content {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 MQTT WebSocket 测试客户端</h1>
<p>RuoYi-Vue-Pro IoT 模块 - MQTT over WebSocket 在线测试工具</p>
</div>
<!-- 协议格式说明 -->
<div class="info-box">
<h3>📌 标准协议格式说明</h3>
<ul>
<li><strong>Topic 格式:</strong><code>/sys/{productKey}/{deviceName}/thing/property/post</code></li>
<li><strong>Client ID 格式:</strong><code>{productKey}.{deviceName}</code> 例如:<code>zOXKLvHjUqTo7ipD.ceshi001</code>
</li>
<li><strong>Username 格式:</strong><code>{deviceName}&{productKey}</code> 例如:<code>ceshi001&zOXKLvHjUqTo7ipD</code>
</li>
<li><strong>消息格式Alink 协议):</strong>
<pre style="background: #e9ecef; padding: 10px; border-radius: 5px; margin-top: 5px; overflow-x: auto;">
{
"id": "消息 ID唯一标识",
"version": "1.0",
"method": "thing.property.post",
"params": {
"temperature": 25.5,
"humidity": 60
}
}</pre>
</li>
<li><strong>常用 Topic下行 - 服务端推送):</strong>
<ul style="margin-top: 5px;">
<li>属性设置:<code>/sys/{pk}/{dn}/thing/property/set</code></li>
<li>服务调用:<code>/sys/{pk}/{dn}/thing/service/invoke</code></li>
<li>配置推送:<code>/sys/{pk}/{dn}/thing/config/push</code></li>
<li>OTA 升级:<code>/sys/{pk}/{dn}/thing/ota/upgrade</code></li>
</ul>
</li>
<li><strong>常用 Topic上行 - 设备上报):</strong>
<ul style="margin-top: 5px;">
<li>状态更新:<code>/sys/{pk}/{dn}/thing/state/update</code></li>
<li>属性上报:<code>/sys/{pk}/{dn}/thing/property/post</code></li>
<li>事件上报:<code>/sys/{pk}/{dn}/thing/event/post</code></li>
<li>OTA 进度:<code>/sys/{pk}/{dn}/thing/ota/progress</code></li>
</ul>
</li>
</ul>
</div>
<div class="content">
<!-- 连接配置面板 -->
<div class="panel">
<h2>📡 连接配置</h2>
<div class="status disconnected" id="statusBar">
⚫ 未连接
</div>
<div class="form-group">
<label>服务器地址</label>
<input id="serverUrl" placeholder="ws://host:port/path" type="text" value="ws://localhost:8083/mqtt">
<small style="color: #666; font-size: 12px;">WebSocket 地址,支持 ws:// 和 wss://</small>
</div>
<div class="form-group">
<label>Client ID</label>
<input id="clientId" placeholder="设备客户端 ID" type="text" value="fqTn4Afs982Nak4N.jiali001">
<small style="color: #666; font-size: 12px;">格式:{productKey}.{deviceName}</small>
</div>
<div class="form-group">
<label>Username</label>
<input id="username" placeholder="用户名" type="text" value="jiali001&fqTn4Afs982Nak4N">
<small style="color: #666; font-size: 12px;">格式:{deviceName}&{productKey}</small>
</div>
<div class="form-group">
<label>Password</label>
<input id="password" placeholder="设备密钥"
type="password" value="ae10188f93febbb6b37bd57f463b2a795ae2800fab8933aef75d3c6422873f28">
<small style="color: #666; font-size: 12px;">设备的认证密钥Device Secret</small>
</div>
<div class="btn-group">
<button class="btn btn-success" id="connectBtn" onclick="connect()">🔌 连接</button>
<button class="btn btn-danger" disabled id="disconnectBtn" onclick="disconnect()">🔌 断开</button>
<button class="btn btn-warning" onclick="clearLogs()">🗑️ 清空日志</button>
</div>
<!-- 统计信息 -->
<div class="stats">
<div class="stat-item">
<div class="value" id="sentCount">0</div>
<div class="label">发送消息数</div>
</div>
<div class="stat-item">
<div class="value" id="receivedCount">0</div>
<div class="label">接收消息数</div>
</div>
<div class="stat-item">
<div class="value" id="errorCount">0</div>
<div class="label">错误次数</div>
</div>
</div>
</div>
<!-- 消息发布面板 -->
<div class="panel">
<h2>📤 消息发布</h2>
<div class="form-group">
<label>快捷主题选择(上行消息 - 设备 → 服务端)</label>
<select id="quickPublishTopicSelect" onchange="selectQuickPublishTopic()"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
<option value="">-- 选择上行消息类型 --</option>
<option value="thing.state.update">设备状态更新 (thing.state.update)</option>
<option value="thing.property.post">属性上报 (thing.property.post)</option>
<option value="thing.event.post">事件上报 (thing.event.post)</option>
<option value="thing.ota.progress">OTA 升级进度 (thing.ota.progress)</option>
</select>
</div>
<div class="form-group">
<label>主题 (Topic)</label>
<input id="pubTopic" placeholder="消息主题,格式:/sys/{productKey}/{deviceName}/thing/property/post" type="text"
value="/sys/fqTn4Afs982Nak4N/jiali001/thing/property/post">
<small style="color: #666; font-size: 12px;">标准格式:/sys/{productKey}/{deviceName}/thing/property/post</small>
</div>
<div class="form-group">
<label>QoS 级别</label>
<select id="pubQos">
<option value="0">0 - 最多一次</option>
<option selected value="1">1 - 至少一次</option>
<option value="2">2 - 刚好一次</option>
</select>
</div>
<div class="form-group">
<label>消息内容 (JSON - Alink 协议格式)</label>
<textarea id="pubMessage" placeholder='Alink 协议格式消息'>{
"id": "123456789",
"version": "1.0",
"method": "thing.property.post",
"params": {
"temperature": 25.5,
"humidity": 60
}
}</textarea>
<small style="color: #666; font-size: 12px;">
Alink 协议格式id消息 ID、version协议版本、method方法、params参数
</small>
</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="publish()">📤 发布消息</button>
<button class="btn btn-success" onclick="publishSampleData()">📊 发送样例数据</button>
</div>
<h2 style="margin-top: 30px;">📥 主题订阅</h2>
<div class="form-group">
<label>快捷主题选择(下行消息 - 服务端 → 设备)</label>
<select id="quickTopicSelect" onchange="selectQuickTopic()"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 5px;">
<option value="">-- 选择下行消息类型 --</option>
<optgroup label="📥 下行消息">
<option value="thing.property.set">属性设置 (thing.property.set)</option>
<option value="thing.service.invoke">服务调用 (thing.service.invoke)</option>
<option value="thing.config.push">配置推送 (thing.config.push)</option>
<option value="thing.ota.upgrade">OTA 固件推送 (thing.ota.upgrade)</option>
</optgroup>
<optgroup label="🔄 回复主题(上行消息的回复)">
<option value="thing.property.post_reply">属性上报回复 (thing.property.post_reply)</option>
<option value="thing.event.post_reply">事件上报回复 (thing.event.post_reply)</option>
</optgroup>
<optgroup label="🔧 通配符订阅">
<option value="wildcard_all">订阅所有主题 (/sys/+/+/#)</option>
<option value="wildcard_thing">订阅所有 thing 主题 (/sys/+/+/thing/#)</option>
<option value="wildcard_reply">订阅所有回复主题 (/sys/+/+/#_reply)</option>
</optgroup>
</select>
</div>
<div class="form-group">
<label>订阅主题</label>
<input id="subTopic" placeholder="订阅主题,格式:/sys/{productKey}/{deviceName}/thing/property/set" type="text"
value="/sys/fqTn4Afs982Nak4N/jiali001/thing/property/set">
<small style="color: #666; font-size: 12px;">标准格式:/sys/{productKey}/{deviceName}/thing/method 或使用通配符
/sys/+/+/#</small>
</div>
<div class="form-group">
<label>QoS 级别</label>
<select id="subQos">
<option value="0">0 - 最多一次</option>
<option selected value="1">1 - 至少一次</option>
<option value="2">2 - 刚好一次</option>
</select>
</div>
<div class="btn-group">
<button class="btn btn-primary" onclick="subscribe()">📥 订阅</button>
<button class="btn btn-danger" onclick="unsubscribe()">❌ 取消订阅</button>
</div>
</div>
<!-- 日志面板 -->
<div class="panel" style="grid-column: 1 / -1;">
<h2>📝 日志输出</h2>
<div class="log-area" id="logArea"></div>
</div>
</div>
</div>
<!-- 使用 MQTT.js 库 -->
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
let client = null;
let sentCount = 0;
let receivedCount = 0;
let errorCount = 0;
// 添加日志
function addLog(message, type = 'info') {
const logArea = document.getElementById('logArea');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
logEntry.textContent = `[${timestamp}] ${message}`;
logArea.appendChild(logEntry);
logArea.scrollTop = logArea.scrollHeight;
}
// 更新状态栏
function updateStatus(status, text) {
const statusBar = document.getElementById('statusBar');
statusBar.className = `status ${status}`;
const icons = {
'disconnected': '⚫',
'connecting': '🟡',
'connected': '🟢'
};
statusBar.textContent = `${icons[status]} ${text}`;
}
// 更新统计信息
function updateStats() {
document.getElementById('sentCount').textContent = sentCount;
document.getElementById('receivedCount').textContent = receivedCount;
document.getElementById('errorCount').textContent = errorCount;
}
// 连接到服务器
function connect() {
const serverUrl = document.getElementById('serverUrl').value;
const clientId = document.getElementById('clientId').value;
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!serverUrl || !clientId) {
addLog('❌ 请填写服务器地址和 Client ID', 'error');
errorCount++;
updateStats();
return;
}
updateStatus('connecting', '正在连接...');
addLog(`🔄 正在连接到 ${serverUrl}...`, 'info');
const options = {
clientId: clientId,
username: username,
password: password,
clean: true,
reconnectPeriod: 5000,
connectTimeout: 30000,
};
client = mqtt.connect(serverUrl, options);
// 连接成功
client.on('connect', () => {
updateStatus('connected', '已连接');
addLog('✅ 连接成功!', 'success');
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
});
// 接收消息
client.on('message', (topic, message) => {
receivedCount++;
updateStats();
addLog(`📥 收到消息 [${topic}]: ${message.toString()}`, 'success');
});
// 连接错误
client.on('error', (error) => {
errorCount++;
updateStats();
addLog(`❌ 连接错误: ${error.message}`, 'error');
});
// 断开连接
client.on('close', () => {
updateStatus('disconnected', '未连接');
addLog('🔌 连接已断开', 'warning');
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
});
// 离线
client.on('offline', () => {
updateStatus('disconnected', '离线');
addLog('⚠️ 客户端离线', 'warning');
});
// 重连
client.on('reconnect', () => {
updateStatus('connecting', '正在重连...');
addLog('🔄 正在重连...', 'info');
});
}
// 断开连接
function disconnect() {
if (client) {
client.end();
addLog('👋 主动断开连接', 'info');
}
}
// 发布消息
function publish() {
if (!client || !client.connected) {
addLog('❌ 请先连接到服务器', 'error');
errorCount++;
updateStats();
return;
}
const topic = document.getElementById('pubTopic').value;
const qos = parseInt(document.getElementById('pubQos').value);
const message = document.getElementById('pubMessage').value;
if (!topic || !message) {
addLog('❌ 请填写主题和消息内容', 'error');
errorCount++;
updateStats();
return;
}
// 验证 JSON 格式
try {
JSON.parse(message);
} catch (e) {
addLog('⚠️ 消息不是有效的 JSON 格式,将作为纯文本发送', 'warning');
}
client.publish(topic, message, {qos: qos}, (error) => {
if (error) {
errorCount++;
updateStats();
addLog(`❌ 发布失败: ${error.message}`, 'error');
} else {
sentCount++;
updateStats();
addLog(`📤 消息已发布 [${topic}] (QoS ${qos})`, 'success');
}
});
}
// 发送样例数据
function publishSampleData() {
// 使用 Alink 协议格式的样例数据
const sampleData = {
id: Date.now().toString(),
version: "1.0",
method: "thing.property.post",
params: {
temperature: parseFloat((20 + Math.random() * 10).toFixed(2)),
humidity: parseFloat((50 + Math.random() * 20).toFixed(2)),
pressure: parseFloat((1000 + Math.random() * 50).toFixed(2))
}
};
document.getElementById('pubMessage').value = JSON.stringify(sampleData, null, 2);
addLog('样例数据已生成Alink 协议格式)', 'info');
publish();
}
// 获取 productKey 和 deviceName
function getDeviceInfo() {
const clientId = document.getElementById('clientId').value;
const parts = clientId.split('.');
if (parts.length !== 2) {
addLog('❌ Client ID 格式不正确(应为 {productKey}.{deviceName}),无法生成主题', 'error');
return null;
}
return {
productKey: parts[0],
deviceName: parts[1]
};
}
// 快捷主题选择(消息发布 - 上行消息)
function selectQuickPublishTopic() {
const select = document.getElementById('quickPublishTopicSelect');
const selectedValue = select.value;
console.log('[selectQuickPublishTopic] 选择的值:', selectedValue);
if (!selectedValue) {
return;
}
const deviceInfo = getDeviceInfo();
if (!deviceInfo) {
return;
}
console.log('[selectQuickPublishTopic] 设备信息:', deviceInfo);
// 构建标准主题,将枚举中的点号替换为斜杠
// 例如thing.property.post -> /sys/{pk}/{dn}/thing/property/post
const topic = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/${selectedValue.replace(/\./g, '/')}`;
console.log('[selectQuickPublishTopic] 生成的主题:', topic);
const pubTopicInput = document.getElementById('pubTopic');
pubTopicInput.value = topic;
console.log('[selectQuickPublishTopic] 输入框的值已设置为:', pubTopicInput.value);
addLog(`📋 已选择发布主题: ${topic}`, 'info');
// 需要 reply 的消息类型(不在 REPLY_DISABLED 列表中)
const needsReply = [
'thing.property.post',
'thing.event.post'
];
// 如果需要 reply自动订阅 reply 主题
if (needsReply.includes(selectedValue)) {
const replyTopic = `${topic}_reply`;
if (client && client.connected) {
// 自动订阅 reply 主题
client.subscribe(replyTopic, {qos: 1}, (err) => {
if (!err) {
addLog(`✅ 已自动订阅回复主题: ${replyTopic}`, 'success');
} else {
addLog(`❌ 自动订阅回复主题失败: ${err.message}`, 'error');
}
});
} else {
addLog(`💡 提示: 该消息需要订阅回复主题 ${replyTopic}`, 'warning');
}
}
// 重置下拉框到默认选项
select.selectedIndex = 0;
console.log('[selectQuickPublishTopic] 下拉框已重置');
}
// 快捷主题选择(主题订阅 - 下行消息)
function selectQuickTopic() {
const select = document.getElementById('quickTopicSelect');
const selectedValue = select.value;
console.log('[selectQuickTopic] 选择的值:', selectedValue);
if (!selectedValue) {
return;
}
const subTopicInput = document.getElementById('subTopic');
// 处理通配符订阅
if (selectedValue === 'wildcard_all') {
subTopicInput.value = '/sys/+/+/#';
addLog('📋 已选择订阅主题: /sys/+/+/#(订阅所有主题)', 'info');
console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value);
select.selectedIndex = 0;
console.log('[selectQuickTopic] 下拉框已重置');
return;
} else if (selectedValue === 'wildcard_thing') {
subTopicInput.value = '/sys/+/+/thing/#';
addLog('📋 已选择订阅主题: /sys/+/+/thing/#(订阅所有 thing 主题)', 'info');
console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value);
select.selectedIndex = 0;
console.log('[selectQuickTopic] 下拉框已重置');
return;
} else if (selectedValue === 'wildcard_reply') {
const deviceInfo = getDeviceInfo();
if (!deviceInfo) {
select.selectedIndex = 0;
return;
}
subTopicInput.value = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/#_reply`;
addLog(`📋 已选择订阅主题: /sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/#_reply订阅所有回复主题`, 'info');
console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value);
select.selectedIndex = 0;
console.log('[selectQuickTopic] 下拉框已重置');
return;
}
const deviceInfo = getDeviceInfo();
if (!deviceInfo) {
select.selectedIndex = 0;
return;
}
console.log('[selectQuickTopic] 设备信息:', deviceInfo);
// 构建标准主题,将枚举中的点号替换为斜杠
// 例如thing.property.set -> /sys/{pk}/{dn}/thing/property/set
const topic = `/sys/${deviceInfo.productKey}/${deviceInfo.deviceName}/${selectedValue.replace(/\./g, '/')}`;
console.log('[selectQuickTopic] 生成的主题:', topic);
subTopicInput.value = topic;
addLog(`📋 已选择订阅主题: ${topic}`, 'info');
console.log('[selectQuickTopic] 输入框的值已设置为:', subTopicInput.value);
// 重置下拉框到默认选项
select.selectedIndex = 0;
console.log('[selectQuickTopic] 下拉框已重置');
}
// 订阅主题
function subscribe() {
if (!client || !client.connected) {
addLog('❌ 请先连接到服务器', 'error');
errorCount++;
updateStats();
return;
}
const topic = document.getElementById('subTopic').value;
const qos = parseInt(document.getElementById('subQos').value);
if (!topic) {
addLog('❌ 请填写订阅主题', 'error');
errorCount++;
updateStats();
return;
}
client.subscribe(topic, {qos: qos}, (error) => {
if (error) {
errorCount++;
updateStats();
addLog(`❌ 订阅失败: ${error.message}`, 'error');
} else {
addLog(`📥 已订阅主题 [${topic}] (QoS ${qos})`, 'success');
}
});
}
// 取消订阅
function unsubscribe() {
if (!client || !client.connected) {
addLog('❌ 请先连接到服务器', 'error');
errorCount++;
updateStats();
return;
}
const topic = document.getElementById('subTopic').value;
if (!topic) {
addLog('❌ 请填写要取消的订阅主题', 'error');
errorCount++;
updateStats();
return;
}
client.unsubscribe(topic, (error) => {
if (error) {
errorCount++;
updateStats();
addLog(`❌ 取消订阅失败: ${error.message}`, 'error');
} else {
addLog(`❌ 已取消订阅 [${topic}]`, 'info');
}
});
}
// 清空日志
function clearLogs() {
document.getElementById('logArea').innerHTML = '';
sentCount = 0;
receivedCount = 0;
errorCount = 0;
updateStats();
addLog('🗑️ 日志已清空', 'info');
}
// 页面加载完成
window.onload = function () {
addLog('👋 欢迎使用 MQTT WebSocket 测试客户端!', 'success');
addLog('📚 请配置连接参数后点击"连接"按钮', 'info');
};
// 页面关闭前断开连接
window.onbeforeunload = function () {
if (client && client.connected) {
client.end();
}
};
</script>
</body>
</html>

View File

@@ -0,0 +1,193 @@
# TCP 二进制协议数据包格式说明
## 1. 协议概述
TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景。
### 1.1 协议特点
- **高效传输**:完全二进制格式,减少数据传输量
- **版本控制**:内置协议版本号,支持协议升级
- **类型安全**:明确的消息类型标识
- **简洁设计**:去除冗余字段,协议更加精简
- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容
## 2. 协议格式
### 2.1 整体结构
```
+--------+--------+--------+---------------------------+--------+--------+
| 魔术字 | 版本号 | 消息类型| 消息长度(4字节) |
+--------+--------+--------+---------------------------+--------+--------+
| 消息 ID 长度(2字节) | 消息 ID (变长字符串) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 方法名长度(2字节) | 方法名(变长字符串) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 消息体数据(变长) |
+--------+--------+--------+--------+--------+--------+--------+--------+
```
### 2.2 字段详细说明
| 字段 | 长度 | 类型 | 说明 |
|------|------|------|------|
| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 |
| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 |
| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 |
| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) |
| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 |
| 消息 ID | 变长 | string | 消息唯一标识符UTF-8编码 |
| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 |
| 方法名 | 变长 | string | 消息方法名UTF-8编码 |
| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 |
**⚠️ 重要说明**deviceId 不包含在协议中,由服务器根据连接上下文自动设置
### 2.3 协议常量定义
```java
// 协议标识
private static final byte MAGIC_NUMBER = (byte) 0x7E;
private static final byte PROTOCOL_VERSION = (byte) 0x01;
// 消息类型
private static final byte REQUEST = (byte) 0x01; // 请求消息
private static final byte RESPONSE = (byte) 0x02; // 响应消息
// 协议长度
private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度
private static final int MIN_MESSAGE_LENGTH = 11; // 最小消息长度
```
## 3. 消息类型和格式
### 3.1 请求消息 (REQUEST - 0x01)
请求消息用于设备向服务器发送数据或请求。
#### 3.1.1 消息体格式
```
消息体 = params 数据(JSON格式)
```
#### 3.1.2 示例:设备认证请求
**消息内容:**
- 消息 ID: `auth_1704067200000_123`
- 方法名: `auth`
- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}`
**二进制数据包结构:**
```
7E // 魔术字 (0x7E)
01 // 版本号 (0x01)
01 // 消息类型 (REQUEST)
00 00 00 89 // 消息长度 (137字节)
00 19 // 消息 ID 长度 (25字节)
61 75 74 68 5F 31 37 30 34 30 // 消息 ID: "auth_1704067200000_123"
36 37 32 30 30 30 30 30 5F 31
32 33
00 04 // 方法名长度 (4字节)
61 75 74 68 // 方法名: "auth"
7B 22 63 6C 69 65 6E 74 49 64 // JSON参数数据
22 3A 22 64 65 76 69 63 65 5F // {"clientId":"device_001",
30 30 31 22 2C 22 75 73 65 72 // "username":"productKey_deviceName",
6E 61 6D 65 22 3A 22 70 72 6F // "password":"device_password"}
64 75 63 74 4B 65 79 5F 64 65
76 69 63 65 4E 61 6D 65 22 2C
22 70 61 73 73 77 6F 72 64 22
3A 22 64 65 76 69 63 65 5F 70
61 73 73 77 6F 72 64 22 7D
```
#### 3.1.3 示例:属性数据上报
**消息内容:**
- 消息 ID: `property_1704067200000_456`
- 方法名: `thing.property.post`
- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}`
### 3.2 响应消息 (RESPONSE - 0x02)
响应消息用于服务器向设备回复请求结果。
#### 3.2.1 消息体格式
```
消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON)
```
#### 3.2.2 字段说明
| 字段 | 长度 | 类型 | 说明 |
|------|------|------|------|
| 响应码 | 4字节 | int | HTTP状态码风格0=成功,其他=错误 |
| 响应消息长度 | 2字节 | short | 响应消息字符串的字节长度 |
| 响应消息 | 变长 | string | 响应提示信息UTF-8编码 |
| 响应数据 | 变长 | binary | JSON格式的响应数据可选 |
#### 3.2.3 示例:认证成功响应
**消息内容:**
- 消息 ID: `auth_response_1704067200000_123`
- 方法名: `auth`
- 响应码: `0`
- 响应消息: `认证成功`
- 响应数据: `{"success":true,"message":"认证成功"}`
**二进制数据包结构:**
```
7E // 魔术字 (0x7E)
01 // 版本号 (0x01)
02 // 消息类型 (RESPONSE)
00 00 00 A4 // 消息长度 (164字节)
00 22 // 消息 ID 长度 (34字节)
61 75 74 68 5F 72 65 73 70 6F // 消息 ID: "auth_response_1704067200000_123"
6E 73 65 5F 31 37 30 34 30 36
37 32 30 30 30 30 30 5F 31 32
33
00 04 // 方法名长度 (4字节)
61 75 74 68 // 方法名: "auth"
00 00 00 00 // 响应码 (0 = 成功)
00 0C // 响应消息长度 (12字节)
E8 AE A4 E8 AF 81 E6 88 90 E5 // 响应消息: "认证成功" (UTF-8)
8A 9F
7B 22 73 75 63 63 65 73 73 22 // JSON响应数据
3A 74 72 75 65 2C 22 6D 65 73 // {"success":true,"message":"认证成功"}
73 61 67 65 22 3A 22 E8 AE A4
E8 AF 81 E6 88 90 E5 8A 9F 22
7D
```
## 4. 编解码器标识
```java
public static final String TYPE = "TCP_BINARY";
```
## 5. 协议优势
- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量
- **解析高效**:直接二进制操作,减少字符串转换开销
- **类型安全**:明确的消息类型和字段定义
- **设计简洁**:去除冗余字段,协议更加精简高效
- **版本控制**:内置版本号支持协议升级
## 6. 与 JSON 协议对比
| 特性 | 二进制协议 | JSON协议 |
|------|-------------|--------|
| 数据大小 | 小节省30-50% | 大 |
| 解析性能 | 高 | 中等 |
| 网络开销 | 低 | 高 |
| 可读性 | 差 | 优秀 |
| 调试难度 | 高 | 低 |
| 扩展性 | 良好 | 优秀 |
**推荐场景**
-**高频数据传输**:传感器数据实时上报
-**带宽受限环境**:移动网络、卫星通信
-**性能要求高**:需要低延迟、高吞吐的场景
-**设备资源有限**:嵌入式设备、低功耗设备
-**开发调试阶段**:调试困难,建议使用 JSON 协议
-**快速原型开发**:开发效率低

View File

@@ -0,0 +1,191 @@
# TCP JSON 格式协议说明
## 1. 协议概述
TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点:
- **标准化**:使用标准 JSON 格式,易于解析和处理
- **可读性**:人类可读,便于调试和维护
- **扩展性**:可以轻松添加新字段,向后兼容
- **跨平台**JSON 格式支持所有主流编程语言
- **安全优化**:移除冗余的 deviceId 字段,提高安全性
## 2. 消息格式
### 2.1 基础消息结构
```json
{
"id": "消息唯一标识",
"method": "消息方法",
"params": {
// 请求参数
},
"data": {
// 响应数据
},
"code": 响应码,
"msg": "响应消息",
"timestamp": 时间戳
}
```
**⚠️ 重要说明**
- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID
- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息
### 2.2 字段详细说明
| 字段名 | 类型 | 必填 | 用途 | 说明 |
|--------|------|------|------|------|
| id | String | 是 | 所有消息 | 消息唯一标识 |
| method | String | 是 | 所有消息 | 消息方法,如 `auth``thing.property.post` |
| params | Object | 否 | 请求消息 | 请求参数具体内容根据method而定 |
| data | Object | 否 | 响应消息 | 响应数据,服务器返回的结果数据 |
| code | Integer | 否 | 响应消息 | 响应码0=成功,其他=错误 |
| msg | String | 否 | 响应消息 | 响应提示信息 |
| timestamp | Long | 是 | 所有消息 | 时间戳(毫秒),编码时自动生成 |
### 2.3 消息分类
#### 2.3.1 请求消息(上行)
- **特征**:包含 `params` 字段,不包含 `code``msg` 字段
- **方向**:设备 → 服务器
- **用途**:设备认证、数据上报、状态更新等
#### 2.3.2 响应消息(下行)
- **特征**:包含 `code``msg` 字段,可能包含 `data` 字段
- **方向**:服务器 → 设备
- **用途**:认证结果、指令响应、错误提示等
## 3. 消息示例
### 3.1 设备认证 (auth)
#### 认证请求格式
**消息方向**:设备 → 服务器
```json
{
"id": "auth_1704067200000_123",
"method": "auth",
"params": {
"clientId": "device_001",
"username": "productKey_deviceName",
"password": "设备密码"
},
"timestamp": 1704067200000
}
```
**认证参数说明:**
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| clientId | String | 是 | 客户端唯一标识,用于连接管理 |
| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` |
| password | String | 是 | 设备密码,在设备管理平台配置 |
#### 认证响应格式
**消息方向**:服务器 → 设备
**认证成功响应:**
```json
{
"id": "response_auth_1704067200000_123",
"method": "auth",
"data": {
"success": true,
"message": "认证成功"
},
"code": 0,
"msg": "认证成功",
"timestamp": 1704067200001
}
```
**认证失败响应:**
```json
{
"id": "response_auth_1704067200000_123",
"method": "auth",
"data": {
"success": false,
"message": "认证失败:用户名或密码错误"
},
"code": 401,
"msg": "认证失败",
"timestamp": 1704067200001
}
```
### 3.2 属性数据上报 (thing.property.post)
**消息方向**:设备 → 服务器
**示例:温度传感器数据上报**
```json
{
"id": "property_1704067200000_456",
"method": "thing.property.post",
"params": {
"temperature": 25.5,
"humidity": 60.2,
"pressure": 1013.25,
"battery": 85,
"signal_strength": -65
},
"timestamp": 1704067200000
}
```
### 3.3 设备状态更新 (thing.state.update)
**消息方向**:设备 → 服务器
**示例:心跳请求**
```json
{
"id": "heartbeat_1704067200000_321",
"method": "thing.state.update",
"params": {
"state": "online",
"uptime": 86400,
"memory_usage": 65.2,
"cpu_usage": 12.8
},
"timestamp": 1704067200000
}
```
## 4. 编解码器标识
```java
public static final String TYPE = "TCP_JSON";
```
## 5. 协议优势
- **开发效率高**JSON 格式,开发和调试简单
- **跨语言支持**:所有主流语言都支持 JSON
- **可读性优秀**:可以直接查看消息内容
- **扩展性强**:可以轻松添加新字段
- **安全性高**:移除 deviceId 字段,防止伪造攻击
## 6. 与二进制协议对比
| 特性 | JSON协议 | 二进制协议 |
|------|----------|------------|
| 开发难度 | 低 | 高 |
| 调试难度 | 低 | 高 |
| 可读性 | 优秀 | 差 |
| 数据大小 | 中等 | 小节省30-50% |
| 解析性能 | 中等 | 高 |
| 学习成本 | 低 | 高 |
**推荐场景**
-**开发调试阶段**:调试友好,开发效率高
-**快速原型开发**:实现简单,快速迭代
-**多语言集成**:广泛的语言支持
-**高频数据传输**:建议使用二进制协议
- ❌ **带宽受限环境**:建议使用二进制协议