Skip to content

07_作业(Assignments)API 开发说明

本说明用于实现老师布置作业、学生提交与老师批改的后端接口,补充字段:作业名称(name)、作业描述(description)、作业指引(guidance)、作业状态(status);学生提交新增作品名称(work_name)与作品描述(work_description)、批改状态(grading_status)、发布状态(is_public);老师批改新增批改意见(feedback_text)、AI 辅助批改建议(ai_suggestions,可采纳标记 ai_adopted)与评分(score)。支持 AI 交互溯源(ai_session_id)与附件(attachments)。


目标与范围

  • 老师:创建/管理班级作业;查看提交;评分;设置学生作品发布到广场。
  • 学生:查看待做作业;提交与查看评分反馈;查看作品发布状态。
  • 权限:老师需为该班级教师或管理员;学生需为该班级成员;学生仅能访问自己的提交。
  • 广场:公开展示优秀学生作品,支持浏览与点赞。

数据库与模型变更

目标结构:

  • assignments(id, course_id, class_id, step_id?, deadline, created_at, name, description, guidance, status)
  • assignment_submissions(id, assignment_id, user_id, answer_text, score, submitted_at, work_name, work_description, feedback_text, ai_suggestions, ai_adopted, graded_at, graded_by, ai_session_id, attachments, grading_status, is_public)

需要新增/变更:

  1. assignments 增加字段:

    • name VARCHAR(128) NOT NULL
    • description TEXT NULL
    • guidance TEXT NULL
    • status assignment_status NOT NULL DEFAULT 'draft' -- 作业状态
    • step_id INT NULL → 外键 chapter_steps.id(可选绑定某一步骤;用于区分“作业位于 step1/step4 等”)
      • 索引:ix_assignments_step_id
      • 级联:ON DELETE SET NULL(删除步骤不影响历史作业,仅置空绑定)
    • 且移除 task_id 绑定(作业不再依赖课程内 task 实例)
  2. assignment_submissions 增加字段、唯一约束与索引:

    • 字段:
      • work_name VARCHAR(128) NULL -- 学生作品名称
      • work_description TEXT NULL -- 学生作品描述
      • feedback_text TEXT NULL -- 老师批改意见
      • ai_suggestions JSON NULL -- AI 批改建议(结构化 JSON)
      • ai_adopted BOOLEAN NOT NULL DEFAULT FALSE -- 老师是否采纳 AI 建议
      • graded_at timestamptz NULL -- 批改时间
      • graded_by bigint NULL REFERENCES users(id) -- 批改老师
      • grading_status grading_status NOT NULL DEFAULT 'not_submitted' -- 批改状态
      • ai_session_id bigint NULL REFERENCES ai_sessions(id) -- 关联 AI 生成会话
      • attachments JSON NULL -- 附件清单(数组)
      • is_public BOOLEAN NOT NULL DEFAULT FALSE -- 是否发布到广场
    • UNIQUE(assignment_id, user_id)(避免同一学生重复多条提交;后续用"覆盖提交"更新该条)
    • 索引:idx_assignment_submissions_assignment_ididx_assignment_submissions_user_id

Alembic 迁移要点(示意 SQL):

sql
-- 创建枚举类型
CREATE TYPE assignment_status AS ENUM ('draft', 'published', 'in_progress', 'closed', 'archived');
CREATE TYPE grading_status AS ENUM ('not_submitted', 'submitted', 'grading', 'graded');

-- assignments 新增字段
ALTER TABLE assignments
  ADD COLUMN name VARCHAR(128) NOT NULL DEFAULT 'Untitled',
  ADD COLUMN description TEXT NULL,
  ADD COLUMN guidance TEXT NULL,
  ADD COLUMN status assignment_status NOT NULL DEFAULT 'draft',
  ADD COLUMN step_id INT NULL;
CREATE INDEX IF NOT EXISTS ix_assignments_step_id ON assignments(step_id);
ALTER TABLE assignments
  ADD CONSTRAINT fk_assignments_step_id_chapter_steps FOREIGN KEY (step_id)
  REFERENCES chapter_steps(id) ON DELETE SET NULL;

