From ac6dfeca89715dffdf6166bdd30d6ee004095ade Mon Sep 17 00:00:00 2001 From: kjqwer <2990346238@qq.com> Date: Thu, 9 Oct 2025 09:27:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E5=90=AF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/index.js | 2 + backend/routes/system.js | 79 +++++++++++++++ backend/server.js | 48 +++++++++- ui/src/assets/theme.css | 101 +++++++++++++++++++- ui/src/components/common/SettingsWidget.vue | 44 ++++++++- 5 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 backend/routes/system.js diff --git a/backend/routes/index.js b/backend/routes/index.js index 5486f40..0f032e4 100644 --- a/backend/routes/index.js +++ b/backend/routes/index.js @@ -13,6 +13,7 @@ const repositoryRoutes = require('./repository'); const rankingRoutes = require('./ranking'); const watchlistRoutes = require('./watchlist'); const updateRoutes = require('./update'); +const systemRoutes = require('./system'); // 导入认证中间件 const { authMiddleware } = require('../middleware/auth'); @@ -47,6 +48,7 @@ function setupRoutes(app, backend) { app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证 app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证 app.use('/api/update', updateRoutes); // 更新检查,不需要认证 + app.use('/api/system', systemRoutes); // 系统管理,不需要认证 // 404 处理 app.use((req, res) => { diff --git a/backend/routes/system.js b/backend/routes/system.js new file mode 100644 index 0000000..db9b85d --- /dev/null +++ b/backend/routes/system.js @@ -0,0 +1,79 @@ +const express = require('express'); +const router = express.Router(); +const { defaultLogger } = require('../utils/logger'); + +// 创建logger实例 +const logger = defaultLogger.child('SystemRouter'); + +/** + * 重启服务器 + * POST /api/system/restart + */ +router.post('/restart', async (req, res) => { + try { + logger.info('收到重启请求'); + + // 立即返回响应,避免客户端等待超时 + res.json({ + success: true, + message: '服务器正在重启,请稍后刷新页面' + }); + + // 延迟执行重启,给响应时间发送 + setTimeout(async () => { + try { + // 获取服务器实例(通过全局变量或其他方式) + const server = req.app.locals.serverInstance; + if (server && typeof server.restart === 'function') { + await server.restart(); + } else { + logger.error('无法获取服务器实例或重启方法'); + // 如果无法优雅重启,则退出进程让进程管理器重启 + process.exit(1); + } + } catch (error) { + logger.error('重启失败:', error); + // 强制退出进程 + process.exit(1); + } + }, 1000); // 延迟1秒执行重启 + + } catch (error) { + logger.error('处理重启请求失败:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +/** + * 获取系统状态 + * GET /api/system/status + */ +router.get('/status', (req, res) => { + try { + const status = { + uptime: process.uptime(), + memory: process.memoryUsage(), + version: process.version, + platform: process.platform, + arch: process.arch, + pid: process.pid, + timestamp: new Date().toISOString() + }; + + res.json({ + success: true, + data: status + }); + } catch (error) { + logger.error('获取系统状态失败:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 1542547..4a8a496 100644 --- a/backend/server.js +++ b/backend/server.js @@ -27,6 +27,7 @@ class PixivServer { constructor() { this.app = express(); this.backend = null; + this.server = null; // 添加server实例引用 this.port = 3000; // 默认端口,会在init时重新设置 this.logLevel = process.env.LOG_LEVEL || 'info'; // 获取日志级别 this.isVerboseMode = ['debug', 'trace'].includes(this.logLevel.toLowerCase()); // 检查是否为详细模式 @@ -91,6 +92,9 @@ class PixivServer { // 后端实例注入中间件 this.app.use(backendInjector(this.backend)); + + // 将服务器实例保存到app.locals中,供路由使用 + this.app.locals.serverInstance = this; } /** @@ -111,7 +115,7 @@ class PixivServer { * 启动服务器 */ start() { - this.app.listen(this.port, () => { + this.server = this.app.listen(this.port, () => { logger.info('Pixiv 后端服务器已启动'); logger.info(`服务地址: http://localhost:${this.port}`); logger.info(`健康检查: http://localhost:${this.port}/health`); @@ -174,6 +178,48 @@ class PixivServer { } } + /** + * 重启服务器 + */ + async restart() { + logger.info('正在重启服务器...'); + + try { + // 清理代理环境变量 + proxyConfig.clearEnvironmentVariables(); + + // 关闭当前服务器 + if (this.server) { + await new Promise((resolve) => { + this.server.close(() => { + logger.info('HTTP服务器已关闭'); + resolve(); + }); + }); + } + + // 清理后端实例 + if (this.backend) { + await this.backend.cleanup?.(); + } + + logger.info('正在重新初始化服务器...'); + + // 重新初始化 + await this.init(); + + // 重新启动 + this.start(); + + logger.info('服务器重启完成'); + return { success: true, message: '服务器重启成功' }; + + } catch (error) { + logger.error('服务器重启失败:', error); + throw error; + } + } + /** * 优雅关闭 */ diff --git a/ui/src/assets/theme.css b/ui/src/assets/theme.css index 671ac62..1a8ed8e 100644 --- a/ui/src/assets/theme.css +++ b/ui/src/assets/theme.css @@ -477,8 +477,107 @@ border: 1px solid var(--color-danger); } -/* 响应式工具类 */ +/* 按钮组布局样式 */ +.btn-group { + display: flex; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +.btn-group-vertical { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.btn-group-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: var(--spacing-md); +} + +.btn-group-space-between { + display: flex; + justify-content: space-between; + gap: var(--spacing-md); + flex-wrap: wrap; +} + +/* 操作组容器样式 */ +.action-group { + background: var(--color-bg-secondary); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + border: 1px solid var(--color-border); + transition: all var(--transition-normal); + margin-bottom: var(--spacing-lg); +} + +.action-group:hover { + box-shadow: var(--shadow-sm); + border-color: var(--color-border-hover); +} + +.action-group-title { + margin: 0 0 var(--spacing-md) 0; + font-size: 0.875rem; + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.action-group-title::before { + content: ''; + width: 3px; + height: 12px; + border-radius: 2px; + background: var(--color-primary); +} + +.primary-actions .action-group-title::before { + background: var(--color-primary); +} + +.secondary-actions .action-group-title::before { + background: var(--color-info); +} + +.danger-actions .action-group-title::before { + background: var(--color-danger); +} + +/* 按钮图标样式 */ +.btn-icon { + width: 1rem; + height: 1rem; + margin-right: var(--spacing-sm); + flex-shrink: 0; +} + +.btn-icon-only { + width: 1rem; + height: 1rem; + margin: 0; +} + +/* 响应式按钮组 */ @media (max-width: 768px) { + .btn-group-grid { + grid-template-columns: 1fr; + } + + .btn-group-space-between { + flex-direction: column; + } + + .action-group { + padding: var(--spacing-md); + } + .mobile-stack { flex-direction: column !important; } diff --git a/ui/src/components/common/SettingsWidget.vue b/ui/src/components/common/SettingsWidget.vue index a07b77b..3dea85f 100644 --- a/ui/src/components/common/SettingsWidget.vue +++ b/ui/src/components/common/SettingsWidget.vue @@ -137,7 +137,7 @@
配置管理
-
+