-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-simple-server.js
More file actions
445 lines (388 loc) · 12.5 KB
/
test-simple-server.js
File metadata and controls
445 lines (388 loc) · 12.5 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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
const express = require('express')
const http = require('http')
const cors = require('cors')
const WebSocket = require('ws')
const app = express()
const PORT = process.env.PORT || 8080
// CORS配置
app.use(cors({
origin: ['http://localhost:5173', 'http://127.0.0.1:5173', 'http://localhost:3000', 'http://117.72.79.92:5173'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}))
// 中间件配置
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// 请求日志
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`)
next()
})
// 健康检查端点
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
message: 'Server is running'
})
})
// 根路由
app.get('/', (req, res) => {
res.json({
message: 'KAS Backend Test Server',
version: '1.0.0',
timestamp: new Date().toISOString(),
endpoints: [
'GET /health',
'POST /login',
'GET /api/reports/today/stats',
'GET /api/reports/today/details'
]
})
})
// 登录端点
app.post('/login', (req, res) => {
console.log('登录请求:', req.body)
const { username, password, totp } = req.body
// 简单验证
if (username || password || totp) {
res.json({
success: true,
message: '登录成功',
user: { username: username || 'test' },
token: 'test-token-' + Date.now()
})
} else {
res.status(400).json({
success: false,
message: '请提供用户名、密码或TOTP'
})
}
})
// API登录端点(TOTP)
app.post('/api/login', (req, res) => {
console.log('API登录请求:', req.body)
const { totp } = req.body
if (totp) {
res.json({
success: true,
message: 'TOTP验证成功',
token: 'totp-token-' + Date.now()
})
} else {
res.status(400).json({
success: false,
message: 'TOTP不能为空'
})
}
})
// 模拟今日统计数据
app.get('/api/reports/today/stats', (req, res) => {
console.log('获取今日统计数据')
res.json({
success: true,
data: {
total: Math.floor(Math.random() * 10) + 1,
praise: Math.floor(Math.random() * 5) + 1,
criticism: Math.floor(Math.random() * 3) + 1
}
})
})
// 模拟今日详细数据
app.get('/api/reports/today/details', (req, res) => {
console.log('获取今日详细数据')
const reports = []
const reportCount = Math.floor(Math.random() * 5) + 1
for (let i = 0; i < reportCount; i++) {
const isAdd = Math.random() > 0.5
reports.push({
id: i + 1,
class: `10${Math.floor(Math.random() * 9) + 1}`,
isadd: isAdd,
changescore: Math.floor(Math.random() * 5) + 1,
note: isAdd ?
['积极参与课堂讨论', '帮助同学', '主动打扫卫生', '拾金不昧'][Math.floor(Math.random() * 4)] :
['上课说话', '迟到', '作业未交', '违反纪律'][Math.floor(Math.random() * 4)],
submitter: ['张老师', '李老师', '王老师', '赵老师'][Math.floor(Math.random() * 4)],
submittime: new Date(Date.now() - Math.random() * 3600000).toISOString()
})
}
const praise = reports.filter(r => r.isadd).length
const criticism = reports.filter(r => !r.isadd).length
res.json({
success: true,
data: {
summary: {
total: reports.length,
praise: praise,
criticism: criticism
},
allReports: reports.sort((a, b) => new Date(b.submittime) - new Date(a.submittime))
}
})
})
// 模拟班级列表
app.get('/api/classes', (req, res) => {
console.log('获取班级列表')
const classes = Array.from({ length: 18 }, (_, i) => ({
class: i + 1,
headteacher: `班主任${i + 1}`
}))
res.json({
success: true,
data: classes
})
})
// 提交通报数据端点 - 这是缺失的关键端点
app.post('/api/inputdata', (req, res) => {
console.log('📝 收到通报提交请求:', req.body)
const { class: classNum, isadd, changescore, note, submitter, reducetype } = req.body
// 验证必需字段
if (!classNum || isadd === undefined || !changescore || !note || !submitter) {
console.log('❌ 缺少必需字段:', { classNum, isadd, changescore, note, submitter })
return res.status(400).json({
success: false,
message: '缺少必需字段',
received: { classNum, isadd, changescore, note, submitter }
})
}
// 验证数据范围
if (changescore < 1 || changescore > 20) {
return res.status(400).json({
success: false,
message: '分数必须在1-20之间'
})
}
// 模拟成功响应
const result = {
id: Math.floor(Math.random() * 10000) + 1,
database: new Date().toISOString().slice(0, 7), // YYYY-MM
submittime: new Date().toISOString(),
class: parseInt(classNum),
headteacher: `班主任${classNum}`
}
console.log(`✅ 模拟数据插入成功, 记录ID: ${result.id}`)
// 广播新通报给所有WebSocket客户端
const newReport = {
id: result.id,
class: parseInt(classNum),
headteacher: `班主任${classNum}`,
isadd,
changescore: parseInt(changescore),
note,
submitter,
submittime: result.submittime,
reducetype
}
let broadcastCount = 0
// 发送给所有订阅的WebSocket客户端
wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN && ws.subscribed) {
try {
ws.send(JSON.stringify({
type: 'new-report',
channel: 'reports',
data: newReport,
time: new Date().toISOString()
}))
broadcastCount++
console.log(`✅ 测试服务器消息已发送到客户端 ${ws.clientId}`)
} catch (error) {
console.error('❌ 广播消息失败:', error)
}
}
})
console.log(`📡 已向 ${broadcastCount} 个客户端广播新通报`)
res.json({
success: true,
message: '数据提交成功',
data: {
...result,
broadcastCount
}
})
})
// 获取今日Excel报告数据
app.get('/api/reports/today/excel', (req, res) => {
console.log('获取今日Excel报告数据')
const reports = []
const reportCount = Math.floor(Math.random() * 15) + 5
for (let i = 0; i < reportCount; i++) {
const isAdd = Math.random() > 0.4 // 60%概率是违纪,40%概率是表彰
const classNum = Math.floor(Math.random() * 18) + 1
const reducetype = isAdd ? null : (Math.random() > 0.5 ? 'discipline' : 'hygiene')
reports.push({
id: i + 1,
class: classNum,
headteacher: `班主任${classNum}`,
isadd: isAdd,
changescore: Math.floor(Math.random() * 8) + 1,
note: isAdd ?
['积极参与课堂讨论', '帮助同学解决问题', '主动打扫卫生', '拾金不昧'][Math.floor(Math.random() * 4)] :
['上课说话影响他人学习', '课间大声喧哗', '作业未按时完成', '迟到违反纪律'][Math.floor(Math.random() * 4)],
submitter: ['张老师', '李老师', '王老师', '赵老师', '陈老师'][Math.floor(Math.random() * 5)],
submittime: new Date(Date.now() - Math.random() * 3600000).toISOString(),
reducetype,
typeText: isAdd ? '表彰' : '违纪',
reduceTypeText: isAdd ? '' : (reducetype === 'discipline' ? '纪律违纪' : '卫生违纪'),
scoreDisplay: isAdd ? `+${Math.floor(Math.random() * 8) + 1}` : `-${Math.floor(Math.random() * 8) + 1}`,
timeDisplay: new Date(Date.now() - Math.random() * 3600000).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
})
}
// 按班级和类型排序(班级升序,违纪在前表彰在后)
reports.sort((a, b) => {
if (a.class !== b.class) return a.class - b.class
return a.isadd - b.isadd // false(违纪)在前,true(表彰)在后
})
// 按班级分组
const reportsByClass = {}
reports.forEach(report => {
if (!reportsByClass[report.class]) {
reportsByClass[report.class] = {
class: report.class,
headteacher: report.headteacher,
violations: [],
praises: []
}
}
if (report.isadd) {
reportsByClass[report.class].praises.push(report)
} else {
reportsByClass[report.class].violations.push(report)
}
})
const classReports = Object.values(reportsByClass)
.sort((a, b) => a.class - b.class)
const summary = {
date: new Date().toISOString().split('T')[0],
totalReports: reports.length,
totalViolations: reports.filter(r => !r.isadd).length,
totalPraises: reports.filter(r => r.isadd).length,
activeClasses: classReports.length,
generatedAt: new Date().toISOString()
}
res.json({
success: true,
data: {
summary,
reports,
classReports,
metadata: {
title: `垦利校区高三学部${new Date().getFullYear()}年${String(new Date().getMonth() + 1).padStart(2, '0')}月${String(new Date().getDate()).padStart(2, '0')}日违纪表彰通报`,
headers: ['班级', '班主任', '通报类型', '违纪类型', '原因', '分数变动', '通报提交人', '时间']
}
}
})
})
// 错误处理
app.use((err, req, res, next) => {
console.error('服务器错误:', err)
res.status(500).json({
success: false,
message: '服务器内部错误'
})
})
// 404处理
app.use('*', (req, res) => {
console.log(`404 - 路由不存在: ${req.method} ${req.originalUrl}`)
res.status(404).json({
success: false,
message: `接口不存在: ${req.method} ${req.originalUrl}`,
availableEndpoints: [
'GET /health',
'POST /login',
'POST /api/login',
'GET /api/classes',
'POST /api/inputdata',
'GET /api/reports/today/stats',
'GET /api/reports/today/details'
]
})
})
// 创建HTTP服务器
const server = http.createServer(app)
// 初始化WebSocket服务器
const wss = new WebSocket.Server({ server })
wss.on('connection', (ws) => {
console.log('📡 WebSocket客户端连接成功')
ws.clientId = Date.now() + Math.random().toString(36).substr(2, 5)
ws.isAlive = true
// 处理客户端消息
ws.on('message', (message) => {
try {
const data = JSON.parse(message)
console.log(`收到来自客户端 ${ws.clientId} 的消息:`, data)
if (data.type === 'subscribe') {
ws.subscribed = true
ws.channels = data.channels || ['reports']
ws.send(JSON.stringify({
type: 'subscribed',
channels: ws.channels,
message: `已订阅频道: ${ws.channels.join(', ')}`
}))
}
} catch (error) {
console.error('WebSocket消息解析失败:', error)
}
})
// 心跳检测
ws.on('pong', () => {
ws.isAlive = true
})
ws.on('close', () => {
console.log(`📡 WebSocket客户端 ${ws.clientId} 已断开`)
})
ws.on('error', (error) => {
console.error(`WebSocket客户端 ${ws.clientId} 错误:`, error)
})
// 发送连接确认消息
ws.send(JSON.stringify({
type: 'connected',
clientId: ws.clientId,
message: '已连接到实时通报系统',
time: new Date().toISOString()
}))
})
// 定期检查WebSocket连接
const pingInterval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
console.log(`关闭无响应的客户端 ${ws.clientId}`)
return ws.terminate()
}
ws.isAlive = false
ws.ping()
})
}, 30000)
// 启动服务器
server.listen(PORT, '0.0.0.0', () => {
console.log('🚀 KAS 测试服务器启动成功!')
console.log(`📍 HTTP服务: http://localhost:${PORT}`)
console.log(`📡 WebSocket: ws://localhost:${PORT}`)
console.log('📋 可用端点:')
console.log(' GET /health - 健康检查')
console.log(' POST /login - 用户登录')
console.log(' POST /api/login - TOTP登录')
console.log(' GET /api/classes - 班级列表')
console.log(' POST /api/inputdata - 提交通报 ⭐️')
console.log(' GET /api/reports/today/stats - 今日统计')
console.log(' GET /api/reports/today/details - 今日详情')
console.log('\n🔄 服务器正在运行,等待连接...')
console.log('\n⚠️ 请确保使用这个测试服务器而不是主服务器来测试提交功能')
})
// 优雅关闭
process.on('SIGINT', () => {
console.log('\n🛑 收到停止信号,正在关闭服务器...')
clearInterval(pingInterval)
wss.close(() => {
server.close(() => {
console.log('✅ 服务器已关闭')
process.exit(0)
})
})
})
module.exports = server