1. 项目背景与技术选型思考
这个选课推荐系统的开发背景源于高校教务管理数字化的迫切需求。传统选课系统普遍存在几个痛点:高峰期服务器崩溃、课程推荐匹配度低、界面交互体验差。我们团队在2021年接手某省属高校的系统重构项目时,发现原有ASP.NET系统在3000人并发选课时响应时间超过15秒,且推荐算法仅基于简单的"热门课程"排序。
技术栈选择上,我们采用PHP+JavaScript的全栈方案:
- 后端框架对比了Laravel和ThinkPHP的QPS表现:在8核16G服务器上,Laravel 8的纯API路由吞吐量达到1123req/s,而ThinkPHP 6.0为897req/s。但ThinkPHP的中文文档更完善,最终决定核心业务用Laravel,管理后台用ThinkPHP
- 前端选用Vue 2.6 + Element UI的组合,实测比React方案减少28%的包体积
- 数据库采用MySQL 8.0,利用其JSON字段存储动态课程属性
关键决策点:选课系统的推荐算法要求实时计算,Laravel的队列系统配合Redis的sorted set实现了毫秒级响应,这是选择该框架的决定性因素
2. 系统架构设计与核心模块
2.1 整体架构分层
系统采用经典的三层架构,但针对选课场景做了特殊优化:
code复制前端层
├─ Vue SPA (课程展示/选课操作)
├─ SSR渲染 (SEO优化)
└─ Web Worker (并发请求处理)
业务层
├─ 选课服务 (Laravel)
├─ 推荐服务 (Python微服务)
└─ 管理后台 (ThinkPHP)
数据层
├─ MySQL主从集群
├─ Redis缓存
└─ Elasticsearch (课程搜索)
2.2 高并发选课实现
课程库存管理采用分段锁方案:
- 将热门课程ID哈希分片(如course_101分成course_101_0到course_101_9)
- 使用Redis的WATCH+MULTI实现乐观锁
- 前端采用指数退避重试策略(初始间隔200ms,最大重试5次)
实测在5000并发下,选课成功率从传统方案的63%提升到98.7%。核心代码如下:
php复制// Laravel选课控制器片段
public function selectCourse(Request $request) {
Redis::funnel('course_'.$courseId.'_slot'.($studentId%10))
->limit(1)
->then(function () use ($studentId) {
// 执行选课事务
DB::transaction(function() {
// 验证库存
// 创建选课记录
// 更新学生课表
});
}, function () {
abort(429, '当前选课人数过多');
});
}
2.3 推荐算法实现
混合推荐策略包含三个维度:
- 协同过滤:基于学生历史选课记录
- 内容匹配:分析课程大纲文本相似度
- 热度加权:结合实时选课数据
算法服务用Python实现,通过gRPC与PHP交互。特征工程处理流程:
code复制原始数据 → 标准化处理 →
│
├─ 用户特征 (专业/年级/GPA)
├─ 课程特征 (类型/学分/时间)
└─ 交互特征 (点击/收藏/历史选课)
3. 前端工程化实践
3.1 Vue架构优化
采用模块化设计:
javascript复制// src目录结构
├─ api/ // 按业务划分的API
├─ components/ // 通用组件
├─ views/ // 路由页面
├─ store/ // Vuex模块化
│ ├─ courses.js // 课程状态管理
│ └─ user.js // 用户信息
└─ utils/ // 工具函数
性能优化关键点:
- 路由懒加载:使首屏体积减少40%
- 虚拟滚动:万级课程列表渲染优化
- WebP图片:通过
实现自适应
3.2 选课交互设计
核心交互流程:
- 课程卡片hover时预加载详情
- 选课按钮防抖处理(300ms)
- 实时显示剩余名额(WebSocket推送)
- 冲突课程自动标记(课表可视化)
vue复制<template>
<el-popover
trigger="hover"
@show="loadCourseDetail(courseId)">
<course-detail :data="detailData"/>
<el-button
slot="reference"
:disabled="isConflict"
@click="handleSelect">
{{ remaining }}个名额
</el-button>
</el-popover>
</template>
4. 踩坑与性能调优
4.1 Laravel性能陷阱
N+1查询问题:
- 错误写法导致单个API产生87次SQL查询
- 解决方案:
php复制// 原始写法 $courses = Course::all(); foreach($courses as $course) { $teacher = $course->teacher; // 产生查询 } // 优化后 Course::with(['teacher', 'classroom'])->get();
4.2 Vue内存泄漏
在keep-alive组件中未及时销毁:
javascript复制// 错误案例
created() {
window.addEventListener('resize', this.handleResize)
}
// 正确做法
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
}
4.3 MySQL索引优化
课程查询的复合索引策略:
sql复制-- 原始表结构
CREATE TABLE `courses` (
`id` INT,
`name` VARCHAR(255),
`department` VARCHAR(50),
`credit` TINYINT,
`time` VARCHAR(100)
);
-- 优化后索引
ALTER TABLE `courses`
ADD INDEX `idx_search` (`department`, `credit`, `time`),
ADD FULLTEXT `ft_name` (`name`);
执行计划对比:
- 无索引时:type=ALL,rows=12,345
- 优化后:type=ref,rows=23
5. 安全防护方案
5.1 选课防刷策略
多层防护机制:
- 人机验证:选课前完成reCAPTCHA v3验证
- 频率限制:Laravel的RateLimiter
php复制RateLimiter::for('select-course', function ($job) { return Limit::perMinute(3)->by($job->userIP); }); - 行为分析:记录鼠标移动轨迹特征
5.2 API安全加固
JWT增强措施:
- 双Token机制(access_token + refresh_token)
- 指纹绑定:
Header: X-Device-Fingerprint - 关键操作二次验证
6. 部署与监控体系
6.1 容器化部署
Docker-compose编排方案:
yaml复制version: '3'
services:
app:
image: laravel:8.2
volumes:
- ./:/var/www/html
depends_on:
- redis
- mysql
vue:
image: nginx:1.21
ports:
- "8080:80"
volumes:
- ./dist:/usr/share/nginx/html
6.2 监控指标
Prometheus监控重点:
- 选课API的P99延迟
- MySQL的活跃连接数
- Vue前端性能指标:
- FCP (First Contentful Paint)
- API错误率
7. 项目演进方向
当前系统在以下方面仍需优化:
- 推荐算法:引入图神经网络分析课程关联
- 弹性扩容:基于K8s的HPA自动伸缩
- 离线计算:用ClickHouse重构统计模块
我在实际部署中发现,当Redis集群出现网络分区时,选课事务可能产生不一致。目前的解决方案是通过定时任务比对MySQL和Redis数据,但更理想的方案是采用Redis的Redlock算法
