后端改为使用日志记录器管理日志

This commit is contained in:
2025-08-31 18:55:22 +08:00
parent ad5dfc64cb
commit a09d6cab0e
30 changed files with 962 additions and 323 deletions
+127 -18
View File
@@ -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 };
}
+7 -2
View File
@@ -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('代理环境变量已清除');
}
};
+17 -12
View File
@@ -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;
}
}
+23 -18
View File
@@ -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
}
}
+37 -18
View File
@@ -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 };
}
+6 -1
View File
@@ -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;
+6 -1
View File
@@ -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
+7 -2
View File
@@ -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);
}
+6 -1
View File
@@ -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'
+4 -1
View File
@@ -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));
}
});
+15 -10
View File
@@ -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)
}
}
+32 -15
View File
@@ -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;
+20 -15
View File
@@ -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,
+14 -9
View File
@@ -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,
+14 -9
View File
@@ -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,
+13 -8
View File
@@ -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: 实现批量下载的恢复逻辑
}
+25 -20
View File
@@ -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,
+11 -7
View File
@@ -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);
// 不抛出错误,避免影响其他操作
}
}
+10 -5
View File
@@ -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);
+20 -16
View File
@@ -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,
+7 -1
View File
@@ -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);
}
});
}
+29 -23
View File
@@ -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
}
}
+9 -5
View File
@@ -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;
+12 -8
View File
@@ -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);
});
+49 -44
View File
@@ -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 = {
+44 -39
View File
@@ -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 <file>');
console.error('2. 检查目录权限: chmod 755 <directory>');
console.error('3. 检查用户权限');
logger.error('Unix/Linux 权限问题解决方案:');
logger.error('1. 检查文件权限: chmod 755 <file>');
logger.error('2. 检查目录权限: chmod 755 <directory>');
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',
+16 -11
View File
@@ -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;
}
}
+324
View File
@@ -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
};
+6 -3
View File
@@ -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);
+52 -1
View File
@@ -10,6 +10,7 @@ export const useAuthStore = defineStore('auth', () => {
});
const loading = ref(false);
const error = ref<string | null>(null);
const statusCheckTimer = ref<number | null>(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
};
});