-- assignments 取消 task 绑定
ALTER TABLE assignments
  DROP CONSTRAINT IF EXISTS assignments_task_id_fkey;
ALTER TABLE assignments
  DROP COLUMN IF EXISTS task_id;

-- assignment_submissions 唯一约束与索引
ALTER TABLE assignment_submissions
  ADD COLUMN work_name VARCHAR(128),
  ADD COLUMN work_description TEXT;
-- 批改相关字段
ALTER TABLE assignment_submissions
  ADD COLUMN feedback_text TEXT,
  ADD COLUMN ai_suggestions JSON,
  ADD COLUMN ai_adopted BOOLEAN NOT NULL DEFAULT FALSE,
  ADD COLUMN graded_at timestamptz,
  ADD COLUMN graded_by bigint REFERENCES users(id);
-- AI 与附件
ALTER TABLE assignment_submissions
  ADD COLUMN ai_session_id bigint REFERENCES ai_sessions(id),
  ADD COLUMN attachments JSON,
  ADD COLUMN grading_status grading_status NOT NULL DEFAULT 'not_submitted',
  ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE assignment_submissions
  ADD CONSTRAINT uq_assignment_user UNIQUE (assignment_id, user_id);
CREATE INDEX IF NOT EXISTS idx_assignment_submissions_assignment_id ON assignment_submissions(assignment_id);
CREATE INDEX IF NOT EXISTS idx_assignment_submissions_user_id ON assignment_submissions(user_id);

模型同步(Python,仅说明):

  • app/models/assignment.py 增加字段:name: Mapped[str]description: Mapped[str|None]guidance: Mapped[str|None]status: Mapped[AssignmentStatus],并移除 task_id
  • app/models/assignment_submission.py 增加字段:work_name: Mapped[str|None]work_description: Mapped[str|None]feedback_text: Mapped[str|None]ai_suggestions: Mapped[dict|None]ai_adopted: Mapped[bool](默认 False)、 graded_at: Mapped[datetime|None]graded_by: Mapped[int|None]grading_status: Mapped[GradingStatus]ai_session_id: Mapped[int|None]attachments: Mapped[dict|None]is_public: Mapped[bool]

Schemas(Pydantic)

