在大学英语学院的教学管理中,学生选课系统一直是教务工作的核心痛点。传统选课系统往往存在界面老旧、移动端适配差、高峰期系统崩溃等问题。我们团队基于Uniapp+Vue3和Python Flask技术栈,开发了一套轻量高效的微信小程序选课系统,完美解决了这些痛点。
这套系统最显著的特点是"三端协同"架构:
技术选型上我们做了以下关键决策:
关键提示:微信小程序开发必须提前在manifest.json中配置合法域名,否则无法调用后端API。我们曾因此耽误两天审核时间,建议开发初期就完成配置。
学生端功能设计遵循"最小交互路径"原则:
wx.login()获取code,后端用appid+appsecret+code向微信服务器换取openidpython复制# Flask登录API示例
@app.route('/api/login', methods=['POST'])
def login():
code = request.json.get('code')
# 调用微信接口获取openid
wechat_url = f"https://api.weixin.qq.com/sns/jscode2session?appid={APPID}&secret={SECRET}&js_code={code}&grant_type=authorization_code"
resp = requests.get(wechat_url).json()
openid = resp.get('openid')
# 查询或创建用户
student = Student.query.filter_by(wechat_openid=openid).first()
if not student:
student = Student(wechat_openid=openid)
db.session.add(student)
# 生成JWT令牌
token = create_access_token(identity=student.id)
return jsonify({'token': token})
python复制filters = []
if request.args.get('major'):
filters.append(Course.major == request.args['major'])
if request.args.get('teacher'):
filters.append(Course.teacher_id == request.args['teacher'])
courses = Course.query.filter(*filters).paginate(page=1, per_page=20)
python复制@app.route('/api/enroll', methods=['POST'])
@jwt_required()
def enroll():
student_id = get_jwt_identity()
course_id = request.json.get('course_id')
try:
db.session.begin()
# 检查课程余量
course = Course.query.with_for_update().get(course_id)
if course.current >= course.max_capacity:
return jsonify({'error': '课程已满'}), 400
# 创建选课记录
enrollment = Enrollment(student_id=student_id, course_id=course_id)
course.current += 1
db.session.add(enrollment)
db.session.commit()
return jsonify({'success': True})
except:
db.session.rollback()
return jsonify({'error': '选课失败'}), 500
教师端核心在于数据可视化与批量操作:
python复制from openpyxl import Workbook
@app.route('/api/export/<int:course_id>')
@jwt_required()
def export_students(course_id):
wb = Workbook()
ws = wb.active
ws.append(['学号', '姓名', '班级'])
enrollments = Enrollment.query.filter_by(course_id=course_id).join(Student).all()
for e in enrollments:
ws.append([e.student.id, e.student.name, e.student.class_name])
from io import BytesIO
buffer = BytesIO()
wb.save(buffer)
buffer.seek(0)
return send_file(buffer, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
python复制class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True)
permissions = db.Column(db.Integer)
class UserRoles(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
python复制from flask_caching import Cache
cache = Cache(config={'CACHE_TYPE': 'SimpleCache'})
@app.route('/api/config', methods=['POST'])
@admin_required
def update_config():
data = request.json
cache.set('system_config', data)
return jsonify({'success': True})
系统采用经典的"学生-课程-选课"三元模型:
python复制class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
wechat_openid = db.Column(db.String(50), unique=True)
name = db.Column(db.String(20))
enrollments = db.relationship('Enrollment', back_populates='student')
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
teacher_id = db.Column(db.Integer, db.ForeignKey('teacher.id'))
max_capacity = db.Column(db.Integer)
current = db.Column(db.Integer, default=0)
enrollments = db.relationship('Enrollment', back_populates='course')
class Enrollment(db.Model):
student_id = db.Column(db.Integer, db.ForeignKey('student.id'), primary_key=True)
course_id = db.Column(db.Integer, db.ForeignKey('course.id'), primary_key=True)
grade = db.Column(db.Float)
student = db.relationship('Student', back_populates='enrollments')
course = db.relationship('Course', back_populates='enrollments')
API设计遵循以下原则:
/api/<资源>/<id>典型API示例:
code复制GET /api/courses # 课程列表
POST /api/courses # 创建课程
GET /api/courses/1 # 课程详情
PUT /api/courses/1 # 更新课程
DELETE /api/courses/1 # 删除课程
GET /api/courses/1/students # 课程学生列表
微信登录流程有三个关键点:
wx.login()获取临时codejavascript复制// 小程序登录示例
uni.login({
provider: 'weixin',
success: (res) => {
uni.request({
url: 'https://yourdomain.com/api/login',
method: 'POST',
data: { code: res.code },
success: (resp) => {
uni.setStorageSync('token', resp.data.token)
}
})
}
})
由于微信支付需要企业资质,我们采用了两阶段方案:
python复制@app.route('/api/pay', methods=['POST'])
@jwt_required()
def mock_pay():
return jsonify({
'success': True,
'payment_id': str(uuid.uuid4())
})
python复制from wechatpayv3 import WeChatPay
wxpay = WeChatPay(
appid=APPID,
mchid=MCHID,
private_key=PRIVATE_KEY,
cert_serial_no=CERT_SERIAL_NO,
apiv3_key=APIV3_KEY
)
@app.route('/api/pay', methods=['POST'])
@jwt_required()
def create_payment():
order = {
'amount': 100,
'description': '选课押金',
'out_trade_no': str(uuid.uuid4()),
'notify_url': 'https://yourdomain.com/api/pay/notify'
}
result = wxpay.pay(order)
return jsonify(result)
推荐使用Nginx+Gunicorn组合:
bash复制# Gunicorn启动命令
gunicorn -w 4 -b 0.0.0.0:5000 wsgi:app
# Nginx配置示例
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
}
}
我们实施了三级缓存策略:
python复制from redis import Redis
r = Redis()
def get_course_capacity(course_id):
key = f"course:{course_id}:capacity"
val = r.get(key)
if not val:
val = Course.query.get(course_id).current
r.setex(key, 60, val) # 60秒过期
return int(val)
python复制from sqlalchemy.pool import QueuePool
engine = create_engine(
'mysql+pymysql://user:pass@localhost/db',
poolclass=QueuePool,
pool_size=20,
max_overflow=10
)
典型错误场景:
事务处理必须注意:
常见原因及解决方案:
基于协同过滤的课程推荐:
python复制from surprise import Dataset, KNNBasic
def train_recommend_model():
# 加载用户-课程评分数据
data = Dataset.load_from_df(ratings_df, reader)
trainset = data.build_full_trainset()
# 使用KNN算法
algo = KNNBasic()
algo.fit(trainset)
return algo
def recommend_courses(user_id, n=5):
algo = load_model() # 加载预训练模型
courses = Course.query.all()
predictions = [algo.predict(user_id, c.id) for c in courses]
return sorted(predictions, key=lambda x: x.est, reverse=True)[:n]
使用Flask-Babel实现i18n:
python复制from flask_babel import Babel, _
babel = Babel(app)
@app.route('/hello')
def hello():
return _('Hello World')
# 翻译文件示例(messages.po)
msgid "Hello World"
msgstr "你好世界"
在Uniapp中通过全局变量管理语言切换:
javascript复制// i18n.js
export default {
en: {
course: 'Course'
},
zh: {
course: '课程'
}
}
// 使用示例
import i18n from './i18n'
const t = i18n[currentLang]
console.log(t.course)
经验之谈:在第二期开发中,我们过度追求新功能导致代码质量下降。建议采用"小步快跑"的迭代策略,每个版本聚焦1-2个核心功能,确保代码可维护性。