记录一下优化广告展现路程的总体思路。
前端本地缓存方案(旧版)
在项目初期,为了支持不同模块获取特定的 key 对应的 value(例如账号名、商品名等),我们采用了以下查找顺序:
localStorage -> IndexedDB -> 接口请求
示例核心代码:
const count = await getCommonAdvertActionFrequencyRecord(key, 'PC');
if (count > 0) {
personalCache.set(key, '1');
return;
}
存在问题:
展示商品名:
└─ 查 localStorage
└─ 没有?查 IndexedDB
└─ 还没有?调接口
└─ 接口返回后写入 localStorage + IndexedDB
每个 key 都重复以上流程
-
缓存有效期不好控制,删除后用户可能再次看到广告。
-
重复代码多,几乎每个页面都要写一遍获取逻辑。
-
请求次数多,每个 key 可能都需要单独发请求。
-
localStorage 容量有限,容易爆仓。
-
多终端/多用户/多账号时不易同步。
引入后端统一记录(新版)
我们将流程统一改造为:
一次性从后端获取所有 key-value -> 存入内存(内存 Map)
核心改动点:
系统初始化时:
└─ 一次性请求所有 key-value
└─ 存入内存 Map(如:const dictMap = new Map())
使用时:
└─ 直接 dictMap.get(key) 拿数据
关键点:
- 后端通过两张表,支持子账号区分,返回完整 key-value 对应关系。
- 前端统一请求一次后,将数据缓存到内存中(如
Map结构)。 - 所有需要展示 key 的模块直接从内存读取,无需再重复查找逻辑。
读取逻辑:
await initStroageFromDataBase(); // 一次性拉取全部记录
getStoageInfoByKey({ key }); // 判断当前广告是否展示过
设置逻辑:
setStoageInfoByKey({ key }); // 设置为“已展示”
前端逻辑:
| 操作 | 方法 | 说明 |
|---|---|---|
| 初始化 | initStroageFromDataBase() | 拉取并缓存在内存中 |
| 查询是否展示 | getStoageInfoByKey() | 判断是否设置过 |
| 设置展示过 | setStoageInfoByKey() | 调用后端 API 保存状态 |
后端逻辑:
| 表名 | 作用 |
|---|---|
multiterminal_key_stroage_resource | 控制广告项配置,包括是否启用、是否按子账号区分、是否过期 |
multiterminal_key_stroage_info | 实际记录用户维度的“已展示”状态 |
通过前端内存 + 后端统一记录的迁移过程,实现了:
- 广告展示记录的统一管理
- 避免重复展示带来的用户骚扰
- 支持账号、子账号粒度的记录
- 实现了更安全、可控的系统架构
这个优化过程是一个经典的“从内存缓存 ➝ 后端状态同步”的升级路线,值得在中大型系统中推广。
优化后代码示例
初始化(一次请求所有 key)
// 初始化阶段调用
const dictMap = new Map<string, string>();
async function initDictMap() {
const res = await fetch('/api/dict/all'); // 后端返回所有 key-value
res.data.forEach(item => {
dictMap.set(item.key, item.value);
});
}
使用(统一查)
function getLabelByKey(key: string): string {
return dictMap.get(key) || '--';
}
优势:
- 请求只需一次
- 使用统一
Map读取,代码简洁 - 无 localStorage 容量限制问题
这样就完成了广告展现的优化,去除了之前繁琐的操作,重复代码一大堆的问题,提高开发效率,并且不会出现loacl爆仓的问题,同时功能还更齐全了,可以控制子账号的权限,嘎嘎好用。