增加重启功能
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优雅关闭
|
||||
*/
|
||||
|
||||
+100
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
<!-- 主要操作按钮组 -->
|
||||
<div class="action-group primary-actions">
|
||||
<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">
|
||||
<SvgIcon name="save" class="btn-icon" />
|
||||
{{ saving ? '保存中...' : '保存配置' }}
|
||||
@@ -152,7 +152,7 @@
|
||||
<!-- 缓存管理按钮组 -->
|
||||
<div class="action-group secondary-actions">
|
||||
<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"
|
||||
:disabled="clearing">
|
||||
<SvgIcon name="clean" class="btn-icon" />
|
||||
@@ -167,8 +167,12 @@
|
||||
|
||||
<!-- 危险操作按钮组 -->
|
||||
<div class="action-group danger-actions">
|
||||
<h6 class="action-group-title">重置操作</h6>
|
||||
<div class="action-buttons">
|
||||
<h6 class="action-group-title">系统操作</h6>
|
||||
<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">
|
||||
<SvgIcon name="reset" class="btn-icon" />
|
||||
{{ resetting ? '重置中...' : '重置为默认配置' }}
|
||||
@@ -187,6 +191,7 @@
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import cacheService, { type CacheConfig, type CacheStats } from '@/services/cache';
|
||||
import authService from '@/services/auth';
|
||||
import apiService from '@/services/api';
|
||||
import LoadingSpinner from './LoadingSpinner.vue';
|
||||
import ErrorMessage from './ErrorMessage.vue';
|
||||
|
||||
@@ -196,6 +201,7 @@ const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const clearing = ref(false);
|
||||
const resetting = ref(false);
|
||||
const restarting = ref(false);
|
||||
const saving = ref(false);
|
||||
const refreshing = ref(false);
|
||||
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 = () => {
|
||||
error.value = null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user