feat: 全面优化系统 - 修复task1语法错误 + Bot超时保护 + 熔断重试 + Redis缓存 + SEO + 日志清理 + 内容质量评估
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import Redis from "ioredis";
|
||||
|
||||
let _redis = null;
|
||||
|
||||
function getRedis() {
|
||||
if (!_redis) {
|
||||
_redis = new Redis({
|
||||
host: process.env.REDIS_HOST || "127.0.0.1",
|
||||
port: parseInt(process.env.REDIS_PORT || "6379"),
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
db: parseInt(process.env.REDIS_DB || "1"),
|
||||
lazyConnect: true,
|
||||
maxRetriesPerRequest: 2,
|
||||
retryStrategy: (times) => Math.min(times * 200, 3000),
|
||||
});
|
||||
_redis.on("error", (err) => {
|
||||
console.warn(`[redis-cache] Connection error: ${err.message}`);
|
||||
});
|
||||
}
|
||||
return _redis;
|
||||
}
|
||||
|
||||
async function cacheGet(key) {
|
||||
try {
|
||||
const redis = getRedis();
|
||||
if (redis.status !== "ready") {
|
||||
await redis.connect().catch(() => {});
|
||||
}
|
||||
if (redis.status !== "ready") return null;
|
||||
const data = await redis.get(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function cacheSet(key, value, ttlSeconds = 3600) {
|
||||
try {
|
||||
const redis = getRedis();
|
||||
if (redis.status !== "ready") {
|
||||
await redis.connect().catch(() => {});
|
||||
}
|
||||
if (redis.status !== "ready") return;
|
||||
await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
|
||||
} catch {
|
||||
// 静默失败,不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
async function cacheDel(key) {
|
||||
try {
|
||||
const redis = getRedis();
|
||||
if (redis.status !== "ready") return;
|
||||
await redis.del(key);
|
||||
} catch {
|
||||
// 静默失败
|
||||
}
|
||||
}
|
||||
|
||||
async function disconnectRedis() {
|
||||
if (_redis) {
|
||||
await _redis.quit().catch(() => {});
|
||||
_redis = null;
|
||||
}
|
||||
}
|
||||
|
||||
export { getRedis, cacheGet, cacheSet, cacheDel, disconnectRedis };
|
||||
+36
-4
@@ -1,18 +1,50 @@
|
||||
// 简易熔断器:连续失败 N 次后,在冷却期内直接抛错
|
||||
const circuitState = { failures: 0, openUntil: 0 };
|
||||
const CIRCUIT_THRESHOLD = 5; // 连续失败 5 次触发熔断
|
||||
const CIRCUIT_COOLDOWN_MS = 60000; // 熔断冷却 60 秒
|
||||
|
||||
function checkCircuit(label) {
|
||||
if (Date.now() < circuitState.openUntil) {
|
||||
throw new Error(`[circuit-breaker] ${label} 熔断中,${Math.ceil((circuitState.openUntil - Date.now()) / 1000)}s 后恢复`);
|
||||
}
|
||||
}
|
||||
|
||||
function recordSuccess() {
|
||||
circuitState.failures = 0;
|
||||
circuitState.openUntil = 0;
|
||||
}
|
||||
|
||||
function recordFailure(label) {
|
||||
circuitState.failures++;
|
||||
if (circuitState.failures >= CIRCUIT_THRESHOLD) {
|
||||
circuitState.openUntil = Date.now() + CIRCUIT_COOLDOWN_MS;
|
||||
console.warn(` 🔌 [circuit-breaker] ${label} 连续失败 ${circuitState.failures} 次,熔断 ${CIRCUIT_COOLDOWN_MS / 1000}s`);
|
||||
}
|
||||
}
|
||||
|
||||
async function withRetry(fn, { maxRetries = 3, baseDelay = 1000, label = "operation" } = {}) {
|
||||
checkCircuit(label);
|
||||
let lastError;
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
const result = await fn();
|
||||
recordSuccess();
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
recordFailure(label);
|
||||
if (attempt < maxRetries) {
|
||||
const delay = baseDelay * Math.pow(2, attempt - 1);
|
||||
console.warn(` ⚠ ${label} 第${attempt}次失败,${delay / 1000}s后重试...`);
|
||||
// 指数退避 + 随机抖动(避免雷群效应)
|
||||
const jitter = Math.random() * baseDelay * 0.5;
|
||||
const delay = baseDelay * Math.pow(2, attempt - 1) + jitter;
|
||||
console.warn(` ⚠ ${label} 第${attempt}次失败,${(delay / 1000).toFixed(1)}s后重试...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
// 每次重试前检查熔断
|
||||
checkCircuit(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export { withRetry };
|
||||
export { withRetry };
|
||||
|
||||
Reference in New Issue
Block a user