432 lines
11 KiB
JavaScript
432 lines
11 KiB
JavaScript
/**
|
||
* 本地存储工具
|
||
*/
|
||
|
||
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<Object>} 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,
|
||
}; |