news 2026/4/18 10:56:58

自动化毕设选题系统实战:基于规则引擎与协同过滤的可扩展架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
自动化毕设选题系统实战:基于规则引擎与协同过滤的可扩展架构


自动化毕设选题系统实战:基于规则引擎与协同过滤的可扩展架构

写在前面:去年 5 月,我帮学院把毕设选题从“微信群抢题”搬到线上,两周内用 Python 搭了一套可灰度、可回滚、可压测的自动化选题服务。上线当天 1200 名同学并发提交,峰值 QPS 3 200,零超卖、零重复。今天把踩过的坑和代码全部摊开,供下一届同学抄作业。

也欢迎直接拉到文末,把仓库 clone 下来跑一遍单元测试,再思考“没有历史数据时怎么冷启动推荐”。如果你能给出比“随机推荐”更好的方案,欢迎 PR。


1. 传统流程的四大痛点

  1. 并发竞争:抢题瞬间,几百人同时点“提交”,MySQL 行锁排队,页面 504。
  2. 冷启动:新生第一次选题,没有行为日志,协同过滤全是空矩阵。
  3. 公平性缺失:手速党秒抢热门题,慢网同学只能捡“剩”。
  4. 审计困难:导师临时加名额、教务手动改库,事后无法溯源。

一句话:人工 + 单体 + 无锁 = 高并发下的灾难现场。


2. 技术选型:规则引擎与推荐算法如何拍板

维度Drools自研轻量 DSL协同过滤内容相似度
学习成本高,引入 FACT 模型低,Python 链式判断需要历史矩阵需要题目文本特征
灵活度热更新 drl 文件发版重启即可冷启动无数据可结合 Jieba+TF-IDF
性能规则多≈1 ms1000 条规则 <0.2 ms矩阵稀疏后 <5 ms向量检索 <10 ms
运维需 KIE Server无额外依赖Redis 存矩阵Redis 存向量

结论:

  • 业务规则(导师配额、跨专业限制):用自研 DSL,写到 yml,规则变更随配置中心热更。
  • 推荐排序(学生兴趣):用“轻量协同过滤 + 内容相似度”混合,解决冷启动。
  • 规则与推荐解耦,前者在rule-service,后者在rec-service,通过 gRPC 通信,互不影响扩缩容。

3. 核心实现:Python 搭建选题服务

整体架构图:

