增加图片缓存,页数跳转,搜索页面返回query
This commit is contained in:
@@ -89,6 +89,12 @@ backend/
|
||||
|
||||
- `GET /api/proxy/image` - 图片代理服务
|
||||
- 参数: `url` (图片URL)
|
||||
- `GET /api/proxy/cache/stats` - 获取图片缓存统计信息
|
||||
- `DELETE /api/proxy/cache` - 清理所有图片缓存
|
||||
- `DELETE /api/proxy/cache/expired` - 清理过期图片缓存
|
||||
- `GET /api/proxy/cache/config` - 获取缓存配置
|
||||
- `PUT /api/proxy/cache/config` - 更新缓存配置
|
||||
- `POST /api/proxy/cache/config/reset` - 重置缓存配置为默认值
|
||||
|
||||
### 仓库管理相关
|
||||
|
||||
@@ -158,6 +164,7 @@ backend/
|
||||
- **artist.js**: 作者服务,处理作者API调用
|
||||
- **download.js**: 下载服务,处理文件下载
|
||||
- **repository.js**: 仓库管理服务,处理文件管理和配置
|
||||
- **image-cache.js**: 图片缓存服务,管理图片代理缓存
|
||||
|
||||
### 工具类
|
||||
|
||||
@@ -204,6 +211,13 @@ backend/
|
||||
- 磁盘使用情况监控
|
||||
- 作品删除管理
|
||||
|
||||
### 7. 图片缓存管理
|
||||
- 图片代理缓存功能
|
||||
- 自动缓存过期清理
|
||||
- 缓存大小限制管理
|
||||
- 缓存统计信息查看
|
||||
- 手动缓存清理功能
|
||||
|
||||
## 🔒 安全特性
|
||||
|
||||
- 统一的错误处理
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 缓存配置管理器
|
||||
* 负责管理图片缓存的配置选项
|
||||
*/
|
||||
class CacheConfigManager {
|
||||
constructor() {
|
||||
// 检测是否在pkg打包环境中运行
|
||||
const isPkg = process.pkg !== undefined;
|
||||
|
||||
if (isPkg) {
|
||||
// 在打包环境中,使用可执行文件所在目录
|
||||
this.configPath = path.join(process.cwd(), 'data', 'cache-config.json');
|
||||
} else {
|
||||
// 在开发环境中,使用相对路径
|
||||
this.configPath = path.join(__dirname, 'cache-config.json');
|
||||
}
|
||||
|
||||
// 确保路径是绝对路径
|
||||
this.configPath = path.resolve(this.configPath);
|
||||
|
||||
// 默认配置
|
||||
this.defaultConfig = {
|
||||
// 缓存配置
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24小时缓存
|
||||
maxSize: 100 * 1024 * 1024, // 100MB最大缓存大小
|
||||
cleanupInterval: 60 * 60 * 1000, // 1小时清理一次
|
||||
|
||||
// 缓存启用状态
|
||||
enabled: true,
|
||||
|
||||
// 代理配置
|
||||
proxy: {
|
||||
enabled: true,
|
||||
timeout: 30000, // 30秒超时
|
||||
retryCount: 3, // 重试次数
|
||||
retryDelay: 1000, // 重试延迟(毫秒)
|
||||
},
|
||||
|
||||
// 文件类型过滤
|
||||
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'],
|
||||
|
||||
// 最后更新时间
|
||||
lastUpdated: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 确保配置目录存在
|
||||
this.ensureConfigDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保配置目录存在
|
||||
*/
|
||||
ensureConfigDir() {
|
||||
try {
|
||||
const configDir = path.dirname(this.configPath);
|
||||
if (!require('fs').existsSync(configDir)) {
|
||||
require('fs').mkdirSync(configDir, { recursive: true });
|
||||
console.log('缓存配置目录创建成功:', configDir);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建缓存配置目录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置文件
|
||||
*/
|
||||
async initialize() {
|
||||
try {
|
||||
// 检查配置文件是否存在
|
||||
await fs.access(this.configPath);
|
||||
console.log('缓存配置文件已存在');
|
||||
} catch (error) {
|
||||
// 配置文件不存在,创建默认配置
|
||||
console.log('创建默认缓存配置文件...');
|
||||
await this.createDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认配置文件
|
||||
*/
|
||||
async createDefaultConfig() {
|
||||
try {
|
||||
const configContent = JSON.stringify(this.defaultConfig, null, 2);
|
||||
await fs.writeFile(this.configPath, configContent, 'utf8');
|
||||
console.log('默认缓存配置文件创建成功:', this.configPath);
|
||||
} catch (error) {
|
||||
console.error('创建默认缓存配置文件失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置
|
||||
*/
|
||||
async loadConfig() {
|
||||
try {
|
||||
const configContent = await fs.readFile(this.configPath, 'utf8');
|
||||
const config = JSON.parse(configContent);
|
||||
|
||||
// 合并默认配置,确保所有字段都存在
|
||||
return { ...this.defaultConfig, ...config };
|
||||
} catch (error) {
|
||||
console.error('加载缓存配置失败:', error);
|
||||
return this.defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
async saveConfig(config) {
|
||||
try {
|
||||
// 更新最后修改时间
|
||||
config.lastUpdated = new Date().toISOString();
|
||||
|
||||
const configContent = JSON.stringify(config, null, 2);
|
||||
await fs.writeFile(this.configPath, configContent, 'utf8');
|
||||
console.log('缓存配置保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存缓存配置失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
async updateConfig(updates) {
|
||||
try {
|
||||
const currentConfig = await this.loadConfig();
|
||||
const newConfig = { ...currentConfig, ...updates };
|
||||
await this.saveConfig(newConfig);
|
||||
return newConfig;
|
||||
} catch (error) {
|
||||
console.error('更新缓存配置失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置为默认配置
|
||||
*/
|
||||
async resetToDefault() {
|
||||
try {
|
||||
await this.saveConfig(this.defaultConfig);
|
||||
console.log('缓存配置已重置为默认值');
|
||||
return this.defaultConfig;
|
||||
} catch (error) {
|
||||
console.error('重置缓存配置失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置
|
||||
*/
|
||||
validateConfig(config) {
|
||||
const errors = [];
|
||||
|
||||
if (config.maxAge < 0) {
|
||||
errors.push('maxAge 必须大于等于 0');
|
||||
}
|
||||
|
||||
if (config.maxSize < 0) {
|
||||
errors.push('maxSize 必须大于等于 0');
|
||||
}
|
||||
|
||||
if (config.cleanupInterval < 0) {
|
||||
errors.push('cleanupInterval 必须大于等于 0');
|
||||
}
|
||||
|
||||
if (config.proxy.timeout < 0) {
|
||||
errors.push('proxy.timeout 必须大于等于 0');
|
||||
}
|
||||
|
||||
if (config.proxy.retryCount < 0) {
|
||||
errors.push('proxy.retryCount 必须大于等于 0');
|
||||
}
|
||||
|
||||
if (config.proxy.retryDelay < 0) {
|
||||
errors.push('proxy.retryDelay 必须大于等于 0');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置路径
|
||||
*/
|
||||
getConfigPath() {
|
||||
return this.configPath;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CacheConfigManager;
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"maxAge": 86400000,
|
||||
"maxSize": 104857600,
|
||||
"cleanupInterval": 3600000,
|
||||
"enabled": true,
|
||||
"proxy": {
|
||||
"enabled": true,
|
||||
"timeout": 30000,
|
||||
"retryCount": 3,
|
||||
"retryDelay": 1000
|
||||
},
|
||||
"allowedExtensions": [
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webp",
|
||||
".bmp"
|
||||
],
|
||||
"lastUpdated": "2025-08-25T06:14:10.559Z"
|
||||
}
|
||||
+150
-16
@@ -1,6 +1,9 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const axios = require('axios');
|
||||
const ImageCacheService = require('../services/image-cache');
|
||||
|
||||
// 创建缓存服务实例
|
||||
const imageCache = new ImageCacheService();
|
||||
|
||||
/**
|
||||
* 图片代理
|
||||
@@ -17,28 +20,22 @@ router.get('/image', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: decodeURIComponent(url),
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'Referer': 'https://www.pixiv.net/',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
},
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const decodedUrl = decodeURIComponent(url);
|
||||
|
||||
// 使用缓存服务获取图片
|
||||
const imageData = await imageCache.getImage(decodedUrl);
|
||||
|
||||
// 设置响应头
|
||||
res.set({
|
||||
'Content-Type': response.headers['content-type'],
|
||||
'Cache-Control': 'public, max-age=3600', // 缓存1小时
|
||||
'Content-Type': getContentType(decodedUrl),
|
||||
'Cache-Control': 'public, max-age=3600', // 浏览器缓存1小时
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
});
|
||||
|
||||
// 流式传输图片数据
|
||||
response.data.pipe(res);
|
||||
// 发送图片数据
|
||||
res.send(imageData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Image proxy error:', error.message);
|
||||
@@ -49,4 +46,141 @@ router.get('/image', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 获取缓存统计信息
|
||||
* GET /api/proxy/cache/stats
|
||||
*/
|
||||
router.get('/cache/stats', async (req, res) => {
|
||||
try {
|
||||
const stats = await imageCache.getCacheStats();
|
||||
res.json({
|
||||
success: true,
|
||||
data: stats
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 清理所有缓存
|
||||
* DELETE /api/proxy/cache
|
||||
*/
|
||||
router.delete('/cache', async (req, res) => {
|
||||
try {
|
||||
await imageCache.clearAllCache();
|
||||
res.json({
|
||||
success: true,
|
||||
message: '所有缓存已清理'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 清理过期缓存
|
||||
* DELETE /api/proxy/cache/expired
|
||||
*/
|
||||
router.delete('/cache/expired', async (req, res) => {
|
||||
try {
|
||||
await imageCache.cleanupExpiredCache();
|
||||
res.json({
|
||||
success: true,
|
||||
message: '过期缓存已清理'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 获取缓存配置
|
||||
* GET /api/proxy/cache/config
|
||||
*/
|
||||
router.get('/cache/config', async (req, res) => {
|
||||
try {
|
||||
const config = await imageCache.getConfig();
|
||||
res.json({
|
||||
success: true,
|
||||
data: config
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 更新缓存配置
|
||||
* PUT /api/proxy/cache/config
|
||||
*/
|
||||
router.put('/cache/config', async (req, res) => {
|
||||
try {
|
||||
const updates = req.body;
|
||||
const config = await imageCache.updateConfig(updates);
|
||||
res.json({
|
||||
success: true,
|
||||
data: config,
|
||||
message: '缓存配置已更新'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 缓存管理 - 重置缓存配置
|
||||
* POST /api/proxy/cache/config/reset
|
||||
*/
|
||||
router.post('/cache/config/reset', async (req, res) => {
|
||||
try {
|
||||
const config = await imageCache.resetConfig();
|
||||
res.json({
|
||||
success: true,
|
||||
data: config,
|
||||
message: '缓存配置已重置为默认值'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取文件内容类型
|
||||
* @param {string} url 图片URL
|
||||
* @returns {string} 内容类型
|
||||
*/
|
||||
function getContentType(url) {
|
||||
const ext = url.split('.').pop()?.toLowerCase();
|
||||
const contentTypeMap = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp',
|
||||
'svg': 'image/svg+xml'
|
||||
};
|
||||
|
||||
return contentTypeMap[ext] || 'image/jpeg';
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,421 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const axios = require('axios');
|
||||
const CacheConfigManager = require('../config/cache-config');
|
||||
|
||||
/**
|
||||
* 图片缓存服务
|
||||
* 负责管理图片代理的缓存功能
|
||||
*/
|
||||
class ImageCacheService {
|
||||
constructor() {
|
||||
// 检测是否在pkg打包环境中运行
|
||||
const isPkg = process.pkg !== undefined;
|
||||
|
||||
if (isPkg) {
|
||||
// 在打包环境中,使用可执行文件所在目录
|
||||
this.cacheDir = path.join(process.cwd(), 'data', 'image-cache');
|
||||
} else {
|
||||
// 在开发环境中,使用相对路径
|
||||
this.cacheDir = path.join(__dirname, '..', 'data', 'image-cache');
|
||||
}
|
||||
|
||||
// 确保路径是绝对路径
|
||||
this.cacheDir = path.resolve(this.cacheDir);
|
||||
|
||||
// 创建配置管理器
|
||||
this.configManager = new CacheConfigManager();
|
||||
|
||||
// 默认缓存配置
|
||||
this.config = {
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24小时缓存
|
||||
maxSize: 100 * 1024 * 1024, // 100MB最大缓存大小
|
||||
cleanupInterval: 60 * 60 * 1000, // 1小时清理一次
|
||||
enabled: true,
|
||||
proxy: {
|
||||
enabled: true,
|
||||
timeout: 30000,
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
},
|
||||
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'],
|
||||
};
|
||||
|
||||
// 初始化配置
|
||||
this.initializeConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置
|
||||
*/
|
||||
async initializeConfig() {
|
||||
try {
|
||||
await this.configManager.initialize();
|
||||
const config = await this.configManager.loadConfig();
|
||||
this.config = { ...this.config, ...config };
|
||||
|
||||
// 确保缓存目录存在
|
||||
await this.ensureCacheDir();
|
||||
|
||||
// 启动定期清理任务
|
||||
this.startCleanupTask();
|
||||
|
||||
console.log('图片缓存服务初始化完成');
|
||||
} catch (error) {
|
||||
console.error('图片缓存服务初始化失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保缓存目录存在
|
||||
*/
|
||||
async ensureCacheDir() {
|
||||
try {
|
||||
await fs.mkdir(this.cacheDir, { recursive: true });
|
||||
console.log('图片缓存目录创建成功:', this.cacheDir);
|
||||
} catch (error) {
|
||||
console.error('创建图片缓存目录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存文件名
|
||||
* @param {string} url 原始图片URL
|
||||
* @returns {string} 缓存文件名
|
||||
*/
|
||||
generateCacheKey(url) {
|
||||
const hash = crypto.createHash('md5').update(url).digest('hex');
|
||||
const ext = this.getFileExtension(url);
|
||||
return `${hash}${ext}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
* @param {string} url 图片URL
|
||||
* @returns {string} 文件扩展名
|
||||
*/
|
||||
getFileExtension(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const pathname = urlObj.pathname;
|
||||
const ext = path.extname(pathname);
|
||||
return ext || '.jpg'; // 默认使用.jpg
|
||||
} catch (error) {
|
||||
return '.jpg';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存文件路径
|
||||
* @param {string} url 原始图片URL
|
||||
* @returns {string} 缓存文件路径
|
||||
*/
|
||||
getCacheFilePath(url) {
|
||||
const cacheKey = this.generateCacheKey(url);
|
||||
return path.join(this.cacheDir, cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存是否存在且有效
|
||||
* @param {string} url 原始图片URL
|
||||
* @returns {Promise<boolean>} 缓存是否有效
|
||||
*/
|
||||
async isCacheValid(url) {
|
||||
try {
|
||||
const cachePath = this.getCacheFilePath(url);
|
||||
const stats = await fs.stat(cachePath);
|
||||
|
||||
// 检查文件是否过期
|
||||
const age = Date.now() - stats.mtime.getTime();
|
||||
return age < this.config.maxAge;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从缓存获取图片
|
||||
* @param {string} url 原始图片URL
|
||||
* @returns {Promise<Buffer|null>} 图片数据,如果缓存不存在则返回null
|
||||
*/
|
||||
async getFromCache(url) {
|
||||
try {
|
||||
if (!(await this.isCacheValid(url))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachePath = this.getCacheFilePath(url);
|
||||
const data = await fs.readFile(cachePath);
|
||||
|
||||
// 更新文件访问时间
|
||||
await fs.utimes(cachePath, new Date(), new Date());
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('读取缓存失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片保存到缓存
|
||||
* @param {string} url 原始图片URL
|
||||
* @param {Buffer} data 图片数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async saveToCache(url, data) {
|
||||
try {
|
||||
const cachePath = this.getCacheFilePath(url);
|
||||
await fs.writeFile(cachePath, data);
|
||||
|
||||
// 检查缓存大小,如果超过限制则清理
|
||||
await this.checkCacheSize();
|
||||
} catch (error) {
|
||||
console.error('保存缓存失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网络下载图片并缓存
|
||||
* @param {string} url 图片URL
|
||||
* @returns {Promise<Buffer>} 图片数据
|
||||
*/
|
||||
async downloadAndCache(url) {
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 1; attempt <= this.config.proxy.retryCount; attempt++) {
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'arraybuffer',
|
||||
headers: {
|
||||
'Referer': 'https://www.pixiv.net/',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
},
|
||||
timeout: this.config.proxy.timeout
|
||||
});
|
||||
|
||||
const data = Buffer.from(response.data);
|
||||
|
||||
// 异步保存到缓存(不等待完成)
|
||||
if (this.config.enabled) {
|
||||
this.saveToCache(url, data).catch(error => {
|
||||
console.error('异步保存缓存失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
|
||||
if (attempt < this.config.proxy.retryCount) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.config.proxy.retryDelay));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片(优先从缓存,缓存不存在则下载)
|
||||
* @param {string} url 图片URL
|
||||
* @returns {Promise<Buffer>} 图片数据
|
||||
*/
|
||||
async getImage(url) {
|
||||
// 检查缓存是否启用
|
||||
if (!this.config.enabled) {
|
||||
return await this.downloadAndCache(url);
|
||||
}
|
||||
|
||||
// 检查文件类型是否允许缓存
|
||||
const ext = this.getFileExtension(url);
|
||||
if (!this.config.allowedExtensions.includes(ext)) {
|
||||
return await this.downloadAndCache(url);
|
||||
}
|
||||
|
||||
// 首先尝试从缓存获取
|
||||
const cachedData = await this.getFromCache(url);
|
||||
if (cachedData) {
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
// 缓存不存在,从网络下载
|
||||
return await this.downloadAndCache(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存大小并清理
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async checkCacheSize() {
|
||||
try {
|
||||
const files = await fs.readdir(this.cacheDir);
|
||||
let totalSize = 0;
|
||||
const fileStats = [];
|
||||
|
||||
// 计算总大小和收集文件信息
|
||||
for (const file of files) {
|
||||
const filePath = path.join(this.cacheDir, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
totalSize += stats.size;
|
||||
fileStats.push({
|
||||
path: filePath,
|
||||
size: stats.size,
|
||||
mtime: stats.mtime
|
||||
});
|
||||
}
|
||||
|
||||
// 如果超过最大大小,删除最旧的文件
|
||||
if (totalSize > this.config.maxSize) {
|
||||
console.log(`缓存大小 ${totalSize} 超过限制 ${this.config.maxSize},开始清理...`);
|
||||
|
||||
// 按修改时间排序,删除最旧的文件
|
||||
fileStats.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
|
||||
|
||||
for (const file of fileStats) {
|
||||
await fs.unlink(file.path);
|
||||
totalSize -= file.size;
|
||||
|
||||
if (totalSize <= this.config.maxSize * 0.8) { // 清理到80%
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`缓存清理完成,当前大小: ${totalSize}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查缓存大小失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期缓存
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async cleanupExpiredCache() {
|
||||
try {
|
||||
const files = await fs.readdir(this.cacheDir);
|
||||
let cleanedCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(this.cacheDir, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
const age = Date.now() - stats.mtime.getTime();
|
||||
if (age > this.config.maxAge) {
|
||||
await fs.unlink(filePath);
|
||||
cleanedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedCount > 0) {
|
||||
console.log(`清理了 ${cleanedCount} 个过期缓存文件`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('清理过期缓存失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定期清理任务
|
||||
*/
|
||||
startCleanupTask() {
|
||||
setInterval(() => {
|
||||
this.cleanupExpiredCache().catch(error => {
|
||||
console.error('定期清理任务失败:', error);
|
||||
});
|
||||
}, this.config.cleanupInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动清理所有缓存
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clearAllCache() {
|
||||
try {
|
||||
const files = await fs.readdir(this.cacheDir);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(this.cacheDir, file);
|
||||
await fs.unlink(filePath);
|
||||
}
|
||||
|
||||
console.log('所有缓存已清理');
|
||||
} catch (error) {
|
||||
console.error('清理所有缓存失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
* @returns {Promise<Object>} 缓存统计信息
|
||||
*/
|
||||
async getCacheStats() {
|
||||
try {
|
||||
const files = await fs.readdir(this.cacheDir);
|
||||
let totalSize = 0;
|
||||
let fileCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(this.cacheDir, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
totalSize += stats.size;
|
||||
fileCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
fileCount,
|
||||
totalSize,
|
||||
maxSize: this.config.maxSize,
|
||||
maxAge: this.config.maxAge,
|
||||
enabled: this.config.enabled,
|
||||
config: this.config
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取缓存统计失败:', error);
|
||||
return {
|
||||
fileCount: 0,
|
||||
totalSize: 0,
|
||||
maxSize: this.config.maxSize,
|
||||
maxAge: this.config.maxAge,
|
||||
enabled: this.config.enabled,
|
||||
config: this.config
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存配置
|
||||
* @returns {Promise<Object>} 缓存配置
|
||||
*/
|
||||
async getConfig() {
|
||||
return await this.configManager.loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存配置
|
||||
* @param {Object} updates 配置更新
|
||||
* @returns {Promise<Object>} 更新后的配置
|
||||
*/
|
||||
async updateConfig(updates) {
|
||||
const newConfig = await this.configManager.updateConfig(updates);
|
||||
this.config = { ...this.config, ...newConfig };
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置缓存配置
|
||||
* @returns {Promise<Object>} 重置后的配置
|
||||
*/
|
||||
async resetConfig() {
|
||||
const defaultConfig = await this.configManager.resetToDefault();
|
||||
this.config = { ...this.config, ...defaultConfig };
|
||||
return defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ImageCacheService;
|
||||
Reference in New Issue
Block a user