-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.js
More file actions
245 lines (209 loc) · 7.89 KB
/
server.js
File metadata and controls
245 lines (209 loc) · 7.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/**
* 班级积分管理系统 - 主服务器文件
*
* 功能概述:
* - 提供Web服务器和API接口
* - 支持三种界面:大屏展示、教师管理、学生查询
* - 实现实时数据推送和性能监控
* - 提供完整的错误处理和日志记录
*
* 技术栈:
* - Express.js: Web框架
* - JSON文件: 数据存储
* - SSE: 实时数据推送
* - 自定义中间件: 错误处理和性能监控
*
* @author 班级积分管理系统开发团队
* @version 1.0.0
* @since 2025-09-09
*/
const express = require('express');
const path = require('path');
const DataInitializer = require('./utils/dataInitializer');
const { errorHandler, notFoundHandler, errorLogger, performanceMonitor, rateLimiter } = require('./middleware/errorHandler');
// 创建Express应用实例
const app = express();
const PORT = process.env.PORT || 3000;
// ==================== 数据初始化 ====================
async function startServer() {
try {
console.log('开始初始化系统数据...');
const dataInitializer = new DataInitializer();
await dataInitializer.initializeAllData();
console.log('系统数据初始化完成');
// ==================== 中间件配置 ====================
/**
* 配置请求体解析中间件
* - 支持JSON格式数据,最大10MB
* - 支持URL编码数据,最大10MB
*/
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 忽略对 favicon.ico 的请求,避免抛出 404 错误并污染日志
app.get('/favicon.ico', (req, res) => res.status(204).end());
/**
* 性能监控中间件
* 监控API响应时间、内存使用等性能指标
*/
app.use(performanceMonitor());
/**
* 请求日志中间件
* 记录所有HTTP请求的详细信息,包括:
* - 请求方法和URL
* - 响应状态码和处理时间
* - 客户端IP和User-Agent
*
* 优化策略:
* - 只记录错误请求和非GET请求,减少日志量
* - 使用异步日志记录,避免阻塞请求处理
*/
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const logEntry = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
ip: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent')
};
// 只记录错误请求和重要操作,减少日志噪音
if (res.statusCode >= 400 || req.method !== 'GET') {
console.log('请求日志:', JSON.stringify(logEntry));
}
});
next();
});
// 静态文件服务
app.use(express.static(path.join(__dirname, 'public')));
// SSE服务初始化
const sseService = require('./services/sseService');
const { router: sseRouter, broadcastSSEMessage } = require('./api/sse');
// 设置SSE服务的广播函数
sseService.setBroadcastFunction(broadcastSSEMessage);
// API路由
// 登录接口限流保护(每IP每分钟最多20次)
app.use('/api/auth', rateLimiter(20, 60000));
app.use('/api/auth', require('./api/auth'));
app.use('/api/teachers', require('./api/teachers'));
app.use('/api/points', require('./api/points'));
app.use('/api/students', require('./api/students'));
app.use('/api/products', require('./api/products'));
app.use('/api/orders', require('./api/orders'));
app.use('/api/config', require('./api/config'));
app.use('/api/semesters', require('./api/semesters'));
app.use('/api/backup', require('./api/backup'));
app.use('/api/logs', require('./api/logs'));
app.use('/api/feedback', require('./api/feedback'));
app.use('/api/sse', sseRouter);
// 主页路由
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 大屏展示路由
app.get('/display', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'display', 'index.html'));
});
// 教师管理路由
app.get('/teacher', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'teacher', 'index.html'));
});
// 学生查询路由
app.get('/student', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'student', 'index.html'));
});
// 健康检查接口
app.get('/api/health', async (req, res) => {
try {
const { errorMonitor } = require('./middleware/errorHandler');
const health = await errorMonitor.getSystemHealth();
res.status(health.status === 'HEALTHY' ? 200 : 503).json({
success: health.status === 'HEALTHY',
status: health.status,
message: health.message,
timestamp: new Date().toISOString(),
...(health.statistics && { statistics: health.statistics })
});
} catch (error) {
res.status(500).json({
success: false,
status: 'ERROR',
message: '健康检查失败',
timestamp: new Date().toISOString()
});
}
});
// 错误统计接口(仅开发环境)
if (process.env.NODE_ENV === 'development') {
app.get('/api/debug/errors', async (req, res) => {
try {
const hours = parseInt(req.query.hours) || 24;
const statistics = await errorLogger.getErrorStatistics(hours);
res.json({
success: true,
data: statistics,
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取错误统计失败',
timestamp: new Date().toISOString()
});
}
});
}
// 404处理中间件
app.use(notFoundHandler);
// 统一错误处理中间件(必须放在最后)
app.use(errorHandler);
// 启动服务器
const serverInstance = app.listen(PORT, () => {
console.log(`班级积分管理系统已启动`);
console.log(`服务器运行在: http://localhost:${PORT}`);
console.log(`大屏展示: http://localhost:${PORT}/display`);
console.log(`教师管理: http://localhost:${PORT}/teacher`);
console.log(`学生查询: http://localhost:${PORT}/student`);
});
// 优雅关机处理
const gracefulShutdown = async (signal) => {
console.log(`\n[${signal}] 收到关闭信号,准备安全退出服务...`);
// 安全锁:10秒后强制退出
setTimeout(() => {
console.error('[Shutdown] 未能在限定时间内安全关闭,执行强制退出!');
process.exit(1);
}, 10000).unref();
// 1. 断开存活的 SSE
try {
const { closeAllConnections } = require('./api/sse');
closeAllConnections();
} catch (err) {
console.error('[Shutdown] SSE释放抛错:', err);
}
// 2. 关闭 HTTP 监听
if (serverInstance) {
console.log('[Shutdown] 正在关闭 HTTP 监听服务...');
await new Promise(resolve => serverInstance.close(resolve));
}
// 3. 关闭数据库连接
try {
const DataAccess = require('./utils/dataAccess');
await DataAccess.closeAll();
} catch (err) {
console.error('[Shutdown] 数据库释放抛错:', err);
}
console.log('[Shutdown] 资源已全部释放,进程退出。');
process.exit(0);
};
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
} catch (error) {
console.error('服务器启动失败:', error);
process.exit(1);
}
}
startServer();
module.exports = app;