mqtt gateway project

This commit is contained in:
marlonz
2025-07-05 13:47:49 +09:30
parent aed54bb3a6
commit 076aaae944
12 changed files with 1899 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
class ConfigManager extends EventEmitter {
constructor(fileName) {
super();
this.config = {}; // 移除默认的 apiKeys 配置
this.configPath = path.join(__dirname, "..", "config", fileName);
this.loadConfig();
this.watchConfig();
// 添加防抖计时器变量
this.watchDebounceTimer = null;
}
loadConfig() {
try {
const data = fs.readFileSync(this.configPath, 'utf8');
const newConfig = JSON.parse(data);
// 检测配置是否发生变化
if (JSON.stringify(this.config) !== JSON.stringify(newConfig)) {
console.log('配置已更新', this.configPath);
this.config = newConfig;
// 发出配置更新事件
this.emit('configChanged', this.config);
}
} catch (error) {
console.error('加载配置出错:', error, this.configPath);
if (error.code === 'ENOENT') {
this.createEmptyConfig();
}
}
}
createEmptyConfig() {
try {
const dir = path.dirname(this.configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const defaultConfig = {}; // 空配置对象
fs.writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2));
console.log('已创建空配置文件', this.configPath);
} catch (error) {
console.error('创建空配置文件出错:', error, this.configPath);
}
}
watchConfig() {
fs.watch(path.dirname(this.configPath), (eventType, filename) => {
if (filename === path.basename(this.configPath) && eventType === 'change') {
// 清除之前的计时器
if (this.watchDebounceTimer) {
clearTimeout(this.watchDebounceTimer);
}
// 设置新的计时器,300ms 后执行
this.watchDebounceTimer = setTimeout(() => {
this.loadConfig();
}, 300);
}
});
}
// 获取配置的方法
getConfig() {
return this.config;
}
// 获取特定配置项的方法
get(key) {
return this.config[key];
}
}
module.exports = {
ConfigManager
};
+103
View File
@@ -0,0 +1,103 @@
require('dotenv').config();
const crypto = require('crypto');
function generatePasswordSignature(content, secretKey) {
// Create an HMAC object using SHA256 and the secretKey
const hmac = crypto.createHmac('sha256', secretKey);
// Update the HMAC object with the clientId
hmac.update(content);
// Generate the HMAC digest in binary format
const binarySignature = hmac.digest();
// Encode the binary signature to Base64
const base64Signature = binarySignature.toString('base64');
return base64Signature;
}
function validateMqttCredentials(clientId, username, password) {
// 验证密码签名
const signatureKey = process.env.MQTT_SIGNATURE_KEY;
if (signatureKey) {
const expectedSignature = generatePasswordSignature(clientId + '|' + username, signatureKey);
if (password !== expectedSignature) {
throw new Error('密码签名验证失败');
}
} else {
console.warn('缺少MQTT_SIGNATURE_KEY环境变量,跳过密码签名验证');
}
// 验证clientId
if (!clientId || typeof clientId !== 'string') {
throw new Error('clientId必须是非空字符串');
}
// 验证clientId格式(必须包含@@@分隔符)
const clientIdParts = clientId.split('@@@');
// 新版本 MQTT 参数
if (clientIdParts.length !== 3) {
throw new Error('clientId格式错误,必须包含@@@分隔符');
}
// 验证username
if (!username || typeof username !== 'string') {
throw new Error('username必须是非空字符串');
}
// 尝试解码username(应该是base64编码的JSON
let userData;
try {
const decodedUsername = Buffer.from(username, 'base64').toString();
userData = JSON.parse(decodedUsername);
} catch (error) {
throw new Error('username不是有效的base64编码JSON');
}
// 解析clientId中的信息
const [groupId, macAddress, uuid] = clientIdParts;
// 如果验证成功,返回解析后的有用信息
return {
groupId,
macAddress: macAddress.replace(/_/g, ':'),
uuid,
userData
};
}
function generateMqttConfig(groupId, macAddress, uuid, userData) {
const endpoint = process.env.MQTT_ENDPOINT;
const signatureKey = process.env.MQTT_SIGNATURE_KEY;
if (!signatureKey) {
console.warn('No signature key, skip generating MQTT config');
return;
}
const deviceIdNoColon = macAddress.replace(/:/g, '_');
const clientId = `${groupId}@@@${deviceIdNoColon}@@@${uuid}`;
const username = Buffer.from(JSON.stringify(userData)).toString('base64');
const password = generatePasswordSignature(clientId + '|' + username, signatureKey);
return {
endpoint,
port: 8883,
client_id: clientId,
username,
password,
publish_topic: 'device-server',
subscribe_topic: 'null' // 旧版本固件不返回此字段会出错
}
}
module.exports = {
generateMqttConfig,
validateMqttCredentials
}
if (require.main === module) {
const config = generateMqttConfig('GID_test', '11:22:33:44:55:66', '36c98363-3656-43cb-a00f-8bced2391a90', { ip: '222.222.222.222' });
console.log('config', config);
const credentials = validateMqttCredentials(config.client_id, config.username, config.password);
console.log('credentials', credentials);
}