在这里插入图片描述

📖 引言

在知识问答学习应用中,测验服务(QuizService)是核心业务组件之一。它负责管理整个测验流程,包括题目获取、答题处理、结果计算、统计更新等关键操作。一个设计良好的测验服务能够提供流畅的答题体验,确保数据的准确性和一致性。

本文将详细讲解 QuizService 的完整实现,包括测验会话管理、答题逻辑、结果计算等核心功能。通过本文,你将掌握:

  • 如何设计测验会话管理机制
  • 如何实现答题流程和时间管理
  • 如何计算测验结果和星级评定
  • 如何更新用户统计和成就

🎯 学习目标

完成本文后,你将能够:

  • ✅ 理解 QuizService 的核心职责和功能
  • ✅ 实现测验会话的创建和管理
  • ✅ 处理用户答题和时间限制
  • ✅ 计算测验结果和奖励
  • ✅ 集成用户统计和成就系统

💡 需求分析

功能模块设计

模块 功能描述 技术要点
测验会话 创建和管理测验会话 状态管理、题目顺序
题目获取 获取关卡题目 随机抽取、难度匹配
答题处理 处理用户答案 正确性判断、时间记录
结果计算 计算测验结果 正确率、星级、奖励
统计更新 更新用户统计 答题数、正确率、积分

🛠️ 核心实现

步骤1: QuizService 接口设计

功能说明

定义 QuizService 的核心接口,规范服务的方法签名和返回类型。

完整代码
// services/interfaces/QuizServiceInterface.ts

import { ServiceResponse } from './ServiceInterface';
import { QuizSession, QuizRecord, QuestionResult, Question } from '../models/User';

/**
 * 测验服务接口
 */
export interface QuizServiceInterface {
  /**
   * 创建测验会话
   * @param userId - 用户ID
   * @param levelId - 关卡ID
   * @returns 测验会话
   */
  createSession(userId: string, levelId: string): Promise<ServiceResponse<QuizSession>>;

  /**
   * 获取当前会话
   * @returns 当前测验会话
   */
  getCurrentSession(): ServiceResponse<QuizSession | null>;

  /**
   * 提交答案
   * @param questionId - 题目ID
   * @param answer - 用户答案
   * @returns 答题结果
   */
  submitAnswer(questionId: string, answer: string | string[]): ServiceResponse<QuestionResult>;

  /**
   * 使用工具
   * @param toolType - 工具类型
   * @returns 是否成功
   */
  useTool(toolType: string): ServiceResponse<boolean>;

  /**
   * 结束测验
   * @returns 测验记录
   */
  finishQuiz(): Promise<ServiceResponse<QuizRecord>>;

  /**
   * 获取测验历史记录
   * @param userId - 用户ID
   * @param limit - 返回数量限制
   * @returns 测验记录列表
   */
  getQuizHistory(userId: string, limit?: number): Promise<ServiceResponse<QuizRecord[]>>;

  /**
   * 获取关卡测验记录
   * @param userId - 用户ID
   * @param levelId - 关卡ID
   * @returns 关卡测验记录列表
   */
  getLevelRecords(userId: string, levelId: string): Promise<ServiceResponse<QuizRecord[]>>;

  /**
   * 获取错题本
   * @param userId - 用户ID
   * @returns 错题列表
   */
  getWrongQuestions(userId: string): Promise<ServiceResponse<Question[]>>;

  /**
   * 添加错题
   * @param userId - 用户ID
   * @param questionId - 题目ID
   * @returns 是否成功
   */
  addWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>>;

  /**
   * 移除错题
   * @param userId - 用户ID
   * @param questionId - 题目ID
   * @returns 是否成功
   */
  removeWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>>;

  /**
   * 获取测验统计
   * @param userId - 用户ID
   * @returns 统计数据
   */
  getQuizStatistics(userId: string): Promise<ServiceResponse<QuizStatistics>>;
}

/**
 * 测验统计接口
 */
