101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
// server/utils/cache.ts - 简单内存缓存,按题目、题型和选项生成缓存键
|
|
import { createHash } from "node:crypto";
|
|
|
|
import { serverEnv } from "~~/server/utils/env";
|
|
|
|
/**
|
|
* 旧 Python 服务使用进程内字典缓存答案
|
|
*
|
|
* Nuxt 版保持同样的“内存缓存”语义:服务重启后缓存清空,不跨实例共享;
|
|
* 这对个人题库服务足够简单,也避免为了缓存引入数据库或 Redis
|
|
*/
|
|
export class SimpleCache {
|
|
/** Map key 是题目、题型和选项计算出的 md5;value 保存写入时间和答案 */
|
|
private readonly cache = new Map<
|
|
string,
|
|
{
|
|
timestamp: number;
|
|
value: string;
|
|
}
|
|
>();
|
|
|
|
constructor(private readonly expirationSeconds: number) {}
|
|
|
|
/**
|
|
* 根据题目、题型和选项生成缓存键
|
|
*
|
|
* 同一道题如果选项不同,不能复用旧答案,所以三个字段都参与计算
|
|
*/
|
|
private generateKey(question: string, questionType = "", options = "") {
|
|
return createHash("md5")
|
|
.update(`${question}|${questionType}|${options}`, "utf8")
|
|
.digest("hex");
|
|
}
|
|
|
|
/**
|
|
* 读取缓存答案
|
|
*
|
|
* 命中过期项时会顺手删除,避免长时间运行后堆积无效缓存
|
|
*/
|
|
get(question: string, questionType = "", options = "") {
|
|
const key = this.generateKey(question, questionType, options);
|
|
const item = this.cache.get(key);
|
|
if (!item) return undefined;
|
|
|
|
if (Date.now() - item.timestamp < this.expirationSeconds * 1000) {
|
|
return item.value;
|
|
}
|
|
|
|
this.cache.delete(key);
|
|
return undefined;
|
|
}
|
|
|
|
/** 写入答案缓存,时间戳使用当前进程时间即可 */
|
|
set(question: string, answer: string, questionType = "", options = "") {
|
|
const key = this.generateKey(question, questionType, options);
|
|
this.cache.set(key, {
|
|
timestamp: Date.now(),
|
|
value: answer
|
|
});
|
|
}
|
|
|
|
/** 清空全部缓存,对应 `/api/cache/clear` */
|
|
clear() {
|
|
this.cache.clear();
|
|
}
|
|
|
|
/**
|
|
* 批量移除过期项
|
|
*
|
|
* 目前在统计缓存大小时调用,避免 stats 返回已经过期的数量
|
|
*/
|
|
removeExpired() {
|
|
const now = Date.now();
|
|
let removedCount = 0;
|
|
|
|
for (const [key, item] of this.cache.entries()) {
|
|
if (now - item.timestamp >= this.expirationSeconds * 1000) {
|
|
this.cache.delete(key);
|
|
removedCount += 1;
|
|
}
|
|
}
|
|
|
|
return removedCount;
|
|
}
|
|
|
|
/** 获取当前有效缓存数量 */
|
|
size() {
|
|
this.removeExpired();
|
|
return this.cache.size;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 全局答案缓存实例
|
|
*
|
|
* 如果 `ENABLE_CACHE=false`,导出 undefined,调用处用可选链即可保持逻辑简洁
|
|
*/
|
|
export const answerCache = serverEnv.enableCache
|
|
? new SimpleCache(serverEnv.cacheExpiration)
|
|
: undefined;
|