☰
Oppia 探索玩家共享服务层(Shared Services)源码指南:Lesson Player 双实现下的架构、维护规范与测试策略
2026/10/15 15:12:49 网站建设 项目流程
  • 教育
  • 后端
  • 前端

【免费下载链接】oppia

A free, online learning platform to make quality education accessible for all.

项目地址:https://gitcode.com/gh_mirrors/op/oppia
点击查看免费下载

本指南以 Oppia 仓库中 core/templates/pages/exploration-player-page/services/README.md 为核心骨架,结合该目录下 39 个 Angular 服务实现与配套测试,系统讲解共享服务层在“现有 Lesson Player”与“新 Lesson Player”双实现共存架构中的定位、职责划分、编码规范与测试方法。读完本文,你将掌握该目录内每个核心服务的功能边界、它们之间的调用关系,以及如何安全地新增或修改一个被多页面共享的 Angular 服务。

一、背景:为什么需要一个“共享服务层”

在 Oppia 中,学习者与“探索(Exploration)”交互的页面统称为 Lesson Player。目前仓库中存在两套并行的播放器实现:

  • current-lesson-player:现有播放器,按其目录内 README.md 的说明,该实现已被标记为deprecated,待新播放器完全上线后将整体移除;
  • new-lesson-player:新播放器,目标是更好的用户体验与可访问性、更强的移动端响应式表现,以及更清晰的模块化代码架构,其推进状态跟踪见 README.md。

两套播放器虽然 UI 与组件树不同,但底层有一大批业务逻辑是完全一致的:答案判定、卡片流转、提示/解答管理、进度统计、音频图片预加载……如果各自复制一份,必然导致行为漂移与维护灾难。因此 exploration-player-page 目录 下的 services 目录专门存放被两套播放器共享的服务文件,从源码结构看,它是整个播放器模块的“公共业务内核”,也是原 README 定义本文主题的出发点。

从目录级 README(exploration-player-page/README.md)可以确认这一分层决策:current-lesson-player与new-lesson-player各管各的组件,/services则承载两者共用的逻辑。

二、服务清单:39 个共享服务一图览

目录下共有 39 个*.service.ts文件,且每个服务都配有同名*.spec.ts单元测试(共 39 个),形成“一服务一测试”的强约束。按职责可划分为如下几组:

1. 引擎与模式(播放器的“大脑”)

服务文件核心职责
ExplorationEngineServiceexploration-engine.service.ts探索(Exploration)的核心引擎:初始化探索、维护当前状态、解析答案、驱动卡片与反馈。从源码看它聚合了AnswerClassificationService、ImagePreloaderService、PlayerTranscriptService等十余个服务,是全目录依赖面最广的枢纽
QuestionPlayerEngineServicequestion-player-engine.service.ts练习题会话与学前测(pretest)模式的问题引擎,跟踪每题作答、提示使用与解答查看情况
DiagnosticTestPlayerEngineServicediagnostic-test-player-engine.service.ts诊断测试播放器引擎,配合DiagnosticTestTopicTrackerModel按主题/技能动态出题
CurrentEngineServicecurrent-engine.service.ts三个引擎之上的动态委托器,通过setExplorationEngineService()/setQuestionPlayerEngineService()/setDiagnosticTestPlayerEngineService()切换当前活跃引擎,让上层组件无需耦合具体实现
ExplorationModeServiceexploration-mode.service.ts通过 URL 与页面上下文判定播放模式,定义EXPLORATION_MODE枚举(EXPLORATION、EDITOR_PREVIEW、QUESTION_PLAYER、PRETEST、STORY_CHAPTER、LESSON_PLAYER、DIAGNOSTIC_TEST_PLAYER),并在切模式时同步切换CurrentEngineService

2. 会话状态与流转(卡片、转录、位置)

服务文件核心职责
ConversationFlowServiceconversation-flow.service.ts对话流总控,负责“提交答案→得到反馈→推进下一张卡”的主循环,聚合了转录、提示、统计、引擎等全部关键服务(约 1900 行,是全目录最复杂的服务)
PlayerTranscriptServiceplayer-transcript.service.ts维护学习过程的转录日志(看过哪些卡片、提交过哪些答案),支持restore/restoreImmutably恢复、addNewCard/addPreviousCard/addNewInput追加记录
PlayerPositionServiceplayer-position.service.ts管理当前卡片位置、卡片序号与展示状态,是转录与视图之间的桥梁
CardAnimationServicecard-animation.service.ts卡片切换动画控制
CurrentInteractionServicecurrent-interaction.service.ts当前交互组件的运行时适配(提交答案、更新交互实例等)
ChapterProgressServicechapter-progress.service.ts章节进度百分比计算与进度指示

3. 检查点(Checkpoint)与进度持久化

服务文件核心职责
CheckpointProgressServicecheckpoint-progress.service.ts基于探索状态图的深度(depth graph)计算检查点列表,跟踪最近到达的检查点,支持跨会话进度恢复
CheckpointCelebrationUtilityServicecheckpoint-celebration-utility.service.ts到达检查点时的庆祝弹窗等交互效用
ProgressUrlServiceprogress-url.service.ts生成携带进度参数的 URL,用于检查点跳转与恢复
NumberAttemptsServicenumber-attempts.service.ts以静态字段统计单卡内作答次数,供“学习者真的卡住”判定使用

