按照以下内容一、学生端功能闭环(避免漏操作)
1. 新增“未提交作业查询接口”:学生能查看所有“未截止且未提交”的作业,按截止时间倒序排序(优先显示快截止的),避免漏做。需关联 Assignment 和 Submission 表,筛选“学生未提交”的作业。
2. 提交作业支持“重新提交”:当前重复提交直接拦截,可改为允许覆盖(需提示“重新提交会覆盖原分数”),满足学生修改答案的需求,只需在 submit_assignment 接口中,若存在旧提交则更新 answer 和 score,而非创建新记录。
二、老师端教学辅助(提升管理效率)
1. 作业详情补充“提交率统计”:在老师查看作业列表(view_assignments)时,增加“已提交人数/总学生数”“提交率”字段(如“30/50,60%”),方便老师快速掌握作业完成情况。
2. 新增“人工复评接口”:老师可修改系统评分并添加评语,需在 Submission 表加 teacher_comment 字段,同时新增接口允许老师传入 submission_id、new_score、comment 进行更新。
三、数据与体验细节(降低使用门槛)
1. MusicXML返回优化:当前 convert_and_grade 可能返回 None,需在接口中判断,若为 None 则返回“简谱格式错误,无法生成五线谱”的友好提示,避免前端接收异常数据。
2. 作业内容支持“音频链接存储”:当前 Assignment.content 可存储音频文件路径(如 /audio/melody1.mid),需新增“获取作业音频接口”(如 /assignments/<<int:assignment_id>/audio),返回音频文件流,学生做题时可直接播放(无需手动上传)。
3. 接口返回“格式化时间”:所有涉及时间的字段(如作业截止时间、提交时间),统一返回格式化字符串(如 2025-12-31 23:59:59),避免前端处理 datetime 类型数据出错。
四、部署前安全收尾(避免线上风险)
1. 关闭Debug模式:线上部署时需将 app.run(debug=True) 改为 app.run(debug=False),并配置 app.config['SECRET_KEY'](随机字符串),避免暴露代码和敏感信息。
2. 音频文件权限控制:若后端托管音频文件,需在“获取音频接口”中加Token校验,确保只有已登录用户能访问,避免音频文件被非法下载。修改一下代码,然后给我完整版代码from flask import Flask, request, jsonify, make_response
from flask_sqlalchemy import SQLAlchemy
from music21 import converter, note, stream, pitch
import datetime
from werkzeug.security import generate_password_hash, check_password_hash
import re
from functools import wraps
app = Flask(__name__)
# 需自行修改数据库连接信息
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///music_app.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# 用户模型
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(20), nullable=False) # 'teacher' or 'student'
def set_password(self, password):
# 密码强度校验
if not re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$', password):
raise ValueError("Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one digit.")
self.password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password, password)
# 作业模型
class Assignment(db.Model):
id = db.Column(db.Integer, primary_key=True)
teacher_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
deadline = db.Column(db.DateTime, nullable=False)
answer = db.Column(db.Text, nullable=False)
assignment_type = db.Column(db.String(50), nullable=False) # 新增作业类型字段
# 学生提交作业模型
class Submission(db.Model):
id = db.Column(db.Integer, primary_key=True)
student_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
assignment_id = db.Column(db.Integer, db.ForeignKey('assignment.id'), nullable=False)
answer = db.Column(db.Text, nullable=False)
score = db.Column(db.Float)
# 权限验证装饰器
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
if 'x-access-token' in request.headers:
token = request.headers['x-access-token']
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
user = User.query.filter_by(id=token).first()
if not user:
return jsonify({'message': 'Invalid token!'}), 401
except:
return jsonify({'message': 'Invalid token!'}), 401
return f(user, *args, **kwargs)
return decorated
# 注册接口
@app.route('/register', methods=['POST'])
def register():
data = request.get_json()
username = data.get('username')
password = data.get('password')
role = data.get('role')
if not username or not password or not role:
return jsonify({"message": "Missing required fields"}), 400
if User.query.filter_by(username=username).first():
return jsonify({"message": "Username already exists"}), 400
new_user = User(username=username, role=role)
try:
new_user.set_password(password)
except ValueError as e:
return jsonify({"message": str(e)}), 400
db.session.add(new_user)
db.session.commit()
return jsonify({"message": "User registered successfully"}), 201
# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
return jsonify({"message": "Login successful", "role": user.role, "x-access-token": user.id}), 200
else:
return jsonify({"message": "Invalid username or password"}), 401
# 老师布置作业接口
@app.route('/assignments', methods=['POST'])
@token_required
def create_assignment(user):
if user.role != 'teacher':
return jsonify({"message": "Only teachers can create assignments"}), 403
data = request.get_json()
teacher_id = user.id
title = data.get('title')
content = data.get('content')
deadline_str = data.get('deadline')
answer = data.get('answer')
assignment_type = data.get('assignment_type') # 获取作业类型
if not title or not content or not deadline_str or not answer or not assignment_type:
return jsonify({"message": "Missing required fields"}), 400
try:
deadline = datetime.datetime.strptime(deadline_str, '%Y-%m-%d %H:%M:%S')
except ValueError:
return jsonify({"message": "Invalid deadline format. Use 'YYYY-MM-DD HH:MM:SS'"}), 400
new_assignment = Assignment(teacher_id=teacher_id, title=title, content=content, deadline=deadline, answer=answer, assignment_type=assignment_type)
db.session.add(new_assignment)
db.session.commit()
return jsonify({"message": "Assignment created successfully"}), 201
# 学生提交作业接口
@app.route('/submissions', methods=['POST'])
@token_required
def submit_assignment(user):
if user.role != 'student':
return jsonify({"message": "Only students can submit assignments"}), 403
data = request.get_json()
student_id = user.id
assignment_id = data.get('assignment_id')
answer = data.get('answer')
if not assignment_id or not answer:
return jsonify({"message": "Missing required fields"}), 400
assignment = Assignment.query.get(assignment_id)
if not assignment:
return jsonify({"message": "Assignment not found"}), 404
if datetime.datetime.now() > assignment.deadline:
return jsonify({"message": "Assignment deadline has passed"}), 400
# 重复提交控制
existing_submission = Submission.query.filter_by(student_id=student_id, assignment_id=assignment_id).first()
if existing_submission:
return jsonify({"message": "You have already submitted this assignment"}), 400
# 自动批阅
score = 0
if answer == assignment.answer:
score = 100
new_submission = Submission(student_id=student_id, assignment_id=assignment_id, answer=answer, score=score)
db.session.add(new_submission)
db.session.commit()
musicxml, _ = convert_and_grade(answer, assignment.answer)
return jsonify({"message": "Assignment submitted successfully", "score": score, "musicxml": musicxml}), 201
# 老师查看学生作业情况接口
@app.route('/teachers/<int:teacher_id>/submissions', methods=['GET'])
@token_required
def view_submissions(user, teacher_id):
if user.role != 'teacher' or user.id != teacher_id:
return jsonify({"message": "You are not authorized to view these submissions"}), 403
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
assignments = Assignment.query.filter_by(teacher_id=teacher_id).paginate(page=page, per_page=per_page, error_out=False)
submissions = []
for assignment in assignments.items:
sub_list = Submission.query.filter_by(assignment_id=assignment.id).all()
for sub in sub_list:
student = User.query.get(sub.student_id)
submissions.append({
"assignment_title": assignment.title,
"student_username": student.username,
"answer": sub.answer,
"score": sub.score
})
return jsonify(submissions), 200
# 学生查看自己的作业/提交记录接口
@app.route('/students/<int:student_id>/submissions', methods=['GET'])
@token_required
def view_student_submissions(user, student_id):
if user.role != 'student' or user.id != student_id:
return jsonify({"message": "You are not authorized to view these submissions"}), 403
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
submissions = Submission.query.filter_by(student_id=student_id).paginate(page=page, per_page=per_page, error_out=False)
result = []
for sub in submissions.items:
assignment = Assignment.query.get(sub.assignment_id)
result.append({
"assignment_title": assignment.title,
"answer": sub.answer,
"score": sub.score
})
return jsonify(result), 200
# 简谱转五线谱并批阅(扩展支持高低音和休止符)
def convert_and_grade(jianpu, answer):
try:
s = stream.Stream()
for note_str in jianpu.split():
if note_str == '0': # 处理休止符
n = note.Rest()
else:
# 处理高低音,这里简单示例,实际可能需要更复杂处理
# 假设高音用 '+' 后缀,低音用 '-' 后缀
if note_str.endswith('+'):
n = note.Note(note_str[:-1])
n.octave += 1
elif note_str.endswith('-'):
n = note.Note(note_str[:-1])
n.octave -= 1
else:
n = note.Note(note_str)
s.append(n)
# 简单评分逻辑,可根据需求扩展
correct_count = sum([1 for i, j in zip(jianpu.split(), answer.split()) if i == j])
total_count = len(answer.split())
score = (correct_count / total_count) * 100 if total_count > 0 else 0
return s.write('musicxml'), score
except Exception as e:
return None, 0
# 老师查询作业接口,可按类型分类
@app.route('/teachers/<int:teacher_id>/assignments', methods=['GET'])
@token_required
def view_assignments(user, teacher_id):
if user.role != 'teacher' or user.id != teacher_id:
return jsonify({"message": "You are not authorized to view these assignments"}), 403
assignment_type = request.args.get('assignment_type')
if assignment_type:
assignments = Assignment.query.filter_by(teacher_id=teacher_id, assignment_type=assignment_type).all()
else:
assignments = Assignment.query.filter_by(teacher_id=teacher_id).all()
result = []
for assignment in assignments:
result.append({
"id": assignment.id,
"title": assignment.title,
"content": assignment.content,
"deadline": assignment.deadline.strftime('%Y-%m-%d %H:%M:%S'),
"answer": assignment.answer,
"assignment_type": assignment.assignment_type
})
return jsonify(result), 200
# 成绩统计接口
@app.route('/teachers/<int:teacher_id>/statistics', methods=['GET'])
@token_required
def view_statistics(user, teacher_id):
if user.role != 'teacher' or user.id != teacher_id:
return jsonify({"message": "You are not authorized to view these statistics"}), 403
assignments = Assignment.query.filter_by(teacher_id=teacher_id).all()
total_students = User.query.filter_by(role='student').count()
total_assignments = len(assignments)
total_submissions = 0
average_score = 0
for assignment in assignments:
sub_list = Submission.query.filter_by(assignment_id=assignment.id).all()
total_submissions += len(sub_list)
scores = [sub.score for sub in sub_list]
if scores:
average_score += sum(scores) / len(scores)
if total_assignments > 0:
average_score /= total_assignments
return jsonify({
"total_students": total_students,
"total_assignments": total_assignments,
"total_submissions": total_submissions,
"average_score": average_score
}), 200
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
最新发布