3.1 锁定与幂等设计

  1. 学生点击“提交” → 后端生成order_token = hash(student_id + topic_id + nonce)
  2. 先写 Redis 分布式锁:SETNX lock:topic:{topic_id} {order_token} EX 5
  3. 锁成功后,把订单写入 MySQL 唯一索引 `(student_id, topic_id),status=PRE。
  4. 释放锁使用 Lua Script,保证“只有自己能解自己的锁”。

伪代码:

def submit_topic(student_id, topic_id): order_token = uuid4() lock_key = f"lock:topic:{topic_id}" ok = redis.set(lock_key, order_token, nx=True, ex=5) if not ok: raise Conflict("手慢无") try: dao.insert_order(student_id, topic_id, status="PRE") except IntegrityError: # 幂等:重复提交直接返回成功 return {"code": 0, "msg": "已选过"} finally: # 用 Lua 保证原子 lua = """ if redis.call("GET",KEYS[1])==ARGV[1] then return redis.call("DEL",KEYS[1]) else return 0 end """ redis.eval(lua, 1, lock_key, order_token) return {"code": 0, "msg": "选题成功"}

3.2 回滚机制

导师拒收、学生改主意,需要回滚。

  • 订单表加version乐观锁,状态机:PRE → CONFIRM → ROLLBACK。
  • 回滚时把topicremain+1,并删除 Redis 中该学生缓存的推荐列表,保证下次请求重新算。

4. 完整可运行示例(精简版)

项目结构:

auto_topic/ ├── app.py # Flask 入口 ├── service/ │ ├── rule.py # 规则 DSL 解析 │ ├── rec.py # 协同过滤 │ └── order.py # 订单事务 ├── tests/ │ └── test_concurrent.py └── script/ └── jmeter.jmx # 压测脚本

核心入口app.py

from flask import Flask, request, jsonify from service.order import submit_topic from service.rec import get_recall_list from service.rule import check_rule app = Flask(__name__) @app.post("/topic/submit") def topic_submit(): data = request.json student_id = data["student_id"] topic_id = data["topic_id"] # 1. 规则校验 if not check_rule(student_id, topic_id): return jsonify(code=403, msg="不符合选题规则"), 403 # 2. 提交订单 return jsonify(**submit_topic(student_id, topic_id)) @app.get("/topic/recommend") def topic_recommend(): student_id = request.args.get("student_id") return jsonify(data=get_recall_list(student_id)) if __name__ == "__main__": app.run(host="0.0.0.0", port=8000, threaded=True)

service/rec.py冷启动兜底:

import random, redis, json r = redis.Redis() def get_recall_list(student_id): # 优先读协同过滤 cf_key = f"cf:{student_id}" cf_list = r.get(cf_key) if cf_list: return json.loads(cf_list) # 冷启动:读内容相似度 Top10,再随机打散 content_key = f"content:{student_id}" content_list = r.zrevrange(content_key, 0, 9) if content_list: return [int(t) for t in content_list] # 仍为空,随机兜底 all_topics = r.zrevrange("all", 0, -1) return random.sample([int(t) for t in all_topics], k=10)

Clean Code 要点:

  • 函数长度 < 30 行,只做一件事。
  • 统一返回结构{code, msg, data},前端好封装。
  • 所有魔法值(锁超时 5s、随机 10 条)提到settings.py,方便压测调参。

5. 性能与安全

5.1 JMeter 压测结果

硬件:4C8G 容器单实例,后端 + Redis 同机部署。

场景:1200 并发用户,1 秒内起压,每人一次提交。

指标:

  • 平均 RT 63 ms
  • P99 110 ms
  • 错误率 0 %
  • 吞吐量 3 200 req/s(比旧系统 900 req/s 提升 3.5 倍)

5.2 Redis 分布式锁可靠性

  • 锁过期 5 s,业务 SQL 平均 15 ms,远小于过期时间。
  • 采用order_token唯一值 + Lua 脚本,杜绝“误删别人锁”。
  • Redlock 争议:单实例已满足学院量级;若跨机房,可升级redlock-py

5.3 防刷策略

  • 接口网关层(Kong)限流:同一 IP 10 rps,超出直接 429。
  • 业务层兜底:学生维度 1 分钟最多 5 次提交,用 RedisINCR+EXPIRE
  • 验证码:首次进入选题页加载图形验证码,防止脚本批量刷推荐列表。

6. 生产环境避坑指南

  1. 时间窗口配置
    把“选题开始、结束、导师审核”三段时间写进数据库,服务启动即缓存,避免硬编码。教务临时调时间,只需改表,不用发版。

  2. 导师配额超卖
    不要先SELECT remainUPDATE,高并发下必超卖。
    正确姿势:
    UPDATE topic SET remain = remain - 1 WHERE id = ? AND remain > 0
    返回影响行数 =1 才表示扣减成功,否则回滚订单并提示“已满”。

  3. 学生误操作恢复
    提供“一键撤选”按钮,状态机改为 PRE → CANCEL,并remain+1
    记录op_log表,字段(student_id, topic_id, from_status, to_status, op_uid, ctime),方便教务审计。

  4. 灰度发布
    选课系统一年只用一次,但流量集中。上线前用 Nginx 按 Cookie 灰度 10% 流量,观察错误率,再全量。

  5. 监控告警

    • Prometheus 采集order_status_total各状态计数,突增可预警。
    • 锁等待时间用redis_slowlog监控,超过 1 ms 即打印。

7. 留给读者的思考题

没有历史选题数据时,协同过滤就是空矩阵。除了“随机分配”和“按 GPA 排序”,你还能想到哪些零数据冷启动方案?

  • 用问卷采集学生关键词,再用内容相似度召回?
  • 把导师研究方向做 Embedding,与学生简历文本做语义匹配?
  • 或者干脆把第一次选题当做多臂老虎机,用 UCB 策略探索?

动手把代码跑通,再把你想到的冷启动策略提交 PR,让下一届学弟妹不被“抢题”支配。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 8:04:39

ChatGPT与Claude技术对比:如何选择适合你的AI对话模型

AI 对话模型已经从“尝鲜”变成“基建”。打开 GitHub&#xff0c;10 个新项目里 8 个都在调用 LLM&#xff0c;但真到选型阶段&#xff0c;不少开发者还是卡在同一个问题&#xff1a;ChatGPT 还是 Claude&#xff1f;两者都能写代码、写文案、做摘要&#xff0c;可一旦落到生产…

作者头像 李华
网站建设 2026/4/18 9:33:05

PDF补丁丁:零基础小白必备的免费PDF处理效率神器

PDF补丁丁&#xff1a;零基础小白必备的免费PDF处理效率神器 【免费下载链接】PDFPatcher PDF补丁丁——PDF工具箱&#xff0c;可以编辑书签、剪裁旋转页面、解除限制、提取或合并文档&#xff0c;探查文档结构&#xff0c;提取图片、转成图片等等 项目地址: https://gitcode…

作者头像 李华
网站建设 2026/4/17 15:24:57

3大突破!ClickHouse如何重构大数据分析性能

3大突破&#xff01;ClickHouse如何重构大数据分析性能 【免费下载链接】ClickHouse ClickHouse 是一个免费的大数据分析型数据库管理系统。 项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse 在当今数据爆炸的时代&#xff0c;企业面临着海量数据处理的严…

作者头像 李华
网站建设 2026/4/15 12:46:02

3个革命性步骤:Langflow实现企业级RAG应用的技术民主化指南

3个革命性步骤&#xff1a;Langflow实现企业级RAG应用的技术民主化指南 【免费下载链接】langflow ⛓️ Langflow is a visual framework for building multi-agent and RAG applications. Its open-source, Python-powered, fully customizable, model and vector store agnos…

作者头像 李华
网站建设 2026/4/18 8:44:43

5个技巧掌握推理算法助手:从加密识别到智能解密实战指南

5个技巧掌握推理算法助手&#xff1a;从加密识别到智能解密实战指南 【免费下载链接】help_tool 推理算法助手(降维打击) 项目地址: https://gitcode.com/gh_mirrors/he/help_tool 在信息安全领域&#xff0c;加密算法识别和智能解密是必不可少的技能。推理算法助手作为…

作者头像 李华