export interface QuizStatistics {
  totalQuizzes: number;        // 总测验次数
  averageAccuracy: number;     // 平均正确率
  averageTime: number;         // 平均用时(秒)
  highestStars: number;        // 最高星级
  perfectCount: number;        // 满分次数
  totalScore: number;          // 累计获得积分
  totalExperience: number;     // 累计获得经验
}
代码解析

1. 接口方法分类

分类 方法 功能说明
会话管理 createSession(), getCurrentSession() 创建和获取测验会话
答题操作 submitAnswer(), useTool() 提交答案和使用工具
结果处理 finishQuiz() 结束测验并计算结果
记录查询 getQuizHistory(), getLevelRecords() 查询测验记录
错题管理 getWrongQuestions(), addWrongQuestion(), removeWrongQuestion() 错题本管理
统计查询 getQuizStatistics() 获取测验统计

步骤2: QuizService 实现

功能说明

实现 QuizService 的核心逻辑,包括测验会话管理、答题处理、结果计算等功能。

完整代码
// services/QuizService.ts

import { Singleton } from './base/BaseService';
import { BaseService } from './base/BaseService';
import { ServiceResponse, ServiceError, createSuccessResponse, createErrorResponse } from './interfaces/ServiceInterface';
import { QuizServiceInterface, QuizStatistics } from './interfaces/QuizServiceInterface';
import { QuizSession, QuizRecord, QuestionResult, Question } from '../models/User';
import { UserService } from './UserService';
import { LevelService } from './LevelService';
import { AchievementService } from './AchievementService';
import { QuestionService } from './QuestionService';

/**
 * 测验服务实现
 */
@Singleton
export class QuizService extends BaseService implements QuizServiceInterface {
  private userService: UserService;
  private levelService: LevelService;
  private achievementService: AchievementService;
  private questionService: QuestionService;
  private currentSession: QuizSession | null = null;

  constructor() {
    super();
    this.userService = UserService.getInstance();
    this.levelService = LevelService.getInstance();
    this.achievementService = AchievementService.getInstance();
    this.questionService = QuestionService.getInstance();
  }

  /**
   * 创建测验会话
   */
  async createSession(userId: string, levelId: string): Promise<ServiceResponse<QuizSession>> {
    try {
      // 获取用户
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<QuizSession>(ServiceError.NOT_FOUND, '用户不存在');
      }

      // 获取关卡
      const level = this.levelService.getLevelById(levelId);
      if (!level) {
        return createErrorResponse<QuizSession>(ServiceError.NOT_FOUND, '关卡不存在');
      }

      // 检查关卡是否解锁
      if (!this.levelService.isLevelUnlocked(levelId, userResult.data)) {
        return createErrorResponse<QuizSession>(ServiceError.CONFLICT, '关卡未解锁');
      }

      // 获取题目
      const questions = await this.questionService.getRandomQuestions(
        levelId, 
        level.questionCount
      );

      if (questions.length === 0) {
        return createErrorResponse<QuizSession>(ServiceError.NOT_FOUND, '该关卡暂无题目');
      }

      // 创建会话
      const session: QuizSession = {
        id: this.generateSessionId(),
        userId,
        levelId,
        questions,
        startTime: new Date().toISOString(),
        answers: [],
        currentIndex: 0,
        timeUsed: 0,
        toolsUsed: {
          hint: 0,
          skip: 0,
          refresh: 0,
          doubleScore: false
        }
      };

      this.currentSession = session;
      return createSuccessResponse<QuizSession>(session);
    } catch (error) {
      return createErrorResponse<QuizSession>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 获取当前会话
   */
  getCurrentSession(): ServiceResponse<QuizSession | null> {
    return createSuccessResponse<QuizSession | null>(this.currentSession);
  }

  /**
   * 提交答案
   */
  submitAnswer(questionId: string, answer: string | string[]): ServiceResponse<QuestionResult> {
    if (!this.currentSession) {
      return createErrorResponse<QuestionResult>(ServiceError.NOT_INITIALIZED, '没有活动的测验会话');
    }

    const question = this.currentSession.questions.find(q => q.id === questionId);
    if (!question) {
      return createErrorResponse<QuestionResult>(ServiceError.NOT_FOUND, '题目不存在');
    }

    // 判断答案是否正确
    const isCorrect = this.checkAnswer(question, answer);

    // 记录答题结果
    const result: QuestionResult = {
      id: this.generateResultId(),
      questionId: question.id,
      userAnswer: Array.isArray(answer) ? answer.join(',') : answer,
      isCorrect,
      timeUsed: this.calculateQuestionTime(),
      usedTools: []
    };

    // 记录使用的工具
    if (this.currentSession.toolsUsed.hint > 0) {
      result.usedTools.push('hint');
    }

    this.currentSession.answers.push(result);
    this.currentSession.currentIndex++;

    return createSuccessResponse<QuestionResult>(result);
  }

  /**
   * 使用工具
   */
  useTool(toolType: string): ServiceResponse<boolean> {
    if (!this.currentSession) {
      return createErrorResponse<boolean>(ServiceError.NOT_INITIALIZED, '没有活动的测验会话');
    }

    const userResult = this.userService.getCurrentUser();
    if (!userResult.success || !userResult.data) {
      return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '用户不存在');
    }

    const user = userResult.data;

    switch (toolType) {
      case 'hint':
        if (user.tools.hintCount <= 0) {
          return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '提示次数不足');
        }
        user.tools.hintCount--;
        this.currentSession.toolsUsed.hint++;
        break;

      case 'skip':
        if (user.tools.skipCount <= 0) {
          return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '跳过次数不足');
        }
        user.tools.skipCount--;
        this.currentSession.toolsUsed.skip++;
        // 跳过当前题目
        this.currentSession.currentIndex++;
        break;

