去年帮母校信息学院开发课表系统时,我深刻体会到大学生对课程管理的三大痛点:课表查询不便(需要反复登录教务系统)、容易错过上课时间(特别是早八课程)、手动录入课程信息繁琐。这个用Python Flask构建的系统,经过三个学期的实际运行检验,日均活跃用户保持在1200+,课程提醒准确率达到99.3%。下面分享从零构建的全过程,包含那些教科书不会告诉你的实战细节。
在技术选型阶段,我们对比了两种主流Python框架:
最终选择Flask的核心考量:
实际开发中发现:Flask的蓝本(Blueprint)功能让模块划分更清晰,比如将用户认证、课表操作、提醒服务拆分为独立模块,这在多人协作时特别有用。
虽然MongoDB等NoSQL适合非结构化数据,但课表系统需要严格的关联查询(如"查询某学生周一所有课程"),最终选用MySQL 5.7(学校IT部门指定的版本)。
课程表(classes)设计经历过两次迭代:
sql复制-- 初版(问题:无法处理单双周课程)
CREATE TABLE classes (
id INT PRIMARY KEY,
name VARCHAR(50),
classroom VARCHAR(20),
weekday TINYINT,
start_time TIME
);
-- 终版(增加week_type和持续周数)
CREATE TABLE classes (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
name VARCHAR(50) NOT NULL,
teacher VARCHAR(20),
classroom VARCHAR(20),
weekday TINYINT CHECK (weekday BETWEEN 1 AND 7),
start_time TIME NOT NULL,
end_time TIME NOT NULL,
week_type TINYINT DEFAULT 0 COMMENT '0全周/1单周/2双周',
start_week TINYINT NOT NULL COMMENT '起始周',
end_week TINYINT NOT NULL COMMENT '结束周',
FOREIGN KEY (user_id) REFERENCES users(id)
);
通过EXPLAIN分析发现,课表查询90%集中在user_id+weekday组合条件,最终建立复合索引:
sql复制CREATE INDEX idx_user_weekday ON classes(user_id, weekday);
使用openpyxl处理教务导出的Excel:
python复制from openpyxl import load_workbook
def import_from_excel(user_id, file_path):
wb = load_workbook(filename=file_path)
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
# 解析每行数据并验证
if not validate_class_time(row[3], row[4]):
raise ValueError(f"无效的时间格式: {row[3]}-{row[4]}")
Class.create(
user_id=user_id,
name=row[0],
teacher=row[1],
classroom=row[2],
# 其他字段...
)
通过requests模拟登录抓取课表:
python复制def fetch_jwxt_schedule(student_id, password):
session = requests.Session()
# 处理验证码(需PIL+tesseract OCR)
captcha = solve_captcha(session.get(CAPTCHA_URL).content)
# 模拟登录
session.post(LOGIN_URL, data={
'stu_id': student_id,
'password': password,
'captcha': captcha
})
# 解析课表页面
return parse_schedule_page(session.get(SCHEDULE_URL).text)
重要提示:此方法可能违反学校规定,我们最终仅用于技术验证,正式版本改用官方API对接。
核心问题:如何正确处理单双周课程和节假日?
python复制def should_remind_today(class_obj):
current_week = get_school_week() # 获取当前教学周
current_weekday = datetime.now().weekday() + 1
# 基础条件检查
if not (class_obj.weekday == current_weekday and
class_obj.start_week <= current_week <= class_obj.end_week):
return False
# 单双周处理
if class_obj.week_type == 1 and current_week % 2 != 1: # 单周
return False
if class_obj.week_type == 2 and current_week % 2 != 0: # 双周
return False
# 节假日检查(需提前导入校历)
if is_holiday(datetime.now().date()):
return False
return True
小程序端需提前获取订阅授权:
javascript复制// 小程序端
wx.requestSubscribeMessage({
tmplIds: ['REMIND_TEMPLATE_ID'],
success(res) {
console.log('订阅成功', res)
}
})
服务端推送实现:
python复制def send_wechat_reminder(openid, class_info):
access_token = get_wechat_token()
url = f"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}"
payload = {
"touser": openid,
"template_id": "REMIND_TEMPLATE_ID",
"data": {
"thing1": {"value": class_info['name']},
"time2": {"value": class_info['start_time']},
"thing3": {"value": class_info['classroom']}
}
}
requests.post(url, json=payload)
现象:部分用户反映收到凌晨3点的课程提醒
根本原因:服务器默认UTC时间,未做时区转换
解决方案:
python复制# 在Flask应用初始化时设置
app.config['JSON_AS_ASCII'] = False
app.config['TIMEZONE'] = 'Asia/Shanghai'
考试周前夕的集中提醒导致CPU飙升,通过三项改进:
改进后架构:
code复制用户请求 → Flask路由 → Celery任务队列 → Redis缓存 → MySQL持久化
核心SQL查询:
sql复制SELECT classroom
FROM classrooms
WHERE id NOT IN (
SELECT DISTINCT classroom
FROM classes
WHERE weekday = ?
AND NOT (end_time <= ? OR start_time >= ?)
)
python复制def check_schedule_conflict(user_id, new_class):
existing_classes = Class.query.filter_by(user_id=user_id, weekday=new_class.weekday).all()
for cls in existing_classes:
if (cls.start_week <= new_class.end_week and
cls.end_week >= new_class.start_week and
not (cls.end_time <= new_class.start_time or
cls.start_time >= new_class.end_time)):
return True
return False
nginx复制server {
listen 80;
server_name schedule.example.com;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:5000;
uwsgi_read_timeout 300;
}
# 静态文件缓存
location /static {
alias /var/www/schedule/static;
expires 30d;
}
}
python复制from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler(timezone='Asia/Shanghai')
scheduler.add_job(check_reminders, 'interval', minutes=5)
scheduler.start()
# 重要!防止Flask退出时调度器被关闭
atexit.register(lambda: scheduler.shutdown())
开发过程中最让我意外的是,最初认为简单的"周次计算"功能,实际需要处理教学周起始日、节假日调课、军训周等十余种特殊情况。建议同类项目开发时,务必提前与教务部门确认校历安排规则。系统上线后,通过添加"一键反馈课表错误"功能,我们收集到237条有效改进建议,这才是项目持续优化的真正宝藏。