高校毕设选题的“效率之痛”与技术破局

每到毕业季,高校教务老师和学生都要面临一场“大战”——毕设选题。传统的线下或简单线上流程,往往伴随着服务器卡顿、页面白屏、心仪课题秒没、匹配结果不尽人意等一系列问题。这背后,是几个典型的效率瓶颈在作祟。

首先,高并发下的数据竞争是首要难题。成百上千的学生在同一时间点提交志愿,对数据库的同一行记录(如导师的已选名额)进行“读-改-写”操作,极易引发超选、数据错乱。其次,系统冷启动与偏好表达不足。新生系统缺乏历史数据,难以进行有效推荐;同时,简单的志愿列表无法充分表达学生对课题方向、导师风格、自身能力的复杂偏好。最后,业务规则复杂且多变。比如“每位导师限带5人”、“学生只能选本专业课题”、“某课题要求先修某课程”,这些规则硬编码在业务逻辑里,维护和调整成本极高。

一位开发者正在查看系统架构图

面对这些问题,一个高效的自动化选题系统,其核心价值就在于:将混乱、低效的人工或半人工流程,转变为稳定、公平、智能的自动化服务,从而释放人力,提升整体体验

技术选型:规则、算法还是混合?

在设计之初,我们面临几个技术路径的选择:

  1. 纯业务规则引擎驱动:将所有的匹配逻辑(资格、限额、冲突)用规则引擎(如Drools)描述。优点是规则与业务代码解耦,变更灵活,执行效率高。缺点是无法处理“推荐”和“冷启动”问题,结果可能合规但非最优。
  2. 纯推荐算法驱动:使用协同过滤、内容推荐等算法,根据学生与课题/导师的“相似度”进行匹配。优点是能挖掘潜在偏好,提升满意度。缺点是难以严格保证复杂的业务规则约束,且冷启动问题需要额外处理。
  3. 混合方案(规则引擎 + 轻量推荐):这是我们最终选择的道路。用规则引擎充当“硬过滤器”和“裁判”,确保所有匹配结果绝对符合业务规则;用轻量级推荐算法充当“软排序器”和“启动器”,在规则划定的安全范围内,优化匹配质量,并解决冷启动。这种架构兼顾了合规性与智能性,也便于分模块优化。

核心实现:规则约束与智能排序的协同

我们的系统架构分为三层:接入层、核心决策层、数据层。核心决策层由“规则执行引擎”和“推荐排序器”串联工作。

1. 基于Drools实现复杂选题约束

我们选用Drools作为规则引擎。首先,将业务规则抽象为事实(Facts)和规则(Rules)。

关键事实对象设计:

// 学生申请事实
public class StudentApplication {
    private String studentId;
    private String major;
    private List<String> completedCourses;
    private String preferredTopicId; // 第一志愿课题ID
    // ... 其他属性及getter/setter
}

// 课题事实
public class ThesisTopic {
    private String topicId;
    private String supervisorId;
    private String requiredMajor;
    private List<String> prerequisiteCourses;
    private int capacity; // 容量
    private int selectedCount; // 已选数量
    // ... 其他属性及getter/setter
}

// 匹配结果事实
public class AssignmentResult {
    private String studentId;
    private String topicId;
    private boolean approved; // 是否匹配成功
    private String rejectionReason; // 拒绝原因
    // ... 其他属性及getter/setter
}

核心规则定义示例(DRL文件):

// 规则1: 专业匹配规则
rule "Major Matching Rule"
    when
        $application: StudentApplication(major != null)
        $topic: ThesisTopic(requiredMajor != null, requiredMajor != $application.major)
        $result: AssignmentResult(studentId == $application.studentId, topicId == $topic.topicId)
    then
        $result.setApproved(false);
        $result.setRejectionReason("专业不符");
        update($result);
end

// 规则2: 导师容量规则
rule "Supervisor Capacity Rule"
    when
        $topic: ThesisTopic(selectedCount >= capacity)
        $application: StudentApplication()
        $result: AssignmentResult(studentId == $application.studentId, topicId == $topic.topicId)
    then
        $result.setApproved(false);
        $result.setRejectionReason("导师名额已满");
        update($result);
end

// 规则3: 先修课程规则
rule "Prerequisite Course Rule"
    when
        $application: StudentApplication()
        $topic: ThesisTopic($prereqs: prerequisiteCourses)
        // 检查学生已修课程是否包含所有先修课
        not ( CourseFact(courseId in $prereqs) from $application.completedCourses )
        $result: AssignmentResult(studentId == $application.studentId, topicId == $topic.topicId)
    then
        $result.setApproved(false);
        $result.setRejectionReason("未满足先修课程要求");
        update($result);
end

在服务中,我们将学生申请和所有相关课题事实插入到Drools会话中,引擎会自动运行所有规则,输出每个申请-课题对的AssignmentResult。只有approvedtrue的结果才会进入下一阶段。

2. 轻量协同过滤处理冷启动与排序

对于通过规则筛选的课题列表,我们需要对其进行个性化排序。这里采用基于物品的协同过滤(Item-CF)

思路:将“学生选择课题”视为“用户-物品”交互。对于新生系统(冷启动),我们引入“课题-标签”矩阵作为补充。计算课题之间的相似度时,既考虑历史共现数据,也考虑标签相似度。

Python实现核心计算片段:

import numpy as np
from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity

class HybridItemCFRecommender:
    def __init__(self, history_matrix, tag_matrix, alpha=0.7):
        """
        :param history_matrix: 历史选择矩阵,形状为 [学生数, 课题数]
        :param tag_matrix: 课题-标签矩阵,形状为 [课题数, 标签数]
        :param alpha: 历史相似度权重,标签相似度权重为 (1-alpha)
        """
        self.history_sim = self._calc_history_similarity(history_matrix)
        self.tag_sim = cosine_similarity(tag_matrix)
        self.alpha = alpha
        self.final_sim = alpha * self.history_sim + (1 - alpha) * self.tag_sim

    def _calc_history_similarity(self, matrix):
        # 计算余弦相似度,并处理冷门物品(课题)
        norm_matrix = matrix / (np.sqrt(np.sum(matrix**2, axis=0)) + 1e-8) # 避免除零
        sim = norm_matrix.T @ norm_matrix
        # 可以对相似度进行平滑或降权处理,这里省略
        return sim

    def recommend(self, student_vector, candidate_topic_ids, top_k=10):
        """
        :param student_vector: 该学生的历史兴趣向量(或当前志愿的one-hot)
        :param candidate_topic_ids: 通过规则筛选后的候选课题ID列表
        :param top_k: 返回推荐数量
        """
        # 计算学生对每个候选课题的预测兴趣分
        scores = student_vector @ self.final_sim[:, candidate_topic_ids]
        # 获取top_k的索引
        top_indices = np.argsort(scores)[-top_k:][::-1]
        recommended_ids = [candidate_topic_ids[i] for i in top_indices]
        return recommended_ids

# 使用示例
# 假设已有历史矩阵和标签矩阵
recommender = HybridItemCFRecommender(history_matrix, tag_matrix, alpha=0.6)
# 对于新生,student_vector可以是其填写的志愿课题的one-hot编码,或者是其专业对应的标签向量
student_pref_vector = np.zeros(n_topics)
student_pref_vector[preferred_topic_id] = 1
final_recommendations = recommender.recommend(
    student_pref_vector,
    filtered_candidate_ids, # 来自规则引擎的结果
    top_k=5
)

最终,系统将规则引擎通过的课题,再按推荐分数排序后呈现给学生或进行自动分配。

性能与安全:支撑千人并发

性能测试

在模拟1000名学生并发提交的场景下(4核8G服务器),我们对核心匹配接口进行压测。

  • QPS(每秒查询率):纯规则匹配部分,QPS可达 1200+。加入推荐排序后,因涉及矩阵计算,QPS下降至约 300,但仍远高于实际需求峰值(通常选题集中在1-2小时内)。
  • 平均响应延迟:规则匹配平均在 50ms 内完成,完整流程(规则+推荐)平均在 200ms 内。
  • 优化点
    • 规则结果缓存:对于同一批课题和规则,不同学生的申请中,规则匹配结果大部分相同。可缓存“课题-规则”的中间状态。
    • 推荐模型预计算:课题相似度矩阵final_sim可以离线计算,每天更新一次,API服务直接加载,将在线计算复杂度降至 O(n)。

安全性考量

  • 幂等性保障:每个学生提交请求携带唯一令牌(如studentId + sessionId + timestamp),服务端利用Redis setnx 或数据库唯一索引实现幂等,防止重复提交和重复扣减名额。
  • 防刷题与限流:对IP和用户ID进行滑动窗口限流(如每秒1次)。关键资源(导师名额)的扣减使用数据库乐观锁(update ... set selected_count = selected_count + 1 where topic_id = ? and selected_count < capacity)确保原子性。
  • 数据一致性:采用“先校验,后扣减,异步记录”的事务模式。核心扣减操作在数据库事务内完成,匹配成功的日志记录可异步化,提升吞吐。

生产环境避坑指南

  1. 事务边界要清晰:规则引擎的执行本身不包含在数据库事务内。我们的做法是:在内存中通过规则引擎快速筛选出可能成功的申请,然后针对这批申请,在数据库事务中进行最终的名额扣减和结果确认。如果扣减失败(如乐观锁冲突),则返回失败信息,避免状态不一致。
  2. 缓存一致性挑战:如果使用了缓存来存储导师已选人数等动态数据,必须谨慎。我们最终选择了不缓存强一致性要求的数据,而是缓存静态或准静态数据(如课题信息、规则定义)。动态数据直接读库,并通过数据库事务保证正确性,用数据库的性能换取简单和可靠。
  3. 设计完备的回滚机制:在自动分配模式下,如果某个学生的分配因后续校验失败,需要能够释放其占用的名额。我们为每个分配操作生成一个全局唯一的“分配事务ID”,关联该学生占用的所有资源。回滚时,根据此ID进行精准释放。
  4. 规则引擎的版本化管理:业务规则会变。我们对DRL文件进行Git版本控制,并在服务中支持规则的热加载或多版本并存,通过配置决定当前生效的规则版本,便于回滚和A/B测试。

写在最后:架构的泛化思考

回顾这个自动化毕设选题系统,其本质是一个多约束条件下的资源智能匹配与分配系统。这个架构模式具有很强的泛化能力。

你可以思考:

  • 如何将它应用于公司内部会议室与设备的预约系统?规则可能是“部门优先级”、“设备类型匹配”,推荐算法可以学习员工的预约习惯。
  • 如何应用于在线教育中的课程-学生匹配?规则是“年级匹配”、“时间不冲突”,推荐算法可以根据学生的学习进度和兴趣推荐课程。
  • 甚至是在物流调度、计算资源分配等领域,核心模式依然是:用规则引擎定义不可逾越的边界和硬性条件,用优化/推荐算法在边界内寻找更优解

技术服务于业务,而好的架构设计,往往源于对业务核心矛盾(如效率与公平)的深刻理解,以及用恰当的技术手段进行精准拆解与组合。希望这次关于自动化选题系统的实践分享,能为你解决类似的资源分配难题,打开一扇新的思路之门。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