Article

记忆库 SimpleMem

更新于:2026-07-18

第一章:SimpleMem 简介与快速入门

1.1 什么是 SimpleMem

概念名称说明注意事项
SimpleMem一个轻量级、纯内存的键值存储库,用于在 Node.js 或浏览器环境中实现本地缓存功能不适用于持久化存储,重启后数据丢失
设计目标提供简单、高性能、零依赖的内存缓存能力,支持 TTL、命名空间、批量操作等特性仅适用于单进程场景,不支持分布式
适用语言环境原生 JavaScript(兼容 CommonJS 与 ESM),可在 Node.js ≥14 或现代浏览器中运行浏览器中需注意全局变量污染问题
核心特点- 同步 API
- 支持过期时间(TTL)
- 支持命名空间隔离
- 内置 LRU/FIFO 驱逐策略
异步操作需自行封装,框架本身不提供 Promise

1.2 安装与环境配置

步骤名称操作细节注意事项
安装 SimpleMem在项目根目录执行:npm install simple-mem确保已初始化 package.json(可先运行 npm init -y
导入模块(Node.js)使用 CommonJS:const SimpleMem = require('simple-mem'); 或使用 ESM:import SimpleMem from 'simple-mem';ESM 需在 package.json 中设置 "type": "module"
导入模块(浏览器)通过 CDN 引入:<script src="https://cdn.jsdelivr.net/npm/simple-mem@latest/dist/simple-mem.min.js"></script> 然后通过 window.SimpleMem 使用生产环境建议锁定版本号,避免自动升级导致兼容问题
验证安装执行 console.log(typeof SimpleMem) 应输出 "function"若报错”module not found”,检查网络或 node_modules 是否完整

1.3 第一个 SimpleMem 示例程序

方法名称语法用途代码示例注意事项
构造函数new SimpleMem(options?)创建一个内存存储实例const mem = new SimpleMem();
mem.set('name', 'Alice');
console.log(mem.get('name')); // 输出: Alice
options 可选,用于配置最大容量、TTL 等
setmem.set(key, value, ttl?)存储键值对,可设过期时间mem.set('token', 'abc123', 5000); // 5秒后自动失效ttl 单位为毫秒;若未提供则永不过期
getmem.get(key)获取指定键的值const val = mem.get('token');
if (val === undefined) {
console.log('已过期或不存在');
}
若键不存在或已过期,返回 undefined

示例完整程序:

const SimpleMem = require('simple-mem');
const cache = new SimpleMem();
cache.set('hello', 'world', 3000);
console.log(cache.get('hello')); // world
setTimeout(() => {
  console.log(cache.get('hello')); // undefined
}, 3500);

程序需在支持 setTimeout 的环境(如 Node.js)中运行。


第二章:核心概念与数据模型

2.1 内存存储单元(MemUnit)

概念名称说明注意事项
MemUnitSimpleMem 内部用于封装单个缓存项的数据结构,包含值、过期时间、命名空间等元信息用户不直接操作 MemUnit,由框架内部管理
value存储的实际数据,可以是任意 JavaScript 类型(string、number、object、array 等)引用类型(如对象)在内存中共享,修改会影响缓存内容
expiry过期时间戳(Unix 毫秒时间),若为 nullundefined 表示永不过期框架在 get 时检查当前时间是否超过 expiry,超时则返回 undefined
namespace所属命名空间标识符(字符串),用于逻辑隔离不同用途的缓存默认为 'default';相同 key 在不同 namespace 中互不影响
size(可选)该单元占用的”逻辑大小”,用于容量控制(如 maxSize 按条目数或字节数)当前版本默认按条目数计算,size 字段预留用于未来扩展

2.2 键值对结构与命名空间

概念名称说明注意事项
键(key)字符串类型,唯一标识一个缓存项;支持字母、数字、下划线、连字符等常规字符不建议使用特殊符号(如空格、斜杠),可能影响调试或序列化
值(value)任意可序列化的 JavaScript 值函数、Symbol、undefined 等不可序列化类型可能导致持久化失败(若启用快照)
命名空间(namespace)逻辑分组机制,允许同一 key 在不同 namespace 中存储不同值切换 namespace 需通过 new SimpleMem({ namespace: 'xxx' }) 创建新实例
全局键格式内部实际存储键 = ${namespace}:${key}用户无需手动拼接,框架自动处理;但调试时可见此格式
命名空间隔离效果cacheA = new SimpleMem({ ns: 'user' });
cacheB = new SimpleMem({ ns: 'admin' }); 两者 set('id', 1) 互不影响
namespace 参数简写为 ns,全称 namespace,两者等效

2.3 数据生命周期与过期策略

概念名称说明注意事项
TTL(Time-To-Live)数据存活时间,单位毫秒;从 set 时刻开始计时若 ttl ≤ 0,视为立即过期,get 返回 undefined
绝对过期时间可通过传入 Date 对象或时间戳实现绝对过期(部分版本支持)标准 API 仅支持相对 TTL,绝对时间需自行计算差值
惰性过期(Lazy Expiry)SimpleMem 采用惰性删除:仅在 get / has / keys 等读操作时检查是否过期过期数据可能仍占用内存,直到被访问或触发驱逐
自动驱逐(Eviction)当条目数超过 maxSize 时,按配置策略(LRU/FIFO)移除旧数据驱逐发生在 set 操作时,不影响 get 性能
生命周期事件支持 onExpire 回调,在数据被访问且发现过期时触发回调在 get 执行期间同步调用,避免长时间阻塞

示例:TTL 使用

const mem = new SimpleMem();
mem.set('temp', 'data', 1000); // 1秒后过期
setTimeout(() => {
  console.log(mem.get('temp')); // undefined(若1秒后执行)
}, 1500);

第三章:基础 API 使用

3.1 写入数据(set / put)

方法名称语法用途代码示例注意事项
setmem.set(key, value, ttl?)写入键值对,可选设置过期时间const mem = new SimpleMem();
mem.set('username', 'alice');
mem.set('token', 'xyz', 5000); // 5秒过期
key 必须为字符串;ttl 为数字(毫秒),非正数视为立即过期
putmem.put(key, value, ttl?)set 的别名,功能完全相同mem.put('count', 42, 10000);putset 指向同一函数,仅命名偏好不同,建议统一使用 set

3.2 读取数据(get)

方法名称语法用途代码示例注意事项
getmem.get(key)获取指定键的值const val = mem.get('username');
if (val !== undefined) {
console.log('User:', val);
}
若 key 不存在、已过期或被删除,返回 undefined;不会抛出异常
get(带默认值)无内置支持,需自行封装提供默认回退值function getWithDefault(mem, key, defaultValue) { return mem.get(key) ?? defaultValue; }
const theme = getWithDefault(mem, 'theme', 'light');
SimpleMem 本身不提供 defaultValue 参数,需用户逻辑处理

3.3 删除数据(delete / remove)

方法名称语法用途代码示例注意事项
deletemem.delete(key)删除指定键的缓存项mem.set('temp', 'data');
mem.delete('temp');
console.log(mem.get('temp')); // undefined
若 key 不存在,操作静默成功,无错误
removemem.remove(key)delete 的别名,功能完全相同mem.remove('token');removedelete 指向同一函数,建议统一使用 delete

3.4 检查键是否存在(has / exists)

方法名称语法用途代码示例注意事项
hasmem.has(key)检查键是否存在且未过期if (mem.has('username')) { console.log('User logged in'); }仅当 key 存在且未过期时返回 true;过期项视为不存在
existsmem.exists(key)has 的别名,功能完全相同console.log(mem.exists('token')); // true 或 falseexistshas 指向同一函数,建议统一使用 has

第四章:高级功能

4.1 批量操作(batchSet / batchGet / batchDelete)

方法名称语法用途代码示例注意事项
batchSetmem.batchSet(entries, ttl?)批量写入多个键值对const entries = [['name', 'Alice'], ['age', 30]];
mem.batchSet(entries, 10000); // 全部10秒过期
entries[key, value][] 数组;ttl 应用于所有项;若某 key 非字符串,跳过或抛错(取决于实现)
batchGetmem.batchGet(keys)批量读取多个键的值const values = mem.batchGet(['name', 'age', 'email']);
// 返回 ['Alice', 30, undefined]
返回数组顺序与 keys 一致;不存在或过期项返回 undefined
batchDeletemem.batchDelete(keys)批量删除多个键mem.batchDelete(['temp1', 'temp2', 'cache_id']);忽略不存在的键,无错误抛出;操作原子性仅限于单线程语义

4.2 原子操作与事务支持

概念名称说明注意事项
原子操作支持SimpleMem 本身不提供传统数据库式事务,但所有 API 为同步且单线程执行,天然具备”操作原子性”(不会被其他 JS 任务中断)在 Node.js 单线程模型下,set/get/delete 等操作不可分割
compareAndSet无内置 CAS 方法,需自行封装可通过 get + set 实现简易 CAS

简易 CAS 实现:

function cas(mem, key, expected, newValue, ttl) {
  if (mem.get(key) === expected) {
    mem.set(key, newValue, ttl);
    return true;
  }
  return false;
}
概念名称说明注意事项
事务模拟可通过快照 + 回滚机制模拟事务(需开发者自行管理)SimpleMem 不提供 begin/commit/rollback 接口;建议在业务层维护操作日志

注:SimpleMem 定位为轻量缓存,不提供 ACID 事务,高一致性场景应使用数据库。

4.3 监听器与回调机制(onChange / onExpire)

方法名称语法用途代码示例注意事项
onChangemem.onChange(callback)注册数据变更监听器mem.onChange((key, newValue, oldValue) => { console.log(Key ${key} changed from ${oldValue} to ${newValue}); });
mem.set('status', 'active');
callback 在 set/put/batchSet 后同步调用;delete 视为 newValue = undefined
onExpiremem.onExpire(callback)注册过期事件监听器mem.onExpire((key, value) => { console.log(Expired: ${key} = ${value}); });
mem.set('sess', 'abc', 1000);
仅在 get/has 等读操作触发惰性过期检查时调用;后台不主动扫描过期项
移除监听器mem.offChange() / mem.offExpire()移除已注册的监听器mem.offExpire(); // 清除所有过期回调通常用于组件卸载或测试清理

4.4 持久化与快照导出

方法名称语法用途代码示例注意事项
exportSnapshotmem.exportSnapshot()导出当前所有有效缓存项const snapshot = mem.exportSnapshot();
// 返回 { 'ns:key': { value, expiry } } 对象
仅包含未过期项;expiry 为时间戳;值为原始 JavaScript 对象
importSnapshotmem.importSnapshot(snapshot)从快照恢复缓存状态mem.importSnapshot({ 'default:counter': { value: 5, expiry: null } });快照格式必须匹配;会覆盖同名键;不触发 onChange 回调

持久化到文件示例:

const fs = require('fs');

// 导出快照并写入文件
fs.writeFileSync('cache.json', JSON.stringify(mem.exportSnapshot()));

// 恢复时:
const snap = JSON.parse(fs.readFileSync('cache.json'));
mem.importSnapshot(snap);

仅适用于可 JSON 序列化的值;函数、undefinedSymbol 会丢失。

自动持久化示例:

setInterval(() => {
  fs.writeFileSync('backup.json', JSON.stringify(mem.exportSnapshot()));
}, 60000);

频繁写入影响性能;建议在应用退出前保存(如 process.on('exit'))。


第五章:配置与优化

5.1 配置项详解(maxSize、ttl、evictionPolicy 等)

配置项名称类型默认值说明代码示例注意事项
maxSizenumberInfinity最大缓存条目数;超过时触发驱逐策略const mem = new SimpleMem({ maxSize: 100 });设为 0 或负数将禁止写入;Infinity 表示不限制
ttlnumberundefined全局默认过期时间(毫秒);若未在 set 时指定 ttl,则使用此值const mem = new SimpleMem({ ttl: 30000 }); // 所有项默认30秒过期单次 set 的 ttl 参数优先级高于全局 ttl
namespacestring'default'实例所属命名空间,用于逻辑隔离const userCache = new SimpleMem({ namespace: 'user' });不同 namespace 的实例互不影响
evictionPolicystring'lru'内存满时的驱逐策略,可选 'lru''fifo'const mem = new SimpleMem({ maxSize: 10, evictionPolicy: 'fifo' });仅当 maxSize 有限时生效
enableListenersbooleantrue是否启用 onChange / onExpire 监听器const mem = new SimpleMem({ enableListeners: false });关闭可提升高频写入性能
autoPrunebooleanfalse是否在每次操作后主动清理过期项(非惰性)const mem = new SimpleMem({ autoPrune: true });开启会降低写入性能,但减少内存占用

5.2 内存回收策略对比(LRU / FIFO / TTL-based)

策略名称触发条件回收逻辑适用场景注意事项
LRU(最近最少使用)缓存条目数 > maxSize驱逐最久未被访问(get/set)的项访问模式有局部性(热点数据反复访问)需维护访问顺序,内存开销略高
FIFO(先进先出)缓存条目数 > maxSize驱逐最早写入的项数据访问均匀,无明显热点实现简单,性能稳定
TTL-based(基于过期)仅在读操作或 autoPrune=true不主动驱逐,仅在 get/has 时移除已过期项;若配合 maxSize,仍需 LRU/FIFO 处理容量溢出以时间有效性为主导的缓存(如会话、令牌)单独使用无法控制内存上限,需搭配 maxSize

:SimpleMem 的”TTL-based”不是独立驱逐策略,而是过期机制;evictionPolicy 仅控制容量超限时的行为,过期清理始终独立存在。

示例:策略选择

// 热点数据缓存 → LRU
const hotCache = new SimpleMem({ maxSize: 50, evictionPolicy: 'lru' });
// 日志缓冲队列 → FIFO
const logBuffer = new SimpleMem({ maxSize: 1000, evictionPolicy: 'fifo' });

5.3 性能调优建议

建议名称操作细节适用场景注意事项
合理设置 maxSize根据可用内存和预期 QPS 设置上限,避免 OOM高并发服务、嵌入式环境可通过压力测试确定最佳值
避免存储大对象将大对象拆分为多个小键,或仅缓存 ID + 从 DB 获取详情缓存图片、长文本、复杂结构体单个 MemUnit 过大会加剧 GC 压力
关闭监听器(高频写入)设置 enableListeners: false每秒数千次写入的指标采集场景会失去 onChange/onExpire 能力
使用批量操作batchSet/batchGet 替代循环单次调用初始化缓存、批量查询减少函数调用开销,提升吞吐
预估 TTL 避免无效缓存根据业务设置合理 TTL,避免缓存长期无效数据临时令牌、会话、计算中间结果TTL 过长浪费内存,过短失去缓存意义
定期快照备份(可选)在应用空闲期执行 exportSnapshot 并持久化需要重启后恢复状态的 CLI 工具快照期间避免大量写入,防止不一致

第六章:集成与扩展

6.1 与 Web 框架集成(Express / Koa / Fastify)

框架名称集成方式代码示例注意事项
Express将 SimpleMem 实例挂载到 app.locals 或通过中间件注入const express = require('express');
const SimpleMem = require('simple-mem');
const app = express();
const cache = new SimpleMem({ ttl: 60000 });
app.locals.cache = cache;
app.get('/data', (req, res) => {
const cached = req.app.locals.cache.get('data');
if (cached) return res.json(cached);
// ...生成并缓存
});
避免在每次请求中创建新实例;建议在应用启动时初始化一次
Koa通过 ctx.state 或全局变量共享缓存实例const Koa = require('koa');
const SimpleMem = require('simple-mem');
const app = new Koa();
const cache = new SimpleMem();
app.context.cache = cache;
app.use(async (ctx) => {
const data = ctx.cache.get('api_result');
if (data) return ctx.body = data;
// ...
});
Koa 的 context 可直接扩展;注意 async/await 不影响缓存同步性
Fastify使用 decorate 注册缓存实例const fastify = require('fastify')();
const SimpleMem = require('simple-mem');
const cache = new SimpleMem();
fastify.decorate('cache', cache);
fastify.get('/info', async (request, reply) => {
const info = request.server.cache.get('info');
if (info) return reply.send(info);
// ...
});
decorate 是 Fastify 推荐的插件式扩展方式;确保在路由注册前完成

6.2 自定义序列化器

概念名称说明代码示例注意事项
默认序列化SimpleMem 内部直接存储原始 JavaScript 值,不进行序列化mem.set('obj', { a: 1 }); // 直接引用对象引用类型修改会影响缓存内容
自定义序列化需求当需控制内存布局、支持不可变数据或兼容外部格式(如 JSON、MessagePack)时见下方自定义序列化器示例SimpleMem 本身不提供 serializer 配置项,需在业务层封装
不可变缓存实践返回深拷贝以防止外部修改function getImmutable(key) {
const val = mem.get(key);
return val ? JSON.parse(JSON.stringify(val)) : undefined;
}
性能开销大,仅在必要时使用

自定义序列化器示例:

class CustomSerializer {
  serialize(value) {
    return JSON.stringify(value);
  }
  deserialize(str) {
    return JSON.parse(str);
  }
}

// 使用包装层:
function setSafe(key, value) {
  mem.set(key, serializer.serialize(value));
}
function getSafe(key) {
  const raw = mem.get(key);
  return raw ? serializer.deserialize(raw) : undefined;
}

6.3 插件系统与中间件开发

扩展方式操作细节代码示例注意事项
中间件模式封装通用缓存逻辑为可复用函数见下方中间件示例适用于 API 响应缓存;需拦截 res.json 等输出方法
插件注册机制通过原型扩展或静态方法注入功能见下方插件注册示例修改原型影响所有实例;生产环境慎用
生命周期插件利用 onChange/onExpire 构建监控或日志插件见下方日志插件示例适合开发调试;生产环境可关闭以提升性能

中间件示例(Express 缓存中间件):

function cacheMiddleware(cache, keyGen, ttl) {
  return async (req, res, next) => {
    const key = keyGen(req);
    const data = cache.get(key);
    if (data) return res.json(data);
    res.originalJson = res.json;
    res.json = (body) => {
      cache.set(key, body, ttl);
      return res.originalJson(body);
    };
    next();
  };
}

// Express 使用:
app.get('/user/:id', cacheMiddleware(cache, req => `user:${req.params.id}`, 10000), handler);

插件注册示例:

SimpleMem.prototype.stats = function() {
  return { size: Object.keys(this._store).length };
};
// 使用:
const mem = new SimpleMem();
console.log(mem.stats());

日志插件示例:

function loggingPlugin(mem) {
  mem.onChange((key, newVal) => {
    console.log(`[Cache] SET ${key}`);
  });
  mem.onExpire((key) => {
    console.log(`[Cache] EXPIRED ${key}`);
  });
}
loggingPlugin(cache);

第七章:原理剖析

7.1 内部数据结构设计

概念名称说明注意事项
底层存储结构使用原生 JavaScript 对象({})作为主存储容器,键为字符串,值为 MemUnit 对象对象属性查找平均 O(1),但极端情况下(大量属性)可能退化;不使用 Map 是为了兼容旧环境和简化序列化
MemUnit 结构每个缓存项封装为对象:{ value: any, expiry: number | null, namespace: string }expiry 为 Unix 毫秒时间戳;namespace 用于逻辑隔离,但同一实例内所有项共享同一命名空间
命名空间实现不同 namespace 通过创建独立 SimpleMem 实例实现,而非单实例内部分区内存隔离彻底,但无法跨 namespace 批量操作;若需统一管理,需自行维护实例池
访问顺序记录(LRU)使用额外 Map 或数组记录 key 的访问顺序,set/get 时更新LRU 模式下内存占用略高;FIFO 则仅记录插入顺序,开销更低
键的内部格式实际存储键 = ${namespace}:${key}(仅调试可见,用户 API 仍用原始 key)用户无需拼接;该设计便于未来支持跨 namespace 查询(当前未开放)

7.2 事件循环与异步处理机制

概念名称说明注意事项
同步执行模型所有 API(set/get/delete 等)均为同步函数,直接操作内存,不涉及 Promise 或回调队列符合”轻量缓存”定位;避免异步开销,保证低延迟
惰性过期检查过期判断仅在读操作(get/has/keys)或写操作触发驱逐时执行,不启动后台定时器节省 CPU 资源;但过期数据可能滞留内存直至被访问
监听器调用时机onChange/onExpire 回调在 set/delete/get 等操作同步执行期间立即调用回调阻塞主流程;避免在回调中执行耗时操作或再次修改缓存(可能引发递归)
无微任务/宏任务调度不使用 setTimeout/setImmediate/Promise.then 触发清理或通知保证可预测性;适用于对时序敏感的测试或嵌入式场景
与 Node.js 事件循环关系SimpleMem 本身不注册任何事件循环句柄,完全被动响应调用可安全用于 Worker Thread(若环境支持),但实例不共享

7.3 线程安全与并发控制(如适用)

概念名称说明注意事项
单线程假设SimpleMem 基于 JavaScript 单线程模型设计,默认运行在单一 JS 执行上下文中在浏览器主线程或 Node.js 主线程中天然线程安全
多线程环境(Worker)若在多个 Worker 中分别创建实例,则彼此完全隔离;不支持跨线程共享同一实例SharedArrayBufferAtomics 未被使用;无法实现多线程协同缓存
并发写入安全性在单线程内,由于操作不可中断,连续 set/get 具备”逻辑原子性”例如:setget 不会被其他任务打断;但复合操作(如 get-then-set)非原子
无锁设计无需互斥锁(Mutex)或读写锁,因无真正的并行执行性能高,但限制了在多进程/多线程架构中的适用性
集群/多进程场景每个进程持有独立缓存副本,数据不一致;需配合外部缓存(如 Redis)实现共享SimpleMem 定位为本地缓存,非分布式缓存解决方案

第八章:实战案例

8.1 构建本地缓存服务

步骤名称操作细节代码示例注意事项
初始化缓存实例创建带 TTL 和容量限制的 SimpleMem 实例const SimpleMem = require('simple-mem');
const cacheService = new SimpleMem({
maxSize: 500,
ttl: 60000, // 60秒
evictionPolicy: 'lru'
});
根据业务预估 QPS 和数据大小设置 maxSize
封装 getOrFetch 方法提供”缓存穿透”防护:若未命中,则调用异步函数生成并缓存见下方 getOrFetch 示例高并发下可能多次调用 fetchFn(无锁),可加”锁”优化(如使用 Map 记录 pending key)
提供清除接口暴露手动清理能力(如管理后台调用)function clearCache() {
const keys = Object.keys(cacheService['_store']);
keys.forEach(k => cacheService.delete(k));
}
SimpleMem 无 clear 方法,需遍历删除

getOrFetch 实现:

async function getOrFetch(key, fetchFn) {
  const cached = cacheService.get(key);
  if (cached !== undefined) return cached;
  const data = await fetchFn();
  cacheService.set(key, data);
  return data;
}

最小可运行服务示例:

const getUser = async (id) => ({ id, name: `User${id}` });

async function handleRequest(userId) {
  return await getOrFetch(`user:${userId}`, () => getUser(userId));
}

// 调用:
handleRequest(123).then(console.log);

适用于 API 网关、微服务本地缓存等场景。

8.2 实现请求去重中间件

步骤名称操作细节代码示例注意事项
使用 pending 请求池在缓存基础上增加”进行中请求”记录,避免同一参数并发重复调用见下方 dedupedFetch 示例pending Map 需与缓存生命周期协调;错误需清除 pending 避免阻塞
集成到 Express 中间件封装为通用中间件,基于请求路径+查询参数生成唯一 key见下方中间件集成示例仅适用于幂等 GET 请求;POST/PUT 不适用
错误处理与超时为 pending Promise 添加超时控制见下方超时控制示例防止上游慢响应导致 pending 长期占用内存

dedupedFetch 实现:

const pending = new Map(); // key → Promise

async function dedupedFetch(key, fn) {
  if (pending.has(key)) {
    return pending.get(key);
  }
  const promise = (async () => {
    try {
      const result = await fn();
      cacheService.set(key, result, 10000);
      return result;
    } finally {
      pending.delete(key);
    }
  })();
  pending.set(key, promise);
  return promise;
}

集成到 Express 中间件:

function dedupeMiddleware() {
  return async (req, res, next) => {
    const key = req.originalUrl;
    try {
      const data = await dedupedFetch(key, () => fetchDataFromUpstream(req));
      res.json(data);
    } catch (err) {
      next(err);
    }
  };
}

app.get('/api/data', dedupeMiddleware());

超时控制:

const withTimeout = (promise, ms) => {
  return Promise.race([
    promise,
    new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), ms))
  ]);
};
// 在 dedupedFetch 中:
const result = await withTimeout(fn(), 5000);

