客流体验环境指标弹窗

parent 44f637ff
...@@ -19,7 +19,7 @@ import { mysqlModelMap } from "../model/sqlModelBind"; ...@@ -19,7 +19,7 @@ import { mysqlModelMap } from "../model/sqlModelBind";
import { acLinkageControl } from "./acLinkage"; import { acLinkageControl } from "./acLinkage";
import { systemConfig } from "../config/serverConfig"; import { systemConfig } from "../config/serverConfig";
import Sequelize from "sequelize"; import Sequelize from "sequelize";
import { DEVICE_STATE, WORK_MODE_TEXT_MAP, FAN_SPEED_TEXT_MAP } from "../config/businessEnum"; import { DEVICE_STATE, WORK_MODE_TEXT_MAP, FAN_SPEED_TEXT_MAP, REGION_NAME } from "../config/businessEnum";
import moment from "moment"; import moment from "moment";
import XLSX from "xlsx"; import XLSX from "xlsx";
import path from "path"; import path from "path";
...@@ -1230,34 +1230,62 @@ export async function diqinRegion() { ...@@ -1230,34 +1230,62 @@ export async function diqinRegion() {
const stations = detail?.stations || []; const stations = detail?.stations || [];
for (const st of stations) { for (const st of stations) {
if (!st.id || !st.name) continue; if (!st.id || !st.name) continue;
// 转换后的统一区域名:迪勤监测点与飞奕区域(房间)为同一物理房间时,
// 用飞奕侧 region_name(如 大会议室→亚马逊)入库,使其能与飞奕已建区域按名合并,
// 避免同一房间在 region 表生成两条记录(未配置映射的监测点保持迪勤原始名)。
const mappedName: string =
(REGION_NAME as Record<string, string>)[st.name] || st.name;
// 当前迪勤监测点 id 是否已有入库记录
let stExisting = await regionModel.findOne({ where: { region_key: st.id } }); let stExisting = await regionModel.findOne({ where: { region_key: st.id } });
// mappedName 被谁占用(飞奕已建区域,或历史遗留的迪勤冗余记录)
const nameOwner = await regionModel.findOne({
where: stExisting
? { region_name: mappedName, id: { [Op.ne]: stExisting.id } }
: { region_name: mappedName },
});
if (stExisting) { if (stExisting) {
if (stExisting.region_name !== st.name if (nameOwner) {
// 撞名:存量里该监测点曾以原始名建过记录(stExisting),而 mappedName
// 已归属 nameOwner(通常是飞奕房间)→ 把迪勤id并入 nameOwner 后清理冗余记录
if (!nameOwner.region_ad || nameOwner.region_ad !== st.id) {
await regionModel.update(
{ region_ad: st.id, updated_at: new Date() },
{ where: { id: nameOwner.id } },
);
stUpdate++;
console.log(`[迪勤区域集成] 监测点撞名合并: ${st.name}(${st.id}) → ${mappedName}(${nameOwner.region_key})`);
}
await regionModel.destroy({ where: { id: stExisting.id } });
stUpdate++;
} else if (stExisting.region_name !== mappedName
|| stExisting.parent_key !== regionKey || stExisting.parent_key !== regionKey
|| stExisting.region_type !== '监测点') { || stExisting.region_type !== '监测点') {
// 无撞名:按需更新为转换后的名称/归属
await regionModel.update( await regionModel.update(
{ region_name: st.name, parent_key: regionKey, region_type: '监测点', updated_at: new Date() }, { region_name: mappedName, parent_key: regionKey, region_type: '监测点', updated_at: new Date() },
{ where: { id: stExisting.id } }, { where: { id: stExisting.id } },
); );
stUpdate++; stUpdate++;
} }
} else { } else {
// 按名称查是否存在同名区域(飞奕或其他平台已创建) if (nameOwner) {
stExisting = await regionModel.findOne({ where: { region_name: st.name } }); // 同名区域已存在(飞奕或其他平台已创建),将迪勤内部ID写入 region_ad,不新建
if (stExisting) { if (!nameOwner.region_ad || nameOwner.region_ad !== st.id) {
// 同名区域已存在,将迪勤内部ID写入 region_ad
if (!stExisting.region_ad || stExisting.region_ad !== st.id) {
await regionModel.update( await regionModel.update(
{ region_ad: st.id, updated_at: new Date() }, { region_ad: st.id, updated_at: new Date() },
{ where: { id: stExisting.id } }, { where: { id: nameOwner.id } },
); );
stUpdate++; stUpdate++;
console.log(`[迪勤区域集成] 同名监测点补充 region_ad: ${st.name} → ${st.id}`); console.log(`[迪勤区域集成] 同名监测点补充 region_ad: ${st.name} → ${mappedName}(${st.id})`);
} }
} else { } else {
await regionModel.create({ await regionModel.create({
region_key: st.id, region_key: st.id,
region_name: st.name, region_name: mappedName,
parent_id: null, parent_id: null,
parent_key: regionKey, parent_key: regionKey,
region_type: '监测点', region_type: '监测点',
...@@ -1318,7 +1346,7 @@ export async function diqinDevice() { ...@@ -1318,7 +1346,7 @@ export async function diqinDevice() {
if (!deviceId) continue; if (!deviceId) continue;
const deviceType = DIQIN_DEVICE_TYPE_MAP[ds.data_source_model_name]; const deviceType = DIQIN_DEVICE_TYPE_MAP[ds.data_source_model_name];
const deviceName = ds.name || ds.identifier || deviceId; const deviceName = `IEQ-${ds.name || ds.identifier || deviceId}`;
const existing = await deviceModel.findOne({ where: { device_id: deviceId } }); const existing = await deviceModel.findOne({ where: { device_id: deviceId } });
if (existing) { if (existing) {
......
...@@ -325,6 +325,7 @@ export async function editAcDeviceControl(params: { ...@@ -325,6 +325,7 @@ export async function editAcDeviceControl(params: {
*/ */
export async function batchEditDevices(params: { export async function batchEditDevices(params: {
regionKeys: number[]; regionKeys: number[];
deviceIds: string[];
deviceName?: string; deviceName?: string;
power?: string; power?: string;
state?: string; state?: string;
...@@ -337,11 +338,11 @@ export async function batchEditDevices(params: { ...@@ -337,11 +338,11 @@ export async function batchEditDevices(params: {
linkageStart?: string; linkageStart?: string;
}; };
}): Promise<{ total: number; successCount: number; failCount: number; details: any[] }> { }): Promise<{ total: number; successCount: number; failCount: number; details: any[] }> {
const { regionKeys, deviceName, power, state, deviceMode, startDate, endDate, edits } = params; const { regionKeys, deviceIds, deviceName, power, state, deviceMode, startDate, endDate, edits } = params;
// 校验必填参数 // 校验必填参数
if (!regionKeys || !Array.isArray(regionKeys) || regionKeys.length === 0) { if ((!regionKeys || !Array.isArray(regionKeys) || regionKeys.length === 0) && (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0)) {
throw new BizError(ERRORENUM.参数错误, "regionKeys 不能为空"); throw new BizError(ERRORENUM.参数错误, "regionKeys 和 deviceIds 不能同时为空");
} }
if (!edits || typeof edits !== 'object') { if (!edits || typeof edits !== 'object') {
throw new BizError(ERRORENUM.参数错误, "edits 不能为空"); throw new BizError(ERRORENUM.参数错误, "edits 不能为空");
...@@ -353,7 +354,12 @@ export async function batchEditDevices(params: { ...@@ -353,7 +354,12 @@ export async function batchEditDevices(params: {
// 1. 构建设备查询条件 // 1. 构建设备查询条件
let whereParam: any = { device_type: '空调' }; let whereParam: any = { device_type: '空调' };
whereParam.region_key = { "%in%": regionKeys }; if (regionKeys && regionKeys.length > 0) {
whereParam.region_key = { "%in%": regionKeys };
}
if (deviceIds && deviceIds.length > 0) {
whereParam.device_id = { "%in%": deviceIds };
}
if (deviceName) { if (deviceName) {
whereParam.device_name = { "%like%": deviceName }; whereParam.device_name = { "%like%": deviceName };
......
...@@ -21,14 +21,14 @@ const USER_COLUMNS = ['login_id', 'name', 'phone', 'role', 'department', 'status ...@@ -21,14 +21,14 @@ const USER_COLUMNS = ['login_id', 'name', 'phone', 'role', 'department', 'status
* @param pwd * @param pwd
*/ */
export async function registerUser(loginId:string, pwd:string, name:string) { export async function registerUser(loginId:string, pwd:string, name:string) {
let filesList = ["loginId", "id", "pwd", "name"]; let filesList = ["login_id", "id", "pwd", "name"];
let adminUserInfo : any = await selectOneDataByParam(TABLENAME.用户信息表, {loginId}, filesList); let adminUserInfo : any = await selectOneDataByParam(TABLENAME.用户信息表, {login_id: loginId}, filesList);
if (!adminUserInfo) { if (!adminUserInfo) {
throw new BizError(ERRORENUM.用户已存在); throw new BizError(ERRORENUM.用户已存在);
} }
await addData(TABLENAME.用户信息表, {loginId, pwd: getPwdMd5(loginId, pwd), name: name}); await addData(TABLENAME.用户信息表, {login_id: loginId, pwd: getPwdMd5(loginId, pwd), name: name});
return { success: true }; return { success: true };
} }
......
...@@ -88,6 +88,17 @@ export enum REGION_TYPE { ...@@ -88,6 +88,17 @@ export enum REGION_TYPE {
监测点 = '监测点', 监测点 = '监测点',
} }
/**
* 区域名称映射
*/
export enum REGION_NAME {
/** 迪勤 - 飞奕 */
大会议室 = '亚马逊',
大办公室 = '环球港',
小办公室 = '综合办',
仓库 = '仓库408',
}
// ==================== 关联相关枚举 ==================== // ==================== 关联相关枚举 ====================
/** /**
......
...@@ -334,6 +334,11 @@ export const TablesConfig = [ ...@@ -334,6 +334,11 @@ export const TablesConfig = [
comment: '修改时间' comment: '修改时间'
} }
}, },
// 大表查询索引:device_id + device_time 组合覆盖“按设备查时间区间”,device_time 单独覆盖跨设备按时间区间查询
indexes: [
{ name: 'idx_device_data_device_id_time', fields: ['device_id', 'device_time'] },
{ name: 'idx_device_data_device_time', fields: ['device_time'] }
],
association: [ association: [
{ {
type: 'belongsTo', type: 'belongsTo',
...@@ -597,6 +602,11 @@ export const TablesConfig = [ ...@@ -597,6 +602,11 @@ export const TablesConfig = [
comment: '修改时间' comment: '修改时间'
} }
}, },
// 故障统计/列表查询索引:device_id 覆盖单设备维度,fault_status + fault_time 覆盖状态计数与最新故障排序
indexes: [
{ name: 'idx_device_fault_device_id', fields: ['device_id'] },
{ name: 'idx_device_fault_status_time', fields: ['fault_status', 'fault_time'] }
],
association: [ association: [
{ {
type: 'belongsTo', type: 'belongsTo',
......
...@@ -25,7 +25,7 @@ export async function initMysqlModel() { ...@@ -25,7 +25,7 @@ export async function initMysqlModel() {
/**第一步:初始化所有表 */ /**第一步:初始化所有表 */
for (let i = 0; i < TablesConfig.length; i++) { for (let i = 0; i < TablesConfig.length; i++) {
let { tableName, schema } = TablesConfig[i]; let { tableName, schema, indexes } = TablesConfig[i];
if (!tableName) { if (!tableName) {
console.warn(`⚠️ 第 ${i} 个表配置缺少 tableName,跳过`); console.warn(`⚠️ 第 ${i} 个表配置缺少 tableName,跳过`);
...@@ -39,11 +39,16 @@ export async function initMysqlModel() { ...@@ -39,11 +39,16 @@ export async function initMysqlModel() {
console.log(`🔄 正在初始化表: ${tableName}`); console.log(`🔄 正在初始化表: ${tableName}`);
let schemaConf = { let schemaConf: any = {
freezeTableName: true, freezeTableName: true,
timestamps: false timestamps: false
}; };
// 表级索引(新环境 sync 建表时生效;存量库需人工执行对应 DDL)
if (indexes && indexes.length > 0) {
schemaConf.indexes = indexes;
}
try { try {
let model = mysqlDB.define(tableName, schema, schemaConf); let model = mysqlDB.define(tableName, schema, schemaConf);
mysqlModelMap[tableName] = await model.sync({}); mysqlModelMap[tableName] = await model.sync({});
......
...@@ -352,6 +352,7 @@ async function editDevice(req, res) { ...@@ -352,6 +352,7 @@ async function editDevice(req, res) {
async function batchEditDevices(req, res) { async function batchEditDevices(req, res) {
let reqConf = { let reqConf = {
regionKeys: 'Array', regionKeys: 'Array',
deviceIds: 'Array',
deviceName: 'String', deviceName: 'String',
power: 'String', power: 'String',
state: 'String', state: 'String',
...@@ -360,12 +361,12 @@ async function batchEditDevices(req, res) { ...@@ -360,12 +361,12 @@ async function batchEditDevices(req, res) {
endDate: 'String', endDate: 'String',
edits: 'Object', edits: 'Object',
}; };
const NotMustHaveKeys = ["deviceName", "power", "state", "deviceMode", "startDate", "endDate"]; const NotMustHaveKeys = ["regionKeys", "deviceIds", "deviceName", "power", "state", "deviceMode", "startDate", "endDate"];
let { regionKeys, deviceName, power, state, deviceMode, startDate, endDate, edits } = let { regionKeys, deviceIds, deviceName, power, state, deviceMode, startDate, endDate, edits } =
eccReqParamater(reqConf, req.body, NotMustHaveKeys); eccReqParamater(reqConf, req.body, NotMustHaveKeys);
if (!regionKeys || !regionKeys.length) { if ((!regionKeys || !regionKeys.length) && (!deviceIds || !deviceIds.length)) {
res.success({success: false, msg: "缺少必填参数:regionKeys"}); res.success({success: false, msg: "缺少必填参数:regionKeys 或 deviceIds"});
return; return;
} }
if (!edits || typeof edits !== 'object') { if (!edits || typeof edits !== 'object') {
...@@ -394,6 +395,7 @@ async function batchEditDevices(req, res) { ...@@ -394,6 +395,7 @@ async function batchEditDevices(req, res) {
const data = await deviceBiz.batchEditDevices({ const data = await deviceBiz.batchEditDevices({
regionKeys, regionKeys,
deviceIds: deviceIds,
deviceName: deviceName || undefined, deviceName: deviceName || undefined,
power: power || undefined, power: power || undefined,
state: state || undefined, state: state || undefined,
...@@ -533,14 +535,14 @@ async function getLinkageList(req, res) { ...@@ -533,14 +535,14 @@ async function getLinkageList(req, res) {
async function getLinkageDetail(req, res) { async function getLinkageDetail(req, res) {
const id = Number(req.query?.id); const id = Number(req.query?.id);
if (!id) { if (!id) {
res.error("缺少必填参数:id"); res.success({success: false, msg: "缺少必填参数:id"});
return; return;
} }
const data = await deviceLinkageBiz.getLinkageDetail(id); const data = await deviceLinkageBiz.getLinkageDetail(id);
if (data.isSuccess) { if (data.isSuccess) {
res.success(data.data); res.success(data.data);
} else { } else {
res.error(data.msg); res.success({success: false, msg: data.msg});
} }
} }
...@@ -571,7 +573,7 @@ async function addLinkage(req, res) { ...@@ -571,7 +573,7 @@ async function addLinkage(req, res) {
let { deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, divideMethod, beginValidity, endValidity } = let { deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, divideMethod, beginValidity, endValidity } =
eccReqParamater(reqConf, req.body, NotMustHaveKeys); eccReqParamater(reqConf, req.body, NotMustHaveKeys);
if (!deviceId || !linkageType) { if (!deviceId || !linkageType) {
res.error("缺少必填参数:deviceId, linkageType"); res.success({success: false, msg: "缺少必填参数:deviceId, linkageType"});
return; return;
} }
const data = await deviceLinkageBiz.addLinkage({ const data = await deviceLinkageBiz.addLinkage({
...@@ -620,7 +622,7 @@ async function batchAddLinkage(req, res) { ...@@ -620,7 +622,7 @@ async function batchAddLinkage(req, res) {
let { deviceIds, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, divideMethod, beginValidity, endValidity } = let { deviceIds, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, divideMethod, beginValidity, endValidity } =
eccReqParamater(reqConf, req.body, NotMustHaveKeys); eccReqParamater(reqConf, req.body, NotMustHaveKeys);
if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0 || !linkageType) { if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0 || !linkageType) {
res.error("缺少必填参数:deviceIds, linkageType"); res.success({success: false, msg: "缺少必填参数:deviceIds, linkageType"});
return; return;
} }
const data = await deviceLinkageBiz.batchAddLinkage({ const data = await deviceLinkageBiz.batchAddLinkage({
...@@ -669,7 +671,7 @@ async function editLinkage(req, res) { ...@@ -669,7 +671,7 @@ async function editLinkage(req, res) {
const NotMustHaveKeys = ["deviceId", "linkageType", "thresholdMax", "thresholdMin", "thresholdMid", "settingMax", "settingMin", "settingMid", "fanSpeedMax", "fanSpeedMid", "fanSpeedMin", "isUse", "divideMethod", "beginValidity", "endValidity"]; const NotMustHaveKeys = ["deviceId", "linkageType", "thresholdMax", "thresholdMin", "thresholdMid", "settingMax", "settingMin", "settingMid", "fanSpeedMax", "fanSpeedMid", "fanSpeedMin", "isUse", "divideMethod", "beginValidity", "endValidity"];
let { id, deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, isUse, divideMethod, beginValidity, endValidity } = eccReqParamater(reqConf, req.body, NotMustHaveKeys); let { id, deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid, settingMax, settingMin, settingMid, fanSpeedMax, fanSpeedMid, fanSpeedMin, isUse, divideMethod, beginValidity, endValidity } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
if (!id) { if (!id) {
res.error("缺少必填参数:id"); res.success({success: false, msg: "缺少必填参数:id"});
return; return;
} }
const data = await deviceLinkageBiz.editLinkage({ const data = await deviceLinkageBiz.editLinkage({
...@@ -701,7 +703,7 @@ async function editLinkage(req, res) { ...@@ -701,7 +703,7 @@ async function editLinkage(req, res) {
async function deleteLinkage(req, res) { async function deleteLinkage(req, res) {
const { id } = req.body || {}; const { id } = req.body || {};
if (!id) { if (!id) {
res.error("缺少必填参数:id"); res.success({success: false, msg: "缺少必填参数:id"});
return; return;
} }
const data = await deviceLinkageBiz.deleteLinkage(Number(id)); const data = await deviceLinkageBiz.deleteLinkage(Number(id));
......
...@@ -4,18 +4,38 @@ ...@@ -4,18 +4,38 @@
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import * as customsBiz from '../biz/customs'; import * as customsBiz from '../biz/customs';
import { eccReqParamater } from '../util/verificationParam'; import { eccReqParamater } from '../util/verificationParam';
import { getCache, setCache } from '../util/ttlCache';
export function setRouter(httpServer) { export function setRouter(httpServer) {
/** 客流体验分析 */ /** 客流体验分析 */
httpServer.post('/api/dq/customs/analysis', asyncHandler(getCustomsAnalysis)); httpServer.post('/api/dq/customs/analysis', asyncHandler(getCustomsAnalysis));
/** 客流体验环境指标弹窗 */
httpServer.post('/api/dq/customs/env_popup', asyncHandler(getCustomsEnvPopup));
} }
/** /**
* 客流体验分析 * 客流体验分析
* 大屏只读接口:加 30s TTL 缓存兜底,避免高频轮询重复打库
*/ */
async function getCustomsAnalysis(req, res) { const CACHE_KEY = 'dq:customs:analysis';
const CACHE_TTL = 30 * 1000;
async function getCustomsAnalysis(req, res) {
const { } = req.body; const { } = req.body;
const cached = getCache(CACHE_KEY);
if (cached) return res.success(cached);
const result = await customsBiz.getCustomsAnalysis(); const result = await customsBiz.getCustomsAnalysis();
setCache(CACHE_KEY, result, CACHE_TTL);
res.success(result);
}
/**
* 客流体验环境指标弹窗
* @param req
* @param res
*/
async function getCustomsEnvPopup(req, res) {
const { env } = req.body;
const result = await customsBiz.getCustomsEnvPopup(env);
res.success(result); res.success(result);
} }
\ No newline at end of file
...@@ -8,6 +8,8 @@ const config = { ...@@ -8,6 +8,8 @@ const config = {
"/api/public/deviceType":enumBiz.DEVICE_TYPE,//设备类型 "/api/public/deviceType":enumBiz.DEVICE_TYPE,//设备类型
"/api/public/deviceState":enumBiz.DEVICE_STATE,//设备状态 "/api/public/deviceState":enumBiz.DEVICE_STATE,//设备状态
"/api/public/regionType":enumBiz.REGION_TYPE,//区域类型 "/api/public/regionType":enumBiz.REGION_TYPE,//区域类型
"/api/public/fanSpeed":enumBiz.fanSpeedMap,//风速
"/api/public/workMode":enumBiz.workModeMap,//模式
} }
export function setRouter(httpServer) { export function setRouter(httpServer) {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import * as runningBiz from '../biz/running'; import * as runningBiz from '../biz/running';
import { eccReqParamater } from '../util/verificationParam'; import { eccReqParamater } from '../util/verificationParam';
import { getCache, setCache } from '../util/ttlCache';
export function setRouter(httpServer) { export function setRouter(httpServer) {
...@@ -29,10 +30,16 @@ export function setRouter(httpServer) { ...@@ -29,10 +30,16 @@ export function setRouter(httpServer) {
/** /**
* 运行分析 * 运行分析
* 大屏只读接口:加 30s TTL 缓存兜底,避免高频轮询重复打库
*/ */
const CACHE_TTL = 30 * 1000;
async function getRunAnalysis(req, res) { async function getRunAnalysis(req, res) {
const { } = req.body; const { } = req.body;
const CACHE_KEY = 'dq:run:analysis';
const cached = getCache(CACHE_KEY);
if (cached) return res.success(cached);
const result = await runningBiz.getRunAnalysis(); const result = await runningBiz.getRunAnalysis();
setCache(CACHE_KEY, result, CACHE_TTL);
res.success(result); res.success(result);
} }
...@@ -67,10 +74,15 @@ async function getRunEnvironmental(req, res) { ...@@ -67,10 +74,15 @@ async function getRunEnvironmental(req, res) {
/** /**
* 智能监控 * 智能监控
* 大屏只读接口:加 30s TTL 缓存兜底,避免高频轮询重复打库
*/ */
async function getRunMonitoring(req, res) { async function getRunMonitoring(req, res) {
const { } = req.body; const { } = req.body;
const CACHE_KEY = 'dq:run:monitoring';
const cached = getCache(CACHE_KEY);
if (cached) return res.success(cached);
const result = await runningBiz.getRunMonitoring(); const result = await runningBiz.getRunMonitoring();
setCache(CACHE_KEY, result, CACHE_TTL);
res.success(result); res.success(result);
} }
......
/**
* 进程内 TTL 缓存(用于大屏只读分析接口防抖)
* 注意:仅适合无参或参数极少的只读接口;服务重启后缓存失效。
*/
interface CacheItem {
expire: number;
data: any;
}
const DEFAULT_TTL = 30 * 1000; // 默认 30 秒
const MAX_SIZE = 200; // 缓存条目上限,防止异常 key 膨胀
const store = new Map<string, CacheItem>();
export function getCache<T = any>(key: string): T | undefined {
const item = store.get(key);
if (!item) return undefined;
if (item.expire <= Date.now()) {
store.delete(key);
return undefined;
}
return item.data;
}
export function setCache(key: string, data: any, ttlMs: number = DEFAULT_TTL) {
if (store.size >= MAX_SIZE) {
// 简单淘汰:优先清掉已过期项;都没有过期则整体清空(极少发生)
const now = Date.now();
for (const [k, v] of store) {
if (v.expire <= now) {
store.delete(k);
break;
}
}
if (store.size >= MAX_SIZE) store.clear();
}
store.set(key, { expire: Date.now() + ttlMs, data });
}
/** 主动失效(例如数据被写入后需要立刻反映的场景) */
export function delCache(key: string) {
store.delete(key);
}
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"strict": false, "strict": false,
"noImplicitAny": false, "noImplicitAny": false,
"strictNullChecks": false, "strictNullChecks": false,
"skipLibCheck": true,
"types": ["node"] "types": ["node"]
}, },
"exclude": [ "exclude": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment