Appearance
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)
需要新增/变更:
为
assignments增加字段:nameVARCHAR(128) NOT NULLdescriptionTEXT NULLguidanceTEXT NULLstatusassignment_status NOT NULL DEFAULT 'draft' -- 作业状态step_idINT NULL → 外键chapter_steps.id(可选绑定某一步骤;用于区分“作业位于 step1/step4 等”)- 索引:
ix_assignments_step_id - 级联:
ON DELETE SET NULL(删除步骤不影响历史作业,仅置空绑定)
- 索引:
- 且移除
task_id绑定(作业不再依赖课程内 task 实例)
为
assignment_submissions增加字段、唯一约束与索引:- 字段:
work_nameVARCHAR(128) NULL -- 学生作品名称work_descriptionTEXT NULL -- 学生作品描述feedback_textTEXT NULL -- 老师批改意见ai_suggestionsJSON NULL -- AI 批改建议(结构化 JSON)ai_adoptedBOOLEAN NOT NULL DEFAULT FALSE -- 老师是否采纳 AI 建议graded_attimestamptz NULL -- 批改时间graded_bybigint NULL REFERENCES users(id) -- 批改老师grading_statusgrading_status NOT NULL DEFAULT 'not_submitted' -- 批改状态ai_session_idbigint NULL REFERENCES ai_sessions(id) -- 关联 AI 生成会话attachmentsJSON NULL -- 附件清单(数组)is_publicBOOLEAN NOT NULL DEFAULT FALSE -- 是否发布到广场
UNIQUE(assignment_id, user_id)(避免同一学生重复多条提交;后续用"覆盖提交"更新该条)- 索引:
idx_assignment_submissions_assignment_id、idx_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_idapp/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:
入参
AssignmentCreatecourse_id: intclass_id: intstep_id: int | null(可选;若提供将校验该 step 属于同一course_id)name: str(必填,1-128 字)description: str | Noneguidance: str | Nonestatus: AssignmentStatus(默认 'draft')deadline: datetime | None
AssignmentUpdatename?: strdescription?: str | Noneguidance?: str | Nonestep_id?: int | null(可选;若提供将校验该 step 属于作业所在课程)status?: AssignmentStatusdeadline?: datetime | None
SubmissionUpsertanswer_text: str(若需附件,前置使用已有上传接口获取 URL 后写入文本或后续扩展)work_name: str | None(作品名称,≤128 字)work_description: str | None(作品描述)ai_session_id: int | Noneattachments: list[object] | None(附件对象数组,字段示例:{ url, type, title?, size?, meta? })grading_status: GradingStatus(默认 'submitted')
GradeUpdatescore: float(0-100 或业务范围)feedback_text: str | Noneai_adopted: bool | None
PublicationUpdateis_public: bool-- 是否发布到广场
出参
AssignmentOutid, course_id, class_id, step_id?, name, description?, guidance?, status, deadline, created_at
SubmissionOutuser_id, answer_text, work_name?, work_description?, score?, feedback_text?, ai_suggestions?, ai_adopted?, grading_status, is_public, submitted_at, graded_at?, graded_by?
AssignmentProgressOuttotal_students, submitted_count, graded_count
SquareWorkOutid, 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_text、work_*)调用 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- 已批改:完成评分和反馈
状态流转逻辑
- 作业状态:
draft→published→in_progress→closed→archived - 批改状态:
not_submitted→submitted→grading→graded - 学生提交时自动设置
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的注册方式)。
开发任务清单与文件变更
- Alembic 迁移(新增字段与唯一约束/索引)
- 新增:
alembic/versions/<ts>_add_assignment_status_and_grading_fields.py - 包含:枚举类型、状态字段、批改状态字段、发布状态字段
- 新增:
- Schemas:
- 新建:
app/schemas/assignment.py - 包含:
AssignmentStatus、GradingStatus枚举、PublicationUpdate、SquareWorkOut
- 新建:
- Service:
- 新建:
app/services/assignment_service.py - 包含:状态流转逻辑、批改状态管理、发布状态管理、广场作品查询
- 新建:
- API:
- 新建:
app/api/assignments.py - 包含:状态筛选、批改状态管理接口、发布状态管理接口、广场作品接口
- 在
app/main.py注册路由
- 新建:
- 测试:
- 新增:权限、截止校验、重复提交覆盖、评分与统计用例、状态流转测试、发布状态管理测试
测试要点(建议)
- 老师创建作业(含 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后需管理员确认。