Open edX Enhanced Staff Grader 模拟 BFF 指南:基于 JSON 数据存储的 Mock API 详解
2026/9/17 3:16:17 网站建设 项目流程

Open edX Enhanced Staff Grader 模拟 BFF 指南:基于 JSON 数据存储的 Mock API 详解

【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform

本文围绕 Open edXopenedx-platform仓库中 lms/djangoapps/ora_staff_grader/mock/README.md 展开,完整讲解 ESG(Enhanced Staff Grader,增强型工作人员评分器)模拟后端 BFF(Backend-for-Frontend)的设计思路、JSON 数据存储结构、全部 Mock 端点、交互式数据修改机制以及基于 Postman 的无头(headless)测试流程。读完本文,你将能够独立启动并调用{lms-url}/api/ora_staff_grader/mock/{endpoint}系列端点,读懂并编辑mock/data/下的四个 JSON 数据文件,实现真实/模拟 BFF 的无缝切换,并利用源码与测试用例验证端点的实际行为。

一、什么是 ESG 与 Mock BFF

ESG(Enhanced Staff Grader)是 Open edX 中构建在 ORA(Open Response Assessment,开放问答评估)之上的一个应用,用于简化工作人员对作业的人工评分流程。其 BFF 层负责服务 ESG 微前端(MFE),将前端请求聚合、打包后转发给edx-platformedx-ora2(见 lms/djangoapps/ora_staff_grader/README.md)。

而本文的主角——mock子应用(lms/djangoapps/ora_staff_grader/mock/)则是一个模拟版 BFF:它在http(s)://{lms-url}/api/ora_staff_grader/mock/{endpoint}路径下提供与真实 BFF 形状一致的 Mock 端点,使前端团队可以在不依赖真实 ORA 数据与edx-ora2服务的情况下,独立完成 ESG 界面的开发、联调与验收。

Mock 与真实 BFF 的关键差异只在一个 URL 片段上:

类型路径前缀数据来源
真实 BFF{lms-url}/api/ora_staff_grader/{endpoint}edx-platform+edx-ora2真实数据
Mock BFF{lms-url}/api/ora_staff_grader/mock/{endpoint}mock/data/目录下的 JSON 文件

由于路径结构完全平行(见 mock/urls.py 与 urls.py 中path("mock/", include(...))的挂载方式),只需通过配置基础 API 路径即可在真实与模拟版本之间切换,无需改动任何前端调用逻辑。

二、架构核心:JSON 数据存储

Mock 本质上是对一个JSON 数据存储(JSON data store)的包装器。所有关键数据都存放在lms/djangoapps/ora_staff_grader/mock/data/目录下,共四个文件:

lms/djangoapps/ora_staff_grader/mock/data/ ├── course_metadata.json # 课程元数据,按 ORA block location 索引 ├── ora_metadata.json # ORA 组件元数据(rubric 等),按 ORA block location 索引 ├── submissions.json # 提交数据(评分状态、锁定状态、评分明细),按 ora_location → submissionUUID 二级索引 └── responses.json # 学生作答内容(文本 + 附件),按 submissionUUID 索引

数据一般以请求中携带的键(通常是submissionUUID和/或ora_location)进行分组。要增删改数据,直接编辑对应的 JSON 文件即可——无需数据库迁移、无需重启特殊服务(文件由请求时实时读取)。

2.1 course_metadata.json:课程元数据

{ "block-a": { "title": "Defense against the dark arts", "org": "Hogwarts", "number": "DADA101", "courseId": "course-v1:Hogwarts+DADA101+2021_Winter" }, "block-b": { "title": "Introduction to Time Travel", "org": "Oxford", "number": "TT101", "courseId": "course-v1:Oxford+TT101+2021_Winter" } }

顶层键即oraLocation(ORA 组件在课程中的 block location),对应 mock/utils.py 中get_course_metadata(ora_location)read_data_file("course_metadata.json")[ora_location]读取逻辑。字段titleorgnumbercourseId构成了 ESG 界面展示课程信息所需的完整元数据。

2.2 ora_metadata.json:ORA 组件元数据

该文件描述 ORA 组件本身的配置,包括组件名称、类型(individual个人作答 /team团队作答)、提示语(prompts)、文本/文件上传应答配置,以及完整的rubricConfig评分量规:

{ "block-a": { "name": "Individual ORA", "prompts": ["<p>Enter a text/files response.</p>"], "type": "individual", "rubricConfig": { "feedback_prompt": "How would you grade this response?", "feedback_default_text": "I believe this response...", "feedback": "optional", "criteria": [ { "orderNum": 0, "name": "grammar", "label": "Grammar", "prompt": "How correct is the submitter's grammar?", "feedback": "optional", "options": [ {"orderNum": 0, "name": "poor", "label": "Poor", "explanation": "Absolute rubbish", "points": 0}, {"orderNum": 1, "name": "good", "label": "Good", "explanation": "pretty good", "points": 3}, {"orderNum": 2, "name": "excellent", "label": "Excelent", "explanation": "Not absolute rubbish", "points": 5} ] } ] }, "textResponseConfig": "optional", "fileUploadResponseConfig": "optional" } }

从源码结构可以推断,rubricConfig直接为 ESG 前端渲染评分表单提供依据:每个criteria项携带namelabelpromptfeedbackoptions列表,每个option又包含orderNumnamelabelexplanation和分值points,这些字段与submissions.jsongradeData.criteria的结构一一对应,模拟了工作人员按量规逐条打分的完整流程。

2.3 responses.json:学生作答内容

responses.jsonsubmissionUUID索引,存放学生提交的正文text(HTML 数组)与附件files列表(namedescriptiondownloadUrlsize字段)。例如SUBMISSION_ID-0下内置了 bmp、doc、docx、html、jpeg、jpg、ppt、pptx、tiff、txt、xls、xlsx 等多种格式的示例附件。注意fetch_response(submission_id)的默认实现是"所有提交返回同一份默认应答"(见 mock/utils.py),这正体现了 Mock 数据"够用即可"的简化原则。

2.4 submissions.json:提交与评分状态(核心数据)

submissions.json是 Mock 中最核心的数据文件,采用ora_locationsubmissionUUID两级索引:

{ "block-a": { "SUBMISSION_ID-0": { "submissionUUID": "SUBMISSION_ID-0", "username": "USERNAME-0", "dateSubmitted": 1631215154955, "score": {"pointsEarned": 70, "pointsPossible": 100}, "gradeData": { "showValidation": false, "overallFeedback": "", "criteria": [ {"orderNum": 0, "name": "grammar", "selectedOption": "excellent", "feedback": "test3"} ], "score": {"pointsEarned": 0, "pointsPossible": 100} }, "gradeStatus": "graded", "lockStatus": "unlocked" }, "SUBMISSION_ID-3": { "submissionUUID": "SUBMISSION_ID-3", "username": "USERNAME-3", "dateSubmitted": 1631474354955, "score": null, "gradeData": null, "gradeStatus": "ungraded", "lockStatus": "in-progress" } }, "block-b": { "TEAM_SUBMISSION_ID-0": { "submissionUUID": "TEAM_SUBMISSION_ID-0", "teamName": "TEAMNAME-0", "dateSubmitted": 1631215154955, "score": null, "gradeData": null, "gradeStatus": "ungraded", "lockStatus": "in-progress" } } }

每个提交对象包含以下关键字段:

字段含义取值示例
submissionUUID提交唯一标识SUBMISSION_ID-0
username/teamName个人提交者用户名或团队名USERNAME-0/TEAMNAME-0
dateSubmitted提交时间(毫秒时间戳)1631215154955
score总分,未评分时为null{"pointsEarned": 70, "pointsPossible": 100}
gradeData评分明细(量规逐条打分),未评分时为nullcriteriaoverallFeedbackscore
gradeStatus评分状态graded/ungraded
lockStatus锁定状态unlocked/locked/in-progress

从示例数据看,block-a对应个人 ORA(SUBMISSION_ID-0~SUBMISSION_ID-4),block-b对应团队 ORA(TEAM_SUBMISSION_ID-0~TEAM_SUBMISSION_ID-4),两组数据刻意覆盖了"已评分/未评分 × 已解锁/已锁定/进行中"的全部状态组合,为前端状态渲染提供了完备的测试样本。

三、Mock 端点全览与源码实现

Mock 的五个端点定义在 mock/urls.py(app_name = "mock-ora-staff-grader"),视图实现在 mock/views.py。真实 BFF 端点与 Mock 端点一一对应,只是路径中少了mock段。

