v0.2.7: 修复Redis连接 + 启动管理后台
- 修复Redis认证 (配置密码) - 启动Python管理后台 (端口9531, 15个功能开关) - 统一版本号 0.2.7 - 更新docker-compose.yml (镜像版本/Redis URL/Admin服务)
This commit is contained in:
161
source_clean/src/middleware/cache.ts
Executable file
161
source_clean/src/middleware/cache.ts
Executable file
@@ -0,0 +1,161 @@
|
||||
import Redis from 'ioredis';
|
||||
|
||||
let client: Redis | null = null;
|
||||
let currentUrl: string = '';
|
||||
|
||||
export function getRedis(): Redis | null {
|
||||
return client;
|
||||
}
|
||||
|
||||
export function getRedisClient(): Redis {
|
||||
if (client) return client;
|
||||
return createClient();
|
||||
}
|
||||
|
||||
function createClient(url?: string): Redis {
|
||||
const redisUrl = url || process.env.REDIS_URL || currentUrl || getSystemConfigRedisUrl();
|
||||
currentUrl = redisUrl;
|
||||
|
||||
if (client) {
|
||||
try { client.disconnect(); } catch {}
|
||||
client = null;
|
||||
}
|
||||
|
||||
client = new Redis(redisUrl, {
|
||||
maxRetriesPerRequest: 3,
|
||||
retryStrategy(times: number) {
|
||||
if (times > 3) return null;
|
||||
return Math.min(times * 200, 2000);
|
||||
},
|
||||
lazyConnect: true,
|
||||
});
|
||||
|
||||
client.on('error', (err: Error) => {
|
||||
console.error('[Redis] Error:', err.message);
|
||||
});
|
||||
|
||||
client.on('connect', () => {
|
||||
console.log('[Redis] Connected to', currentUrl);
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
function getSystemConfigRedisUrl(): string {
|
||||
try {
|
||||
const { getSystemConfig } = require('./admin/system-config.service');
|
||||
return getSystemConfig('redis_url') || process.env.REDIS_URL || 'redis://redis:6379';
|
||||
} catch {
|
||||
return 'redis://redis:6379';
|
||||
}
|
||||
}
|
||||
|
||||
export async function connectRedis(): Promise<void> {
|
||||
const redis = createClient();
|
||||
try {
|
||||
await redis.connect();
|
||||
} catch (err) {
|
||||
console.warn('[Redis] Connection failed, running without cache');
|
||||
}
|
||||
}
|
||||
|
||||
export async function reconnectRedis(url: string): Promise<boolean> {
|
||||
try {
|
||||
if (client) {
|
||||
await client.quit().catch(() => {});
|
||||
client = null;
|
||||
}
|
||||
const redis = createClient(url);
|
||||
await redis.connect();
|
||||
console.log('[Redis] Reconnected with new URL:', url);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('[Redis] Reconnect failed:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function disconnectRedis(): Promise<void> {
|
||||
if (client) {
|
||||
await client.quit();
|
||||
client = null;
|
||||
currentUrl = '';
|
||||
console.log('[Redis] Disconnected');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a Redis URL without affecting the running client.
|
||||
* @returns { ok: boolean, latency: number, info?: string }
|
||||
*/
|
||||
export async function testRedisConnection(url: string): Promise<{ ok: boolean; latency: number; info?: string }> {
|
||||
const start = Date.now();
|
||||
const testClient = new Redis(url, {
|
||||
maxRetriesPerRequest: 1,
|
||||
retryStrategy() { return null; },
|
||||
lazyConnect: true,
|
||||
connectTimeout: 5000,
|
||||
});
|
||||
try {
|
||||
await testClient.connect();
|
||||
const pong = await testClient.ping();
|
||||
const latency = Date.now() - start;
|
||||
await testClient.quit();
|
||||
return { ok: pong === 'PONG', latency, info: `响应时间 ${latency}ms` };
|
||||
} catch (err: any) {
|
||||
try { await testClient.disconnect(); } catch {}
|
||||
const latency = Date.now() - start;
|
||||
return { ok: false, latency, info: err.message || '连接失败' };
|
||||
}
|
||||
}
|
||||
|
||||
export class RedisClient {
|
||||
private redis: Redis;
|
||||
|
||||
constructor() {
|
||||
this.redis = getRedisClient();
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | null> {
|
||||
try {
|
||||
return await this.redis.get(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set(key: string, value: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.set(key, value);
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
}
|
||||
|
||||
async setEx(key: string, ttl: number, value: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.setex(key, ttl, value);
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.del(key);
|
||||
} catch {
|
||||
// silently fail
|
||||
}
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.redis.exists(key);
|
||||
return result === 1;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RedisClient;
|
||||
53
source_clean/src/middleware/rate-limit.ts
Executable file
53
source_clean/src/middleware/rate-limit.ts
Executable file
@@ -0,0 +1,53 @@
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
/** 公开搜索接口:较宽松 */
|
||||
export const searchLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 150,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req) => req.socket.remoteAddress ?? 'unknown',
|
||||
message: { error: '搜索请求过于频繁,请稍后再试', code: 429 },
|
||||
});
|
||||
|
||||
/** 管理接口(admin/*):较严格 */
|
||||
export const adminLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 30,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req) => req.socket.remoteAddress ?? 'unknown',
|
||||
message: { error: '操作过于频繁,请稍后再试', code: 429 },
|
||||
});
|
||||
|
||||
/** 登录接口:极严格,防暴力破解 */
|
||||
export const loginLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req) => req.socket.remoteAddress ?? 'unknown',
|
||||
message: { error: '登录尝试次数过多,请一分钟后重试', code: 429 },
|
||||
});
|
||||
|
||||
/** 转存/保存接口:中等等级 */
|
||||
export const saveLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 30,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req) => req.socket.remoteAddress ?? 'unknown',
|
||||
message: { error: '转存操作过于频繁,请稍后再试', code: 429 },
|
||||
});
|
||||
|
||||
/** 默认全局限流(兜底,未匹配上述规则的路由) */
|
||||
const defaultLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 100,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req) => req.socket.remoteAddress ?? 'unknown',
|
||||
message: { error: 'Too many requests, please try again later.', code: 429 },
|
||||
});
|
||||
|
||||
export default defaultLimiter;
|
||||
Reference in New Issue
Block a user