4. 内容辅助:提示、解答、概念卡、模态框

服务文件核心职责
HintsAndSolutionManagerServicehints-and-solution-manager.service.ts提示与解答的释放节奏控制:定义了ACCELERATED_HINT_WAIT_TIME_MSEC = 10000、WAIT_FOR_TOOLTIP_TO_BE_SHOWN_MSEC = 500等关键常量,通过BehaviorSubject暴露已释放提示数
HintAndSolutionModalServicehint-and-solution-modal.service.ts提示/解答模态框的显示控制
ConceptCardManagerServiceconcept-card-manager.service.ts概念卡片的加载与展示管理
RefresherExplorationConfirmationModalServicerefresher-exploration-confirmation-modal.service.ts“推荐复习探索”确认模态框
FatigueDetectionServicefatigue-detection.service.ts学习者疲劳检测(连续作答异常时的提示)

5. 资源预加载:图片、音频、翻译

服务文件核心职责
ImagePreloaderServiceimage-preloader.service.ts以 BFS 顺序按状态图预取图片到缓存,跟踪下载中/失败/待下载文件名,并暴露getImageUrl供组件按需取图
AudioPreloaderServiceaudio-preloader.service.ts音频文件的预加载缓存
AudioTranslationManagerServiceaudio-translation-manager.service.ts音频翻译(voiceover)的加载与播放管理
VoiceoverPlayerServicevoiceover-player.service.ts配音播放器状态控制
ExtractImageFilenamesFromModelServiceextract-image-filenames-from-model.service.ts从状态模型/HTML 中抽取图片文件名,供预加载器使用
ContentTranslationManagerServicecontent-translation-manager.service.ts内容多语言翻译的获取与切换
ContentTranslationLanguageServicecontent-translation-language.service.ts当前内容翻译语言状态管理

6. 答案与学习行为数据

服务文件核心职责
AnswerClassificationServiceanswer-classification.service.ts答案判定核心:按顺序匹配各答案组(AnswerGroup)内的规则,命中即返回AnswerClassificationResult;全部未命中则回退默认结果(DEFAULT_OUTCOME_CLASSIFICATION),无默认结果时告警并抛错。同时导出InteractionRulesService规则函数接口类型
LearnerAnswerInfoServicelearner-answer-info.service.ts记录学习者的答案信息,供后续“继续练习/复习”分析
LearnerParamsServicelearner-params.service.ts学习者参数(ExplorationParams)的计算与更新
LearnerViewRatingServicelearner-view-rating.service.ts学习者评分逻辑(如探索结束后是否允许评分)
StatsReportingServicestats-reporting.service.ts统计上报中枢:聚合各状态的作答数、有效反馈数、命中数、解答查看次数等,每 300000ms(5 分钟)通过StatsReportingBackendApiService批量上报后端,并集成SiteAnalyticsService与PlaythroughService

7. 后端 API 封装与杂项

服务文件核心职责
FeedbackPopupBackendApiServicefeedback-popup-backend-api.service.ts反馈弹窗的后端 API 封装
LearnerLocalNavBackendApiServicelearner-local-nav-backend-api.service.ts学习者本地导航相关 API
LearnerViewInfoBackendApiServicelearner-view-info-backend-api.service.ts学习者视图信息 API
LearnerViewRatingBackendApiServicelearner-view-rating-backend-api.service.ts评分提交/获取 API
ExplorationRecommendationsServiceexploration-recommendations.service.ts完成探索后的推荐探索加载
MobileMenuServicemobile-menu.service.ts移动端菜单开关状态

8. 初始化

服务文件核心职责
ExplorationInitializationServiceexploration-initialization.service.ts播放器的统一初始化入口。从其文件头注释可知,它是为了避免ExplorationEngineService与ExplorationModeService之间的循环依赖而引入的临时服务,负责按模式(编辑预览/正式播放)加载探索数据、特性开关、学前测问题并完成引擎初始化

从依赖方向上看,目录内存在清晰的分层结构:底层是AnswerClassificationService、PlayerTranscriptService、NumberAttemptsService等无副作用的基础服务;中间是ExplorationEngineService、QuestionPlayerEngineService、DiagnosticTestPlayerEngineService三个引擎;顶层是ConversationFlowService、ExplorationInitializationService与CurrentEngineService/ExplorationModeService组成的调度层。新播放器可以复用除顶层 UI 状态之外的几乎所有服务。

三、编码规范:README 的两条硬性要求

原 README 在 Guidelines 一节给出了两条必须遵守的开发准则,结合源码可以进一步明确其执行标准:

1. 为所有新增服务/新方法编写规范的 JsDoc

查看目录内的服务实现,可以看到每一处公开方法都有标准的 JsDoc 注释,例如 answer-classification.service.ts 中的classifyAnswer注释清晰说明了参数类型与返回值;image-preloader.service.ts 对kickOffImagePreloader(sourceStateName)的参数含义也做了逐行说明。

