From a09d6cab0ecb918458e35bf647591699fe11df91 Mon Sep 17 00:00:00 2001 From: kjqwer <2990346238@qq.com> Date: Sun, 31 Aug 2025 18:55:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=94=B9=E4=B8=BA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E5=99=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/auth.js | 145 ++++++++++-- backend/config.js | 9 +- backend/config/cache-config.js | 29 ++- backend/config/config-manager.js | 41 ++-- backend/core.js | 55 +++-- backend/middleware/errorHandler.js | 7 +- backend/routes/auth.js | 7 +- backend/routes/download.js | 9 +- backend/routes/proxy.js | 7 +- backend/routes/ranking.js | 5 +- backend/scripts/migrate-downloads.js | 25 +- backend/server.js | 47 ++-- backend/services/api-cache.js | 35 +-- backend/services/artist.js | 23 +- backend/services/artwork.js | 23 +- backend/services/download-executor.js | 21 +- backend/services/download.js | 45 ++-- backend/services/file-manager.js | 18 +- backend/services/history-manager.js | 15 +- backend/services/image-cache.js | 36 +-- backend/services/progress-manager.js | 8 +- backend/services/repository.js | 52 +++-- backend/services/task-manager.js | 14 +- backend/start.js | 20 +- backend/test-login.js | 93 ++++---- backend/utils/error-handler.js | 83 +++---- backend/utils/file-utils.js | 27 ++- backend/utils/logger.js | 324 ++++++++++++++++++++++++++ scripts/create-portable.js | 9 +- ui/src/stores/auth.ts | 53 ++++- 30 files changed, 962 insertions(+), 323 deletions(-) create mode 100644 backend/utils/logger.js diff --git a/backend/auth.js b/backend/auth.js index 3e40d09..c38bfb8 100644 --- a/backend/auth.js +++ b/backend/auth.js @@ -4,6 +4,11 @@ const { Base64 } = require('js-base64'); const { stringify } = require('qs'); const moment = require('moment'); const { ProxyAgent } = require('proxy-agent'); +const { defaultLogger } = require('./utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('PixivAuth'); + // OAuth 2.0 配置 const CLIENT_ID = 'MOBrBDS8blbauoSck0ZfDbtuzpyT'; @@ -20,6 +25,8 @@ class PixivAuth { this.proxy = proxy; this.isRefreshing = false; this.failedQueue = []; + this.refreshTimer = null; // 添加定时器引用 + this.onTokenUpdate = null; // 添加token更新回调 // 创建 axios 实例,支持代理 this.axiosInstance = this.createAxiosInstance(); @@ -39,13 +46,13 @@ class PixivAuth { // 如果设置了代理,添加代理配置 if (this.proxy) { - console.log('使用代理:', this.proxy); + logger.info('使用代理:', this.proxy); config.httpsAgent = new ProxyAgent(this.proxy); } else { // 尝试使用系统代理 const systemProxy = process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy; if (systemProxy) { - console.log('使用系统代理:', systemProxy); + logger.info('使用系统代理:', systemProxy); config.httpsAgent = new ProxyAgent(systemProxy); } } @@ -83,7 +90,7 @@ class PixivAuth { this.isRefreshing = true; try { - console.log('检测到token过期,正在自动刷新...'); + logger.info('检测到token过期,正在自动刷新...'); const result = await this.refreshAccessToken(this.refreshToken); if (result.success) { @@ -94,6 +101,9 @@ class PixivAuth { this.user = result.user; } + // 触发token更新回调 + this.triggerTokenUpdate(); + // 处理队列中的请求 this.processQueue(null, result.access_token); @@ -104,7 +114,7 @@ class PixivAuth { throw new Error('Token刷新失败'); } } catch (refreshError) { - console.error('自动刷新token失败:', refreshError.message); + logger.error('自动刷新token失败:', refreshError.message); this.processQueue(refreshError, null); return Promise.reject(refreshError); } finally { @@ -149,6 +159,101 @@ class PixivAuth { if (user) { this.user = user; } + + // 启动主动定时刷新 + this.startProactiveRefresh(); + } + + /** + * 设置token更新回调 + */ + setTokenUpdateCallback(callback) { + this.onTokenUpdate = callback; + } + + /** + * 触发token更新回调 + */ + triggerTokenUpdate() { + if (this.onTokenUpdate && typeof this.onTokenUpdate === 'function') { + try { + this.onTokenUpdate({ + access_token: this.accessToken, + refresh_token: this.refreshToken, + user: this.user + }); + } catch (error) { + logger.error('Token更新回调执行失败:', error.message); + } + } + } + + /** + * 启动主动定时刷新token + */ + startProactiveRefresh() { + // 清除之前的定时器 + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = null; + } + + // 如果没有refreshToken,不启动定时刷新 + if (!this.refreshToken) { + return; + } + + // 计算下次刷新时间(在token过期前30分钟刷新) + // Pixiv的access_token通常有效期为1小时,我们提前30分钟刷新 + const refreshInterval = 30 * 60 * 1000; // 30分钟 + + this.refreshTimer = setTimeout(async () => { + try { + logger.info('主动刷新token...'); + const result = await this.refreshAccessToken(this.refreshToken); + + if (result.success) { + logger.info('主动刷新token成功'); + // 更新token + this.accessToken = result.access_token; + this.refreshToken = result.refresh_token; + if (result.user) { + this.user = result.user; + } + + // 触发token更新回调 + this.triggerTokenUpdate(); + + // 重新启动定时刷新 + this.startProactiveRefresh(); + } else { + logger.error('主动刷新token失败:', result.error); + // 刷新失败,5分钟后重试 + setTimeout(() => { + this.startProactiveRefresh(); + }, 5 * 60 * 1000); + } + } catch (error) { + logger.error('主动刷新token异常:', error.message); + // 发生异常,5分钟后重试 + setTimeout(() => { + this.startProactiveRefresh(); + }, 5 * 60 * 1000); + } + }, refreshInterval); + + logger.info(`主动刷新定时器已启动,${refreshInterval / 1000 / 60}分钟后刷新token`); + } + + /** + * 停止主动定时刷新 + */ + stopProactiveRefresh() { + if (this.refreshTimer) { + clearTimeout(this.refreshTimer); + this.refreshTimer = null; + logger.info('主动刷新定时器已停止'); + } } /** @@ -205,9 +310,9 @@ class PixivAuth { */ async getAccessToken(code, codeVerifier) { try { - console.log('正在获取访问令牌...'); - console.log('Code:', code); - console.log('Code Verifier:', codeVerifier); + logger.info('正在获取访问令牌...'); + logger.info('Code:', code); + logger.info('Code Verifier:', codeVerifier); const data = { client_id: CLIENT_ID, @@ -235,7 +340,7 @@ class PixivAuth { this.refreshToken = tokenData.refresh_token; this.user = tokenData.user; - console.log('获取访问令牌成功'); + logger.info('获取访问令牌成功'); return { success: true, access_token: tokenData.access_token, @@ -244,11 +349,11 @@ class PixivAuth { }; } catch (error) { - console.error('获取访问令牌失败:'); - console.error('错误对象:', error); - console.error('响应状态:', error.response?.status); - console.error('响应数据:', error.response?.data); - console.error('错误消息:', error.message); + logger.error('获取访问令牌失败:'); + logger.error('错误对象:', error); + logger.error('响应状态:', error.response?.status); + logger.error('响应数据:', error.response?.data); + logger.error('错误消息:', error.message); return { success: false, @@ -262,7 +367,7 @@ class PixivAuth { */ async refreshAccessToken(refreshToken) { try { - console.log('正在刷新访问令牌...'); + logger.info('正在刷新访问令牌...'); const data = { client_id: CLIENT_ID, @@ -293,7 +398,7 @@ class PixivAuth { this.user = tokenData.user; } - console.log('刷新访问令牌成功'); + logger.info('刷新访问令牌成功'); return { success: true, access_token: tokenData.access_token, @@ -302,7 +407,7 @@ class PixivAuth { }; } catch (error) { - console.error('刷新访问令牌失败:', error.response?.data || error.message); + logger.error('刷新访问令牌失败:', error.response?.data || error.message); return { success: false, error: error.response?.data || error.message @@ -334,7 +439,7 @@ class PixivAuth { }; } catch (error) { - console.error('获取用户信息失败:', error.response?.data || error.message); + logger.error('获取用户信息失败:', error.response?.data || error.message); return { success: false, error: error.response?.data || error.message @@ -349,7 +454,11 @@ class PixivAuth { this.accessToken = null; this.refreshToken = null; this.user = null; - console.log('已登出'); + + // 停止主动刷新 + this.stopProactiveRefresh(); + + logger.info('已登出'); return { success: true }; } diff --git a/backend/config.js b/backend/config.js index c475116..a933180 100644 --- a/backend/config.js +++ b/backend/config.js @@ -1,3 +1,8 @@ +const { defaultLogger } = require('./utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ProxyConfig'); + // 代理配置 const proxyConfig = { // 系统代理配置 @@ -19,7 +24,7 @@ const proxyConfig = { process.env.http_proxy = this.proxyUrl; process.env.https_proxy = this.proxyUrl; - console.log('代理环境变量已设置:', this.proxyUrl); + logger.info('代理环境变量已设置:', this.proxyUrl); }, // 清除环境变量 @@ -29,7 +34,7 @@ const proxyConfig = { delete process.env.http_proxy; delete process.env.https_proxy; - console.log('代理环境变量已清除'); + logger.info('代理环境变量已清除'); } }; diff --git a/backend/config/cache-config.js b/backend/config/cache-config.js index 65bb7e8..89c955d 100644 --- a/backend/config/cache-config.js +++ b/backend/config/cache-config.js @@ -1,5 +1,10 @@ const fs = require('fs').promises; const path = require('path'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('CacheConfigManager'); + /** * 缓存配置管理器 @@ -58,10 +63,10 @@ class CacheConfigManager { const configDir = path.dirname(this.configPath); if (!require('fs').existsSync(configDir)) { require('fs').mkdirSync(configDir, { recursive: true }); - // console.log('缓存配置目录创建成功:', configDir); + // logger.info('缓存配置目录创建成功:', configDir); } } catch (error) { - console.error('创建缓存配置目录失败:', error); + logger.error('创建缓存配置目录失败:', error); } } @@ -72,10 +77,10 @@ class CacheConfigManager { try { // 检查配置文件是否存在 await fs.access(this.configPath); - // console.log('缓存配置文件已存在'); + // logger.info('缓存配置文件已存在'); } catch (error) { // 配置文件不存在,创建默认配置 - console.log('创建默认缓存配置文件...'); + logger.info('创建默认缓存配置文件...'); await this.createDefaultConfig(); } } @@ -87,9 +92,9 @@ class CacheConfigManager { try { const configContent = JSON.stringify(this.defaultConfig, null, 2); await fs.writeFile(this.configPath, configContent, 'utf8'); - // console.log('默认缓存配置文件创建成功:', this.configPath); + // logger.info('默认缓存配置文件创建成功:', this.configPath); } catch (error) { - console.error('创建默认缓存配置文件失败:', error); + logger.error('创建默认缓存配置文件失败:', error); throw error; } } @@ -105,7 +110,7 @@ class CacheConfigManager { // 合并默认配置,确保所有字段都存在 return { ...this.defaultConfig, ...config }; } catch (error) { - console.error('加载缓存配置失败:', error); + logger.error('加载缓存配置失败:', error); return this.defaultConfig; } } @@ -120,9 +125,9 @@ class CacheConfigManager { const configContent = JSON.stringify(config, null, 2); await fs.writeFile(this.configPath, configContent, 'utf8'); - console.log('缓存配置保存成功'); + logger.info('缓存配置保存成功'); } catch (error) { - console.error('保存缓存配置失败:', error); + logger.error('保存缓存配置失败:', error); throw error; } } @@ -137,7 +142,7 @@ class CacheConfigManager { await this.saveConfig(newConfig); return newConfig; } catch (error) { - console.error('更新缓存配置失败:', error); + logger.error('更新缓存配置失败:', error); throw error; } } @@ -148,10 +153,10 @@ class CacheConfigManager { async resetToDefault() { try { await this.saveConfig(this.defaultConfig); - console.log('缓存配置已重置为默认值'); + logger.info('缓存配置已重置为默认值'); return this.defaultConfig; } catch (error) { - console.error('重置缓存配置失败:', error); + logger.error('重置缓存配置失败:', error); throw error; } } diff --git a/backend/config/config-manager.js b/backend/config/config-manager.js index 4eed7e8..700f605 100644 --- a/backend/config/config-manager.js +++ b/backend/config/config-manager.js @@ -1,5 +1,10 @@ const fs = require('fs').promises const path = require('path') +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ConfigManager'); + /** * 配置管理器 @@ -41,10 +46,10 @@ class ConfigManager { const configDirPath = path.dirname(this.configDir) if (!require('fs').existsSync(configDirPath)) { require('fs').mkdirSync(configDirPath, { recursive: true }) - console.log('配置目录创建成功:', configDirPath) + logger.info('配置目录创建成功:', configDirPath) } } catch (error) { - console.error('创建配置目录失败:', error) + logger.error('创建配置目录失败:', error) } } @@ -56,10 +61,10 @@ class ConfigManager { try { // 检查配置文件是否存在 await fs.access(this.configDir) - console.log('用户配置文件已存在') + logger.info('用户配置文件已存在') } catch (error) { // 配置文件不存在,创建默认配置 - console.log('创建默认用户配置文件...') + logger.info('创建默认用户配置文件...') await this.createDefaultConfig() } } @@ -76,9 +81,9 @@ class ConfigManager { // 检查目录是否创建成功 try { await fs.access(configDirPath) - console.log('配置目录确认存在:', configDirPath) + logger.info('配置目录确认存在:', configDirPath) } catch (accessError) { - console.error('配置目录访问失败:', accessError) + logger.error('配置目录访问失败:', accessError) throw new Error(`无法访问配置目录: ${configDirPath}`) } @@ -89,13 +94,13 @@ class ConfigManager { // 验证文件是否写入成功 try { await fs.access(this.configDir) - console.log('默认配置文件创建成功:', this.configDir) + logger.info('默认配置文件创建成功:', this.configDir) } catch (verifyError) { - console.error('配置文件验证失败:', verifyError) + logger.error('配置文件验证失败:', verifyError) throw new Error('配置文件创建后无法访问') } } catch (error) { - console.error('创建默认配置文件失败:', error) + logger.error('创建默认配置文件失败:', error) throw error } } @@ -108,7 +113,7 @@ class ConfigManager { // 首先检查文件是否存在 const exists = await this.configExists() if (!exists) { - console.log('配置文件不存在,创建默认配置...') + logger.info('配置文件不存在,创建默认配置...') await this.createDefaultConfig() } @@ -118,14 +123,14 @@ class ConfigManager { // 合并默认配置,确保所有必要的字段都存在 return { ...this.defaultConfig, ...config } } catch (error) { - console.error('读取配置文件失败:', error) - console.log('使用默认配置...') + logger.error('读取配置文件失败:', error) + logger.info('使用默认配置...') // 如果读取失败,尝试创建默认配置 try { await this.createDefaultConfig() return { ...this.defaultConfig } } catch (createError) { - console.error('创建默认配置也失败:', createError) + logger.error('创建默认配置也失败:', createError) // 最后返回内存中的默认配置 return { ...this.defaultConfig } } @@ -149,10 +154,10 @@ class ConfigManager { 'utf8' ) - console.log('配置文件保存成功') + logger.info('配置文件保存成功') return true } catch (error) { - console.error('保存配置文件失败:', error) + logger.error('保存配置文件失败:', error) throw error } } @@ -167,7 +172,7 @@ class ConfigManager { await this.saveConfig(newConfig) return newConfig } catch (error) { - console.error('更新配置失败:', error) + logger.error('更新配置失败:', error) throw error } } @@ -178,10 +183,10 @@ class ConfigManager { async resetToDefault() { try { await this.saveConfig(this.defaultConfig) - console.log('配置已重置为默认值') + logger.info('配置已重置为默认值') return this.defaultConfig } catch (error) { - console.error('重置配置失败:', error) + logger.error('重置配置失败:', error) throw error } } diff --git a/backend/core.js b/backend/core.js index a13f495..83465f4 100644 --- a/backend/core.js +++ b/backend/core.js @@ -2,11 +2,14 @@ const Fse = require('fs-extra'); const Path = require('path'); const PixivAuth = require('./auth'); const DownloadService = require('./services/download'); - +const { defaultLogger } = require('./utils/logger'); // 配置文件路径 const CONFIG_FILE_DIR = require('appdata-path').getAppDataPath('pmanager'); const CONFIG_FILE = Path.resolve(CONFIG_FILE_DIR, 'config.json'); +// 创建logger实例 +const logger = defaultLogger.child('PixivBackend'); + // 默认配置 const defaultConfig = { download: { @@ -32,7 +35,7 @@ class PixivBackend { * 初始化后端 */ async init() { - console.log('正在初始化 Pixiv 后端...'); + logger.info('正在初始化 Pixiv 后端...'); // 初始化配置 this.initConfig(); @@ -41,6 +44,15 @@ class PixivBackend { // 创建认证实例,传入代理配置 this.auth = new PixivAuth(this.config.proxy); + // 设置token更新回调 + this.auth.setTokenUpdateCallback((tokens) => { + this.config.access_token = tokens.access_token; + this.config.refresh_token = tokens.refresh_token; + this.config.user = tokens.user; + this.saveConfig(); + logger.info('Token已更新并保存到配置文件'); + }); + // 同步已保存的token状态 if (this.config.access_token && this.config.refresh_token) { this.auth.syncTokens( @@ -56,10 +68,10 @@ class PixivBackend { // 检查登录状态 if (this.config.refresh_token) { - console.log('检测到已保存的登录信息,正在验证...'); + logger.info('检测到已保存的登录信息,正在验证...'); await this.relogin(); } else { - console.log('未检测到登录信息,需要先登录'); + logger.info('未检测到登录信息,需要先登录'); } // 启动token同步定时任务 @@ -79,7 +91,7 @@ class PixivBackend { } }, 5 * 60 * 1000); // 5分钟 - console.log('Token同步定时任务已启动'); + logger.info('Token同步定时任务已启动'); } /** @@ -101,7 +113,7 @@ class PixivBackend { // 合并默认配置 return { ...defaultConfig, ...config }; } catch (error) { - console.error('读取配置文件失败:', error.message); + logger.error('读取配置文件失败:', error.message); return { ...defaultConfig }; } } @@ -112,9 +124,9 @@ class PixivBackend { saveConfig() { try { Fse.writeJsonSync(CONFIG_FILE, this.config); - console.log('配置已保存'); + logger.info('配置已保存'); } catch (error) { - console.error('保存配置失败:', error.message); + logger.error('保存配置失败:', error.message); } } @@ -137,7 +149,7 @@ class PixivBackend { */ async handleLoginCallback(code) { try { - console.log('正在处理登录回调...'); + logger.info('正在处理登录回调...'); if (!this.config.code_verifier) { throw new Error('缺少 code_verifier,请重新获取登录URL'); @@ -152,13 +164,20 @@ class PixivBackend { this.config.access_token = result.access_token; this.config.user = result.user; + // 同步到auth实例并启动主动刷新 + this.auth.syncTokens( + result.access_token, + result.refresh_token, + result.user + ); + // 清理临时数据 delete this.config.code_verifier; this.saveConfig(); this.isLoggedIn = true; - console.log(`登录成功!用户: ${result.user.account}`); + logger.info(`登录成功!用户: ${result.user.account}`); return { success: true, user: result.user @@ -168,7 +187,7 @@ class PixivBackend { } } catch (error) { - console.error('登录失败:', error.message); + logger.error('登录失败:', error.message); return { success: false, error: error.message @@ -185,7 +204,7 @@ class PixivBackend { throw new Error('没有保存的登录信息'); } - console.log('正在使用保存的登录信息重新登录...'); + logger.info('正在使用保存的登录信息重新登录...'); const result = await this.auth.refreshAccessToken(this.config.refresh_token); @@ -199,7 +218,7 @@ class PixivBackend { this.config.user = result.user; } - // 同步到auth实例 + // 同步到auth实例并启动主动刷新 this.auth.syncTokens( result.access_token, result.refresh_token, @@ -209,7 +228,7 @@ class PixivBackend { this.saveConfig(); this.isLoggedIn = true; - console.log('重新登录成功!'); + logger.info('重新登录成功!'); return { success: true }; } else { @@ -217,7 +236,7 @@ class PixivBackend { } } catch (error) { - console.error('重新登录失败:', error.message); + logger.error('重新登录失败:', error.message); // 清除无效的登录信息 this.config.refresh_token = null; this.config.access_token = null; @@ -243,7 +262,7 @@ class PixivBackend { this.isLoggedIn = false; this.saveConfig(); - console.log('已登出'); + logger.info('已登出'); return { success: true }; } @@ -266,7 +285,7 @@ class PixivBackend { setDownloadPath(path) { this.config.download.path = path; this.saveConfig(); - console.log(`下载路径已设置为: ${path}`); + logger.info(`下载路径已设置为: ${path}`); return { success: true }; } @@ -288,7 +307,7 @@ class PixivBackend { this.config.proxy = proxy; this.auth.setProxy(proxy); this.saveConfig(); - console.log(`代理已设置为: ${proxy}`); + logger.info(`代理已设置为: ${proxy}`); return { success: true }; } diff --git a/backend/middleware/errorHandler.js b/backend/middleware/errorHandler.js index 96d3e3d..0a628f4 100644 --- a/backend/middleware/errorHandler.js +++ b/backend/middleware/errorHandler.js @@ -1,8 +1,13 @@ /** * 全局错误处理中间件 */ +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ErrorHandler'); + const errorHandler = (err, req, res, next) => { - console.error('错误详情:', err); + logger.error('错误详情', err); // 默认错误信息 let statusCode = 500; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 030fe23..c4acc1a 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,5 +1,10 @@ const express = require('express'); const router = express.Router(); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('AuthRouter'); + /** * 获取登录状态 @@ -152,7 +157,7 @@ router.post('/refresh-token', async (req, res) => { }); } } catch (error) { - console.error('手动刷新token失败:', error); + logger.error('手动刷新token失败:', error); res.status(500).json({ success: false, error: error.message diff --git a/backend/routes/download.js b/backend/routes/download.js index fb333fb..693509e 100644 --- a/backend/routes/download.js +++ b/backend/routes/download.js @@ -1,6 +1,11 @@ const express = require('express'); const router = express.Router(); const DownloadService = require('../services/download'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('DownloadRouter'); + /** * 下载单个作品 @@ -43,7 +48,7 @@ router.post('/artwork/:id', async (req, res) => { }); } } catch (error) { - console.error('下载路由错误:', error); + logger.error('下载路由错误:', error); res.status(500).json({ success: false, error: error.message @@ -655,7 +660,7 @@ router.get('/stream/:taskId', async (req, res) => { } } } catch (error) { - console.error('SSE写入失败:', error); + logger.error('SSE写入失败:', error); // 连接可能已断开,清理监听器 downloadService.removeProgressListener(taskId, progressListener); } diff --git a/backend/routes/proxy.js b/backend/routes/proxy.js index 930e0b2..c5edf69 100644 --- a/backend/routes/proxy.js +++ b/backend/routes/proxy.js @@ -2,6 +2,11 @@ const express = require('express'); const router = express.Router(); const ImageCacheService = require('../services/image-cache'); const ApiCacheService = require('../services/api-cache'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ProxyRouter'); + // 创建缓存服务实例 const imageCache = new ImageCacheService(); @@ -40,7 +45,7 @@ router.get('/image', async (req, res) => { res.send(imageData); } catch (error) { - console.error('Image proxy error:', error.message); + logger.error('Image proxy error:', error.message); res.status(500).json({ success: false, error: 'Failed to load image' diff --git a/backend/routes/ranking.js b/backend/routes/ranking.js index aae7165..455fd5b 100644 --- a/backend/routes/ranking.js +++ b/backend/routes/ranking.js @@ -1,8 +1,11 @@ const express = require('express'); const ArtworkService = require('../services/artwork'); const ResponseUtil = require('../utils/response'); +const { defaultLogger } = require('../utils/logger'); const router = express.Router(); +const logger = defaultLogger.child('RankingRouter'); + /** * 获取排行榜数据 @@ -34,7 +37,7 @@ router.get('/', async (req, res) => { res.json(ResponseUtil.success(result)); } catch (error) { - console.error('获取排行榜失败:', error); + logger.error('获取排行榜失败:', error); res.status(500).json(ResponseUtil.error(error.message)); } }); diff --git a/backend/scripts/migrate-downloads.js b/backend/scripts/migrate-downloads.js index 42cdfb0..298d870 100644 --- a/backend/scripts/migrate-downloads.js +++ b/backend/scripts/migrate-downloads.js @@ -1,6 +1,11 @@ const fs = require('fs').promises const path = require('path') const fsExtra = require('fs-extra') +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('MigrateDownloads'); + /** * 转换现有的下载格式为仓库管理格式 @@ -11,7 +16,7 @@ async function migrateDownloads() { const downloadsPath = path.join(__dirname, '../../downloads') try { - console.log('开始转换下载格式...') + logger.info('开始转换下载格式...') // 读取downloads目录 const entries = await fs.readdir(downloadsPath, { withFileTypes: true }) @@ -20,12 +25,12 @@ async function migrateDownloads() { if (!entry.isDirectory()) continue const oldDirName = entry.name - console.log(`处理目录: ${oldDirName}`) + logger.info(`处理目录: ${oldDirName}`) // 解析目录名: {artistName}_{artworkId} const match = oldDirName.match(/^(.+)_(\d+)$/) if (!match) { - console.log(`跳过不符合格式的目录: ${oldDirName}`) + logger.info(`跳过不符合格式的目录: ${oldDirName}`) continue } @@ -46,7 +51,7 @@ async function migrateDownloads() { const newArtworkDirName = `${artworkId}_${artworkTitle}` const newArtworkPath = path.join(newArtistDir, newArtworkDirName) - console.log(`转换: ${oldArtworkPath} -> ${newArtworkPath}`) + logger.info(`转换: ${oldArtworkPath} -> ${newArtworkPath}`) try { // 创建新的作者目录 @@ -55,9 +60,9 @@ async function migrateDownloads() { // 移动作品目录 await fsExtra.move(oldArtworkPath, newArtworkPath) - console.log(`✓ 成功转换: ${artworkTitle}`) + logger.info(`✓ 成功转换: ${artworkTitle}`) } catch (error) { - console.error(`✗ 转换失败: ${artworkTitle}`, error.message) + logger.error(`✗ 转换失败: ${artworkTitle}`, error.message) } } @@ -66,17 +71,17 @@ async function migrateDownloads() { const remainingEntries = await fs.readdir(oldDirPath) if (remainingEntries.length === 0) { await fsExtra.remove(oldDirPath) - console.log(`删除空目录: ${oldDirPath}`) + logger.info(`删除空目录: ${oldDirPath}`) } } catch (error) { - console.error(`删除目录失败: ${oldDirPath}`, error.message) + logger.error(`删除目录失败: ${oldDirPath}`, error.message) } } - console.log('转换完成!') + logger.info('转换完成!') } catch (error) { - console.error('转换过程中发生错误:', error) + logger.error('转换过程中发生错误:', error) } } diff --git a/backend/server.js b/backend/server.js index c4fc511..f914e89 100644 --- a/backend/server.js +++ b/backend/server.js @@ -3,6 +3,9 @@ const cors = require('cors'); const morgan = require('morgan'); const path = require('path'); +// 导入logger +const { defaultLogger } = require('./utils/logger'); + // 导入路由模块 const authRoutes = require('./routes/auth'); const artworkRoutes = require('./routes/artwork'); @@ -20,6 +23,9 @@ const { authMiddleware } = require('./middleware/auth'); const PixivBackend = require('./core'); const proxyConfig = require('./config'); +// 创建logger实例 +const logger = defaultLogger.child('Server'); + // 自定义日志中间件 function customLogger(req, res, next) { // 过滤掉静态资源请求和图片代理请求 @@ -43,10 +49,21 @@ function customLogger(req, res, next) { const isImageProxy = req.path === '/api/proxy/image'; // 过滤掉下载任务状态查询请求 - const isDownloadTasksQuery = req.path === '/api/download/tasks'; + const isDownloadTasksQuery = + req.path === '/api/download/tasks' || + req.path === '/api/download/tasks/active' || + req.path === '/api/download/tasks/summary' || + req.path === '/api/download/tasks/changes' || + req.path === '/api/download/tasks/completed'; - // 只记录API请求和重要请求,排除静态资源、图片代理和下载任务查询 - if (!isStaticResource && !isImageProxy && !isDownloadTasksQuery) { + // 过滤掉仓库预览请求(图片预览) + const isRepositoryPreview = req.path === '/api/repository/preview'; + + // 过滤掉健康检查请求 + const isHealthCheck = req.path === '/health'; + + // 只记录重要的API请求,排除静态资源、图片代理、下载任务查询、仓库预览和健康检查 + if (!isStaticResource && !isImageProxy && !isDownloadTasksQuery && !isRepositoryPreview && !isHealthCheck) { const start = Date.now(); // 原始响应结束方法 @@ -110,7 +127,7 @@ function customLogger(req, res, next) { const logMessage = `${statusColor}${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms\x1b[0m`; // 输出日志 - console.log(`[${timeStr}] ${logMessage}`); + logger.info(`${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms`); // 调用原始的end方法 originalEnd.call(this, chunk, encoding); @@ -131,7 +148,7 @@ class PixivServer { * 初始化服务器 */ async init() { - console.log('\x1b[34m🔧 正在初始化 Pixiv 后端服务器...\x1b[0m'); + logger.info('🔧 正在初始化 Pixiv 后端服务器...'); // 重新设置端口(从环境变量获取) this.port = process.env.PORT || 3000; @@ -152,7 +169,7 @@ class PixivServer { // 配置错误处理 - 临时注释掉 this.setupErrorHandling(); - console.log('\x1b[32m✅ 服务器初始化完成\x1b[0m'); + logger.info('✅ 服务器初始化完成'); } /** @@ -239,14 +256,14 @@ class PixivServer { */ start() { this.app.listen(this.port, () => { - console.log('\x1b[32m✅ Pixiv 后端服务器已启动\x1b[0m'); - console.log(`\x1b[36m📍 服务地址: http://localhost:${this.port}\x1b[0m`); - console.log(`\x1b[36m🔗 健康检查: http://localhost:${this.port}/health\x1b[0m`); - console.log(`\x1b[33m📊 登录状态: ${this.backend.isLoggedIn ? '已登录' : '未登录'}\x1b[0m`); + logger.info('✅ Pixiv 后端服务器已启动'); + logger.info(`📍 服务地址: http://localhost:${this.port}`); + logger.info(`🔗 健康检查: http://localhost:${this.port}/health`); + logger.info(`📊 登录状态: ${this.backend.isLoggedIn ? '已登录' : '未登录'}`); if (this.backend.isLoggedIn) { - console.log(`\x1b[33m👤 用户: ${this.backend.config.user?.account}\x1b[0m`); + logger.info(`👤 用户: ${this.backend.config.user?.account}`); } - console.log('\x1b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); }); } @@ -254,10 +271,10 @@ class PixivServer { * 优雅关闭 */ async shutdown() { - console.log('\x1b[33m🔄 正在关闭服务器...\x1b[0m'); + logger.info('🔄 正在关闭服务器...'); // 清理代理环境变量 proxyConfig.clearEnvironmentVariables(); - console.log('\x1b[32m✅ 服务器已关闭\x1b[0m'); + logger.info('✅ 服务器已关闭'); process.exit(0); } } @@ -274,7 +291,7 @@ if (require.main === module) { server .init() .then(() => server.start()) - .catch(console.error); + .catch((error) => logger.error('服务器启动失败', error)); } module.exports = PixivServer; diff --git a/backend/services/api-cache.js b/backend/services/api-cache.js index c1daad9..9d76f60 100644 --- a/backend/services/api-cache.js +++ b/backend/services/api-cache.js @@ -2,6 +2,11 @@ const fs = require('fs').promises; const path = require('path'); const crypto = require('crypto'); const CacheConfigManager = require('../config/cache-config'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ApiCacheService'); + /** * API缓存服务 @@ -72,9 +77,9 @@ class ApiCacheService { // 启动定期清理任务 this.startCleanupTask(); - // console.log('API缓存服务初始化完成'); + // logger.info('API缓存服务初始化完成'); } catch (error) { - console.error('API缓存服务初始化失败:', error); + logger.error('API缓存服务初始化失败:', error); } } @@ -84,9 +89,9 @@ class ApiCacheService { async ensureCacheDir() { try { await fs.mkdir(this.cacheDir, { recursive: true }); - // console.log('API缓存目录创建成功:', this.cacheDir); + // logger.info('API缓存目录创建成功:', this.cacheDir); } catch (error) { - console.error('创建API缓存目录失败:', error); + logger.error('创建API缓存目录失败:', error); } } @@ -188,7 +193,7 @@ class ApiCacheService { return JSON.parse(data); } catch (error) { - console.error('读取API缓存失败:', error); + logger.error('读取API缓存失败:', error); return null; } } @@ -208,7 +213,7 @@ class ApiCacheService { // 检查缓存大小,如果超过限制则清理 await this.checkCacheSize(); } catch (error) { - console.error('保存API缓存失败:', error); + logger.error('保存API缓存失败:', error); } } @@ -303,7 +308,7 @@ class ApiCacheService { // 如果超过最大大小,删除最旧的文件 if (totalSize > this.config.maxSize) { - console.log(`API缓存大小 ${totalSize} 超过限制 ${this.config.maxSize},开始清理...`); + logger.info(`API缓存大小 ${totalSize} 超过限制 ${this.config.maxSize},开始清理...`); // 按修改时间排序,删除最旧的文件 fileStats.sort((a, b) => a.mtime.getTime() - b.mtime.getTime()); @@ -317,10 +322,10 @@ class ApiCacheService { } } - console.log(`API缓存清理完成,当前大小: ${totalSize}`); + logger.info(`API缓存清理完成,当前大小: ${totalSize}`); } } catch (error) { - console.error('检查API缓存大小失败:', error); + logger.error('检查API缓存大小失败:', error); } } @@ -345,10 +350,10 @@ class ApiCacheService { } if (cleanedCount > 0) { - console.log(`清理了 ${cleanedCount} 个过期API缓存文件`); + logger.info(`清理了 ${cleanedCount} 个过期API缓存文件`); } } catch (error) { - console.error('清理过期API缓存失败:', error); + logger.error('清理过期API缓存失败:', error); } } @@ -358,7 +363,7 @@ class ApiCacheService { startCleanupTask() { setInterval(() => { this.cleanupExpiredCache().catch(error => { - console.error('定期清理API缓存任务失败:', error); + logger.error('定期清理API缓存任务失败:', error); }); }, this.config.cleanupInterval); } @@ -376,9 +381,9 @@ class ApiCacheService { await fs.unlink(filePath); } - console.log('所有API缓存已清理'); + logger.info('所有API缓存已清理'); } catch (error) { - console.error('清理所有API缓存失败:', error); + logger.error('清理所有API缓存失败:', error); throw error; } } @@ -410,7 +415,7 @@ class ApiCacheService { config: this.config }; } catch (error) { - console.error('获取API缓存统计失败:', error); + logger.error('获取API缓存统计失败:', error); return { fileCount: 0, totalSize: 0, diff --git a/backend/services/artist.js b/backend/services/artist.js index 4fc3304..6e62c65 100644 --- a/backend/services/artist.js +++ b/backend/services/artist.js @@ -1,6 +1,11 @@ const axios = require('axios'); const { stringify } = require('qs'); const ApiCacheService = require('./api-cache'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ArtistService'); + class ArtistService { constructor(auth) { @@ -197,7 +202,7 @@ class ArtistService { limit, }; - console.log(`请求关注列表: offset=${currentOffset}, limit=${limit}`); + logger.info(`请求关注列表: offset=${currentOffset}, limit=${limit}`); const response = await this.makeRequest('GET', `/v1/user/following?${stringify(params)}`); // 转换数据格式以匹配前端期望 @@ -210,12 +215,12 @@ class ArtistService { })); allArtists.push(...artists); - console.log(`本次获取到 ${artists.length} 个作者,累计 ${allArtists.length} 个`); + logger.info(`本次获取到 ${artists.length} 个作者,累计 ${allArtists.length} 个`); // 如果返回的数量少于limit,说明已经获取完所有数据 if (artists.length < limit) { hasMore = false; - console.log('已获取完所有关注的作者'); + logger.info('已获取完所有关注的作者'); } else { currentOffset += artists.length; } @@ -229,7 +234,7 @@ class ArtistService { }, }; } catch (error) { - console.error('获取关注作者列表失败:', error.message); + logger.error('获取关注作者列表失败:', error.message); return { success: false, error: error.message, @@ -335,11 +340,11 @@ class ArtistService { try { const cachedData = await this.apiCache.get(method, endpoint, data || {}); if (cachedData) { - // console.log(`API缓存命中: ${method} ${endpoint}`); + // logger.info(`API缓存命中: ${method} ${endpoint}`); return cachedData; } } catch (error) { - console.error('读取API缓存失败:', error); + logger.error('读取API缓存失败:', error); } } @@ -378,15 +383,15 @@ class ArtistService { if (method === 'GET') { try { await this.apiCache.set(method, endpoint, data || {}, responseData); - console.log(`API缓存已保存: ${method} ${endpoint}`); + logger.info(`API缓存已保存: ${method} ${endpoint}`); } catch (error) { - console.error('保存API缓存失败:', error); + logger.error('保存API缓存失败:', error); } } return responseData; } catch (error) { - console.error('API请求失败:', { + logger.error('API请求失败:', { method, endpoint, status: error.response?.status, diff --git a/backend/services/artwork.js b/backend/services/artwork.js index 8ff3fae..b507181 100644 --- a/backend/services/artwork.js +++ b/backend/services/artwork.js @@ -1,6 +1,11 @@ const axios = require('axios'); const { stringify } = require('qs'); const ApiCacheService = require('./api-cache'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ArtworkService'); + class ArtworkService { constructor(auth) { @@ -205,8 +210,8 @@ class ArtworkService { }, }; } catch (error) { - console.error('Search error:', error.message); - console.error('Search error details:', error.response?.data); + logger.error('Search error:', error.message); + logger.error('Search error details:', error.response?.data); return { success: false, @@ -293,7 +298,7 @@ class ArtworkService { try { // TODO: 需要研究新的 Pixiv API 端点 // 当前所有收藏相关的 API 端点都返回 404 错误 - console.log(`尝试${action === 'add' ? '添加' : '删除'}收藏 ${artworkId},但API端点不可用`); + logger.info(`尝试${action === 'add' ? '添加' : '删除'}收藏 ${artworkId},但API端点不可用`); return { success: false, @@ -348,7 +353,7 @@ class ArtworkService { }, }; } catch (error) { - console.error('获取收藏列表失败:', { + logger.error('获取收藏列表失败:', { message: error.message, status: error.response?.status, data: error.response?.data, @@ -370,11 +375,11 @@ class ArtworkService { try { const cachedData = await this.apiCache.get(method, endpoint, data || {}); if (cachedData) { - // console.log(`API缓存命中: ${method} ${endpoint}`); + // logger.info(`API缓存命中: ${method} ${endpoint}`); return cachedData; } } catch (error) { - console.error('读取API缓存失败:', error); + logger.error('读取API缓存失败:', error); } } @@ -415,15 +420,15 @@ class ArtworkService { if (method === 'GET') { try { await this.apiCache.set(method, endpoint, data || {}, responseData); - // console.log(`API缓存已保存: ${method} ${endpoint}`); + // logger.info(`API缓存已保存: ${method} ${endpoint}`); } catch (error) { - console.error('保存API缓存失败:', error); + logger.error('保存API缓存失败:', error); } } return responseData; } catch (error) { - console.error('API request failed:', { + logger.error('API request failed:', { method, endpoint, error: error.message, diff --git a/backend/services/download-executor.js b/backend/services/download-executor.js index bc76eb4..adcfec8 100644 --- a/backend/services/download-executor.js +++ b/backend/services/download-executor.js @@ -1,5 +1,10 @@ const path = require('path'); const fs = require('fs-extra'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('DownloadExecutor'); + /** * 下载执行器 - 负责具体的下载逻辑执行 @@ -27,7 +32,7 @@ class DownloadExecutor { // 检查是否应该暂停 if (this.shouldPause(task.id)) { - console.log('任务已暂停,停止下载:', task.id); + logger.info('任务已暂停,停止下载:', task.id); break; } @@ -55,7 +60,7 @@ class DownloadExecutor { // 确保imageUrl是字符串 if (typeof imageUrl !== 'string') { - console.error(`图片URL不是字符串:`, imageUrl); + logger.error(`图片URL不是字符串:`, imageUrl); task.failed_files++; this.progressManager.notifyProgressUpdate(task.id, task); results.push({ success: false, error: '图片URL格式错误' }); @@ -78,7 +83,7 @@ class DownloadExecutor { continue; } else { // 文件不完整,删除重新下载 - console.log(`文件不完整,重新下载: ${filePath}`); + logger.info(`文件不完整,重新下载: ${filePath}`); await this.fileManager.safeDeleteFile(filePath); } } @@ -104,7 +109,7 @@ class DownloadExecutor { results.push({ success: true, file: fileName }); } catch (error) { task.failed_files++; - console.error(`下载图片失败 ${index + 1}: ${error.message}`); + logger.error(`下载图片失败 ${index + 1}: ${error.message}`); this.progressManager.notifyProgressUpdate(task.id, task); results.push({ success: false, error: error.message }); } @@ -139,7 +144,7 @@ class DownloadExecutor { await this.historyManager.addHistoryItem(historyItem); } catch (error) { - console.error('异步下载执行失败:', error); + logger.error('异步下载执行失败:', error); task.status = 'failed'; task.error = error.message; task.end_time = new Date(); @@ -166,7 +171,7 @@ class DownloadExecutor { // 检查是否应该暂停 if (this.shouldPause(task.id)) { - console.log('批量下载任务已暂停,停止下载:', task.id); + logger.info('批量下载任务已暂停,停止下载:', task.id); break; } @@ -508,7 +513,7 @@ class DownloadExecutor { */ getFileExtension(url) { if (typeof url !== 'string') { - console.warn('URL不是字符串,使用默认扩展名:', url); + logger.warn('URL不是字符串,使用默认扩展名:', url); return 'jpg'; } @@ -551,7 +556,7 @@ class DownloadExecutor { } else if (task.type === 'batch' || task.type === 'artist') { // 批量下载和作者下载的恢复逻辑 // 这里需要根据具体实现来恢复 - console.log('恢复批量下载任务:', taskId); + logger.info('恢复批量下载任务:', taskId); // TODO: 实现批量下载的恢复逻辑 } diff --git a/backend/services/download.js b/backend/services/download.js index 9bacd86..3c022ab 100644 --- a/backend/services/download.js +++ b/backend/services/download.js @@ -7,6 +7,11 @@ const ProgressManager = require('./progress-manager'); const HistoryManager = require('./history-manager'); const DownloadExecutor = require('./download-executor'); const fs = require('fs-extra'); // Added for fs-extra +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('DownloadService'); + /** * 下载服务 - 主服务类,协调各个管理器 @@ -56,7 +61,7 @@ class DownloadService { this.initialized = true; // 下载服务初始化完成 } catch (error) { - console.error('下载服务初始化失败:', error); + logger.error('下载服务初始化失败:', error); this.initialized = false; } } @@ -352,7 +357,7 @@ class DownloadService { return files.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); } catch (error) { - console.error('获取下载文件列表失败:', error); + logger.error('获取下载文件列表失败:', error); return []; } } @@ -396,7 +401,7 @@ class DownloadService { return Array.from(downloadedIds); } catch (error) { - console.error('获取已下载作品ID列表失败:', error); + logger.error('获取已下载作品ID列表失败:', error); return []; } } @@ -430,7 +435,7 @@ class DownloadService { const infoContent = await fs.readFile(infoPath, 'utf8'); artworkInfo = JSON.parse(infoContent); } catch (error) { - console.log(`作品 ${artworkId} 缺少信息文件,认为未下载`); + logger.info(`作品 ${artworkId} 缺少信息文件,认为未下载`); return false; } @@ -439,19 +444,19 @@ class DownloadService { const imageFiles = files.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file) && file !== 'artwork_info.json'); if (imageFiles.length === 0) { - console.log(`作品 ${artworkId} 有信息文件但没有图片文件,认为未下载`); + logger.info(`作品 ${artworkId} 有信息文件但没有图片文件,认为未下载`); return false; } // 检查图片数量是否与artwork_info.json中记录的一致 const expectedImageCount = artworkInfo.page_count || 1; if (imageFiles.length < expectedImageCount) { - console.log(`作品 ${artworkId} 图片数量不匹配: 期望 ${expectedImageCount} 个,实际 ${imageFiles.length} 个`); + logger.info(`作品 ${artworkId} 图片数量不匹配: 期望 ${expectedImageCount} 个,实际 ${imageFiles.length} 个`); return false; } // 有信息文件、有图片文件且数量匹配,认为已下载 - // console.log(`作品 ${artworkId} 已完整下载,有信息文件和 ${imageFiles.length}/${expectedImageCount} 个图片文件`); + // logger.info(`作品 ${artworkId} 已完整下载,有信息文件和 ${imageFiles.length}/${expectedImageCount} 个图片文件`); return true; } } @@ -459,7 +464,7 @@ class DownloadService { return false; } catch (error) { - console.error('检查作品下载状态失败:', error); + logger.error('检查作品下载状态失败:', error); return false; } } @@ -508,7 +513,7 @@ class DownloadService { // 如果是重新下载,先删除现有目录 if (!skipExisting && (await this.fileManager.directoryExists(artworkDir))) { - console.log(`删除现有作品目录: ${artworkDir}`); + logger.info(`删除现有作品目录: ${artworkDir}`); await this.fileManager.removeDirectory(artworkDir); } @@ -553,7 +558,7 @@ class DownloadService { }, }; } catch (error) { - console.error('下载作品失败:', error); + logger.error('下载作品失败:', error); return { success: false, error: error.message, @@ -630,7 +635,7 @@ class DownloadService { }, }; } catch (error) { - console.error('批量下载失败:', error); + logger.error('批量下载失败:', error); return { success: false, error: error.message, @@ -678,7 +683,7 @@ class DownloadService { // 如果是重新下载,先删除现有目录 if (!skipExisting && (await this.fileManager.directoryExists(artworkDir))) { - console.log(`删除现有作品目录: ${artworkDir}`); + logger.info(`删除现有作品目录: ${artworkDir}`); await this.fileManager.removeDirectory(artworkDir); } @@ -719,7 +724,7 @@ class DownloadService { // 确保imageUrl是字符串 if (typeof imageUrl !== 'string') { - console.error(`图片URL不是字符串:`, imageUrl); + logger.error(`图片URL不是字符串:`, imageUrl); results.push({ success: false, error: '图片URL格式错误' }); continue; } @@ -739,7 +744,7 @@ class DownloadService { await this.fileManager.downloadFile(imageUrl, filePath); results.push({ success: true, file: fileName }); } catch (error) { - console.error(`下载图片失败 ${index + 1}: ${error.message}`); + logger.error(`下载图片失败 ${index + 1}: ${error.message}`); results.push({ success: false, error: error.message }); } } @@ -763,7 +768,7 @@ class DownloadService { results: results, }; } catch (error) { - console.error(`下载作品 ${artworkId} 失败:`, error); + logger.error(`下载作品 ${artworkId} 失败:`, error); return { success: false, error: error.message, @@ -777,7 +782,7 @@ class DownloadService { */ getFileExtension(url) { if (typeof url !== 'string') { - console.warn('URL不是字符串,使用默认扩展名:', url); + logger.warn('URL不是字符串,使用默认扩展名:', url); return 'jpg'; } @@ -800,7 +805,7 @@ class DownloadService { artistName = artistResult.data.name || `作者 ${artistId}`; } } catch (err) { - console.warn(`获取作者 ${artistId} 信息失败:`, err.message); + logger.warn(`获取作者 ${artistId} 信息失败:`, err.message); } // 创建任务记录 @@ -901,7 +906,7 @@ class DownloadService { }, }; } catch (error) { - console.error('作者作品下载失败:', error); + logger.error('作者作品下载失败:', error); return { success: false, error: error.message, @@ -1013,7 +1018,7 @@ class DownloadService { }, }; } catch (error) { - console.error('排行榜作品下载失败:', error); + logger.error('排行榜作品下载失败:', error); return { success: false, error: error.message, @@ -1046,7 +1051,7 @@ class DownloadService { }, }; } catch (error) { - console.error('获取排行榜失败:', error); + logger.error('获取排行榜失败:', error); return { success: false, error: error.message, diff --git a/backend/services/file-manager.js b/backend/services/file-manager.js index 23e816d..a30b82f 100644 --- a/backend/services/file-manager.js +++ b/backend/services/file-manager.js @@ -5,6 +5,10 @@ const crypto = require('crypto'); const ConfigManager = require('../config/config-manager'); const FileUtils = require('../utils/file-utils'); const ErrorHandler = require('../utils/error-handler'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('FileManager'); /** * 文件管理器 - 负责文件下载、检查和目录管理 @@ -36,7 +40,7 @@ class FileManager { ? downloadDir : path.resolve(process.cwd(), downloadDir); } catch (error) { - console.error('获取下载路径失败:', error); + logger.error('获取下载路径失败:', error); // 返回默认路径 return path.resolve(process.cwd(), 'downloads'); } @@ -141,7 +145,7 @@ class FileManager { fs.access(filePath) .then(() => resolve()) .catch(error => { - console.error(`文件写入验证失败: ${filePath}`, error.message); + logger.error(`文件写入验证失败: ${filePath}`, error.message); reject(error); }); }); @@ -150,7 +154,7 @@ class FileManager { try { await this.safeDeleteFile(filePath); } catch (removeError) { - console.warn('清理失败文件时出错:', removeError.message); + logger.warn('清理失败文件时出错:', removeError.message); } reject(error); }); @@ -161,7 +165,7 @@ class FileManager { // 处理文件系统错误 const errorResult = ErrorHandler.handleFileSystemError(error, filePath, 'download'); - console.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, error.message); + logger.error(`下载文件失败 (尝试 ${attempt}/${maxRetries}): ${filePath}`, error.message); // 如果不是可重试的错误,直接抛出 if (!errorResult.retryable) { @@ -170,13 +174,13 @@ class FileManager { // 如果是最后一次尝试,抛出错误 if (attempt === maxRetries) { - console.error(`下载文件最终失败: ${filePath}`, error.message); + logger.error(`下载文件最终失败: ${filePath}`, error.message); throw error; } // 等待后重试 const retryDelay = ErrorHandler.getRetryDelay(error, attempt); - console.log(`等待 ${retryDelay}ms 后重试下载: ${filePath}`); + logger.info(`等待 ${retryDelay}ms 后重试下载: ${filePath}`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } @@ -323,7 +327,7 @@ class FileManager { await fs.unlink(filePath); } } catch (error) { - console.error(`文件删除失败: ${filePath}`, error.message); + logger.error(`文件删除失败: ${filePath}`, error.message); // 不抛出错误,避免影响其他操作 } } diff --git a/backend/services/history-manager.js b/backend/services/history-manager.js index 7a37875..c1e53bc 100644 --- a/backend/services/history-manager.js +++ b/backend/services/history-manager.js @@ -1,5 +1,10 @@ const fs = require('fs-extra'); const path = require('path'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('HistoryManager'); + /** * 历史记录管理器 - 负责下载历史的管理 @@ -28,9 +33,9 @@ class HistoryManager { await this.cleanupHistory(); this.initialized = true; - console.log('历史记录管理器初始化完成'); + logger.info('历史记录管理器初始化完成'); } catch (error) { - console.error('历史记录管理器初始化失败:', error); + logger.error('历史记录管理器初始化失败:', error); this.initialized = false; } } @@ -44,7 +49,7 @@ class HistoryManager { this.history = await fs.readJson(this.historyFile); } } catch (error) { - console.error('加载下载历史失败:', error); + logger.error('加载下载历史失败:', error); this.history = []; } } @@ -56,7 +61,7 @@ class HistoryManager { try { await fs.writeJson(this.historyFile, this.history, { spaces: 2 }); } catch (error) { - console.error('保存下载历史失败:', error); + logger.error('保存下载历史失败:', error); } } @@ -98,7 +103,7 @@ class HistoryManager { return; } - console.log(`清理历史记录: ${this.history.length} -> ${this.maxHistoryItems}`); + logger.info(`清理历史记录: ${this.history.length} -> ${this.maxHistoryItems}`); // 保留最新的记录 this.history = this.history.slice(0, this.maxHistoryItems); diff --git a/backend/services/image-cache.js b/backend/services/image-cache.js index a46575f..0750223 100644 --- a/backend/services/image-cache.js +++ b/backend/services/image-cache.js @@ -3,6 +3,10 @@ const path = require('path'); const crypto = require('crypto'); const axios = require('axios'); const CacheConfigManager = require('../config/cache-config'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ImageCache'); /** * 图片缓存服务 @@ -61,9 +65,9 @@ class ImageCacheService { // 启动定期清理任务 this.startCleanupTask(); - console.log('图片缓存服务初始化完成'); + logger.info('图片缓存服务初始化完成'); } catch (error) { - console.error('图片缓存服务初始化失败:', error); + logger.error('图片缓存服务初始化失败', error); } } @@ -73,9 +77,9 @@ class ImageCacheService { async ensureCacheDir() { try { await fs.mkdir(this.cacheDir, { recursive: true }); - console.log('图片缓存目录创建成功:', this.cacheDir); + logger.info('图片缓存目录创建成功', { cacheDir: this.cacheDir }); } catch (error) { - console.error('创建图片缓存目录失败:', error); + logger.error('创建图片缓存目录失败', error); } } @@ -153,7 +157,7 @@ class ImageCacheService { return data; } catch (error) { - console.error('读取缓存失败:', error); + logger.error('读取缓存失败:', error); return null; } } @@ -172,7 +176,7 @@ class ImageCacheService { // 检查缓存大小,如果超过限制则清理 await this.checkCacheSize(); } catch (error) { - console.error('保存缓存失败:', error); + logger.error('保存缓存失败:', error); } } @@ -202,7 +206,7 @@ class ImageCacheService { // 异步保存到缓存(不等待完成) if (this.config.enabled) { this.saveToCache(url, data).catch(error => { - console.error('异步保存缓存失败:', error); + logger.error('异步保存缓存失败:', error); }); } @@ -270,7 +274,7 @@ class ImageCacheService { // 如果超过最大大小,删除最旧的文件 if (totalSize > this.config.maxSize) { - console.log(`缓存大小 ${totalSize} 超过限制 ${this.config.maxSize},开始清理...`); + logger.info(`缓存大小 ${totalSize} 超过限制 ${this.config.maxSize},开始清理...`); // 按修改时间排序,删除最旧的文件 fileStats.sort((a, b) => a.mtime.getTime() - b.mtime.getTime()); @@ -284,10 +288,10 @@ class ImageCacheService { } } - console.log(`缓存清理完成,当前大小: ${totalSize}`); + logger.info(`缓存清理完成,当前大小: ${totalSize}`); } } catch (error) { - console.error('检查缓存大小失败:', error); + logger.error('检查缓存大小失败:', error); } } @@ -312,10 +316,10 @@ class ImageCacheService { } if (cleanedCount > 0) { - console.log(`清理了 ${cleanedCount} 个过期缓存文件`); + logger.info(`清理了 ${cleanedCount} 个过期缓存文件`); } } catch (error) { - console.error('清理过期缓存失败:', error); + logger.error('清理过期缓存失败:', error); } } @@ -325,7 +329,7 @@ class ImageCacheService { startCleanupTask() { setInterval(() => { this.cleanupExpiredCache().catch(error => { - console.error('定期清理任务失败:', error); + logger.error('定期清理任务失败:', error); }); }, this.config.cleanupInterval); } @@ -343,9 +347,9 @@ class ImageCacheService { await fs.unlink(filePath); } - console.log('所有缓存已清理'); + logger.info('所有缓存已清理'); } catch (error) { - console.error('清理所有缓存失败:', error); + logger.error('清理所有缓存失败:', error); throw error; } } @@ -376,7 +380,7 @@ class ImageCacheService { config: this.config }; } catch (error) { - console.error('获取缓存统计失败:', error); + logger.error('获取缓存统计失败:', error); return { fileCount: 0, totalSize: 0, diff --git a/backend/services/progress-manager.js b/backend/services/progress-manager.js index dede373..97ffaf1 100644 --- a/backend/services/progress-manager.js +++ b/backend/services/progress-manager.js @@ -1,3 +1,9 @@ +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ProgressManager'); + + /** * 进度管理器 - 负责处理下载进度的监听和通知 */ @@ -89,7 +95,7 @@ class ProgressManager { try { listener(task); } catch (error) { - console.error('进度监听器执行失败:', error); + logger.error('进度监听器执行失败:', error); } }); } diff --git a/backend/services/repository.js b/backend/services/repository.js index f4d7891..e2ed921 100644 --- a/backend/services/repository.js +++ b/backend/services/repository.js @@ -4,6 +4,12 @@ const { promisify } = require('util') const { exec } = require('child_process') const ConfigManager = require('../config/config-manager') const execAsync = promisify(exec) +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('RepositoryService'); + + class RepositoryService { constructor() { @@ -64,7 +70,7 @@ class RepositoryService { try { this.config = await this.configManager.readConfig() } catch (error) { - console.error('加载配置失败:', error) + logger.error('加载配置失败:', error) // 如果加载失败,使用默认配置对象 this.config = { downloadDir: "./downloads", @@ -266,10 +272,10 @@ class RepositoryService { usagePercent: Math.round((used / total) * 100) } } catch (statfsError) { - console.log('fs.statfs 调用失败:', statfsError.message) + logger.info('fs.statfs 调用失败:', statfsError.message) } } else { - console.log('fs.statfs 在打包环境中不可用,尝试使用系统命令') + logger.info('fs.statfs 在打包环境中不可用,尝试使用系统命令') } // 如果 fs.statfs 不可用,尝试使用系统命令 @@ -305,7 +311,7 @@ class RepositoryService { } } } catch (error) { - console.log('PowerShell 方法失败:', error.message) + logger.info('PowerShell 方法失败:', error.message) throw error } }, @@ -340,7 +346,7 @@ class RepositoryService { } } } catch (error) { - console.log('wmic 方法失败:', error.message) + logger.info('wmic 方法失败:', error.message) throw error } } @@ -357,7 +363,7 @@ class RepositoryService { return result } } catch (error) { - console.log(`磁盘使用情况获取方法失败:`, error.message) + logger.info(`磁盘使用情况获取方法失败:`, error.message) continue } } @@ -383,7 +389,7 @@ class RepositoryService { } } } catch (dfError) { - console.log('df 命令失败:', dfError.message) + logger.info('df 命令失败:', dfError.message) } } @@ -391,7 +397,7 @@ class RepositoryService { return await this.getCachedDiskUsage(currentBaseDir, forceRefresh) } catch (error) { - console.error('获取磁盘使用情况失败:', error) + logger.error('获取磁盘使用情况失败:', error) return { total: 0, used: 0, @@ -486,12 +492,12 @@ class RepositoryService { const expectedImageCount = artworkInfo.page_count || 1 if (files.length < expectedImageCount) { // 图片文件数量不足,认为下载不完整 - console.log(`作品 ${artworkId} 图片数量不匹配: 期望 ${expectedImageCount} 个,实际 ${files.length} 个`) + logger.info(`作品 ${artworkId} 图片数量不匹配: 期望 ${expectedImageCount} 个,实际 ${files.length} 个`) return false } // 有信息文件、有图片文件且数量匹配,认为已下载 - // console.log(`作品 ${artworkId} 已完整下载: ${files.length}/${expectedImageCount} 个图片文件`) + // logger.info(`作品 ${artworkId} 已完整下载: ${files.length}/${expectedImageCount} 个图片文件`) return true } } @@ -499,7 +505,7 @@ class RepositoryService { return false } catch (error) { - console.error('检查作品下载状态失败:', error) + logger.error('检查作品下载状态失败:', error) return false } } @@ -772,12 +778,12 @@ class RepositoryService { if (cacheAge < maxCacheAge) { this.diskUsageCache.data = cache.data this.diskUsageCache.timestamp = cache.timestamp - console.log('已加载持久化缓存,缓存年龄:', Math.round(cacheAge / 1000 / 60), '分钟') + logger.info('已加载持久化缓存,缓存年龄:', Math.round(cacheAge / 1000 / 60), '分钟') } else { - console.log('持久化缓存已过期,将重新计算') + logger.info('持久化缓存已过期,将重新计算') } } catch (error) { - console.log('加载持久化缓存失败,将使用内存缓存:', error.message) + logger.info('加载持久化缓存失败,将使用内存缓存:', error.message) } } @@ -795,9 +801,9 @@ class RepositoryService { } await fs.writeFile(this.cacheFilePath, JSON.stringify(cacheData, null, 2), 'utf8') - console.log('持久化缓存已保存') + logger.info('持久化缓存已保存') } catch (error) { - console.error('保存持久化缓存失败:', error.message) + logger.error('保存持久化缓存失败:', error.message) } } @@ -812,9 +818,9 @@ class RepositoryService { if (this.cacheFilePath) { try { await fs.unlink(this.cacheFilePath) - console.log('持久化缓存文件已删除') + logger.info('持久化缓存文件已删除') } catch (error) { - console.log('删除持久化缓存文件失败:', error.message) + logger.info('删除持久化缓存文件失败:', error.message) } } @@ -831,7 +837,7 @@ class RepositoryService { // 检查内存缓存是否有效(除非强制刷新) if (!forceRefresh && this.diskUsageCache.data && (now - this.diskUsageCache.timestamp) < this.diskUsageCache.cacheDuration) { - console.log('使用内存缓存的磁盘使用情况') + logger.info('使用内存缓存的磁盘使用情况') return this.diskUsageCache.data } @@ -862,7 +868,7 @@ class RepositoryService { return result } catch (error) { - console.log('快速估算失败,返回默认值:', error.message) + logger.info('快速估算失败,返回默认值:', error.message) return { total: 0, used: 0, @@ -916,7 +922,7 @@ class RepositoryService { return totalSize } catch (error) { - console.error('快速目录大小估算失败:', error) + logger.error('快速目录大小估算失败:', error) return 0 } } @@ -938,14 +944,14 @@ class RepositoryService { totalSize += stats.size } catch (error) { // 忽略无法访问的文件 - console.log(`无法访问文件: ${fullPath}`) + logger.info(`无法访问文件: ${fullPath}`) } } } return totalSize } catch (error) { - console.error('计算目录大小失败:', error) + logger.error('计算目录大小失败:', error) return 0 } } diff --git a/backend/services/task-manager.js b/backend/services/task-manager.js index 0d8442f..2b7c6d0 100644 --- a/backend/services/task-manager.js +++ b/backend/services/task-manager.js @@ -1,6 +1,10 @@ const fs = require('fs-extra'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('TaskManager'); /** * 任务管理器 - 负责下载任务的生命周期管理 @@ -29,9 +33,9 @@ class TaskManager { await this.cleanupCompletedTasks(); this.initialized = true; - console.log('任务管理器初始化完成'); + logger.info('任务管理器初始化完成'); } catch (error) { - console.error('任务管理器初始化失败:', error); + logger.error('任务管理器初始化失败', error); this.initialized = false; } } @@ -53,7 +57,7 @@ class TaskManager { } } } catch (error) { - console.error('加载任务状态失败:', error); + logger.error('加载任务状态失败', error); this.tasks = new Map(); } } @@ -66,7 +70,7 @@ class TaskManager { const tasksData = Object.fromEntries(this.tasks); await fs.writeJson(this.tasksFile, tasksData, { spaces: 2 }); } catch (error) { - console.error('保存任务状态失败:', error); + logger.error('保存任务状态失败', error); } } @@ -199,7 +203,7 @@ class TaskManager { if (cleanedCount > 0) { await this.saveTasks(); - console.log(`清理已完成任务: ${cleanedCount} 个`); + logger.info(`清理已完成任务: ${cleanedCount} 个`); } return cleanedCount; diff --git a/backend/start.js b/backend/start.js index 3c42414..0526bc9 100644 --- a/backend/start.js +++ b/backend/start.js @@ -5,6 +5,10 @@ */ const PixivServer = require('./server'); +const { defaultLogger } = require('./utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('Start'); // 解析命令行参数 function parseArguments() { @@ -54,39 +58,39 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'; // 如果提供了代理端口,设置环境变量 if (cliOptions.proxyPort) { process.env.PROXY_PORT = cliOptions.proxyPort.toString(); - console.log(`\x1b[36m📡 代理端口已设置为: ${cliOptions.proxyPort}\x1b[0m`); + logger.info(`📡 代理端口已设置为: ${cliOptions.proxyPort}`); } // 如果提供了服务器端口,设置环境变量 if (cliOptions.serverPort) { process.env.PORT = cliOptions.serverPort.toString(); - console.log(`\x1b[36m🌐 服务器端口已设置为: ${cliOptions.serverPort}\x1b[0m`); + logger.info(`🌐 服务器端口已设置为: ${cliOptions.serverPort}`); } -console.log('\x1b[35m🚀 启动 Pixiv 后端服务器...\x1b[0m'); +logger.info('🚀 启动 Pixiv 后端服务器...'); // 创建服务器实例 const server = new PixivServer(); // 处理进程信号 process.on('SIGINT', async () => { - console.log('\n\x1b[33m🛑 收到 SIGINT 信号,正在关闭服务器...\x1b[0m'); + logger.info('🛑 收到 SIGINT 信号,正在关闭服务器...'); await server.shutdown(); }); process.on('SIGTERM', async () => { - console.log('\n\x1b[33m🛑 收到 SIGTERM 信号,正在关闭服务器...\x1b[0m'); + logger.info('🛑 收到 SIGTERM 信号,正在关闭服务器...'); await server.shutdown(); }); // 处理未捕获的异常 process.on('uncaughtException', error => { - console.error('\x1b[31m❌ 未捕获的异常:\x1b[0m', error); + logger.error('❌ 未捕获的异常', error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { - console.error('\x1b[31m❌ 未处理的 Promise 拒绝:\x1b[0m', reason); + logger.error('❌ 未处理的 Promise 拒绝', reason); process.exit(1); }); @@ -95,6 +99,6 @@ server .init() .then(() => server.start()) .catch(error => { - console.error('\x1b[31m❌ 服务器启动失败:\x1b[0m', error); + logger.error('❌ 服务器启动失败', error); process.exit(1); }); diff --git a/backend/test-login.js b/backend/test-login.js index 2285277..7f730a3 100644 --- a/backend/test-login.js +++ b/backend/test-login.js @@ -1,6 +1,11 @@ const PixivBackend = require('./core'); const proxyConfig = require('./config'); const readline = require('readline'); +const { defaultLogger } = require('./utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('TestLogin'); + // 创建命令行交互接口 const rl = readline.createInterface({ @@ -19,84 +24,84 @@ function askQuestion(question) { // 测试登录流程 async function testLogin() { - console.log('=== Pixiv 登录测试脚本 ===\n'); + logger.info('=== Pixiv 登录测试脚本 ===\n'); try { // 1. 设置代理环境变量 - console.log('1. 设置代理配置...'); + logger.info('1. 设置代理配置...'); proxyConfig.setEnvironmentVariables(); // 2. 初始化后端 - console.log('\n2. 初始化 Pixiv 后端...'); + logger.info('\n2. 初始化 Pixiv 后端...'); const backend = new PixivBackend(); await backend.init(); // 3. 检查登录状态 - console.log('\n3. 检查当前登录状态...'); + logger.info('\n3. 检查当前登录状态...'); const loginStatus = backend.getLoginStatus(); - console.log('登录状态:', loginStatus); + logger.info('登录状态:', loginStatus); if (loginStatus.isLoggedIn) { - console.log('✅ 已登录,用户:', loginStatus.username); + logger.info('✅ 已登录,用户:', loginStatus.username); return; } // 4. 获取登录URL - console.log('\n4. 获取登录URL...'); + logger.info('\n4. 获取登录URL...'); const loginData = backend.getLoginUrl(); - console.log('请访问以下URL进行登录:'); - console.log(loginData.login_url); - console.log('\n登录完成后,请复制回调URL中的code参数'); + logger.info('请访问以下URL进行登录:'); + logger.info(loginData.login_url); + logger.info('\n登录完成后,请复制回调URL中的code参数'); // 5. 等待用户输入授权码 const code = await askQuestion('\n请输入授权码 (code参数): '); if (!code || code.trim() === '') { - console.log('❌ 未输入授权码,测试终止'); + logger.info('❌ 未输入授权码,测试终止'); return; } // 6. 处理登录回调 - console.log('\n5. 处理登录回调...'); + logger.info('\n5. 处理登录回调...'); const loginResult = await backend.handleLoginCallback(code.trim()); if (loginResult.success) { - console.log('✅ 登录成功!'); - console.log('用户信息:', loginResult.user); + logger.info('✅ 登录成功!'); + logger.info('用户信息:', loginResult.user); // 7. 再次检查登录状态 - console.log('\n6. 验证登录状态...'); + logger.info('\n6. 验证登录状态...'); const finalStatus = backend.getLoginStatus(); - console.log('最终登录状态:', finalStatus); + logger.info('最终登录状态:', finalStatus); // 8. 测试获取用户信息 - console.log('\n7. 测试获取用户信息...'); + logger.info('\n7. 测试获取用户信息...'); const auth = backend.getAuth(); const userInfo = await auth.getUserInfo(); if (userInfo.success) { - console.log('✅ 获取用户信息成功:', userInfo.user); + logger.info('✅ 获取用户信息成功:', userInfo.user); } else { - console.log('❌ 获取用户信息失败:', userInfo.error); + logger.info('❌ 获取用户信息失败:', userInfo.error); } } else { - console.log('❌ 登录失败:', loginResult.error); + logger.info('❌ 登录失败:', loginResult.error); } } catch (error) { - console.error('❌ 测试过程中发生错误:', error.message); - console.error('错误详情:', error); + logger.error('❌ 测试过程中发生错误:', error.message); + logger.error('错误详情:', error); } finally { // 清理资源 rl.close(); - console.log('\n=== 测试完成 ==='); + logger.info('\n=== 测试完成 ==='); } } // 测试重新登录功能 async function testRelogin() { - console.log('=== 测试重新登录功能 ===\n'); + logger.info('=== 测试重新登录功能 ===\n'); try { // 设置代理 @@ -110,21 +115,21 @@ async function testRelogin() { const loginStatus = backend.getLoginStatus(); if (loginStatus.isLoggedIn) { - console.log('✅ 检测到已保存的登录信息'); - console.log('用户:', loginStatus.username); - console.log('用户ID:', loginStatus.user_id); + logger.info('✅ 检测到已保存的登录信息'); + logger.info('用户:', loginStatus.username); + logger.info('用户ID:', loginStatus.user_id); } else { - console.log('❌ 没有保存的登录信息,无法测试重新登录'); + logger.info('❌ 没有保存的登录信息,无法测试重新登录'); } } catch (error) { - console.error('❌ 重新登录测试失败:', error.message); + logger.error('❌ 重新登录测试失败:', error.message); } } // 测试登出功能 async function testLogout() { - console.log('=== 测试登出功能 ===\n'); + logger.info('=== 测试登出功能 ===\n'); try { // 设置代理 @@ -138,27 +143,27 @@ async function testLogout() { const logoutResult = backend.logout(); if (logoutResult.success) { - console.log('✅ 登出成功'); + logger.info('✅ 登出成功'); // 验证登出状态 const loginStatus = backend.getLoginStatus(); - console.log('登出后状态:', loginStatus); + logger.info('登出后状态:', loginStatus); } else { - console.log('❌ 登出失败'); + logger.info('❌ 登出失败'); } } catch (error) { - console.error('❌ 登出测试失败:', error.message); + logger.error('❌ 登出测试失败:', error.message); } } // 主函数 async function main() { - console.log('请选择测试功能:'); - console.log('1. 测试完整登录流程'); - console.log('2. 测试重新登录'); - console.log('3. 测试登出'); - console.log('4. 运行所有测试'); + logger.info('请选择测试功能:'); + logger.info('1. 测试完整登录流程'); + logger.info('2. 测试重新登录'); + logger.info('3. 测试登出'); + logger.info('4. 运行所有测试'); const choice = await askQuestion('\n请输入选择 (1-4): '); @@ -173,22 +178,22 @@ async function main() { await testLogout(); break; case '4': - console.log('\n=== 运行所有测试 ===\n'); + logger.info('\n=== 运行所有测试 ===\n'); await testLogin(); - console.log('\n' + '='.repeat(50) + '\n'); + logger.info('\n' + '='.repeat(50) + '\n'); await testRelogin(); - console.log('\n' + '='.repeat(50) + '\n'); + logger.info('\n' + '='.repeat(50) + '\n'); await testLogout(); break; default: - console.log('❌ 无效选择'); + logger.info('❌ 无效选择'); rl.close(); } } // 如果直接运行此脚本 if (require.main === module) { - main().catch(console.error); + main().catch(logger.error); } module.exports = { diff --git a/backend/utils/error-handler.js b/backend/utils/error-handler.js index 3326e44..e4d0555 100644 --- a/backend/utils/error-handler.js +++ b/backend/utils/error-handler.js @@ -1,4 +1,9 @@ const path = require('path'); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('ErrorHandler'); + /** * 错误处理工具类 - 专门处理打包后的权限问题 @@ -19,7 +24,7 @@ class ErrorHandler { }; // 记录错误信息 - console.error(`文件系统错误 [${operation}]:`, { + logger.error(`文件系统错误 [${operation}]:`, { filePath: filePath, errorCode: error.code, errorMessage: error.message, @@ -52,25 +57,25 @@ class ErrorHandler { static handlePermissionError(errorInfo) { const { filePath, isPkg, platform } = errorInfo; - console.error('权限错误 (EPERM) 解决方案:'); + logger.error('权限错误 (EPERM) 解决方案:'); if (platform === 'win32') { - console.error('Windows 权限问题解决方案:'); - console.error('1. 以管理员身份运行程序'); - console.error('2. 检查文件/目录权限'); - console.error('3. 检查防病毒软件是否阻止访问'); - console.error('4. 检查文件是否被其他程序占用'); + logger.error('Windows 权限问题解决方案:'); + logger.error('1. 以管理员身份运行程序'); + logger.error('2. 检查文件/目录权限'); + logger.error('3. 检查防病毒软件是否阻止访问'); + logger.error('4. 检查文件是否被其他程序占用'); if (isPkg) { - console.error('5. 打包环境特殊处理:'); - console.error(' - 确保程序有写入权限'); - console.error(' - 尝试使用用户目录而不是程序目录'); + logger.error('5. 打包环境特殊处理:'); + logger.error(' - 确保程序有写入权限'); + logger.error(' - 尝试使用用户目录而不是程序目录'); } } else { - console.error('Unix/Linux 权限问题解决方案:'); - console.error('1. 检查文件权限: chmod 755 '); - console.error('2. 检查目录权限: chmod 755 '); - console.error('3. 检查用户权限'); + logger.error('Unix/Linux 权限问题解决方案:'); + logger.error('1. 检查文件权限: chmod 755 '); + logger.error('2. 检查目录权限: chmod 755 '); + logger.error('3. 检查用户权限'); } return { @@ -87,14 +92,14 @@ class ErrorHandler { static handleAccessError(errorInfo) { const { filePath, platform } = errorInfo; - console.error('访问错误 (EACCES) 解决方案:'); - console.error('1. 检查文件/目录是否存在'); - console.error('2. 检查用户是否有访问权限'); - console.error('3. 检查文件系统权限'); + logger.error('访问错误 (EACCES) 解决方案:'); + logger.error('1. 检查文件/目录是否存在'); + logger.error('2. 检查用户是否有访问权限'); + logger.error('3. 检查文件系统权限'); if (platform === 'win32') { - console.error('4. 检查 Windows 安全设置'); - console.error('5. 尝试以管理员身份运行'); + logger.error('4. 检查 Windows 安全设置'); + logger.error('5. 尝试以管理员身份运行'); } return { @@ -111,11 +116,11 @@ class ErrorHandler { static handleBusyError(errorInfo) { const { filePath } = errorInfo; - console.error('文件占用错误 (EBUSY) 解决方案:'); - console.error('1. 关闭可能占用文件的程序'); - console.error('2. 等待文件释放后重试'); - console.error('3. 重启相关程序'); - console.error('4. 检查是否有其他进程在使用文件'); + logger.error('文件占用错误 (EBUSY) 解决方案:'); + logger.error('1. 关闭可能占用文件的程序'); + logger.error('2. 等待文件释放后重试'); + logger.error('3. 重启相关程序'); + logger.error('4. 检查是否有其他进程在使用文件'); return { type: 'BUSY_ERROR', @@ -132,10 +137,10 @@ class ErrorHandler { static handleNotFoundError(errorInfo) { const { filePath } = errorInfo; - console.error('文件不存在错误 (ENOENT) 解决方案:'); - console.error('1. 检查文件路径是否正确'); - console.error('2. 确保目录存在'); - console.error('3. 检查文件名是否正确'); + logger.error('文件不存在错误 (ENOENT) 解决方案:'); + logger.error('1. 检查文件路径是否正确'); + logger.error('2. 确保目录存在'); + logger.error('3. 检查文件名是否正确'); return { type: 'NOT_FOUND_ERROR', @@ -151,9 +156,9 @@ class ErrorHandler { static handleIsDirectoryError(errorInfo) { const { filePath } = errorInfo; - console.error('是目录错误 (EISDIR) 解决方案:'); - console.error('1. 检查路径是否指向目录而不是文件'); - console.error('2. 确保使用正确的文件路径'); + logger.error('是目录错误 (EISDIR) 解决方案:'); + logger.error('1. 检查路径是否指向目录而不是文件'); + logger.error('2. 确保使用正确的文件路径'); return { type: 'IS_DIRECTORY_ERROR', @@ -169,9 +174,9 @@ class ErrorHandler { static handleNotDirectoryError(errorInfo) { const { filePath } = errorInfo; - console.error('不是目录错误 (ENOTDIR) 解决方案:'); - console.error('1. 检查路径是否指向文件而不是目录'); - console.error('2. 确保使用正确的目录路径'); + logger.error('不是目录错误 (ENOTDIR) 解决方案:'); + logger.error('1. 检查路径是否指向文件而不是目录'); + logger.error('2. 确保使用正确的目录路径'); return { type: 'NOT_DIRECTORY_ERROR', @@ -187,10 +192,10 @@ class ErrorHandler { static handleGenericError(errorInfo) { const { filePath, errorCode, errorMessage } = errorInfo; - console.error(`通用文件系统错误 (${errorCode}): ${errorMessage}`); - console.error('1. 检查文件系统状态'); - console.error('2. 检查磁盘空间'); - console.error('3. 检查文件系统权限'); + logger.error(`通用文件系统错误 (${errorCode}): ${errorMessage}`); + logger.error('1. 检查文件系统状态'); + logger.error('2. 检查磁盘空间'); + logger.error('3. 检查文件系统权限'); return { type: 'GENERIC_ERROR', diff --git a/backend/utils/file-utils.js b/backend/utils/file-utils.js index 022a014..3834ee7 100644 --- a/backend/utils/file-utils.js +++ b/backend/utils/file-utils.js @@ -1,5 +1,10 @@ const fs = require('fs-extra'); const path = require('path'); +const { defaultLogger } = require('./logger'); + +// 创建logger实例 +const logger = defaultLogger.child('FileUtils'); + /** * 文件操作工具类 - 确保与 pkg 打包兼容 @@ -22,7 +27,7 @@ class FileUtils { await nativeFs.unlink(filePath); return true; } catch (nativeError) { - console.error(`文件删除失败: ${filePath}`, nativeError.message); + logger.error(`文件删除失败: ${filePath}`, nativeError.message); return false; } } @@ -44,7 +49,7 @@ class FileUtils { await nativeFs.mkdir(dirPath, { recursive: true }); return true; } catch (nativeError) { - console.error(`目录创建失败: ${dirPath}`, nativeError.message); + logger.error(`目录创建失败: ${dirPath}`, nativeError.message); return false; } } @@ -70,7 +75,7 @@ class FileUtils { return true; } } catch (error) { - console.error(`增强目录创建失败: ${dirPath}`, error.message); + logger.error(`增强目录创建失败: ${dirPath}`, error.message); return false; } } @@ -102,7 +107,7 @@ class FileUtils { // 验证创建是否成功 await fs.access(currentPath); } catch (mkdirError) { - console.error(`创建目录失败: ${currentPath}`, mkdirError.message); + logger.error(`创建目录失败: ${currentPath}`, mkdirError.message); throw mkdirError; } } else { @@ -113,7 +118,7 @@ class FileUtils { return true; } catch (error) { - console.error(`递归创建目录失败: ${dirPath}`, error.message); + logger.error(`递归创建目录失败: ${dirPath}`, error.message); return false; } } @@ -137,7 +142,7 @@ class FileUtils { // 尝试删除现有文件 await fs.remove(filePath); } catch (removeError) { - console.warn(`删除现有文件失败: ${filePath}`, removeError.message); + logger.warn(`删除现有文件失败: ${filePath}`, removeError.message); // 继续尝试写入,可能会覆盖 } } @@ -150,7 +155,7 @@ class FileUtils { return true; } catch (error) { - console.error(`安全写入文件失败: ${filePath}`, error.message); + logger.error(`安全写入文件失败: ${filePath}`, error.message); return false; } } @@ -173,14 +178,14 @@ class FileUtils { try { await fs.remove(filePath); } catch (removeError) { - console.warn(`删除现有文件失败: ${filePath}`, removeError.message); + logger.warn(`删除现有文件失败: ${filePath}`, removeError.message); } } // 创建写入流 return fs.createWriteStream(filePath); } catch (error) { - console.error(`创建写入流失败: ${filePath}`, error.message); + logger.error(`创建写入流失败: ${filePath}`, error.message); throw error; } } @@ -217,7 +222,7 @@ class FileUtils { const nativeFs = require('fs').promises; return await nativeFs.readdir(dirPath); } catch (nativeError) { - console.error(`读取目录失败: ${dirPath}`, nativeError.message); + logger.error(`读取目录失败: ${dirPath}`, nativeError.message); return []; } } @@ -239,7 +244,7 @@ class FileUtils { await nativeFs.writeFile(filePath, jsonString, 'utf8'); return true; } catch (nativeError) { - console.error(`JSON 写入失败: ${filePath}`, nativeError.message); + logger.error(`JSON 写入失败: ${filePath}`, nativeError.message); return false; } } diff --git a/backend/utils/logger.js b/backend/utils/logger.js new file mode 100644 index 0000000..e776176 --- /dev/null +++ b/backend/utils/logger.js @@ -0,0 +1,324 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * 日志级别枚举 + */ +const LogLevel = { + ERROR: 0, + WARN: 1, + INFO: 2, + DEBUG: 3, + TRACE: 4 +}; + +/** + * 日志级别名称映射 + */ +const LogLevelNames = { + [LogLevel.ERROR]: 'ERROR', + [LogLevel.WARN]: 'WARN', + [LogLevel.INFO]: 'INFO', + [LogLevel.DEBUG]: 'DEBUG', + [LogLevel.TRACE]: 'TRACE' +}; + +/** + * 日志级别颜色映射 + */ +const LogLevelColors = { + [LogLevel.ERROR]: '\x1b[31m', // 红色 + [LogLevel.WARN]: '\x1b[33m', // 黄色 + [LogLevel.INFO]: '\x1b[36m', // 青色 + [LogLevel.DEBUG]: '\x1b[35m', // 紫色 + [LogLevel.TRACE]: '\x1b[90m' // 灰色 +}; + +/** + * 模块颜色映射 - 为不同模块设置不同颜色 + */ +const ModuleColors = { + 'Server': '\x1b[32m', // 绿色 + 'Start': '\x1b[34m', // 蓝色 + 'PixivBackend': '\x1b[35m', // 紫色 + 'PixivAuth': '\x1b[36m', // 青色 + 'TaskManager': '\x1b[33m', // 黄色 + 'ImageCache': '\x1b[37m', // 白色 + 'HistoryManager': '\x1b[90m', // 灰色 + 'ProxyConfig': '\x1b[95m', // 亮紫色 + 'Download': '\x1b[93m', // 亮黄色 + 'Artwork': '\x1b[96m', // 亮青色 + 'Artist': '\x1b[92m', // 亮绿色 + 'Repository': '\x1b[94m', // 亮蓝色 + 'ErrorHandler': '\x1b[91m', // 亮红色 + 'API': '\x1b[97m', // 亮白色 + 'FileManager': '\x1b[98m', // 亮青色 + 'ProgressManager': '\x1b[99m', // 亮紫色 + 'Default': '\x1b[39m' // 默认颜色 +}; + +/** + * 重置颜色 + */ +const RESET_COLOR = '\x1b[0m'; + +/** + * 日志图标映射 + */ +const LogLevelIcons = { + [LogLevel.ERROR]: '❌', + [LogLevel.WARN]: '⚠️', + [LogLevel.INFO]: 'ℹ️', + [LogLevel.DEBUG]: '🔧', + [LogLevel.TRACE]: '🔍' +}; + +class Logger { + constructor(options = {}) { + this.level = options.level || LogLevel.INFO; + this.enableConsole = options.enableConsole !== false; + this.enableFile = options.enableFile || false; + this.logDir = options.logDir || path.join(__dirname, '../logs'); + this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB + this.maxFiles = options.maxFiles || 5; + this.enableColors = options.enableColors !== false; + this.module = options.module || 'App'; + + // 确保日志目录存在 + if (this.enableFile) { + this.ensureLogDir(); + } + } + + /** + * 确保日志目录存在 + */ + ensureLogDir() { + if (!fs.existsSync(this.logDir)) { + fs.mkdirSync(this.logDir, { recursive: true }); + } + } + + /** + * 获取当前时间字符串 + */ + getTimeString() { + const now = new Date(); + return now.toLocaleTimeString('zh-CN', { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); + } + + /** + * 获取日期字符串 + */ + getDateString() { + const now = new Date(); + return now.toISOString().split('T')[0]; + } + + /** + * 格式化日志消息 + */ + formatMessage(level, message, data = null) { + const timeStr = this.getTimeString(); + const levelName = LogLevelNames[level]; + const icon = LogLevelIcons[level]; + + let formattedMessage = `[${timeStr}] [${levelName}] [${this.module}] ${icon} ${message}`; + + if (data !== null && data !== undefined) { + if (typeof data === 'object') { + formattedMessage += ` ${JSON.stringify(data, null, 2)}`; + } else { + formattedMessage += ` ${data}`; + } + } + + return formattedMessage; + } + + /** + * 写入文件日志 + */ + writeToFile(message) { + if (!this.enableFile) return; + + const dateStr = this.getDateString(); + const logFile = path.join(this.logDir, `${dateStr}.log`); + + try { + // 检查文件大小 + if (fs.existsSync(logFile)) { + const stats = fs.statSync(logFile); + if (stats.size > this.maxFileSize) { + this.rotateLogFile(logFile); + } + } + + fs.appendFileSync(logFile, message + '\n', 'utf8'); + } catch (error) { + // 如果写入文件失败,至少输出到控制台 + if (this.enableConsole) { + console.error('Failed to write to log file:', error.message); + } + } + } + + /** + * 轮转日志文件 + */ + rotateLogFile(logFile) { + try { + // 删除最旧的文件 + for (let i = this.maxFiles - 1; i >= 1; i--) { + const oldFile = `${logFile}.${i}`; + const newFile = `${logFile}.${i + 1}`; + if (fs.existsSync(oldFile)) { + if (i === this.maxFiles - 1) { + fs.unlinkSync(oldFile); + } else { + fs.renameSync(oldFile, newFile); + } + } + } + + // 重命名当前文件 + fs.renameSync(logFile, `${logFile}.1`); + } catch (error) { + // 如果轮转失败,删除当前文件 + try { + fs.unlinkSync(logFile); + } catch (e) { + // 忽略删除错误 + } + } + } + + /** + * 输出到控制台 + */ + writeToConsole(message, level) { + if (!this.enableConsole) return; + + if (this.enableColors && level !== undefined) { + const levelColor = LogLevelColors[level]; + const moduleColor = ModuleColors[this.module] || ModuleColors['Default']; + + // 解析消息,为模块名添加颜色 + const coloredMessage = message.replace( + new RegExp(`\\[${this.module}\\]`, 'g'), + `${moduleColor}[${this.module}]${RESET_COLOR}` + ); + + console.log(`${levelColor}${coloredMessage}${RESET_COLOR}`); + } else { + console.log(message); + } + } + + /** + * 记录日志 + */ + log(level, message, data = null) { + if (level > this.level) return; + + const formattedMessage = this.formatMessage(level, message, data); + + this.writeToConsole(formattedMessage, level); + this.writeToFile(formattedMessage); + } + + /** + * 错误日志 + */ + error(message, data = null) { + this.log(LogLevel.ERROR, message, data); + } + + /** + * 警告日志 + */ + warn(message, data = null) { + this.log(LogLevel.WARN, message, data); + } + + /** + * 信息日志 + */ + info(message, data = null) { + this.log(LogLevel.INFO, message, data); + } + + /** + * 调试日志 + */ + debug(message, data = null) { + this.log(LogLevel.DEBUG, message, data); + } + + /** + * 跟踪日志 + */ + trace(message, data = null) { + this.log(LogLevel.TRACE, message, data); + } + + /** + * 创建子logger + */ + child(module) { + return new Logger({ + level: this.level, + enableConsole: this.enableConsole, + enableFile: this.enableFile, + logDir: this.logDir, + maxFileSize: this.maxFileSize, + maxFiles: this.maxFiles, + enableColors: this.enableColors, + module: module + }); + } + + /** + * 设置日志级别 + */ + setLevel(level) { + this.level = level; + } + + /** + * 启用/禁用控制台输出 + */ + setConsoleOutput(enabled) { + this.enableConsole = enabled; + } + + /** + * 启用/禁用文件输出 + */ + setFileOutput(enabled) { + this.enableFile = enabled; + if (enabled) { + this.ensureLogDir(); + } + } +} + +// 创建默认logger实例 +const defaultLogger = new Logger({ + level: process.env.LOG_LEVEL ? LogLevel[process.env.LOG_LEVEL.toUpperCase()] : LogLevel.INFO, + enableConsole: true, + enableFile: process.env.LOG_TO_FILE === 'true', + enableColors: process.env.LOG_COLORS !== 'false' +}); + +module.exports = { + Logger, + LogLevel, + LogLevelNames, + defaultLogger +}; \ No newline at end of file diff --git a/scripts/create-portable.js b/scripts/create-portable.js index 0b97151..26080bd 100644 --- a/scripts/create-portable.js +++ b/scripts/create-portable.js @@ -1,5 +1,8 @@ const fs = require('fs-extra'); const path = require('path'); +const { defaultLogger } = require('../backend/utils/logger'); + +// 创建logger实例 async function createPortable() { const distDir = path.join(__dirname, '..', 'dist'); @@ -91,9 +94,9 @@ pause await fs.ensureDir(path.join(portableDir, 'data')); await fs.ensureDir(path.join(portableDir, 'downloads')); - console.log('✅ 便携版创建完成!'); - console.log(`📁 位置: ${portableDir}`); - console.log('📦 可以将整个文件夹打包分发给用户'); + logger.info('✅ 便携版创建完成!'); + logger.info(`📁 位置: ${portableDir}`); + logger.info('📦 可以将整个文件夹打包分发给用户'); } catch (error) { console.error('❌ 创建便携版失败:', error); diff --git a/ui/src/stores/auth.ts b/ui/src/stores/auth.ts index 4889783..ed680d3 100644 --- a/ui/src/stores/auth.ts +++ b/ui/src/stores/auth.ts @@ -10,6 +10,7 @@ export const useAuthStore = defineStore('auth', () => { }); const loading = ref(false); const error = ref(null); + const statusCheckTimer = ref(null); // 添加状态检查定时器 // 计算属性 const isLoggedIn = computed(() => loginStatus.value.isLoggedIn); @@ -24,15 +25,62 @@ export const useAuthStore = defineStore('auth', () => { const response = await authService.getLoginStatus(); if (response.success && response.data) { loginStatus.value = response.data; + + // 如果登录状态发生变化,启动或停止状态检查 + if (response.data.isLoggedIn) { + startStatusCheck(); + } else { + stopStatusCheck(); + } } } catch (err) { error.value = err instanceof Error ? err.message : '获取登录状态失败'; console.error('获取登录状态失败:', err); + + // 如果获取状态失败,可能是token过期,停止状态检查 + stopStatusCheck(); } finally { loading.value = false; } }; + // 启动定期状态检查 + const startStatusCheck = () => { + // 清除之前的定时器 + stopStatusCheck(); + + // 每5分钟检查一次登录状态 + statusCheckTimer.value = window.setInterval(async () => { + try { + const response = await authService.getLoginStatus(); + if (response.success && response.data) { + // 更新登录状态 + loginStatus.value = response.data; + + // 如果已登出,停止检查 + if (!response.data.isLoggedIn) { + stopStatusCheck(); + } + } + } catch (err) { + console.error('定期检查登录状态失败:', err); + // 检查失败,可能是网络问题或token过期,停止检查 + stopStatusCheck(); + } + }, 5 * 60 * 1000); // 5分钟 + + console.log('登录状态定期检查已启动'); + }; + + // 停止定期状态检查 + const stopStatusCheck = () => { + if (statusCheckTimer.value) { + clearInterval(statusCheckTimer.value); + statusCheckTimer.value = null; + console.log('登录状态定期检查已停止'); + } + }; + // 获取登录URL const getLoginUrl = async () => { try { @@ -100,6 +148,7 @@ export const useAuthStore = defineStore('auth', () => { const response = await authService.logout(); if (response.success) { loginStatus.value = { isLoggedIn: false }; + stopStatusCheck(); // 停止状态检查 return true; } throw new Error(response.error || '登出失败'); @@ -134,6 +183,8 @@ export const useAuthStore = defineStore('auth', () => { handleLoginCallback, relogin, logout, - clearError + clearError, + startStatusCheck, + stopStatusCheck }; }); \ No newline at end of file