最小可运行示例:

const simulateApi = (id) => new Promise(resolve => setTimeout(() => resolve({ id, ts: Date.now() }), 100));

for (let i = 0; i < 5; i++) {
  dedupedFetch('test', () => simulateApi(1)).then(console.log);
} // 所有输出相同 ts

可用于防刷、防重复提交等场景。

8.3 会话(Session)管理示例

步骤名称操作细节代码示例注意事项
生成唯一 Session ID使用 crypto 或简单时间戳+随机数组合const crypto = require('crypto');
function generateSessionId() {
return crypto.randomBytes(16).toString('hex');
}
避免使用 Math.random()(可预测)
存储会话数据以 sid 为 key,用户数据为 value,设置合理 TTL(如 30 分钟)const sessionStore = new SimpleMem({ ttl: 1800000 }); // 30分钟
function createSession(userData) {
const sid = generateSessionId();
sessionStore.set(sid, userData);
return sid;
}
敏感信息(如密码)不应存入 session
读取与验证会话从 Cookie 或 Header 获取 sid,查询并自动延长有效期(可选)function getSession(sid) {
const data = sessionStore.get(sid);
if (data) {
sessionStore.set(sid, data, 1800000);
}
return data;
}
滚动过期需谨慎,可能被恶意利用延长会话
集成到 Web 框架Express 示例:登录创建 session,受保护路由验证 session见下方完整示例Cookie 应设 HttpOnly + Secure(生产环境)

完整会话管理示例:

app.post('/login', (req, res) => {
  const sid = createSession({ userId: req.body.id });
  res.cookie('sid', sid, { httpOnly: true });
  res.json({ ok: true });
});

app.get('/profile', (req, res) => {
  const sid = req.cookies.sid;
  const user = getSession(sid);
  if (!user) return res.status(401).end();
  res.json(user);
});

最小可运行会话系统流程:

  1. POST /login → 返回 cookie
  2. GET /profile(带 cookie)→ 返回用户数据
  3. 等待 30 分钟后再次访问 → 401

仅适用于单机部署;集群需共享 session 存储。