在线考试系统概要设计(二)

时间:2025年 04月 12日
作者:小蒋聊技术
邮箱:wei_wei10@163.com
微信:wei_wei10

前一次的系统设计,客户认为有些太过于简单了。这样的系统应该没有什么商业价值。客户希望要的是一个真实的考试系统设计,从学生作答到评分的整个闭环流程

小蒋接下来会从系统设计思路、评分核心流程、扩展能力三个层面进行完整分析,确保设计即稳健又具备未来成长性

前一次的设计:在线考试系统概要设计-CSDN博客

原始需求:

需求梳理

  1. 课程结构:
    1. 章节和知识点为树形结构
    2. 学校有多个课程,每个课程包含章节,章节下包含知识点
    3. 题库管理:
      1. 支持多种题型:单选、多选、完型填空、阅读理解(后续还会增加)
      2. 每道题:
        1. 有自己的题干、答案、题型、所属章节和知识点
        2. 结构因题型不同而差异较大
          1. 单选题:选项 + 单个正确答案
          2. 多选题:选项 + 多个正确答案
          3. 阅读理解:多个小题 + 文本材料
          4. 完型填空:段落 + 多空填空
      3. 每道题都可加入题库,供多个试卷复用 
  2. 组卷逻辑:
    1. 支持不同的组卷策略(按章节 / 按知识点)
    2. 每种题型可以设置不同分值(如:单选题每次考试分值可变)
  3. 答题 & 评分逻辑:
    1. 学生提交答题数据,系统按题目类型、策略打分
    2. 多选题允许多种评分规则(全对、部分得分、不允许多选等)
    3. 最终结果为:每道题得分 + 总分汇总

设计思路解析(以“评分流程”为核心)

系统应该围绕一个清晰的流程展开:

出卷 → 答题 → 收集学生答案 → 自动评分 → 每题得分 → 汇总试卷总分

其核心功能是:

  1. 支持多种课程结构(课程 → 章节 → 知识点)
  2. 支持多题型考试(单选、多选、完型填空、阅读理解等)
  3. 题型可扩展,每道题有独立的分数、评分方式
  4. 学生在线考试后,系统自动对每道题进行独立评分
  5. 重点:评分规则可扩展,未来需支持机器学习模型辅助判卷

系统的商业价值应该体现在系统的扩展性和未来能够支持机器学习辅助判卷这样的点上。

问题关键点

说明

每种题型结构不同

不能用一个表/类硬塞所有题目内容,否则设计臃肿

答案结构不同

单选是一个选项,多选是多个,阅读理解是一个段落配多个问题

答案提交时格式多样

每题答题结构也需多样化支持

不同题型评分规则不同

策略必须动态绑定,甚至同题型也可能多种评分方式

题型、评分方式需热插拔

无需改主流程就能新增题型 / 策略

核心解决方案思路

将系统中的 题”答”评分” 全部解耦成可以插拔的结构。

设计“题”

每种题型:

  1. 都实现统一接口 Question
public interface Question {
    String getId();  // 获取题目ID
    String getType();  // 获取题型类型
    String getChapterId();  // 获取该题目所属章节ID
    String getKnowledgePointId();  // 获取该题目所属知识点ID
QuestionBody getBody();  // 获取题目题干内容,返回具体的题干结构体
}

public interface QuestionBody {
    // 可以为题干内容提供统一的方法
}

每种题型会有不同的结构,包含不同的题干和答案。

单选题(SingleChoiceQuestion)

class Option {
    String id;  // 选项ID
    String content;  // 选项内容
}

class SingleChoiceBody  implements QuestionBody {
    String title;  // 题目标题
    List<Option> options;  // 选项列表
    String correctOptionId;  // 正确选项ID
}

class SingleChoiceQuestion implements Question {
    String id;
    SingleChoiceBody body;
    String type = "SINGLE_CHOICE";  // 题型类型
}

 示例JSON

{
  "title": "中国的首都是?",
  "options": [
    {"id": "A", "content": "上海"},
    {"id": "B", "content": "北京"},
    {"id": "C", "content": "广州"}
  ],
  "correctOptionId": "B"
}

多选题(MultiChoiceQuestion)

class MultiChoiceBody implements QuestionBody {
    String title;
    List<Option> options;
    List<String> correctOptionIds;
}

class MultiChoiceQuestion implements Question {
    String id;
    MultiChoiceBody body;
    String type = "MULTI_CHOICE";
}

示例JSON 

{
  "title": "以下哪些城市是一线城市?",
  "options": [
    {"id": "A", "content": "北京"},
    {"id": "B", "content": "成都"},
    {"id": "C", "content": "上海"},
    {"id": "D", "content": "深圳"}
  ],
  "correctOptionIds": ["A", "C", "D"]
}

完型填空题(ClozeQuestion)

class ClozeBlank {
    String blankId;  // 空白ID
    List<Option> options;  // 选项列表
    String correctOptionId;  // 正确答案ID
}

class ClozeBody  implements QuestionBody{
    String paragraphWithBlanks;  // 包含空格的段落
    List<ClozeBlank> blanks;  // 空格列表
}

class ClozeQuestion implements Question {
    String id;
    ClozeBody body;
    String type = "CLOZE";  // 题型类型
}

示例JSON