端点方法查询参数功能
/api/ora_staff_grader/mock/initializeGEToraLocation返回应用初始状态(课程元数据 + ORA 元数据 + 提交列表)
/api/ora_staff_grader/mock/submissionGEToraLocationsubmissionUUID获取单个提交(含作答内容)
/api/ora_staff_grader/mock/submission/statusGEToraLocationsubmissionUUID获取提交状态(不含作答内容)
/api/ora_staff_grader/mock/submission/lockPOST / DELETEoraLocationsubmissionUUID锁定 / 解锁提交
/api/ora_staff_grader/mock/submission/gradePOSToraLocationsubmissionUUID,请求体为评分数据提交评分并写回数据存储

3.1 initialize:初始化应用状态

InitializeView(mock/views.py)读取oraLocation参数,将courseMetadataoraMetadatasubmissions三项打包返回,一次性为 ESG 前端提供渲染工作台所需的全部数据。这里调用的get_submissions(ora_location)有一个值得注意的细节:列表场景下会pop掉每个提交的gradeData(见 mock/utils.py),即初始化列表不携带评分明细,只有在单个提交的详情接口中才返回gradeData,这与真实 BFF 的数据裁剪策略保持一致。

3.2 submission:获取单个提交

SubmissionFetchView同时读取提交记录与作答内容,返回四个字段:

{ "gradeData": { "...": "评分明细" }, "response": { "...": "学生作答内容" }, "gradeStatus": "graded", "lockStatus": "unlocked" }

3.3 submission/status:只取状态

SubmissionStatusFetchView与上者唯一区别是不包含response作答内容,仅返回gradeStatuslockStatusgradeData,适合前端用于轮询/刷新评分状态、判断是否显示作答面板等场景。

3.4 submission/lock:交互式锁定/解锁

SubmissionLockView是体现"简单交互性"的典型端点:

  • POST:将lockStatus置为"in-progress"并写回数据文件,响应{"lockStatus": "in-progress"}
  • DELETE:将lockStatus置为"unlocked"并写回,响应{"lockStatus": "unlocked"}

每次命中端点都会调用save_submission_update(ora_location, submission)将变更落盘到submissions.json。这意味着你可以通过读取更新后的 JSON 文件来验证调用结果,也可以通过git checkout该文件一键还原到初始状态,这是 Mock 相比真实后端最方便的开发调试特性。

3.5 submission/grade:写入评分

UpdateGradeView接收请求体中的评分数据,执行update_grade_data完成一次完整的"评分闭环":

submission["gradeData"] = grade_data submission["gradeStatus"] = "graded" submission["lockStatus"] = "unlocked" submission["score"] = {"pointsEarned": 70, "pointsPossible": 100}

注意其中写死了一个静态测试分数{"pointsEarned": 70, "pointsPossible": 100}(注释明确标注 "this is static test data"),即 Mock 评分总是返回 70/100 的固定得分——这是刻意为之的简化,便于前端对评分结果做确定性断言。

四、数据读写底层实现

所有 Mock 数据的读写都集中在 mock/utils.py,其根路径常量DATA_ROOT指向部署环境中的edx-platform/lms/djangoapps/ora_staff_grader/mock/data。核心函数包括:

  • read_data_file(file_name):读取 JSON 文件并反序列化;
  • update_data_file(file_name, update_key_path, update_value):按键路径(update_key_path为逐层遍历的 key 列表)更新单个值,再以indent=4格式写回文件,保证人类可读、可 diff;
  • save_submission_update(ora_location, submission):以[ora_location, submissionUUID]作为键路径调用update_data_file,实现单条提交记录的原地更新。

这套实现保证了"编辑 JSON 即改数据、调用端点即写数据"的双向可操作性,也让 Mock 数据的变更始终处于git版本控制之下,可随时对比与回滚。

五、快速上手:直连端点

在 devstack 环境启动 LMS 后,直接访问(注意使用 devstack 的 lms 地址):

{devstack-url}/api/ora_staff_grader/mock/{endpoint}

例如在浏览器或 curl 中请求初始化端点:

curl "{devstack-url}/api/ora_staff_grader/mock/initialize?oraLocation=block-a"

