From efb68fce1c64b2e53ce7c92ae4e085111bf91c9a Mon Sep 17 00:00:00 2001 From: kjqwer <2990346238@qq.com> Date: Fri, 26 Sep 2025 15:31:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=8B=86=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/middleware/backendInjector.js | 13 ++ backend/middleware/bodyParser.js | 16 +++ backend/middleware/cors.js | 13 ++ backend/middleware/logger.js | 106 ++++++++++++++ backend/middleware/staticFiles.js | 17 +++ backend/routes/index.js | 66 +++++++++ backend/server.js | 195 +++----------------------- check-env.bat | 30 ++++ 8 files changed, 284 insertions(+), 172 deletions(-) create mode 100644 backend/middleware/backendInjector.js create mode 100644 backend/middleware/bodyParser.js create mode 100644 backend/middleware/cors.js create mode 100644 backend/middleware/logger.js create mode 100644 backend/middleware/staticFiles.js create mode 100644 backend/routes/index.js diff --git a/backend/middleware/backendInjector.js b/backend/middleware/backendInjector.js new file mode 100644 index 0000000..89846c2 --- /dev/null +++ b/backend/middleware/backendInjector.js @@ -0,0 +1,13 @@ +/** + * 后端实例注入中间件 + * 将PixivBackend实例注入到请求对象中,供后续中间件和路由处理器使用 + */ + +function backendInjector(backend) { + return (req, res, next) => { + req.backend = backend; + next(); + }; +} + +module.exports = { backendInjector }; \ No newline at end of file diff --git a/backend/middleware/bodyParser.js b/backend/middleware/bodyParser.js new file mode 100644 index 0000000..ffe256c --- /dev/null +++ b/backend/middleware/bodyParser.js @@ -0,0 +1,16 @@ +/** + * Body Parser中间件 + * 处理JSON和URL编码的请求体 + */ +const express = require('express'); + +function bodyParserMiddleware() { + return [ + // JSON 解析中间件 + express.json({ limit: '20mb' }), + // URL编码解析中间件 + express.urlencoded({ extended: true, limit: '20mb' }) + ]; +} + +module.exports = { bodyParserMiddleware }; \ No newline at end of file diff --git a/backend/middleware/cors.js b/backend/middleware/cors.js new file mode 100644 index 0000000..38b4c41 --- /dev/null +++ b/backend/middleware/cors.js @@ -0,0 +1,13 @@ +/** + * CORS中间件配置 + */ +const cors = require('cors'); + +function corsMiddleware() { + return cors({ + origin: process.env.FRONTEND_URL || true, // 允许所有来源,或者通过环境变量指定 + credentials: true, + }); +} + +module.exports = { corsMiddleware }; \ No newline at end of file diff --git a/backend/middleware/logger.js b/backend/middleware/logger.js new file mode 100644 index 0000000..790d642 --- /dev/null +++ b/backend/middleware/logger.js @@ -0,0 +1,106 @@ +/** + * 日志中间件 + */ +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('API'); + +/** + * 自定义日志中间件 + */ +function loggerMiddleware(req, res, next) { + // 过滤掉静态资源请求和图片代理请求 + const isStaticResource = + req.path.startsWith('/assets/') || + req.path.startsWith('/downloads/') || + req.path.includes('.js') || + req.path.includes('.css') || + req.path.includes('.ico') || + req.path.includes('.png') || + req.path.includes('.jpg') || + req.path.includes('.jpeg') || + req.path.includes('.gif') || + req.path.includes('.svg') || + req.path.includes('.woff') || + req.path.includes('.woff2') || + req.path.includes('.ttf') || + req.path.includes('.eot'); + + // 过滤掉图片代理请求 + const isImageProxy = req.path === '/api/proxy/image'; + + // 过滤掉下载任务状态查询请求 + 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'; + + // 过滤掉仓库预览请求(图片预览) + const isRepositoryPreview = req.path === '/api/repository/preview'; + + // 过滤掉健康检查请求 + const isHealthCheck = req.path === '/health'; + + // 只记录重要的API请求,排除静态资源、图片代理、下载任务查询、仓库预览和健康检查 + if (!isStaticResource && !isImageProxy && !isDownloadTasksQuery && !isRepositoryPreview && !isHealthCheck) { + const start = Date.now(); + + // 原始响应结束方法 + const originalEnd = res.end; + + // 重写响应结束方法以获取响应时间 + res.end = function (chunk, encoding) { + const duration = Date.now() - start; + const statusCode = res.statusCode; + const method = req.method; + const url = req.originalUrl; + + // 根据状态码选择图标 + let statusIcon; + if (statusCode >= 200 && statusCode < 300) { + statusIcon = '✅'; + } else if (statusCode >= 300 && statusCode < 400) { + statusIcon = '🔄'; + } else if (statusCode >= 400 && statusCode < 500) { + statusIcon = '⚠️'; + } else { + statusIcon = '❌'; + } + + // 根据请求类型选择图标 + let methodIcon; + switch (method) { + case 'GET': + methodIcon = '📥'; + break; + case 'POST': + methodIcon = '📤'; + break; + case 'PUT': + methodIcon = '🔄'; + break; + case 'DELETE': + methodIcon = '🗑️'; + break; + case 'PATCH': + methodIcon = '🔧'; + break; + default: + methodIcon = '❓'; + } + + // 输出日志 + logger.info(`${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms`); + + // 调用原始的end方法 + originalEnd.call(this, chunk, encoding); + }; + } + + next(); +} + +module.exports = { loggerMiddleware }; \ No newline at end of file diff --git a/backend/middleware/staticFiles.js b/backend/middleware/staticFiles.js new file mode 100644 index 0000000..b8234bd --- /dev/null +++ b/backend/middleware/staticFiles.js @@ -0,0 +1,17 @@ +/** + * 静态文件服务中间件 + */ +const express = require('express'); +const path = require('path'); + +function staticFilesMiddleware() { + return [ + // 下载文件静态服务 + express.static(path.join(__dirname, '../../downloads')), + + // 前端静态文件服务 + express.static(path.join(__dirname, '../../ui/dist')) + ]; +} + +module.exports = { staticFilesMiddleware }; \ No newline at end of file diff --git a/backend/routes/index.js b/backend/routes/index.js new file mode 100644 index 0000000..5486f40 --- /dev/null +++ b/backend/routes/index.js @@ -0,0 +1,66 @@ +/** + * 路由配置文件 + * 统一管理和配置所有路由 + */ + +// 导入路由模块 +const authRoutes = require('./auth'); +const artworkRoutes = require('./artwork'); +const artistRoutes = require('./artist'); +const downloadRoutes = require('./download'); +const proxyRoutes = require('./proxy'); +const repositoryRoutes = require('./repository'); +const rankingRoutes = require('./ranking'); +const watchlistRoutes = require('./watchlist'); +const updateRoutes = require('./update'); + +// 导入认证中间件 +const { authMiddleware } = require('../middleware/auth'); + +/** + * 配置所有路由 + * @param {Express.Application} app Express应用实例 + * @param {PixivBackend} backend Pixiv后端实例 + */ +function setupRoutes(app, backend) { + const path = require('path'); + + // 健康检查 + app.get('/health', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + backend: { + isLoggedIn: backend.isLoggedIn, + user: backend.config.user?.account, + }, + }); + }); + + // API 路由 + app.use('/api/auth', authRoutes); + app.use('/api/artwork', authMiddleware, artworkRoutes); + app.use('/api/artist', authMiddleware, artistRoutes); + app.use('/api/download', authMiddleware, downloadRoutes); + app.use('/api/ranking', authMiddleware, rankingRoutes); + app.use('/api/repository', repositoryRoutes); // 仓库管理,不需要认证 + app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证 + app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证 + app.use('/api/update', updateRoutes); // 更新检查,不需要认证 + + // 404 处理 + app.use((req, res) => { + // 如果是API请求,返回JSON格式的404 + if (req.path.startsWith('/api/')) { + return res.status(404).json({ + error: 'Not Found', + message: `Route ${req.originalUrl} not found`, + }); + } + + // 否则返回前端页面(SPA路由支持) + res.sendFile(path.join(__dirname, '../../ui/dist/index.html')); + }); +} + +module.exports = { setupRoutes }; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index b4411a6..f28857e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,25 +1,20 @@ const express = require('express'); 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'); -const artistRoutes = require('./routes/artist'); -const downloadRoutes = require('./routes/download'); -const proxyRoutes = require('./routes/proxy'); -const repositoryRoutes = require('./routes/repository'); -const rankingRoutes = require('./routes/ranking'); -const watchlistRoutes = require('./routes/watchlist'); -const updateRoutes = require('./routes/update'); - -// 导入中间件 - 临时注释掉来定位问题 +// 导入中间件 const { errorHandler } = require('./middleware/errorHandler'); -const { authMiddleware } = require('./middleware/auth'); +const { loggerMiddleware } = require('./middleware/logger'); +const { corsMiddleware } = require('./middleware/cors'); +const { bodyParserMiddleware } = require('./middleware/bodyParser'); +const { staticFilesMiddleware } = require('./middleware/staticFiles'); +const { backendInjector } = require('./middleware/backendInjector'); + +// 导入路由配置 +const { setupRoutes } = require('./routes'); // 导入核心模块 const PixivBackend = require('./core'); @@ -28,105 +23,6 @@ const proxyConfig = require('./config'); // 创建logger实例 const logger = defaultLogger.child('Server'); -// 自定义日志中间件 -function customLogger(req, res, next) { - // 过滤掉静态资源请求和图片代理请求 - const isStaticResource = - req.path.startsWith('/assets/') || - req.path.startsWith('/downloads/') || - req.path.includes('.js') || - req.path.includes('.css') || - req.path.includes('.ico') || - req.path.includes('.png') || - req.path.includes('.jpg') || - req.path.includes('.jpeg') || - req.path.includes('.gif') || - req.path.includes('.svg') || - req.path.includes('.woff') || - req.path.includes('.woff2') || - req.path.includes('.ttf') || - req.path.includes('.eot'); - - // 过滤掉图片代理请求 - const isImageProxy = req.path === '/api/proxy/image'; - - // 过滤掉下载任务状态查询请求 - 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'; - - // 过滤掉仓库预览请求(图片预览) - const isRepositoryPreview = req.path === '/api/repository/preview'; - - // 过滤掉健康检查请求 - const isHealthCheck = req.path === '/health'; - - // 只记录重要的API请求,排除静态资源、图片代理、下载任务查询、仓库预览和健康检查 - if (!isStaticResource && !isImageProxy && !isDownloadTasksQuery && !isRepositoryPreview && !isHealthCheck) { - const start = Date.now(); - - // 原始响应结束方法 - const originalEnd = res.end; - - // 重写响应结束方法以获取响应时间 - res.end = function (chunk, encoding) { - const duration = Date.now() - start; - const statusCode = res.statusCode; - const method = req.method; - const url = req.originalUrl; - - // 根据状态码选择颜色和图标 - let statusIcon, statusColor; - if (statusCode >= 200 && statusCode < 300) { - statusIcon = '✅'; - statusColor = '\x1b[32m'; // 绿色 - } else if (statusCode >= 300 && statusCode < 400) { - statusIcon = '🔄'; - statusColor = '\x1b[33m'; // 黄色 - } else if (statusCode >= 400 && statusCode < 500) { - statusIcon = '⚠️'; - statusColor = '\x1b[33m'; // 黄色 - } else { - statusIcon = '❌'; - statusColor = '\x1b[31m'; // 红色 - } - - // 根据请求类型选择图标 - let methodIcon; - switch (method) { - case 'GET': - methodIcon = '📥'; - break; - case 'POST': - methodIcon = '📤'; - break; - case 'PUT': - methodIcon = '🔄'; - break; - case 'DELETE': - methodIcon = '🗑️'; - break; - case 'PATCH': - methodIcon = '🔧'; - break; - default: - methodIcon = '❓'; - } - - // 输出日志 - logger.info(`${statusIcon} ${methodIcon} ${method} ${url} ${statusCode} ${duration}ms`); - - // 调用原始的end方法 - originalEnd.call(this, chunk, encoding); - }; - } - - next(); -} - class PixivServer { constructor() { this.app = express(); @@ -156,7 +52,7 @@ class PixivServer { // 配置路由 this.setupRoutes(); - // 配置错误处理 - 临时注释掉 + // 配置错误处理 this.setupErrorHandling(); logger.info('✅ 服务器初始化完成'); @@ -166,74 +62,29 @@ class PixivServer { * 配置中间件 */ setupMiddleware() { - // 自定义日志中间件(替换morgan) - this.app.use(customLogger); + // 自定义日志中间件 + this.app.use(loggerMiddleware); // CORS 中间件 - this.app.use( - cors({ - origin: process.env.FRONTEND_URL || true, // 允许所有来源,或者通过环境变量指定 - credentials: true, - }) - ); + this.app.use(corsMiddleware()); - // JSON 解析中间件 - this.app.use(express.json({ limit: '10mb' })); - this.app.use(express.urlencoded({ extended: true, limit: '10mb' })); + // Body Parser 中间件 + this.app.use(bodyParserMiddleware()); - // 静态文件服务 - this.app.use('/downloads', express.static(path.join(__dirname, '../downloads'))); + // 静态文件服务中间件 + const staticMiddlewares = staticFilesMiddleware(); + this.app.use('/downloads', staticMiddlewares[0]); // 下载文件静态服务 + this.app.use(staticMiddlewares[1]); // 前端静态文件服务 - // 前端静态文件服务 - this.app.use(express.static(path.join(__dirname, '../ui/dist'))); - - // 将后端实例注入到请求对象中 - this.app.use((req, res, next) => { - req.backend = this.backend; - next(); - }); + // 后端实例注入中间件 + this.app.use(backendInjector(this.backend)); } /** * 配置路由 */ setupRoutes() { - // 健康检查 - this.app.get('/health', (req, res) => { - res.json({ - status: 'ok', - timestamp: new Date().toISOString(), - backend: { - isLoggedIn: req.backend.isLoggedIn, - user: req.backend.config.user?.account, - }, - }); - }); - - // API 路由 - this.app.use('/api/auth', authRoutes); - this.app.use('/api/artwork', authMiddleware, artworkRoutes); - this.app.use('/api/artist', authMiddleware, artistRoutes); - this.app.use('/api/download', authMiddleware, downloadRoutes); - this.app.use('/api/ranking', authMiddleware, rankingRoutes); - this.app.use('/api/repository', repositoryRoutes); // 仓库管理,不需要认证 - this.app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证 - this.app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证 - this.app.use('/api/update', updateRoutes); // 更新检查,不需要认证 - - // 404 处理 - this.app.use((req, res) => { - // 如果是API请求,返回JSON格式的404 - if (req.path.startsWith('/api/')) { - return res.status(404).json({ - error: 'Not Found', - message: `Route ${req.originalUrl} not found`, - }); - } - - // 否则返回前端页面(SPA路由支持) - res.sendFile(path.join(__dirname, '../ui/dist/index.html')); - }); + setupRoutes(this.app, this.backend); } /** @@ -286,4 +137,4 @@ if (require.main === module) { .catch((error) => logger.error('服务器启动失败', error)); } -module.exports = PixivServer; +module.exports = PixivServer; \ No newline at end of file diff --git a/check-env.bat b/check-env.bat index bf7f4f2..d536d74 100644 --- a/check-env.bat +++ b/check-env.bat @@ -50,6 +50,36 @@ if not exist "node_modules" ( echo ✅ node_modules 存在 ) +:: 检查前端项目文件 +echo. +echo 🔍 检查前端项目文件... +if not exist "ui/package.json" ( + echo ❌ 未找到 ui/package.json + echo 📁 前端项目文件不存在 + goto :end +) else ( + echo ✅ ui/package.json 存在 +) + +:: 检查前端依赖 +if not exist "ui/node_modules" ( + set /p choice="前端依赖不存在,是否现在安装前端依赖? (y/n): " + if /i "%choice%"=="y" ( + echo 📦 安装前端依赖包... + cd ui + npm install + cd .. + if errorlevel 1 ( + echo ❌ 前端依赖安装失败 + goto :end + ) else ( + echo ✅ 前端依赖安装成功 + ) + ) +) else ( + echo ✅ ui/node_modules 存在 +) + :: 检查前端构建文件 echo. echo 🔍 检查前端构建文件...