import { Server } from 'socket.io'; import { AuthenticatedSocket } from '../middleware/auth.middleware'; import { redisService } from './redis.service'; import { AppDataSource } from '@/server/data-source'; import { SubmissionRecords } from '@/server/modules/submission/submission-records.entity'; import debug from 'debug'; import type { Answer } from '@/client/mobile/components/Exam/types'; const log = debug('socket:exam'); export class ExamService { private io: Server; constructor(io: Server) { this.io = io; } async joinRoom(socket: AuthenticatedSocket, roomId: string) { try { if (!socket.user) throw new Error('用户未认证'); const user = socket.user; socket.join(roomId); await redisService.addRoomMember(roomId, user.id, user.username); socket.emit('exam:joined', { roomId, message: `成功加入考试房间: ${roomId}` }); socket.to(roomId).emit('exam:memberJoined', { roomId, userId: user.id, username: user.username }); log(`用户 ${user.username} 加入考试房间 ${roomId}`); } catch (error) { log('加入考试房间失败:', error); socket.emit('error', '加入考试房间失败'); } } async leaveRoom(socket: AuthenticatedSocket, roomId: string) { try { if (!socket.user) throw new Error('用户未认证'); const user = socket.user; socket.leave(roomId); await redisService.removeRoomMember(roomId, user.id); socket.emit('exam:left', { roomId, message: `已离开考试房间: ${roomId}` }); socket.to(roomId).emit('exam:memberLeft', { roomId, userId: user.id, username: user.username }); log(`用户 ${user.username} 离开考试房间 ${roomId}`); } catch (error) { log('离开考试房间失败:', error); socket.emit('error', '离开考试房间失败'); } } async pushQuestion(socket: AuthenticatedSocket, roomId: string, question: { date: string; price: string }) { try { if (!socket.user) throw new Error('用户未认证'); await redisService.storeCurrentQuestion(roomId, question); await redisService.storePrice(roomId, question.date, question.price); const quizState = { id: question.date, date: question.date, price: question.price }; socket.to(roomId).emit('exam:question', quizState); log(`用户 ${socket.user.username} 在房间 ${roomId} 推送题目`); } catch (error) { log('推送题目失败:', error); socket.emit('error', '推送题目失败'); } } async storeAnswer(socket: AuthenticatedSocket, roomId: string, questionId: string, answer: Answer): Promise { try { if (!socket.user) throw new Error('用户未认证'); const user = socket.user; await redisService.storeAnswer(roomId, user.id, questionId, { ...answer, username: user.username }); socket.to(roomId).emit('exam:answerUpdated', { roomId, questionId, userId: user.id, username: user.username }); log(`用户 ${user.username} 在房间 ${roomId} 存储答案`); return true; } catch (error) { log('存储答案失败:', error); socket.emit('error', '存储答案失败'); return false; } } async getAnswers(roomId: string, questionId?: string): Promise { try { return await redisService.getAnswers(roomId, questionId); } catch (error) { log('获取答案失败:', error); return []; } } async getUserAnswers(roomId: string, userId: number): Promise { try { return await redisService.getUserAnswers(roomId, userId); } catch (error) { log('获取用户答案失败:', error); return []; } } async storePrice(socket: AuthenticatedSocket, roomId: string, date: string, price: string) { try { if (!socket.user) throw new Error('用户未认证'); await redisService.storePrice(roomId, date, price); log(`用户 ${socket.user.username} 存储房间 ${roomId} 的价格历史: ${date} - ${price}`); } catch (error) { log('存储价格历史失败:', error); socket.emit('error', '存储价格历史失败'); } } async getPrice(roomId: string, date: string): Promise { try { return await redisService.getPrice(roomId, date); } catch (error) { log('获取历史价格失败:', error); return null; } } async getAllPrices(roomId: string): Promise> { try { return await redisService.getAllPrices(roomId); } catch (error) { log('获取所有价格历史失败:', error); return {}; } } async getCurrentQuestion(roomId: string) { try { return await redisService.getCurrentQuestion(roomId); } catch (error) { log('获取当前题目失败:', error); return null; } } async cleanupRoomData(socket: AuthenticatedSocket, roomId: string, questionId?: string) { try { if (!socket.user) throw new Error('用户未认证'); const user = socket.user; // 在清理Redis数据前,先保存答题结果到数据库 await this.saveAnswersToSubmissionRecords(roomId, questionId); await redisService.cleanupRoomData(roomId, questionId); socket.to(roomId).emit('exam:cleaned', { roomId, message: questionId ? `已清理房间 ${roomId} 的问题 ${questionId} 数据` : `已清理房间 ${roomId} 的所有数据` }); log(`用户 ${user.username} 清理房间 ${roomId} 数据`); } catch (error) { log('清理房间数据失败:', error); socket.emit('error', '清理房间数据失败'); } } async broadcastSettle(socket: AuthenticatedSocket, roomId: string) { try { if (!socket.user) throw new Error('用户未认证'); const user = socket.user; socket.to(roomId).emit('exam:settle'); log(`用户 ${user.username} 在房间 ${roomId} 广播结算消息`); } catch (error) { log('广播结算消息失败:', error); socket.emit('error', '广播结算消息失败'); } } /** * 保存答题结果到提交记录表 */ private async saveAnswersToSubmissionRecords(roomId: string, questionId?: string): Promise { try { // 获取所有答题数据 const answers = await redisService.getAnswers(roomId, questionId); if (answers.length === 0) { log(`房间 ${roomId} 没有答题数据需要保存`); return; } const submissionRecordsRepository = AppDataSource.getRepository(SubmissionRecords); const recordsToSave: SubmissionRecords[] = []; for (const answer of answers) { // 转换Redis中的答案数据为提交记录实体 const submissionRecord = new SubmissionRecords(); submissionRecord.classroomNo = roomId; submissionRecord.userId = answer.userId || null; submissionRecord.score = this.calculateScore(answer); submissionRecord.code = answer.code || null; submissionRecord.trainingDate = answer.date ? new Date(answer.date) : null; submissionRecord.mark = null; // 标记字段,可根据需要设置 submissionRecord.status = 1; // 状态:1-正常 submissionRecord.holdingStock = answer.holdingStock || null; submissionRecord.holdingCash = answer.holdingCash || null; submissionRecord.price = Number(answer.price); submissionRecord.profitAmount = answer.profitAmount || null; submissionRecord.profitPercent = answer.profitPercent || null; submissionRecord.totalProfitAmount = answer.totalProfitAmount || null; submissionRecord.totalProfitPercent = answer.totalProfitPercent || null; recordsToSave.push(submissionRecord); } // 批量保存到数据库 if (recordsToSave.length > 0) { await submissionRecordsRepository.save(recordsToSave); log(`成功保存 ${recordsToSave.length} 条答题记录到数据库,房间: ${roomId}`); } } catch (error) { log('保存答题记录到数据库失败:', error); // 不抛出错误,避免影响正常的清理操作 } } /** * 计算得分(可根据业务需求自定义评分逻辑) */ private calculateScore(answer: Answer): number | null { // 这里可以根据答题内容计算得分 // 示例:根据收益率计算得分,收益率越高得分越高 if (answer.profitPercent !== undefined && answer.profitPercent !== null) { return Math.max(0, Math.min(100, 50 + (answer.profitPercent * 2))); } return null; } }