即可拿到block-a对应的课程元数据、ORA 元数据(含量规)与提交列表(不含gradeData)。将oraLocation换成block-b则可体验团队 ORA 的数据。对于lock/grade等写操作端点,调用后可打开 mock/data/submissions.json 检查对应submissionUUID记录的lockStatusgradeStatusgradeData是否已更新。

六、Postman 无头测试流程

除直连外,仓库还附带了 Postman 集合用于对端点进行无头(headless)测试。完整的登录流程封装在lms.postman_collection.json(位于 lms/djangoapps/ora_staff_grader/lms.postman_collection.json),按以下步骤执行:

  1. 执行GET Login请求:向{{protocol}}://{{lms_url}}/login发起 GET,集合内的 test 脚本会读取响应中的csrftokenCookie 并写入环境变量:
    var xsrfCookie = postman.getResponseCookie("csrftoken"); postman.setEnvironmentVariable('csrftoken', xsrfCookie.value);
  2. 执行POST Login请求:携带上一步生成的X-CSRFToken头,以email{{user_email}})和password{{user_password}})表单数据调用{{protocol}}://{{lms_url}}/api/user/v1/account/login_session/完成 LMS 会话认证。
  3. 配置环境变量:按 mock 文档要求设置{{mock}} = True(真实 BFF 联调时置为False),并配置{{protocol}}{{lms_url}}{{user_email}}{{user_password}}等变量。
  4. 运行 ESG 示例请求:在ora_staff_grader.postman_collection.json中运行各端点示例请求,验证 initialize / submission / lock / grade 等场景。文档同时提示,由于{{mock}}变量控制着请求的基础路径是否包含mock段,仅靠切换一个环境变量即可在模拟与真实端点之间来回切换,无需修改任何请求定义。

七、真实 BFF 与 Mock 的对照与切换

真实 BFF 的路由挂载在 lms/djangoapps/ora_staff_grader/urls.py,其中一行:

path("mock/", include("lms.djangoapps.ora_staff_grader.mock.urls")),

将 Mock 子路由挂到了mock/前缀下。真实的 BFF 比 Mock 多出submission/batch/unlock(批量解锁)、submission/files(获取作答附件)、assessments/feedback(反馈接口)等能力,且SubmissionLockView等真实实现会经过LockContestedErrorXBlockInternalError等并发/内部错误处理(对应 errors.py 与 constants.py 中的错误码)。Mock 视图则刻意去掉了鉴权、错误分支与真实数据源,只保留"形状一致"的响应结构——这正是它轻量、可预测、适合前端开发的原因。

八、测试用例:如何验证 Mock 行为

仓库在 lms/djangoapps/ora_staff_grader/tests/ 下为整个 ESG 应用提供了测试套件:

  • test_views.py:基于SharedModuleStoreTestCase + APITestCase对视图层进行集成测试,覆盖initializefetch-submissionlockupdate-grade等视图;测试基建(BaseViewTest)会构造课程、openassessment类型 XBlock 与StaffFactory工作人员账号,并断言锁定竞争(LockContestedError)、非法oraLocationERR_BAD_ORA_LOCATION)等错误路径;
  • test_data.py:提供视图测试所需的结构化测试数据;
  • test_serializers.py:验证 BFF 与edx-ora2之间序列化层的字段映射。

这些测试一方面印证了端点的真实调用链路与参数约定,另一方面也为 Mock 的返回结构(如gradeStatus/lockStatus/gradeData三要素)提供了可对照的行为基准。

九、小结与扩展建议

Open edX 的 Mock ESG BFF 用一个目录、四个 JSON 文件、五个 DRF 视图就完成了对整套评分工作流的前端支撑,其核心价值有三:

  1. 路径平行、一键切换mock段的存在让真实/模拟端点切换成本趋近于零;
  2. 数据即代码、可视化调试:所有状态变更都落在可读的 JSON 文件中,git diff/git checkout即可完成验证与回滚;
  3. 覆盖状态全集:内置的个人/团队提交样本覆盖了graded/ungraded × unlocked/locked/in-progress的各种组合,天然适合前端边界场景开发。

在此基础上,你可以按需扩展:在submissions.json中新增submissionUUID以模拟更多提交;调整gradeData.criteria以匹配新的量规结构;或在 mock/views.py 中按真实 BFF 的形状补全submission/filessubmission/batch/unlock等端点的 Mock 版本,让模拟服务与真实服务始终保持在可对比的同一水平线上。

【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询