以下是 Webhook 触发 LRU 缓存清理 的完整可落地代码,适配 Next.js/NestJS 技术栈,无需 Redis 依赖,仅用 LRU 缓存,包含面试高频问题深度解答,可直接落地使用:
确保项目已安装 lru-cache(核心缓存库)和接口相关依赖:
# 安装核心依赖
npm install lru-cache
npm install -D @types/lru-cache # TypeScript 类型支持(可选)
创建 src/cache/lruCache.ts,全局维护一个 LRU 缓存实例,确保所有接口共享缓存:
// src/cache/lruCache.ts
import LRU from 'lru-cache';
// 初始化 LRU 缓存(配置和你的方案对齐)
export const translationLruCache = new LRU({
maxSize: 100 * 1024 * 1024, // 缓存容量:100MB(足够存20+语言翻译)
ttl: 30 * 60 * 1000, // 30分钟过期(和你之前的缓存策略一致)
updateAgeOnGet: true, // 获取缓存时更新过期时间,提升常用数据命中率
sizeCalculation: (value) => JSON.stringify(value).length, // 按 JSON 长度计算缓存大小
});
// 缓存操作工具函数(封装清理逻辑,方便调用)
export const LruCacheTools = {
// 清理指定语言+路由的缓存(精准清理,不影响其他缓存)
clearByLocaleAndRoute: (locale: string, route: string) => {
const cacheKey = `i18n:${locale}:${route}`; // 和你之前的缓存key格式一致
translationLruCache.delete(cacheKey);
console.log(`✅ LRU缓存已清理:${cacheKey}`);
},
// 清理指定语言的所有路由缓存
clearByLocale: (locale: string) => {
translationLruCache.forEach((_, key) => {
if (key.startsWith(`i18n:${locale}:`)) {
translationLruCache.delete(key);
}
});
console.log(`✅ LRU缓存已清理:所有${locale}语言的路由`);
},
// 清理所有缓存(全量清理,慎用)
clearAll: () => {
translationLruCache.clear();
console.log(`✅ LRU缓存已全量清理`);
},
};
根据你的技术栈选择对应代码(Next.js API 路由 或 NestJS 接口),用于接收翻译平台的 Webhook 通知,触发缓存清理:
创建 src/app/api/webhooks/clear-lru/route.ts(Next.js 13+ App Router):
// src/app/api/webhooks/clear-lru/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { LruCacheTools } from '@/cache/lruCache';
import { SUPPORTED_LOCALES, SUPPORTED_ROUTES } from '@/locales/config/locale.config';
// Webhook 密钥(防止恶意请求,必须配置!)
const WEBHOOK_SECRET = process.env.I18N_WEBHOOK_SECRET || 'your-strong-secret-123';
export async function POST(request: NextRequest) {
try {
// 1. 验证请求合法性(关键:防止别人随便调用)
const requestSecret = request.headers.get('X-Webhook-Secret');
if (requestSecret !== WEBHOOK_SECRET) {
console.error('❌ Webhook请求验证失败:密钥不匹配');
return NextResponse.json({ code: 403, msg: '权限不足' }, { status: 403 });
}
// 2. 解析翻译平台传递的参数(格式可和翻译平台协商,这里是通用格式)
const body = await request.json();
const { locale, route, action } = body; // action: clearByLocale/clearByRoute/clearAll
console.log(`📥 收到Webhook通知:`, { locale, route, action });
// 3. 根据参数执行对应缓存清理逻辑
switch (action) {
case 'clearByRoute':
// 清理指定语言+路由(最常用,精准清理)
if (SUPPORTED_LOCALES.includes(locale) && SUPPORTED_ROUTES.includes(route)) {
LruCacheTools.clearByLocaleAndRoute(locale, route);
} else {
return NextResponse.json({ code: 400, msg: '无效的locale或route' }, { status: 400 });
}
break;
case 'clearByLocale':
// 清理指定语言的所有路由
if (SUPPORTED_LOCALES.includes(locale)) {
LruCacheTools.clearByLocale(locale);
} else {
return NextResponse.json({ code: 400, msg: '无效的locale' }, { status: 400 });
}
break;
case 'clearAll':
// 全量清理(慎用,一般翻译平台更新全量时调用)
LruCacheTools.clearAll();
break;
default:
return NextResponse.json({ code: 400, msg: '无效的action' }, { status: 400 });
}
// 4. 响应翻译平台(告知清理成功)
return NextResponse.json({ code: 200, msg: 'LRU缓存清理成功' });
} catch (error) {
console.error('❌ Webhook处理失败:', error);
return NextResponse.json({ code: 500, msg: '服务器内部错误' }, { status: 500 });
}
}
创建 src/modules/webhook/webhook.controller.ts:
// src/modules/webhook/webhook.controller.ts
import { Controller, Post, Body, Headers, HttpStatus, HttpException } from '@nestjs/common';
import { LruCacheTools } from '@/cache/lruCache';
import { SUPPORTED_LOCALES, SUPPORTED_ROUTES } from '@/locales/config/locale.config';
// Webhook 密钥(从环境变量读取,避免硬编码)
const WEBHOOK_SECRET = process.env.I18N_WEBHOOK_SECRET || 'your-strong-secret-123';
// 接收翻译平台参数的DTO(TypeScript类型校验)
class ClearLruDto {
locale?: string; // 可选:语言ISO码(如zh-CN/en)
route?: string; // 可选:路由名(如home/trade)
action: 'clearByRoute' | 'clearByLocale' | 'clearAll'; // 必选:清理动作
}
@Controller('api/webhooks')
export class WebhookController {
@Post('clear-lru')
clearLruCache(
@Headers('X-Webhook-Secret') requestSecret: string,
@Body() body: ClearLruDto,
) {
try {
// 1. 验证密钥(防止恶意请求)
if (requestSecret !== WEBHOOK_SECRET) {
throw new HttpException('权限不足', HttpStatus.FORBIDDEN);
}
const { locale, route, action } = body;
console.log(`📥 收到Webhook通知:`, { locale, route, action });
// 2. 执行缓存清理
switch (action) {
case 'clearByRoute':
if (!locale || !route || !SUPPORTED_LOCALES.includes(locale) || !SUPPORTED_ROUTES.includes(route)) {
throw new HttpException('无效的locale或route', HttpStatus.BAD_REQUEST);
}
LruCacheTools.clearByLocaleAndRoute(locale, route);
break;
case 'clearByLocale':
if (!locale || !SUPPORTED_LOCALES.includes(locale)) {
throw new HttpException('无效的locale', HttpStatus.BAD_REQUEST);
}
LruCacheTools.clearByLocale(locale);
break;
case 'clearAll':
LruCacheTools.clearAll();
break;
default:
throw new HttpException('无效的action', HttpStatus.BAD_REQUEST);
}
return { code: 200, msg: 'LRU缓存清理成功' };
} catch (error) {
console.error('❌ Webhook处理失败:', error);
throw error;
}
}
}
在项目根目录的 .env 文件中添加 Webhook 密钥(必须替换为强随机字符串,比如用 uuidgen 生成):
# .env 文件
I18N_WEBHOOK_SECRET=your-ultra-strong-secret-8f7d6c5b-4a3b-2c1d-0e9f-abcdef123456
登录你的翻译平台(如 Crowdin、Lokalise 或公司内部翻译系统),找到「Webhook 配置」页面,填写以下信息:
| 配置项 | 填写内容 |
|---|---|
| Webhook URL | 你的接口地址(如:https://your-domain.com/api/webhooks/clear-lru) |
| 请求方法 | POST |
| 请求头 | 添加 X-Webhook-Secret: 你的密钥(和 .env 中的 I18N_WEBHOOK_SECRET 一致) |
| 请求体(JSON) | 按接口预期传递参数,示例: |
{
"locale": "zh-CN", // 可选:要清理的语言
"route": "home", // 可选:要清理的路由
"action": "clearByRoute" // 必选:清理动作(clearByRoute/clearByLocale/clearAll)
}
| 触发条件 | 选择「翻译内容更新并发布后」触发(确保翻译生效后才清理缓存) |
X-Webhook-Secret 密钥验证请求合法性,防止恶意第三方调用接口清理缓存;http://localhost:3000/api/webhooks/clear-lruX-Webhook-Secret: 你的密钥{"locale":"zh-CN","route":"home","action":"clearByRoute"}✅ LRU缓存已清理:i18n:zh-CN:home,说明配置成功;缓存清理的核心是「事件驱动+HTTP回调+缓存操作」的全流程自动化,具体拆解:
/api/webhooks/clear-lru接口发送POST请求,携带locale(语言)、route(路由)、action(清理动作)等参数;X-Webhook-Secret是否与环境变量中的密钥一致,拒绝非法请求;locale/route是否在预设的支持列表(SUPPORTED_LOCALES/SUPPORTED_ROUTES)中,防止恶意参数;action调用对应工具函数:
clearByRoute:拼接缓存Key(i18n:${locale}:${route}),调用translationLruCache.delete(key)删除指定缓存;clearByLocale:遍历LRU缓存中所有以i18n:${locale}:开头的Key,逐个删除;clearAll:调用translationLruCache.clear()清空全部缓存;自动触发的本质是「事件驱动+HTTP回调」的异步通信机制:
Webhook是「翻译平台→你的项目」的实时通信桥梁,其核心是HTTP协议的回调能力:
translationLruCache在项目启动时初始化,所有接口共享该实例,调用delete/clear方法会直接修改缓存数据,因此清理操作立即生效。防止恶意调用的核心是「多层安全校验+身份验证」,从核心防护到进阶加固全维度保障,具体拆解:
接口强制校验请求头X-Webhook-Secret的值是否与环境变量I18N_WEBHOOK_SECRET一致,不一致则直接返回403拒绝:
const requestSecret = request.headers.get('X-Webhook-Secret');
if (requestSecret !== WEBHOOK_SECRET) {
return NextResponse.json({ code: 403, msg: '权限不足' }, { status: 403 });
}
这是「共享密钥验证(Shared Secret)」,属于对称身份验证的核心方案:
X-Webhook-Secret的请求头;校验locale/route是否在预设的支持列表中,拒绝非法参数:
if (!SUPPORTED_LOCALES.includes(locale) || !SUPPORTED_ROUTES.includes(route)) {
return NextResponse.json({ code: 400, msg: '无效的locale或route' }, { status: 400 });
}
即使密钥泄露(极端情况),也能限制黑客的操作范围:
// 翻译平台IP列表(从平台获取)
const ALLOWED_IPS = process.env.ALLOWED_WEBHOOK_IPS?.split(',') || ['192.168.1.1'];
const clientIp = request.ip || request.headers.get('X-Forwarded-For')?.split(',')[0];
if (!ALLOWED_IPS.includes(clientIp)) {
return NextResponse.json({ code: 403, msg: 'IP不被允许' }, { status: 403 });
}
next-rate-limit,NestJS用nestjs-throttler。import crypto from 'crypto';
// 生成签名(翻译平台/接口端共用)
const generateSignature = (body: string, secret: string) => {
return crypto.createHmac('sha256', secret).update(body).digest('hex');
};
// 接口端验证签名
const requestSignature = request.headers.get('X-Webhook-Signature');
const rawBody = await request.text(); // 需获取原始请求体,不能先解析JSON
const expectedSignature = generateSignature(rawBody, WEBHOOK_SECRET);
if (requestSignature !== expectedSignature) {
return NextResponse.json({ code: 403, msg: '签名验证失败' }, { status: 403 });
}
所有防护措施的本质是「身份验证+范围限制+行为限制」:
这套方案既满足落地需求,也能完整解答面试中关于缓存清理流程、自动触发原理、接口安全防护的核心问题,适配Next.js/NestJS技术栈,无需修改核心逻辑,仅需配置环境变量和翻译平台Webhook即可落地。