{
  "paragraphWithBlanks": "I ___ to school. He ___ football.",
  "blanks": [
    {
      "blankId": "B1",
      "options": [{"id": "1", "content": "go"}, {"id": "2", "content": "went"}],
      "correctOptionId": "1"
    },
    {
      "blankId": "B2",
      "options": [{"id": "3", "content": "play"}, {"id": "4", "content": "plays"}],
      "correctOptionId": "4"
    }
  ]
}

阅读理解题(ReadingComprehensionQuestion)

class ReadingComprehensionBody  implements QuestionBody{
    String passage;  // 阅读材料
    List<SingleChoiceBody> subQuestions;  // 小题目(单选题)
}

class ReadingComprehensionQuestion implements Question {
    String id;
    ReadingComprehensionBody body;
    String type = "READING";  // 题型类型
}

示例JSON

{
  "passage": "In 2008, a scientist discovered...",
  "subQuestions": [
    {
      "title": "What year was the discovery?",
      "options": [
        {"id": "A", "content": "2008"},
        {"id": "B", "content": "2018"}
      ],
      "correctOptionId": "A"
    }
  ]
}

UML

设计“答”

在考试系统中,每个题型都需要对应一个答案实体类。为了保证系统的扩展性和灵活性,所有题型的答案类都应该实现统一的接口 Answer,每个答案实体类负责根据不同的题型解析答案数据,校验答案的有效性,并提供必要的答案数据。

设计“评分”

在考试系统中,评分是非常关键的部分。每个题型可能有不同的评分规则,因此我们需要根据题型动态选择适当的评分策略。为了保证系统的可扩展性,评分机制应该是解耦和可插拔的。每个题型的评分规则应通过策略模式进行管理,以便于支持不同的评分规则,并能够灵活地扩展。

集成评分到答题逻辑

接下来将会把之前设计的评分部分集成到答题逻辑中。会使用策略模式来实现评分策略的灵活选择,并通过工厂和注册机制来解耦评分逻辑与主流程。这样,当有新的评分策略或新的题型加入时,不需要修改主业务逻辑,可以通过动态配置的方式灵活扩展。

在我们集成评分的过程中,答题逻辑的流程包括以下几个步骤:

  1. 学生提交答案:学生通过前端提交答案,包括试卷 ID 和各个题目的答案。
  2. ExamService 处理提交:ExamService 接收并根据试卷 ID 获取所有题目。
  3. AnswerHandlerFactory 获取答题处理器:根据每个问题的题型,ExamService 查询对应的 AnswerHandler 来解析学生提交的答案。
  4. 答案解析与校验:AnswerHandler 解析学生的原始答案并校验答案的有效性。
  5. 选择评分策略:ExamService 调用适当的 ScoringStrategy 评分策略来计算得分。
  6. 返回评分结果:每道题的分数计算完后,ExamService 汇总得分并返回最终结果。

集成所有答题解析器和评分策略

实现完整的答题与评分流程,并集成所有答题解析器和评分策略

  1. 答题解析器:每个题型对应一个解析器(AnswerHandler),负责解析学生提交的答案。
  2. 评分策略:每个题型对应一个评分策略(ScoringStrategy),负责根据答案和题目计算得分。
  3. 工厂与注册机制:我们使用工厂模式来根据题型动态加载对应的解析器,同时使用注册机制来管理评分策略和解析器。
  4. 核心服务(ExamService:协调答题解析、评分策略的执行,计算最终分数。

  1. 使用工厂模式管理 AnswerHandler 和 ScoringStrategy,可以方便地扩展新的题型和评分策略。
  2. 解耦了答题解析、评分策略和题目,使得系统更具扩展性。
  3. 通过 Spring Boot 自动配置和注入,实现了题型的热插拔,支持动态加载不同的答题解析器和评分策略。

AnswerHandlerFactory 工厂类

@Component
public class AnswerHandlerFactory {

    private final Map<String, AnswerHandler> handlerMap;

    @Autowired
    public AnswerHandlerFactory(Map<String, AnswerHandler> handlerMap) {
        this.handlerMap = handlerMap;
    }

    public AnswerHandler getAnswerHandler(String questionType) {
        return handlerMap.get(questionType);
    }
}

ScoringStrategyFactory 工厂类

@Component
public class ScoringStrategyFactory {

    private final Map<String, ScoringStrategy> strategyMap;

    @Autowired
    public ScoringStrategyFactory(List<ScoringStrategy> scoringStrategies) {
        this.strategyMap = scoringStrategies.stream()
            .collect(Collectors.toMap(strategy -> strategy.getType(), strategy -> strategy));
}

    public ScoringStrategy getScoringStrategy(String questionType) {
        return strategyMap.get(questionType);
    }
}

自动配置与注册

@Configuration
public class AnswerHandlerAutoConfiguration {

    @Autowired
    private List<AnswerHandler> answerHandlers;

    @PostConstruct
    public void registerAnswerHandlers() {
        for (AnswerHandler handler : answerHandlers) {
            AnswerHandlerRegistry.register(handler);
        }
    }
}

public class AnswerHandlerRegistry {

    private static final Map<String, AnswerHandler> handlerMap = new HashMap<>();

    public static void register(AnswerHandler handler) {
        handlerMap.put(handler.getType(), handler);
    }

    public static AnswerHandler getHandler(String questionType) {
        return handlerMap.get(questionType);
    }
}

如果你有任何问题或者想法,欢迎在评论区讨论,我们一起聊聊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蒋聊技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值