新建 app/schemas/assignment.py

  • 入参

    • AssignmentCreate
      • course_id: int
      • class_id: int
      • step_id: int | null(可选;若提供将校验该 step 属于同一 course_id
      • name: str(必填,1-128 字)
      • description: str | None
      • guidance: str | None
      • status: AssignmentStatus(默认 'draft')
      • deadline: datetime | None
    • AssignmentUpdate
      • name?: str
      • description?: str | None
      • guidance?: str | None
      • step_id?: int | null(可选;若提供将校验该 step 属于作业所在课程)
      • status?: AssignmentStatus
      • deadline?: datetime | None
    • SubmissionUpsert
      • answer_text: str(若需附件,前置使用已有上传接口获取 URL 后写入文本或后续扩展)
      • work_name: str | None(作品名称,≤128 字)
      • work_description: str | None(作品描述)
      • ai_session_id: int | None
      • attachments: list[object] | None(附件对象数组,字段示例:{ url, type, title?, size?, meta? }
      • grading_status: GradingStatus(默认 'submitted')
    • GradeUpdate
      • score: float(0-100 或业务范围)
      • feedback_text: str | None
      • ai_adopted: bool | None
    • PublicationUpdate
      • is_public: bool -- 是否发布到广场
  • 出参

    • AssignmentOut
      • id, course_id, class_id, step_id?, name, description?, guidance?, status, deadline, created_at
    • SubmissionOut
      • user_id, answer_text, work_name?, work_description?, score?, feedback_text?, ai_suggestions?, ai_adopted?, grading_status, is_public, submitted_at, graded_at?, graded_by?
    • AssignmentProgressOut
      • total_students, submitted_count, graded_count
    • SquareWorkOut
      • id, assignment_name, work_name, work_description, user_name, class_name, attachments, submitted_at, score?

权限与校验

  • 老师/管理员:Depends(get_teacher_or_admin) + 班级权限校验(class_teachers 中是否为该 class_id 教师)。
  • 学生:Depends(get_current_user) + 班级成员校验(class_members 中是否在该 class_id)。
  • 可见性:
    • 学生仅能读取自己班级的 assignments,仅能读取/提交/查看自己的 submission
    • 老师仅能管理自己班级的 assignments 及全班 submissions
    • 广场作品:所有用户可查看 is_public = true 的作品。
  • 截止校验:默认截止后禁止提交;若日后需要"允许迟交",可在 assignments 增加布尔字段控制。

路由设计(FastAPI)

文件:app/api/assignments.py

老师端

  • POST /assignments(201)创建作业(支持可选绑定 step)

    • 入参:AssignmentCreate
    • 权限:老师/管理员 + 班级权限校验
    • 出参:AssignmentOut
    • 说明:若请求体提供 step_id,后端将校验该步骤存在且属于 course_id 对应课程,否则返回 400
  • GET /assignments?status=... 按状态筛选作业列表

    • 权限:老师/管理员
    • 出参:list[AssignmentOut]
  • GET /classes/{class_id}/assignments 列出某班级作业

    • 权限:老师/管理员 + 班级权限校验
    • 出参:list[AssignmentOut]
  • GET /assignments/{assignment_id} 获取作业详情

    • 权限:老师/管理员且具备作业所属班级权限
    • 出参:AssignmentOut
  • PUT /assignments/{assignment_id} 更新作业(名称/描述/指引/截止时间/step 绑定)

    • 入参:AssignmentUpdate
    • 权限:老师/管理员 + 班级权限校验
    • 出参:AssignmentOut
    • 说明:更新时若提供 step_id,同样校验步骤与课程一致;未提供则保持原绑定不变。
  • DELETE /assignments/{assignment_id} 删除作业

    • 权限:老师/管理员 + 班级权限校验
  • GET /assignments/{assignment_id}/submissions 作业提交列表/进度

    • 权限:老师/管理员 + 班级权限校验
    • 出参:list[SubmissionOut] + 可选统计 AssignmentProgressOut
    • 支持按 grading_status 筛选
  • PUT /assignments/{assignment_id}/submissions/{user_id}/score 评分/改分

    • 入参:GradeUpdate(评分、批改意见、是否采纳 AI 建议)
    • 权限:老师/管理员 + 班级权限校验
    • 出参:SubmissionOut
  • PUT /assignments/{assignment_id}/submissions/{user_id}/publication 设置作品发布状态

    • 入参:PublicationUpdate
    • 权限:老师/管理员 + 班级权限校验
    • 出参:SubmissionOut
  • POST /assignments/{assignment_id}/submissions/{user_id}/ai-suggestions 生成 AI 批改建议

    • 入参:无或 { "regenerate": true }
    • 逻辑:基于作业题面(assignments.name/description/guidance)与学生提交(answer_textwork_*)调用 AI 服务生成结构化建议,保存至 ai_suggestions 并返回
    • 权限:老师/管理员 + 班级权限校验
    • 出参:SubmissionOut(含 ai_suggestions

学生端

  • GET /me/assignments?status=... 我的作业列表(pending/submitted/graded/overdue)

    • 权限:登录学生
    • 出参:list[AssignmentOut]
  • GET /assignments/{assignment_id}/submission 我的提交详情

    • 权限:登录学生 + 班级成员
    • 出参:SubmissionOut | null
  • POST /assignments/{assignment_id}/submission 提交/更新作业

    • 入参:SubmissionUpsert
    • 权限:登录学生 + 班级成员 + 截止校验
    • 出参:SubmissionOut

广场端

  • GET /square/works 广场作品列表

    • 权限:所有用户
    • 筛选:is_public = true
    • 支持分页、按课程/班级筛选
    • 出参:list[SquareWorkOut]
  • GET /square/works/{submission_id} 广场作品详情

    • 权限:所有用户
    • 出参:SquareWorkOut

Service 设计

文件:app/services/assignment_service.py

  • create_assignment(payload: AssignmentCreate)
  • get_assignment(assignment_id: int)
  • list_assignments_for_class(class_id: int, page: int, size: int)
  • list_assignments_for_user(user_id: int, status?: str, page: int, size: int)
  • list_assignments_by_status(status?: AssignmentStatus, page: int, size: int)
  • update_assignment(assignment_id: int, payload: AssignmentUpdate)
  • delete_assignment(assignment_id: int)
  • submit_or_update_submission(assignment_id: int, user_id: int, payload: SubmissionUpsert)
  • get_submission_for_user(assignment_id: int, user_id: int)
  • list_submissions(assignment_id: int, grading_status?: GradingStatus)
  • grade_submission(assignment_id: int, user_id: int, score: float, feedback_text?: str, ai_adopted?: bool, grader_id?: int)
  • set_publication_status(assignment_id: int, user_id: int, is_public: bool)
  • list_square_works(course_id?: int, class_id?: int, page: int, size: int)
  • generate_ai_suggestions(assignment_id: int, user_id: int) -> dict(写入并返回 ai_suggestions
  • 辅助:权限校验(老师-班级、学生-班级)、截止校验、统计汇总。

状态枚举说明

AssignmentStatus(作业状态)

  • draft - 草稿:老师创建但未发布,学生不可见
  • published - 已发布:学生可见,可提交
  • in_progress - 进行中:有学生已提交
  • closed - 已截止:deadline 已过,禁止新提交
  • archived - 已归档:老师手动关闭,学生不可见

GradingStatus(批改状态)

  • not_submitted - 未提交:学生尚未提交
  • submitted - 已提交:学生已提交,待批改
  • grading - 批改中:老师正在批改
  • graded - 已批改:完成评分和反馈

状态流转逻辑

  • 作业状态:draftpublishedin_progressclosedarchived
  • 批改状态:not_submittedsubmittedgradinggraded
  • 学生提交时自动设置 grading_status = 'submitted'
  • 老师开始批改时设置 grading_status = 'grading'
  • 完成评分后设置 grading_status = 'graded'

请求/响应样例

创建作业(老师)

json
POST /assignments
{
  "course_id": 1,
  "class_id": 2,
  "name": "第1次作业:线性函数练习",
  "description": "完成课后第3、4、6题,并简要说明思路。",
  "guidance": "注意画出函数图像并标注关键点。",
  "status": "published",
  "deadline": "2025-09-01T23:59:59Z"
}

响应(201):

json
{
  "id": 123,
  "course_id": 1,
  "class_id": 2,
  "name": "第1次作业:线性函数练习",
  "description": "完成课后第3、4、6题,并简要说明思路。",
  "guidance": "注意画出函数图像并标注关键点。",
  "status": "published",
  "deadline": "2025-09-01T23:59:59Z",
  "created_at": "2025-08-23T10:00:00Z"
}

学生提交

json
POST /assignments/123/submission
{
  "answer_text": "我的答案链接与说明:/static/ai/abcd.png ...",
  "work_name": "线性函数作图作品",
  "work_description": "包含关键点标注与思路说明。",
  "grading_status": "submitted"
}

响应:

json
{
  "user_id": 456,
  "answer_text": "我的答案链接与说明:/static/ai/abcd.png ...",
  "work_name": "线性函数作图作品",
  "work_description": "包含关键点标注与思路说明。",
  "grading_status": "submitted",
  "is_public": false,
  "score": null,
  "submitted_at": "2025-08-23T11:00:00Z"
}

老师评分

json
PUT /assignments/123/submissions/456/score
{
  "score": 95.5,
  "feedback_text": "思路清晰,但坐标轴标注需更规范。",
  "ai_adopted": true
}

响应:

json
{
  "user_id": 456,
  "answer_text": "我的答案链接与说明:/static/ai/abcd.png ...",
  "work_name": "线性函数作图作品",
  "work_description": "包含关键点标注与思路说明。",
  "feedback_text": "思路清晰,但坐标轴标注需更规范。",
  "ai_suggestions": {
    "overall": "整体完成较好,可补充函数与图像关系说明。",
    "points": [
      { "title": "坐标轴标注", "advice": "标注 x/y 轴刻度与单位" }
    ]
  },
  "ai_adopted": true,
  "score": 95.5,
  "grading_status": "graded",
  "is_public": false,
  "submitted_at": "2025-08-23T11:00:00Z",
  "graded_at": "2025-08-23T12:00:00Z",
  "graded_by": 321
}

设置作品发布状态(老师)

json
PUT /assignments/123/submissions/456/publication
{
  "is_public": true
}

响应:

json
{
  "user_id": 456,
  "work_name": "线性函数作图作品",
  "work_description": "包含关键点标注与思路说明。",
  "is_public": true,
  "grading_status": "graded"
}

生成 AI 批改建议(老师)

json
POST /assignments/123/submissions/456/ai-suggestions
{}

响应:

json
{
  "user_id": 456,
  "ai_suggestions": {
    "overall": "整体完成较好,可补充函数与图像关系说明。",
    "points": [
      { "title": "坐标轴标注", "advice": "标注 x/y 轴刻度与单位" }
    ]
  }
}

广场作品列表

json
GET /square/works?page=1&size=20

响应:

json
[
  {
    "id": 456,
    "assignment_name": "第1次作业:线性函数练习",
    "work_name": "线性函数作图作品",
    "work_description": "包含关键点标注与思路说明。",
    "user_name": "张三",
    "class_name": "高一(2)班",
    "attachments": ["/static/ai/abcd.png"],
    "submitted_at": "2025-08-23T11:00:00Z",
    "score": 95.5
  }
]

验证规则与错误码

  • 名称:必填,长度 1-128。
  • 作品名称:可选,若提供长度 ≤ 128。
  • 作业状态:必填,枚举值见上。
  • 批改状态:必填,枚举值见上。
  • 发布状态:必填,布尔值,默认 false。
  • 批改意见:可选,建议 ≤ 10,000 字。
  • AI 建议:结构化 JSON;老师可在评分接口通过 ai_adopted 指定是否采纳。
  • 截止时间:允许为空;若非空,学生提交时需 now <= deadline
  • 权限不足:返回 403;未认证:401;不存在资源:404;参数非法:400。

路由注册

  • app/main.py 中挂载 assignments 路由模块(类似 app/api/courses.py 的注册方式)。

开发任务清单与文件变更

  1. Alembic 迁移(新增字段与唯一约束/索引)
    • 新增:alembic/versions/<ts>_add_assignment_status_and_grading_fields.py
    • 包含:枚举类型、状态字段、批改状态字段、发布状态字段
  2. Schemas:
    • 新建:app/schemas/assignment.py
    • 包含:AssignmentStatusGradingStatus 枚举、PublicationUpdateSquareWorkOut
  3. Service:
    • 新建:app/services/assignment_service.py
    • 包含:状态流转逻辑、批改状态管理、发布状态管理、广场作品查询
  4. API:
    • 新建:app/api/assignments.py
    • 包含:状态筛选、批改状态管理接口、发布状态管理接口、广场作品接口
    • app/main.py 注册路由
  5. 测试:
    • 新增:权限、截止校验、重复提交覆盖、评分与统计用例、状态流转测试、发布状态管理测试

测试要点(建议)

  • 老师创建作业(含 name/description/guidance),越权老师创建失败。
  • 作业状态流转:draft → published → in_progress → closed → archived
  • 学生查看"我的作业"仅含所在线班作业;不可见其它班级。
  • 学生在截止前提交成功;截止后提交失败(如策略禁止迟交)。
  • 同一作业重复提交为覆盖(唯一约束),最终仅 1 条记录。
  • 批改状态流转:not_submitted → submitted → grading → graded
  • 老师可查看全班提交列表并评分;学生仅能查看自己的提交与得分。
  • 老师可按批改状态筛选待处理作业。
  • 老师可设置学生作品发布状态,发布后作品在广场可见。
  • 广场作品列表仅显示已发布作品,支持分页和筛选。

兼容性与演进

  • 后续如需支持附件:可为 assignment_submissions 增加 attachments JSONB 字段,元素为已上传文件 URL/类型。
  • 如需"允许迟交"策略:为 assignments 增加布尔字段 allow_late_submit 并在服务层放宽校验。
  • 状态字段支持后续扩展更多状态值,如"延期"、"补交"等。
  • 广场功能可扩展:增加点赞、评论、收藏等社交功能。
  • 发布审核流程:可增加管理员审核环节,老师设置 is_public=true 后需管理员确认。