实践要点:

  • 每个服务文件头部必须有@fileoverview注释,一句话概括该服务的职责(本目录所有文件均遵守);
  • 每个公开方法写清参数类型、返回值类型与副作用;
  • 如果属性初始化依赖 Angular 生命周期或init方法,需要使用非空断言(!),并在注释中说明原因——这是 exploration-engine.service.ts 等文件的既有模式。

2. 服务被多组件、多页面共享,改动必须谨慎

从服务构造函数可以直观看到共享程度:例如 conversation-flow.service.ts 注入了 30 余个依赖,exploration-engine.service.ts 同样注入十余个服务。任何一个服务的签名或行为变化,都可能沿着依赖图波及其余数十个服务与组件。README 要求:

  • 谨慎改动:优先保持方法签名兼容,新增行为而不是修改既有行为;
  • 彻底测试:按下一节的流程执行完整验证。

四、测试策略:三层验证流程

原 README 在 Testing Recommendations 中给出了三层测试流程,以下是结合仓库实际的展开:

第一层:单元测试(Unit Tests)

每个服务都必须有配套的*.spec.ts,覆盖预期行为、边界情况与错误处理。目录内 39 个服务与 39 个测试文件一一对应。

以 answer-classification.service.spec.ts 为例,测试应至少覆盖:

  • 正常命中:答案命中某个答案组的规则时,返回正确的AnswerClassificationResult(含分组索引、规则索引);
  • 回退行为:所有规则均未命中时,返回默认结果;
  • 错误处理:defaultOutcome缺失时抛出异常,并触发AlertsService警告;
  • 边界情况:空答案组列表、空规则列表、特殊类型答案等。

对于有内部状态的服务(如 player-transcript.service.ts),还需覆盖状态恢复、重复添加卡片、首卡回退抛错等场景。

第二层:依赖组件测试(Dependent Component Tests)

由于服务被多个组件共享,单测通过不代表集成安全。需要运行所有依赖该服务的组件测试,检查是否产生非预期的副作用。在仓库中,播放器相关组件测试位于:

  • exploration-player-page.component.spec.ts(现有播放器)
  • lesson-player-page.component.spec.ts(新播放器)

修改任何共享服务后,建议同时跑通这两个播放器的组件测试,以及所有依赖注入链上的服务测试。

第三层:手动验证(Manual Verification)

自动化测试无法覆盖真实交互体验,README 特别强调手动走查应用,确认所有使用该服务的功能按预期工作。这对“新旧播放器共享”的服务尤其重要。建议手动验证的路径包括:

  1. 以/explore/:id进入现有播放器完成一次完整探索(作答→反馈→提示→解答→下一卡);
  2. 开启新播放器特性开关后以/lesson/:id重复上述流程(开关方式见 new-lesson-player/README.md);
  3. 验证编辑器预览模式(/create/:id)、练习题会话与诊断测试模式下的服务行为;
  4. 验证弱网/慢加载场景下图片与音频预加载的降级表现。

注:新播放器通过new_lesson_player特性开关控制,开启后所有/explore/:id链接会自动重定向至/lesson/:id。因此双实现验证可以高效合并:开关开启时走新播放器,关闭时走旧播放器,手动确认两套 UI 下共享服务行为一致。

五、新增共享服务的完整实践路径

结合 README 规范与仓库既有实现,新增一个共享服务的推荐流程如下:

  1. 先评估共享必要性:确认该逻辑确实会被两套播放器共用;若仅属于某一套播放器的 UI 表现,应放入对应播放器目录(current-lesson-player/或new-lesson-player/)而非 services 目录;
  2. 创建服务文件:参照 number-attempts.service.ts 的最小实现范式——文件头版权声明与@fileoverview、@Injectable({providedIn: 'root'})、公开方法带完整 JsDoc;
  3. 编写单元测试:创建同名.spec.ts,覆盖预期行为、边界与错误分支;
  4. 接入依赖注入:在需要处注入服务;若服务间有循环依赖风险,参照ExplorationInitializationService的“临时服务解耦”思路拆分;
  5. 跑依赖测试:执行依赖该服务的组件测试(新旧播放器两侧);
  6. 手动走查:在真实页面中验证新旧播放器下的行为一致性。

六、总结

exploration-player-page/services目录是 Oppia 播放器前端的“公共内核”:39 个共享服务以 Angular 依赖注入组织成“基础服务 → 三大引擎 → 调度层”的清晰层次,支撑着现有与新两代 Lesson Player。其 README 强调的两条准则——规范 JsDoc、谨慎改共享代码——以及“单元测试 → 依赖组件测试 → 手动验证”的三层流程,正是保障这套被多页面共用的服务层长期稳定演进的工程基石。对于计划向播放器新增能力的开发者,本文的服务清单、调用链分析与测试路径可作为直接的手册与查漏工具。

  • 教育
  • 后端
  • 前端

【免费下载链接】oppia

A free, online learning platform to make quality education accessible for all.

项目地址:https://gitcode.com/gh_mirrors/op/oppia
点击查看免费下载

相关推荐

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

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

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

立即咨询