/** * 本地存储工具 */ const { LIMITS, EXPIRE } = require('./constants.js'); const { encryptFields, decryptFields } = require('./crypto.js'); const TOKEN_KEY = 'yack_token'; const USER_INFO_KEY = 'yack_user_info'; const DEVICE_LIST_KEY = 'yack_device_list'; const PENDING_DATA_KEY = 'yack_pending_data'; const MQTT_CONFIG_KEY = 'yack_mqtt_config'; // 需要加密的字段 const ENCRYPT_FIELDS = ['productSecret']; /** * 获取 Token * @returns {string|null} */ function getToken() { try { return wx.getStorageSync(TOKEN_KEY) || null; } catch (e) { console.error('[Storage] getToken error:', e); return null; } } /** * 设置 Token * @param {string} token */ function setToken(token) { try { wx.setStorageSync(TOKEN_KEY, token); } catch (e) { console.error('[Storage] setToken error:', e); } } /** * 移除 Token */ function removeToken() { try { wx.removeStorageSync(TOKEN_KEY); } catch (e) { console.error('[Storage] removeToken error:', e); } } /** * 获取用户信息 * @returns {Object|null} */ function getUserInfo() { try { return wx.getStorageSync(USER_INFO_KEY) || null; } catch (e) { console.error('[Storage] getUserInfo error:', e); return null; } } /** * 设置用户信息 * @param {Object} userInfo */ function setUserInfo(userInfo) { try { wx.setStorageSync(USER_INFO_KEY, userInfo); } catch (e) { console.error('[Storage] setUserInfo error:', e); } } /** * 移除用户信息 */ function removeUserInfo() { try { wx.removeStorageSync(USER_INFO_KEY); } catch (e) { console.error('[Storage] removeUserInfo error:', e); } } /** * 获取设备列表 * @returns {Array} */ function getDeviceList() { try { const devices = wx.getStorageSync(DEVICE_LIST_KEY) || []; // 解密敏感字段 return devices.map(device => decryptFields(device, ENCRYPT_FIELDS)); } catch (e) { console.error('[Storage] getDeviceList error:', e); return []; } } /** * 保存设备列表(加密敏感字段) * @param {Array} devices */ function saveDeviceList(devices) { try { // 加密敏感字段 const encryptedDevices = devices.map(device => encryptFields(device, ENCRYPT_FIELDS)); wx.setStorageSync(DEVICE_LIST_KEY, encryptedDevices); } catch (e) { console.error('[Storage] saveDeviceList error:', e); } } /** * 添加设备(加密敏感字段) * * 返回值必须被检查:设备数量达上限时是**失败**,调用方若不看返回值继续走 * "激活 → 取回 → 启动采集",会得到"提示已连接、实际既没保存也没连"的假象 * (scan 页曾因此静默吞掉一次添加)。 * 工具层不弹 toast:UI 呈现交给调用方,避免"上限提示"与"连接成功"两个 toast 打架。 * @param {Object} device * @returns {boolean} 是否写入成功 */ function addDevice(device) { const devices = getDeviceList(); // 检查是否已存在 const index = devices.findIndex(d => d.serialNumber === device.serialNumber); // 加密敏感字段 const encryptedDevice = encryptFields(device, ENCRYPT_FIELDS); if (index >= 0) { devices[index] = encryptedDevice; } else { // 检查设备数量限制 if (devices.length >= LIMITS.MAX_DEVICE_COUNT) { console.warn('[Storage] 设备数量已达上限:', LIMITS.MAX_DEVICE_COUNT); return false; } devices.push(encryptedDevice); } saveDeviceList(devices); return true; } /** * 删除设备 * @param {string} serialNumber */ function removeDevice(serialNumber) { const devices = getDeviceList(); const newDevices = devices.filter(d => d.serialNumber !== serialNumber); saveDeviceList(newDevices); } /** * 更新设备 * @param {Object} device */ function updateDevice(device) { const devices = getDeviceList(); const index = devices.findIndex(d => d.serialNumber === device.serialNumber); if (index >= 0) { devices[index] = device; saveDeviceList(devices); } } /** * 获取单个设备(解密敏感字段) * @param {string} serialNumber * @returns {Object|null} */ function getDevice(serialNumber) { const devices = getDeviceList(); return devices.find(d => d.serialNumber === serialNumber) || null; } /** * 激活设备为唯一启用(其余全部禁用) * 保证全列表最多一个 enabled=true * @param {string} serialNumber */ function activateDevice(serialNumber) { // 直接读取加密数据并解密 const devices = getDeviceList(); let found = false; devices.forEach((d) => { if (d.serialNumber === serialNumber) { d.enabled = true; found = true; } else if (d.enabled) { d.enabled = false; } }); if (found) { saveDeviceList(devices); console.log('[Storage] 激活设备为唯一启用:', serialNumber); } } /** * 禁用设备 * @param {string} serialNumber */ function deactivateDevice(serialNumber) { const devices = getDeviceList(); const index = devices.findIndex(d => d.serialNumber === serialNumber); if (index >= 0) { devices[index].enabled = false; saveDeviceList(devices); console.log('[Storage] 禁用设备:', serialNumber); } } /** * 获取当前启用的设备(最多一个) * 存量数据若出现多个 enabled,自动归一化:保留列表中第一个,其余置 false * @returns {Object|null} */ function getEnabledDevice() { const devices = getDeviceList(); const enabledList = devices.filter(d => d.enabled); if (enabledList.length === 0) return null; if (enabledList.length > 1) { console.warn('[Storage] 检测到多个启用设备,自动归一化为第一个:', enabledList.map(d => d.serialNumber)); const keep = enabledList[0].serialNumber; devices.forEach((d) => { if (d.serialNumber !== keep) d.enabled = false; }); saveDeviceList(devices); return devices.find(d => d.serialNumber === keep) || null; } return enabledList[0]; } /** * 清理过期的待上报数据 * @param {Array} pendingList - 待上报数据列表 * @returns {Array} 清理后的列表 */ function _cleanExpiredData(pendingList) { if (!pendingList || pendingList.length === 0) return []; const expireTime = EXPIRE.PENDING_DATA * 24 * 60 * 60 * 1000; // 转换为毫秒 const now = Date.now(); return pendingList.filter(item => { const cacheTime = item.cacheTime || 0; return (now - cacheTime) < expireTime; }); } /** * 缓存待上报数据(带限制和过期清理) * @param {Object} data */ function cachePendingData(data) { try { let pendingList = wx.getStorageSync(PENDING_DATA_KEY) || []; // 清理过期数据 pendingList = _cleanExpiredData(pendingList); // 检查数量限制 if (pendingList.length >= LIMITS.MAX_PENDING_DATA) { console.warn('[Storage] 离线缓存已达上限,移除最旧数据'); pendingList = pendingList.slice(-LIMITS.MAX_PENDING_DATA + 1); } // 添加新数据 pendingList.push({ ...data, cacheTime: Date.now(), }); wx.setStorageSync(PENDING_DATA_KEY, pendingList); console.log('[Storage] 缓存待上报数据:', data); } catch (e) { console.error('[Storage] cachePendingData error:', e); } } /** * 批量缓存待上报数据(一次 setStorageSync,避免高频逐条写磁盘阻塞 BLE 接收) * 带限制和过期清理 * @param {Array} items */ function cachePendingDataBatch(items) { if (!items || items.length === 0) return; try { let pendingList = wx.getStorageSync(PENDING_DATA_KEY) || []; // 清理过期数据 pendingList = _cleanExpiredData(pendingList); // 添加新数据 items.forEach((data) => { // 检查数量限制 if (pendingList.length >= LIMITS.MAX_PENDING_DATA) { console.warn('[Storage] 离线缓存已达上限,移除最旧数据'); pendingList = pendingList.slice(-LIMITS.MAX_PENDING_DATA + 1); } pendingList.push({ ...data, cacheTime: Date.now(), }); }); // 最终限制检查(确保不超过上限) if (pendingList.length > LIMITS.MAX_PENDING_DATA) { pendingList = pendingList.slice(-LIMITS.MAX_PENDING_DATA); } wx.setStorageSync(PENDING_DATA_KEY, pendingList); } catch (e) { console.error('[Storage] cachePendingDataBatch error:', e); } } /** * 获取待上报数据(自动清理过期数据) * @returns {Array} */ function getPendingData() { try { let pendingList = wx.getStorageSync(PENDING_DATA_KEY) || []; // 清理过期数据 pendingList = _cleanExpiredData(pendingList); // 如果清理后数据有变化,更新存储 const originalList = wx.getStorageSync(PENDING_DATA_KEY) || []; if (pendingList.length !== originalList.length) { wx.setStorageSync(PENDING_DATA_KEY, pendingList); console.log('[Storage] 已清理过期离线数据'); } return pendingList; } catch (e) { console.error('[Storage] getPendingData error:', e); return []; } } /** * 覆写待上报数据(用于补传成功后只移除本设备已上传项) * @param {Array} list */ function setPendingData(list) { try { wx.setStorageSync(PENDING_DATA_KEY, list || []); } catch (e) { console.error('[Storage] setPendingData error:', e); } } /** * 清除待上报数据 */ function clearPendingData() { try { wx.removeStorageSync(PENDING_DATA_KEY); } catch (e) { console.error('[Storage] clearPendingData error:', e); } } /** * 获取 MQTT 连接配置("我的"页产品凭证,productSecret 加密存储) * @returns {Object|null} { serialNumber, productId, productSecret } */ function getMqttConfig() { try { const config = wx.getStorageSync(MQTT_CONFIG_KEY) || null; return config ? decryptFields(config, ENCRYPT_FIELDS) : null; } catch (e) { console.error('[Storage] getMqttConfig error:', e); return null; } } /** * 保存 MQTT 连接配置(加密敏感字段) * @param {Object} config { serialNumber, productId, productSecret } */ function setMqttConfig(config) { try { wx.setStorageSync(MQTT_CONFIG_KEY, encryptFields(config, ENCRYPT_FIELDS)); } catch (e) { console.error('[Storage] setMqttConfig error:', e); } } /** * 清除 MQTT 连接配置 */ function removeMqttConfig() { try { wx.removeStorageSync(MQTT_CONFIG_KEY); } catch (e) { console.error('[Storage] removeMqttConfig error:', e); } } /** * 清除所有数据 */ function clearAll() { try { wx.clearStorageSync(); console.log('[Storage] 已清除所有数据'); } catch (e) { console.error('[Storage] clearAll error:', e); } } module.exports = { getToken, setToken, removeToken, getUserInfo, setUserInfo, removeUserInfo, getDeviceList, saveDeviceList, addDevice, removeDevice, updateDevice, getDevice, activateDevice, deactivateDevice, getEnabledDevice, cachePendingData, cachePendingDataBatch, getPendingData, setPendingData, clearPendingData, getMqttConfig, setMqttConfig, removeMqttConfig, clearAll, };