初始化代码
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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 协议
|
||||
- ❌ **快速原型开发**:开发效率低
|
||||
@@ -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%) |
|
||||
| 解析性能 | 中等 | 高 |
|
||||
| 学习成本 | 低 | 高 |
|
||||
|
||||
**推荐场景**:
|
||||
- ✅ **开发调试阶段**:调试友好,开发效率高
|
||||
- ✅ **快速原型开发**:实现简单,快速迭代
|
||||
- ✅ **多语言集成**:广泛的语言支持
|
||||
- ❌ **高频数据传输**:建议使用二进制协议
|
||||
- ❌ **带宽受限环境**:建议使用二进制协议
|
||||
Reference in New Issue
Block a user