增加重启功能

This commit is contained in:
2025-10-09 09:27:06 +08:00
parent 1483f93b99
commit ac6dfeca89
5 changed files with 268 additions and 6 deletions
+2
View File
@@ -13,6 +13,7 @@ const repositoryRoutes = require('./repository');
const rankingRoutes = require('./ranking'); const rankingRoutes = require('./ranking');
const watchlistRoutes = require('./watchlist'); const watchlistRoutes = require('./watchlist');
const updateRoutes = require('./update'); const updateRoutes = require('./update');
const systemRoutes = require('./system');
// 导入认证中间件 // 导入认证中间件
const { authMiddleware } = require('../middleware/auth'); const { authMiddleware } = require('../middleware/auth');
@@ -47,6 +48,7 @@ function setupRoutes(app, backend) {
app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证 app.use('/api/proxy', proxyRoutes); // 图片代理,不需要认证
app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证 app.use('/api/watchlist', authMiddleware, watchlistRoutes); // 待看名单,需要认证
app.use('/api/update', updateRoutes); // 更新检查,不需要认证 app.use('/api/update', updateRoutes); // 更新检查,不需要认证
app.use('/api/system', systemRoutes); // 系统管理,不需要认证
// 404 处理 // 404 处理
app.use((req, res) => { app.use((req, res) => {
+79
View File
@@ -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;
+47 -1
View File
@@ -27,6 +27,7 @@ class PixivServer {
constructor() { constructor() {
this.app = express(); this.app = express();
this.backend = null; this.backend = null;
this.server = null; // 添加server实例引用
this.port = 3000; // 默认端口,会在init时重新设置 this.port = 3000; // 默认端口,会在init时重新设置
this.logLevel = process.env.LOG_LEVEL || 'info'; // 获取日志级别 this.logLevel = process.env.LOG_LEVEL || 'info'; // 获取日志级别
this.isVerboseMode = ['debug', 'trace'].includes(this.logLevel.toLowerCase()); // 检查是否为详细模式 this.isVerboseMode = ['debug', 'trace'].includes(this.logLevel.toLowerCase()); // 检查是否为详细模式
@@ -91,6 +92,9 @@ class PixivServer {
// 后端实例注入中间件 // 后端实例注入中间件
this.app.use(backendInjector(this.backend)); this.app.use(backendInjector(this.backend));
// 将服务器实例保存到app.locals中,供路由使用
this.app.locals.serverInstance = this;
} }
/** /**
@@ -111,7 +115,7 @@ class PixivServer {
* 启动服务器 * 启动服务器
*/ */
start() { start() {
this.app.listen(this.port, () => { this.server = this.app.listen(this.port, () => {
logger.info('Pixiv 后端服务器已启动'); logger.info('Pixiv 后端服务器已启动');
logger.info(`服务地址: http://localhost:${this.port}`); logger.info(`服务地址: http://localhost:${this.port}`);
logger.info(`健康检查: http://localhost:${this.port}/health`); 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;
}
}
/** /**
* 优雅关闭 * 优雅关闭
*/ */
+100 -1
View File
@@ -477,8 +477,107 @@
border: 1px solid var(--color-danger); 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) { @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 { .mobile-stack {
flex-direction: column !important; flex-direction: column !important;
} }
+40 -4
View File
@@ -137,7 +137,7 @@
<!-- 主要操作按钮组 --> <!-- 主要操作按钮组 -->
<div class="action-group primary-actions"> <div class="action-group primary-actions">
<h6 class="action-group-title">配置管理</h6> <h6 class="action-group-title">配置管理</h6>
<div class="action-buttons grid grid-cols-2"> <div class="btn-group grid grid-cols-2">
<button @click="saveConfig" class="btn btn-primary btn-enhanced" :disabled="saving"> <button @click="saveConfig" class="btn btn-primary btn-enhanced" :disabled="saving">
<SvgIcon name="save" class="btn-icon" /> <SvgIcon name="save" class="btn-icon" />
{{ saving ? '保存中...' : '保存配置' }} {{ saving ? '保存中...' : '保存配置' }}
@@ -152,7 +152,7 @@
<!-- 缓存管理按钮组 --> <!-- 缓存管理按钮组 -->
<div class="action-group secondary-actions"> <div class="action-group secondary-actions">
<h6 class="action-group-title">缓存管理</h6> <h6 class="action-group-title">缓存管理</h6>
<div class="action-buttons grid grid-cols-2"> <div class="btn-group grid grid-cols-2">
<button @click="clearExpiredCache" class="btn btn-secondary btn-enhanced hover-primary" <button @click="clearExpiredCache" class="btn btn-secondary btn-enhanced hover-primary"
:disabled="clearing"> :disabled="clearing">
<SvgIcon name="clean" class="btn-icon" /> <SvgIcon name="clean" class="btn-icon" />
@@ -167,8 +167,12 @@
<!-- 危险操作按钮组 --> <!-- 危险操作按钮组 -->
<div class="action-group danger-actions"> <div class="action-group danger-actions">
<h6 class="action-group-title">重置操作</h6> <h6 class="action-group-title">系统操作</h6>
<div class="action-buttons"> <div class="btn-group grid grid-cols-2">
<button @click="restartServer" class="btn btn-warning btn-enhanced" :disabled="restarting">
<SvgIcon name="refresh" class="btn-icon" />
{{ restarting ? '重启中...' : '重启服务器' }}
</button>
<button @click="resetConfig" class="btn btn-danger btn-enhanced" :disabled="resetting"> <button @click="resetConfig" class="btn btn-danger btn-enhanced" :disabled="resetting">
<SvgIcon name="reset" class="btn-icon" /> <SvgIcon name="reset" class="btn-icon" />
{{ resetting ? '重置中...' : '重置为默认配置' }} {{ resetting ? '重置中...' : '重置为默认配置' }}
@@ -187,6 +191,7 @@
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import cacheService, { type CacheConfig, type CacheStats } from '@/services/cache'; import cacheService, { type CacheConfig, type CacheStats } from '@/services/cache';
import authService from '@/services/auth'; import authService from '@/services/auth';
import apiService from '@/services/api';
import LoadingSpinner from './LoadingSpinner.vue'; import LoadingSpinner from './LoadingSpinner.vue';
import ErrorMessage from './ErrorMessage.vue'; import ErrorMessage from './ErrorMessage.vue';
@@ -196,6 +201,7 @@ const loading = ref(false);
const error = ref<string | null>(null); const error = ref<string | null>(null);
const clearing = ref(false); const clearing = ref(false);
const resetting = ref(false); const resetting = ref(false);
const restarting = ref(false);
const saving = ref(false); const saving = ref(false);
const refreshing = ref(false); const refreshing = ref(false);
const successMessage = ref<string | null>(null); const successMessage = ref<string | null>(null);
@@ -497,6 +503,36 @@ const refreshToken = async () => {
} }
}; };
const restartServer = async () => {
if (!confirm('确定要重启服务器吗?这将中断当前的所有连接和下载任务。')) {
return;
}
try {
restarting.value = true;
error.value = null;
// 调用重启接口
const response = await apiService.post('/api/system/restart');
if (response.success) {
showSuccess('服务器重启请求已发送,页面将在几秒后自动刷新...');
// 延迟刷新页面,给服务器时间重启
setTimeout(() => {
window.location.reload();
}, 3000);
} else {
error.value = response.error || '重启服务器失败';
}
} catch (err) {
error.value = err instanceof Error ? err.message : '重启服务器失败';
console.error('重启服务器失败:', err);
} finally {
restarting.value = false;
}
};
const clearError = () => { const clearError = () => {
error.value = null; error.value = null;
}; };