Files
hsa/Html/apps/views/lessons.py
2025-08-31 17:16:28 +08:00

97 lines
4.2 KiB
Python

# views/lesson.py
from flask import Blueprint, jsonify, request, render_template, session, current_app
from werkzeug.exceptions import BadRequest
from bson import ObjectId
from datetime import datetime
from ..auth.decorators import require_role
from ..services.course_service import load_material,save_materials_markdown_cos
bp = Blueprint('lesson', __name__)
# 返回课时元信息(含三份 markdown 的可访问链接或直接内容)
@bp.route('/api/materials/<material_id>/chapters/<chapter_name>/lessons/<lesson_name>', methods=['GET'])
@require_role(roles="teacher")
def edit_lesson_detail(material_id, chapter_name, lesson_name):
teacher_uuid = session.get("user_uuid")
teacher_id = current_app.extensions["uuid2username"][teacher_uuid]
material = load_material(material_id)
for chapter in material.chapters:
if chapter.chapter_name == chapter_name:
for lesson in chapter.lessons:
if lesson.lesson_name == lesson_name:
return render_template('lesson.html', lesson=lesson.model_dump(), role="teacher", material={**material.model_dump(), "material_id": material_id}, chapter=chapter.model_dump())
return jsonify({"message": "课时不存在"}), 404
# 返回某份 markdown 文件内容(你也可以直接返回 S3/OSS 的直链)
@bp.route('/api/lesson_file/<material_id>/<chapter_name>/<lesson_name>/<filename>', methods=['GET'])
def get_markdown_file(material_id, chapter_name, lesson_name, filename):
# TODO: 真实环境从存储读取
demo_map = {
"lesson.md": "# 主题\n## 阶段一\n### 步骤A\n内容A...\n### 步骤B\n内容B...\n",
"prompt.md": "# 主题\n## 阶段一\n### 步骤A\n提示A...\n### 步骤B\n提示B...\n",
"score_prompt.md": "# 主题\n## 阶段一\n### 步骤A\n评分A...\n### 步骤B\n评分B...\n"
}
return demo_map.get(filename, ""), 200
@bp.route("/api/materials/save/<material_id>/<chapter_name>/<lesson_name>", methods=['POST'])
@require_role(roles='teacher')
def save_materials_markdown(material_id, chapter_name, lesson_name):
payload = request.get_json(silent=True, force=True)
if not payload:
raise BadRequest("缺少 JSON payload")
# 基础校验
required_keys = ("lesson", "prompt", "score")
for k in required_keys:
if k not in payload or not isinstance(payload[k], dict):
raise BadRequest(f"缺少或非法字段: {k}")
if "cdn_link" not in payload[k] or "content" not in payload[k]:
raise BadRequest(f"{k} 需要包含 cdn_link 与 content 字段")
if not isinstance(payload[k]["cdn_link"], str) or not isinstance(payload[k]["content"], str):
raise BadRequest(f"{k}.cdn_link 与 {k}.content 必须为字符串")
# 可选:版本/并发控制(若你前端传了)
version = payload.get("version")
docs = {
"lesson": {
"cdn_link": payload["lesson"]["cdn_link"].strip(),
"content" : payload["lesson"]["content"]
},
"prompt": {
"cdn_link": payload["prompt"]["cdn_link"].strip(),
"content" : payload["prompt"]["content"]
},
"score": {
"cdn_link": payload["score"]["cdn_link"].strip(),
"content" : payload["score"]["content"]
}
}
# 轻量防御:限制单次大小(自行按需调整)
max_size = 4 * 1024 * 1024 # 2MB/份
for name, doc in docs.items():
if len(doc["content"].encode("utf-8")) > max_size:
raise BadRequest(f"{name} 文档过大,超过 {max_size} 字节限制")
# 调用服务层保存:写源站、刷新/回写 CDN、更新索引等
# 建议服务层返回新版本/新链接等元信息
result = save_materials_markdown_cos(
material_id=material_id,
chapter_name=chapter_name,
lesson_name=lesson_name,
docs=docs,
version=version, # 可选,用于并发检测
actor=getattr(request, "user", None) # 若你的鉴权中注入了当前教师
)
# 统一响应
return jsonify({
"ok": True,
"material_id": material_id,
"chapter_name": chapter_name,
"lesson_name": lesson_name,
"result": result # e.g. {"lesson":{"cdn_link": "...", "version":"..."}, ...}
}), 200