| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- 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<boolean> {
- 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<Answer[]> {
- try {
- return await redisService.getAnswers(roomId, questionId);
- } catch (error) {
- log('获取答案失败:', error);
- return [];
- }
- }
- async getUserAnswers(roomId: string, userId: number): Promise<Answer[]> {
- 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<string | null> {
- try {
- return await redisService.getPrice(roomId, date);
- } catch (error) {
- log('获取历史价格失败:', error);
- return null;
- }
- }
- async getAllPrices(roomId: string): Promise<Record<string, number>> {
- 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<void> {
- 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;
- }
- }
|