      case 'refresh':
        if (user.tools.refreshCount <= 0) {
          return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '刷新次数不足');
        }
        user.tools.refreshCount--;
        this.currentSession.toolsUsed.refresh++;
        break;

      case 'doubleScore':
        if (user.tools.doubleScoreCount <= 0) {
          return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '双倍积分次数不足');
        }
        user.tools.doubleScoreCount--;
        this.currentSession.toolsUsed.doubleScore = true;
        break;

      default:
        return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '未知工具类型');
    }

    // 保存用户工具更新
    this.userService.updateUser(user.id, { tools: user.tools });

    return createSuccessResponse<boolean>(true);
  }

  /**
   * 结束测验
   */
  async finishQuiz(): Promise<ServiceResponse<QuizRecord>> {
    if (!this.currentSession) {
      return createErrorResponse<QuizRecord>(ServiceError.NOT_INITIALIZED, '没有活动的测验会话');
    }

    try {
      const session = this.currentSession;
      
      // 获取用户
      const userResult = await this.userService.getUserById(session.userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<QuizRecord>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const user = userResult.data;

      // 获取关卡
      const level = this.levelService.getLevelById(session.levelId);
      if (!level) {
        return createErrorResponse<QuizRecord>(ServiceError.NOT_FOUND, '关卡不存在');
      }

      // 计算结果
      const correctCount = session.answers.filter(a => a.isCorrect).length;
      const totalCount = session.questions.length;
      const accuracy = totalCount > 0 ? correctCount / totalCount : 0;
      const stars = this.calculateStars(accuracy);
      const timeUsed = this.calculateTotalTime(session);

      // 计算奖励
      let score = this.calculateScore(level, stars);
      let experience = this.calculateExperience(level, stars);

      // 应用双倍积分工具
      if (session.toolsUsed.doubleScore) {
        score *= 2;
      }

      // 创建测验记录
      const record: QuizRecord = {
        id: session.id,
        userId: session.userId,
        levelId: session.levelId,
        startTime: session.startTime,
        endTime: new Date().toISOString(),
        totalQuestions: totalCount,
        correctQuestions: correctCount,
        accuracy,
        score,
        experience,
        stars,
        timeUsed,
        questionResults: session.answers
      };

      // 更新用户统计
      await this.updateUserStatistics(user, record);

      // 更新关卡完成状态
      await this.updateLevelCompletion(user, session.levelId, stars);

      // 添加错题
      await this.addWrongQuestions(user, session);

      // 检查成就
      await this.achievementService.checkAndUnlockAchievements(user);

      // 保存测验记录到用户
      user.quizHistory.push(record);
      await this.userService.updateUser(user.id, { 
        quizHistory: user.quizHistory,
        tools: user.tools
      });

      // 清理会话
      this.currentSession = null;

      return createSuccessResponse<QuizRecord>(record);
    } catch (error) {
      return createErrorResponse<QuizRecord>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 获取测验历史记录
   */
  async getQuizHistory(userId: string, limit: number = 20): Promise<ServiceResponse<QuizRecord[]>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<QuizRecord[]>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const history = [...userResult.data.quizHistory]
        .sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime())
        .slice(0, limit);

      return createSuccessResponse<QuizRecord[]>(history);
    } catch (error) {
      return createErrorResponse<QuizRecord[]>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 获取关卡测验记录
   */
  async getLevelRecords(userId: string, levelId: string): Promise<ServiceResponse<QuizRecord[]>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<QuizRecord[]>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const records = userResult.data.quizHistory
        .filter(r => r.levelId === levelId)
        .sort((a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime());

      return createSuccessResponse<QuizRecord[]>(records);
    } catch (error) {
      return createErrorResponse<QuizRecord[]>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 获取错题本
   */
  async getWrongQuestions(userId: string): Promise<ServiceResponse<Question[]>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<Question[]>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const wrongQuestionIds = userResult.data.wrongQuestions;
      const questions = await this.questionService.getQuestionsByIds(wrongQuestionIds);

      return createSuccessResponse<Question[]>(questions);
    } catch (error) {
      return createErrorResponse<Question[]>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 添加错题
   */
  async addWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const user = userResult.data;
      
      // 检查是否已存在
      if (!user.wrongQuestions.includes(questionId)) {
        user.wrongQuestions.push(questionId);
        await this.userService.updateUser(userId, { wrongQuestions: user.wrongQuestions });
      }

      return createSuccessResponse<boolean>(true);
    } catch (error) {
      return createErrorResponse<boolean>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 移除错题
   */
  async removeWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<boolean>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const user = userResult.data;
      const index = user.wrongQuestions.indexOf(questionId);
      
      if (index !== -1) {
        user.wrongQuestions.splice(index, 1);
        await this.userService.updateUser(userId, { wrongQuestions: user.wrongQuestions });
      }

      return createSuccessResponse<boolean>(true);
    } catch (error) {
      return createErrorResponse<boolean>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 获取测验统计
   */
  async getQuizStatistics(userId: string): Promise<ServiceResponse<QuizStatistics>> {
    try {
      const userResult = await this.userService.getUserById(userId);
      if (!userResult.success || !userResult.data) {
        return createErrorResponse<QuizStatistics>(ServiceError.NOT_FOUND, '用户不存在');
      }

      const history = userResult.data.quizHistory;
      
      if (history.length === 0) {
        return createSuccessResponse<QuizStatistics>({
          totalQuizzes: 0,
          averageAccuracy: 0,
          averageTime: 0,
          highestStars: 0,
          perfectCount: 0,
          totalScore: 0,
          totalExperience: 0
        });
      }

      const totalQuizzes = history.length;
      const averageAccuracy = history.reduce((sum, r) => sum + r.accuracy, 0) / totalQuizzes;
      const averageTime = history.reduce((sum, r) => sum + r.timeUsed, 0) / totalQuizzes;
      const highestStars = Math.max(...history.map(r => r.stars));
      const perfectCount = history.filter(r => r.stars === 3).length;
      const totalScore = history.reduce((sum, r) => sum + r.score, 0);
      const totalExperience = history.reduce((sum, r) => sum + r.experience, 0);

      return createSuccessResponse<QuizStatistics>({
        totalQuizzes,
        averageAccuracy,
        averageTime,
        highestStars,
        perfectCount,
        totalScore,
        totalExperience
      });
    } catch (error) {
      return createErrorResponse<QuizStatistics>(ServiceError.ERROR, (error as Error).message);
    }
  }

  /**
   * 检查答案是否正确
   */
  private checkAnswer(question: Question, answer: string | string[]): boolean {
    const correctAnswer = question.answer;
    
    if (Array.isArray(correctAnswer)) {
      // 多选题
      if (!Array.isArray(answer)) {
        return false;
      }
      const sortedCorrect = [...correctAnswer].sort();
      const sortedAnswer = [...answer].sort();
      return JSON.stringify(sortedCorrect) === JSON.stringify(sortedAnswer);
    } else {
      // 单选题、判断题、填空题
      return String(answer) === String(correctAnswer);
    }
  }

  /**
   * 计算星级
   */
  private calculateStars(accuracy: number): number {
    if (accuracy >= 0.9) return 3;
    if (accuracy >= 0.7) return 2;
    if (accuracy >= 0.5) return 1;
    return 0;
  }

  /**
   * 计算积分
   */
  private calculateScore(level: any, stars: number): number {
    const baseScore = level.rewards?.baseScore || 100;
    const multiplier = stars === 3 ? 2.0 : stars === 2 ? 1.5 : 1.0;
    return Math.round(baseScore * multiplier);
  }

  /**
   * 计算经验值
   */
  private calculateExperience(level: any, stars: number): number {
    const baseExp = level.rewards?.baseExperience || 50;
    const multiplier = stars === 3 ? 2.0 : stars === 2 ? 1.5 : 1.0;
    return Math.round(baseExp * multiplier);
  }

  /**
   * 计算答题时间
   */
  private calculateQuestionTime(): number {
    // 实际实现中应该记录每题的开始时间
    // 这里简化处理,返回一个模拟值
    return Math.round(Math.random() * 10) + 5;
  }

  /**
   * 计算总用时
   */
  private calculateTotalTime(session: QuizSession): number {
    return session.answers.reduce((sum, a) => sum + a.timeUsed, 0);
  }

  /**
   * 更新用户统计
   */
  private async updateUserStatistics(user: any, record: QuizRecord): Promise<void> {
    // 更新统计数据
    user.statistics.totalQuestions += record.totalQuestions;
    user.statistics.correctQuestions += record.correctQuestions;
    user.statistics.wrongQuestions += record.totalQuestions - record.correctQuestions;
    user.statistics.averageAccuracy = user.statistics.correctQuestions / user.statistics.totalQuestions;
    user.statistics.totalTime += record.timeUsed;
    user.statistics.averageTimePerQuestion = user.statistics.totalTime / user.statistics.totalQuestions;
    user.statistics.completedLevels++;
    
    if (record.stars === 3) {
      user.statistics.perfectLevels++;
    }

    // 更新积分和经验
    user.totalScore += record.score;
    user.totalExperience += record.experience;

    // 检查升级
    const expForNextLevel = user.level * 100;
    if (user.totalExperience >= expForNextLevel) {
      user.level++;
      user.totalScore += user.level * 50;  // 升级奖励
    }

    await this.userService.updateUser(user.id, {
      statistics: user.statistics,
      totalScore: user.totalScore,
      totalExperience: user.totalExperience,
      level: user.level
    });
  }

  /**
   * 更新关卡完成状态
   */
  private async updateLevelCompletion(user: any, levelId: string, stars: number): Promise<void> {
    // 添加到已完成关卡
    if (!user.completedLevels.includes(levelId)) {
      user.completedLevels.push(levelId);
    }

    // 更新星级(保留最高星级)
    const currentStars = user.levelStars.get(levelId) || 0;
    if (stars > currentStars) {
      user.levelStars.set(levelId, stars);
    }

    await this.userService.updateUser(user.id, {
      completedLevels: user.completedLevels,
      levelStars: user.levelStars
    });
  }

  /**
   * 添加错题
   */
  private async addWrongQuestions(user: any, session: QuizSession): Promise<void> {
    const wrongAnswers = session.answers.filter(a => !a.isCorrect);
    
    for (const answer of wrongAnswers) {
      if (!user.wrongQuestions.includes(answer.questionId)) {
        user.wrongQuestions.push(answer.questionId);
      }
    }

    if (wrongAnswers.length > 0) {
      await this.userService.updateUser(user.id, { wrongQuestions: user.wrongQuestions });
    }
  }

  /**
   * 生成会话ID
   */
  private generateSessionId(): string {
    return `quiz_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * 生成结果ID
   */
  private generateResultId(): string {
    return `result_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}
代码解析

1. 测验会话创建流程

async createSession(userId: string, levelId: string): Promise<ServiceResponse<QuizSession>> {
  // 1. 获取用户
  // 2. 获取关卡
  // 3. 检查关卡是否解锁
  // 4. 获取题目
  // 5. 创建会话
}

2. 答题处理逻辑

submitAnswer(questionId: string, answer: string | string[]): ServiceResponse<QuestionResult> {
  // 1. 获取题目
  // 2. 判断答案正确性
  // 3. 记录答题结果
  // 4. 更新当前索引
}

3. 星级计算逻辑

private calculateStars(accuracy: number): number {
  if (accuracy >= 0.9) return 3;   // 90%以上:3星
  if (accuracy >= 0.7) return 2;   // 70%-90%:2星
  if (accuracy >= 0.5) return 1;   // 50%-70%:1星
  return 0;                         // 50%以下:0星
}

4. 奖励计算逻辑

private calculateScore(level: any, stars: number): number {
  const baseScore = level.rewards?.baseScore || 100;
  const multiplier = stars === 3 ? 2.0 : stars === 2 ? 1.5 : 1.0;
  return Math.round(baseScore * multiplier);
}

⚠️ 常见问题与解决方案

问题1: 会话状态丢失

现象:
测验会话在页面刷新或应用重启后丢失。

错误代码:

// ❌ 错误:会话只存储在内存中
private currentSession: QuizSession | null = null;

正确代码:

// ✅ 正确:持久化会话状态
async createSession(userId: string, levelId: string): Promise<ServiceResponse<QuizSession>> {
  const session: QuizSession = { /* ... */ };
  
  // 保存到本地存储
  await this.storageService.saveQuizSession(session);
  
  this.currentSession = session;
  return createSuccessResponse<QuizSession>(session);
}

// 初始化时恢复会话
async initialize(): Promise<void> {
  const saved = await this.storageService.getQuizSession();
  if (saved) {
    this.currentSession = saved;
  }
}

规则/建议:

  • 将会话状态持久化到本地存储
  • 在服务初始化时恢复会话
  • 设置会话超时时间

问题2: 答题超时未处理

现象:
用户长时间不答题,超时后没有自动结束测验。

错误代码:

// ❌ 错误:没有超时处理
submitAnswer(questionId: string, answer: string | string[]): ServiceResponse<QuestionResult> {
  // 直接处理答案
}

正确代码:

// ✅ 正确:添加超时处理
createSession(userId: string, levelId: string): Promise<ServiceResponse<QuizSession>> {
  const session: QuizSession = {
    // ...
    startTime: new Date().toISOString(),
    timeLimit: level.timeLimit * 1000  // 毫秒
  };

  // 设置超时定时器
  setTimeout(() => {
    this.handleTimeout(session.id);
  }, session.timeLimit);

  return createSuccessResponse<QuizSession>(session);
}

private handleTimeout(sessionId: string): void {
  if (this.currentSession?.id === sessionId) {
    // 自动提交未完成的题目
    this.autoSubmitRemaining();
    this.finishQuiz();
  }
}

规则/建议:

  • 设置超时定时器
  • 超时后自动提交剩余题目
  • 通知用户超时情况

问题3: 答案比较逻辑错误

现象:
多选题答案比较时顺序不同被判定为错误。

错误代码:

// ❌ 错误:直接比较数组
private checkAnswer(question: Question, answer: string | string[]): boolean {
  return JSON.stringify(question.answer) === JSON.stringify(answer);
}

正确代码:

// ✅ 正确:排序后比较
private checkAnswer(question: Question, answer: string | string[]): boolean {
  const correctAnswer = question.answer;
  
  if (Array.isArray(correctAnswer)) {
    if (!Array.isArray(answer)) {
      return false;
    }
    const sortedCorrect = [...correctAnswer].sort();
    const sortedAnswer = [...answer].sort();
    return JSON.stringify(sortedCorrect) === JSON.stringify(sortedAnswer);
  } else {
    return String(answer) === String(correctAnswer);
  }
}

规则/建议:

  • 多选题答案排序后比较
  • 统一答案格式(字符串化)
  • 处理类型不一致情况

问题4: 工具使用未扣除次数

现象:
使用工具后用户的工具次数没有减少。

错误代码:

// ❌ 错误:没有更新用户工具次数
useTool(toolType: string): ServiceResponse<boolean> {
  this.currentSession.toolsUsed.hint++;
  return createSuccessResponse<boolean>(true);
}

正确代码:

// ✅ 正确:更新用户工具次数
useTool(toolType: string): ServiceResponse<boolean> {
  const user = this.userService.getCurrentUser().data;
  
  switch (toolType) {
    case 'hint':
      if (user.tools.hintCount <= 0) {
        return createErrorResponse<boolean>(ServiceError.INVALID_PARAMS, '提示次数不足');
      }
      user.tools.hintCount--;  // 扣除次数
      this.currentSession.toolsUsed.hint++;
      break;
  }
  
  // 保存更新
  this.userService.updateUser(user.id, { tools: user.tools });
  
  return createSuccessResponse<boolean>(true);
}

规则/建议:

  • 使用工具前检查剩余次数
  • 使用后扣除次数
  • 保存用户数据更新

问题5: 错题重复添加

现象:
同一道错题被多次添加到错题本。

错误代码:

// ❌ 错误:没有检查是否已存在
addWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>> {
  user.wrongQuestions.push(questionId);
}

正确代码:

// ✅ 正确:检查后添加
addWrongQuestion(userId: string, questionId: string): Promise<ServiceResponse<boolean>> {
  if (!user.wrongQuestions.includes(questionId)) {
    user.wrongQuestions.push(questionId);
  }
}

规则/建议:

  • 添加前检查是否已存在
  • 使用 Set 数据结构或 includes() 检查
  • 避免重复添加

📝 本章小结

核心知识点

本文详细讲解了 QuizService 测验服务的实现,主要包括:

1. 测验会话管理

  • 创建和获取会话
  • 会话状态维护

2. 答题流程

  • 答案提交和正确性判断
  • 工具使用(提示、跳过、刷新、双倍积分)

3. 结果计算

  • 正确率和星级计算
  • 积分和经验奖励

4. 统计更新

  • 用户统计数据更新
  • 关卡完成状态更新
  • 错题本管理

最佳实践总结

会话管理

// 持久化会话
await this.storageService.saveQuizSession(session);

// 恢复会话
const saved = await this.storageService.getQuizSession();

答案比较

// 多选题排序后比较
const sortedCorrect = [...correctAnswer].sort();
const sortedAnswer = [...answer].sort();
return JSON.stringify(sortedCorrect) === JSON.stringify(sortedAnswer);

星级计算

if (accuracy >= 0.9) return 3;
if (accuracy >= 0.7) return 2;
if (accuracy >= 0.5) return 1;
return 0;

工具使用

// 检查次数并扣除
if (user.tools.hintCount <= 0) {
  return createErrorResponse(ServiceError.INVALID_PARAMS, '提示次数不足');
}
user.tools.hintCount--;

下一步预告

在下一篇文章中,我们将:

  • 🎨 讲解 LevelService 关卡服务实现详解
  • 📚 介绍关卡解锁、进度管理和奖励发放
  • 🏷️ 探索关卡数据的增删改查操作

🔗 相关链接


💡 提示: 建议结合项目源码阅读,动手实践效果更好!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