From 38a5c69a37e45e591adabf3b35327ec02ff733c0 Mon Sep 17 00:00:00 2001 From: mazxd Date: Wed, 14 May 2025 22:50:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=99=BE=E5=BA=A6=E7=BD=91=E7=9B=98?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BF=AE=E6=AD=A3=E4=B8=BA=E5=8F=AF=E8=BD=AC?= =?UTF-8?q?=E5=AD=98=E7=9A=84=E6=96=B9=E6=B3=95&redis=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/adapters/BaiduDriveAdapter.ts | 24 +++-- backend/src/app.ts | 9 +- backend/src/lib/redis.ts | 29 ------ backend/src/services/redisService.ts | 108 ++++++++++++++++++++++ backend/src/services/searchService.ts | 57 +++++++----- 5 files changed, 160 insertions(+), 67 deletions(-) delete mode 100644 backend/src/lib/redis.ts create mode 100644 backend/src/services/redisService.ts diff --git a/backend/src/adapters/BaiduDriveAdapter.ts b/backend/src/adapters/BaiduDriveAdapter.ts index 1478171..065a2eb 100644 --- a/backend/src/adapters/BaiduDriveAdapter.ts +++ b/backend/src/adapters/BaiduDriveAdapter.ts @@ -116,11 +116,8 @@ export class BaiduDriveAdapter implements DriveAdapter { try { // 1. 尝试登录 await this.ensureLoggedIn(account); - - // 2. 验证登录状态 - await this.executeCommand(['pwd']); - // 3. 更新账号状态为活跃 + // 2. 更新账号状态为活跃 await this.updateAccountStatus(account, 'active'); return true; } catch (error: any) { @@ -133,14 +130,23 @@ export class BaiduDriveAdapter implements DriveAdapter { private async ensureLoggedIn(account: Account): Promise { try { - // 先尝试执行一个命令来检查是否已登录 - await this.executeCommand(['pwd']); + // 检查登录状态 + const whoResult = await this.executeCommand(['who']); + // 如果 uid 为 0 或用户名为空,说明未登录 + if (whoResult.includes('uid: 0') || whoResult.includes('用户名: ,')) { + // 执行登录 + await this.executeCommand([ + 'login', + `-bduss=${account.accountIdentifier}`, + `-stoken=${account.credentials}` + ]); + } } catch (error) { - // 如果未登录,则执行登录 + // 如果命令执行失败,也尝试登录 await this.executeCommand([ 'login', - account.accountIdentifier, - account.credentials + `-bduss=${account.accountIdentifier}`, + `-stoken=${account.credentials}` ]); } } diff --git a/backend/src/app.ts b/backend/src/app.ts index 5334945..3b446ac 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -4,14 +4,15 @@ import rateLimit from 'express-rate-limit' import swaggerUi from 'swagger-ui-express' import { swaggerSpec } from './config/swagger' import { getSearchResults, getLinkForResource } from './controllers/searchController' -import { initRedis } from './lib/redis' +import { RedisService } from './services/redisService' import logger from './lib/logger' const app = express() -// 初始化 Redis 连接 -initRedis().catch(err => { - logger.error('Redis 初始化失败,应用启动终止', { error: err }) +// 初始化 Redis 服务 +const redisService = RedisService.getInstance(); +redisService.initialize().catch(err => { + logger.error('Redis 服务初始化失败,应用启动终止', { error: err }) process.exit(1) }) diff --git a/backend/src/lib/redis.ts b/backend/src/lib/redis.ts deleted file mode 100644 index 302b8c1..0000000 --- a/backend/src/lib/redis.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createClient } from 'redis'; -import { config } from '../config/config'; -import logger from './logger'; - -// 创建 Redis 客户端实例 -const redis = createClient({ - url: `redis://${config.redis.host}:${config.redis.port}`, - password: config.redis.password -}); - -// Redis 错误处理和连接监控 -redis.on('error', err => logger.error('Redis 客户端错误:', { error: err })); -redis.on('connect', () => logger.info('Redis 客户端已连接')); -redis.on('ready', () => logger.info('Redis 客户端就绪')); -redis.on('reconnecting', () => logger.warn('Redis 客户端正在重连')); - -// 初始化 Redis 连接 -export async function initRedis(): Promise { - try { - await redis.connect(); - logger.info('Redis 连接初始化成功'); - } catch (err) { - logger.error('Redis 连接初始化失败:', { error: err }); - throw err; - } -} - -// 导出 Redis 客户端实例 -export { redis }; \ No newline at end of file diff --git a/backend/src/services/redisService.ts b/backend/src/services/redisService.ts new file mode 100644 index 0000000..a1f3e54 --- /dev/null +++ b/backend/src/services/redisService.ts @@ -0,0 +1,108 @@ +import { createClient, RedisClientType } from 'redis'; +import { config } from '../config/config'; +import logger from '../lib/logger'; + +export class RedisService { + private static instance: RedisService; + private client: RedisClientType; + private isInitialized: boolean = false; + + private constructor() { + this.client = createClient({ + url: `redis://${config.redis.host}:${config.redis.port}`, + password: config.redis.password, + socket: { + reconnectStrategy: (retries) => { + if (retries > 10) { + logger.error('Redis 重连次数过多,停止重连'); + return new Error('Redis 重连失败'); + } + return Math.min(retries * 100, 3000); + } + } + }); + + this.setupEventListeners(); + } + + private setupEventListeners() { + this.client.on('error', err => { + logger.error('Redis 客户端错误:', { error: err }); + }); + + this.client.on('connect', () => { + logger.info('Redis 客户端已连接', { + host: config.redis.host, + port: config.redis.port + }); + }); + + this.client.on('ready', () => { + logger.info('Redis 客户端就绪'); + }); + + this.client.on('reconnecting', () => { + logger.warn('Redis 客户端正在重连'); + }); + + this.client.on('end', () => { + logger.warn('Redis 连接已关闭'); + }); + } + + public static getInstance(): RedisService { + if (!RedisService.instance) { + RedisService.instance = new RedisService(); + } + return RedisService.instance; + } + + public async initialize(): Promise { + if (!this.isInitialized) { + try { + await this.client.connect(); + this.isInitialized = true; + logger.info('Redis 服务初始化成功'); + } catch (err) { + logger.error('Redis 服务初始化失败:', { error: err }); + throw err; + } + } + } + + public async get(key: string): Promise { + if (!this.isInitialized) { + await this.initialize(); + } + return this.client.get(key); + } + + public async set(key: string, value: string, options?: { EX?: number }): Promise { + if (!this.isInitialized) { + await this.initialize(); + } + await this.client.set(key, value, options); + } + + public async del(key: string): Promise { + if (!this.isInitialized) { + await this.initialize(); + } + await this.client.del(key); + } + + public async exists(key: string): Promise { + if (!this.isInitialized) { + await this.initialize(); + } + const result = await this.client.exists(key); + return result === 1; + } + + public async quit(): Promise { + if (this.isInitialized) { + await this.client.quit(); + this.isInitialized = false; + } + } +} \ No newline at end of file diff --git a/backend/src/services/searchService.ts b/backend/src/services/searchService.ts index a8b7fe7..470840b 100644 --- a/backend/src/services/searchService.ts +++ b/backend/src/services/searchService.ts @@ -5,45 +5,52 @@ import { handleError } from '../decorators/errorHandler'; import { AppError } from '../utils/AppError'; import { LoginResponse, SearchResponse, SearchParams, SearchResult, LinkResult } from '../entity/search.types'; import { SEARCH_SERVICE } from '../constants/redis.keys'; -import { redis } from '../lib/redis'; +import { RedisService } from './redisService'; export class SearchService { private baseUrl: string; + private redisService: RedisService; constructor() { this.baseUrl = config.searchService.baseUrl; + this.redisService = RedisService.getInstance(); logger.info('搜索服务已初始化', { baseUrl: this.baseUrl }); } @handleError('获取认证 token 失败') private async getToken(): Promise { - // 尝试从 Redis 获取 token - const cachedToken = await redis.get(SEARCH_SERVICE.TOKEN); - if (cachedToken) { - logger.debug('从缓存中获取到 token'); - return cachedToken; - } - - logger.info('缓存中未找到 token,尝试登录获取'); - // 登录获取新 token - const response = await axios.post( - `${this.baseUrl}${config.searchService.endpoints.login}`, - { - username: process.env.SEARCH_SERVICE_USERNAME, - password: process.env.SEARCH_SERVICE_PASSWORD, + try { + // 尝试从 Redis 获取 token + const cachedToken = await this.redisService.get(SEARCH_SERVICE.TOKEN); + if (cachedToken) { + logger.debug('从缓存中获取到 token'); + return cachedToken; } - ); - if (!response.data.success || !response.data.data.token) { - throw new AppError('登录失败', 401); + logger.info('缓存中未找到 token,尝试登录获取'); + // 登录获取新 token + const response = await axios.post( + `${this.baseUrl}${config.searchService.endpoints.login}`, + { + username: process.env.SEARCH_SERVICE_USERNAME, + password: process.env.SEARCH_SERVICE_PASSWORD, + } + ); + + if (!response.data.success || !response.data.data.token) { + throw new AppError('登录失败', 401); + } + + logger.info('成功获取新 token'); + // 将 token 缓存到 Redis + await this.redisService.set(SEARCH_SERVICE.TOKEN, response.data.data.token, { + EX: 21600, // 6 小时 + }); + return response.data.data.token; + } catch (error) { + logger.error('获取 token 失败:', { error }); + throw error; } - - logger.info('成功获取新 token'); - // 将 token 缓存到 Redis - await redis.set(SEARCH_SERVICE.TOKEN, response.data.data.token, { - EX: 21600, // 6 小时 - }); - return response.data.data.token; } @handleError('搜索执行失败')