HarmonyOS 应用开发《掌上英语》第27篇:学习统计持久化——StatisticsManager 的数据聚合方案
学习统计持久化——StatisticsManager 的数据聚合方案引言学习数据统计是教育类 App 的核心功能之一。用户完成一次练习后系统需要快速计算出正确率、各题型得分率、平均答题时长等指标并将这些数据持久化以便用户在学习报告等页面查看历史趋势。本文将以StatisticsManager的实现为例分析如何设计一套可扩展、可聚合的学习统计方案。一、数据模型设计统计数据的核心是PracticeStatistics接口它包含了全局统计和分题型统计两个维度exportinterfacePracticeStatistics{// 全局统计数据totalQuestions:number// 总答题数correctQuestions:number// 正确题数wrongQuestions:number// 错误题数correctRate:number// 正确率百分比vocabularyMasteryRate:number// 词汇掌握率listeningCorrectRate:number// 听力正确率readingCorrectRate:number// 阅读正确率clozeCorrectRate:number// 完形填空正确率avgAnswerTime:number// 平均答题时长秒// 分题型统计typeStatistics:TypeStat[]}exportinterfaceTypeStat{type:string// 题型编号1词汇 2听力 3阅读 4完形 5语法 6口语total:number// 该题型总题数correct:number// 正确数wrong:number// 错误数}设计亮点typeStatistics数组而非固定字段。这样可以支持未来新增题型而无需修改数据模型——符合开闭原则。二、单次练习统计计算calculateStatistics方法接收一次练习的题目列表和总时长实时计算各项指标publiccalculateStatistics(ques:TopicItemType[],duration:number):PracticeStatistics{try{lettotalQuestionsques.length;letcorrectQuestions0;letwrongQuestions0;lettypeStats:TypeStat[][];ques.forEach(item{if(item.isAnswer){// 判断答案是否正确letisCorrectthis.checkAnswerCorrect(item.selectQues,item.rightQues);if(isCorrect){correctQuestions;}else{wrongQuestions;}// 按题型累加统计lettypeKeyitem.type||其他;letfoundfalse;for(leti0;itypeStats.length;i){if(typeStats[i].typetypeKey){typeStats[i].total;if(isCorrect)typeStats[i].correct;elsetypeStats[i].wrong;foundtrue;break;}}if(!found){typeStats.push({type:typeKey,total:1,correct:isCorrect?1:0,wrong:isCorrect?0:1});}}});letcorrectRatetotalQuestions0?(correctQuestions/totalQuestions*100):0;// 各题型专项正确率letlisteningCorrectRatethis.getTypeCorrectRate(typeStats,2);letreadingCorrectRatethis.getTypeCorrectRate(typeStats,3);letclozeCorrectRatethis.getTypeCorrectRate(typeStats,4);letvocabularyMasteryRatethis.getTypeCorrectRate(typeStats,1);letavgAnswerTimetotalQuestions0?Math.floor(duration/totalQuestions):0;letstatistics:PracticeStatistics{totalQuestions,correctQuestions,wrongQuestions,correctRate:Math.round(correctRate*100)/100,vocabularyMasteryRate:Math.round(vocabularyMasteryRate*100)/100,listeningCorrectRate:Math.round(listeningCorrectRate*100)/100,readingCorrectRate:Math.round(readingCorrectRate*100)/100,clozeCorrectRate:Math.round(clozeCorrectRate*100)/100,avgAnswerTime,typeStatistics:typeStats};// 合并到历史数据this.saveStatistics(statistics);returnstatistics;}catch(e){Logger.error(StatisticsManager,计算统计数据失败:${JSON.stringify(e)});returnthis.getEmptyStatistics();}}核心逻辑遍历题目逐题判断是否正确同时按题型分类统计。答案比较checkAnswerCorrect比较selectQues和rightQues两个数组排序后是否完全一致。四舍五入使用Math.round(x * 100) / 100保留两位小数。降级处理异常时返回空统计数据不影响用户使用。三、历史数据聚合单次练习的数据需要与历史数据合并才能反映用户的长期学习趋势privatesaveStatistics(statistics:PracticeStatistics):void{try{lethistoryStatsthis.getHistoryStatistics();letmergedStats:PracticeStatistics{totalQuestions:historyStats.totalQuestionsstatistics.totalQuestions,correctQuestions:historyStats.correctQuestionsstatistics.correctQuestions,wrongQuestions:historyStats.wrongQuestionsstatistics.wrongQuestions,correctRate:statistics.totalQuestions0?statistics.correctRate:historyStats.correctRate,vocabularyMasteryRate:statistics.vocabularyMasteryRate,listeningCorrectRate:statistics.listeningCorrectRate,readingCorrectRate:statistics.readingCorrectRate,clozeCorrectRate:statistics.clozeCorrectRate,avgAnswerTime:statistics.avgAnswerTime,typeStatistics:this.mergeTypeStatistics(historyStats.typeStatistics,statistics.typeStatistics)};PreferenceUtil.getInstance().put(this.STATISTICS_KEY,mergedStats);}catch(e){Logger.error(StatisticsManager,保存统计数据失败:${JSON.stringify(e)});}}聚合策略累加型字段totalQuestions、correctQuestions、wrongQuestions历史值 新值直接相加。覆盖型字段correctRate、各专项正确率以最新一次计算为准。分题型统计通过mergeTypeStatistics方法进行深度合并。privatemergeTypeStatistics(history:TypeStat[],current:TypeStat[]):TypeStat[]{// 先深度复制历史数据letresult:TypeStat[][];for(leti0;ihistory.length;i){result.push({type:history[i].type,total:history[i].total,correct:history[i].correct,wrong:history[i].wrong});}// 合并当前数据for(leti0;icurrent.length;i){letfoundfalse;for(letj0;jresult.length;j){if(result[j].typecurrent[i].type){result[j].totalcurrent[i].total;result[j].correctcurrent[i].correct;result[j].wrongcurrent[i].wrong;foundtrue;break;}}if(!found){result.push({type:current[i].type,total:current[i].total,correct:current[i].correct,wrong:current[i].wrong});}}returnresult;}这里手动深度复制而非直接赋值是因为TypeStat是引用类型直接复制会共享底层对象导致历史数据被意外修改。四、历史数据读取与展示publicgetHistoryStatistics():PracticeStatistics{try{letstatsPreferenceUtil.getInstance().get(this.STATISTICS_KEY,undefined)asPracticeStatistics;if(!stats){returnthis.getEmptyStatistics();}returnstats;}catch(e){Logger.error(StatisticsManager,获取历史统计数据失败:${JSON.stringify(e)});returnthis.getEmptyStatistics();}}publicgetQuestionTypeStats():QuestionTypeStat[]{try{letstatsthis.getHistoryStatistics();letresult:QuestionTypeStat[][];for(leti0;istats.typeStatistics.length;i){lettypeStatstats.typeStatistics[i];result.push({typeName:this.getTypeName(typeStat.type),total:typeStat.total,correct:typeStat.correct,wrong:typeStat.wrong,correctRate:typeStat.total0?Math.round(typeStat.correct/typeStat.total*10000)/100:0});}returnresult.sort((a,b)b.total-a.total);}catch(e){Logger.error(StatisticsManager,获取题型统计失败:${JSON.stringify(e)});return[];}}getQuestionTypeStats将内部的数字题型编号转换为可读的中文名称并按总题数降序排列方便 UI 层展示。五、最佳实践5.1 空值安全首次使用或数据被清空时getHistoryStatistics返回空统计数据而非null避免调用方做空值检查privategetEmptyStatistics():PracticeStatistics{return{totalQuestions:0,correctQuestions:0,wrongQuestions:0,correctRate:0,vocabularyMasteryRate:0,listeningCorrectRate:0,readingCorrectRate:0,clozeCorrectRate:0,avgAnswerTime:0,typeStatistics:[]};}5.2 精度控制所有百分比值使用Math.round(x * 100) / 100确保保留两位小数避免浮点数精度问题导致 UI 上显示一长串小数。5.3 题型映射集中管理题型编号到中文名称的映射集中在getTypeName方法中避免在 UI 层散落 switch-caseprivategetTypeName(type:string):string{switch(type){case1:return单词拼写case2:return听力理解case3:return阅读理解case4:return完形填空case5:return语法练习case6:return口语练习default:return题型${type}}}六、总结StatisticsManager展现了一个典型的数据聚合场景从原始数据题目列表到计算指标正确率再到历史数据合并累加 覆盖最后到 UI 友好的展示格式题型名称转换 排序。这种分层设计使得统计逻辑高度内聚、易于测试同时在需要新增题型或统计维度时只需扩展TypeStat数组即可无需修改核心聚合逻辑。

相关新闻

你的 AI Agent 还在“裸奔“?2026 年不这样用 AI,你就outdated了

你的 AI Agent 还在“裸奔“?2026 年不这样用 AI,你就outdated了

90% 的人还在把 AI 当搜索引擎用,而真正的高手早就让 AI 自己干活了。一个真实的故事,让我彻底改变了看法 上个月,我和一个做独立开发的朋友喝咖啡。他跟我说:“我每天花 6 个小时写代码,2 个小时开会,剩下…

2026/7/22 4:14:24阅读更多 →
网络安全必备:黑客常用命令解析与实战技巧

网络安全必备:黑客常用命令解析与实战技巧

1. 黑客常用命令解析:从基础到进阶作为一名网络安全从业者,我经常需要处理各种系统诊断、网络分析和安全检测任务。这些工作离不开命令行工具的熟练使用。不同于影视作品中夸张的黑客形象,真正的安全专家更注重对基础命令的深入理解和合理运用…

2026/7/22 4:14:24阅读更多 →
ClaudeCode AI编程助手:功能解析与开发实战

ClaudeCode AI编程助手:功能解析与开发实战

1. ClaudeCode核心功能解析ClaudeCode是Anthropic推出的AI编程助手工具集,它通过深度理解代码库上下文,为开发者提供从终端到IDE的全流程智能编码支持。我在实际使用中发现,这套工具最突出的特点是其"主动式代码理解"能力——不像传…

2026/7/22 4:14:24阅读更多 →
构建交互式推理系统:从数据模型到推理引擎的实践指南

构建交互式推理系统:从数据模型到推理引擎的实践指南

在实际游戏开发或剧情设计中,构建一个“矩阵陨落”这类宏大世界观的时间线,并融入“虚构推理”元素,是一项极具挑战性的任务。它要求开发者或创作者不仅要搭建清晰的历史脉络,还要设计一套允许玩家或读者通过逻辑推演来揭示真相的…

2026/7/22 6:57:11阅读更多 →
mpweixin水果商城微信小程序

mpweixin水果商城微信小程序

一、关键词水果商城、热卖水果、购物车、订单、优惠券、微信小程序二、作品包含源码数据库万字设计文档PPT全套环境和工具资源本地部署教程三、项目技术前端技术: Html、Css、Js、Vue2.6、Element-ui、uniapp后端技术:Java、SpringBoot2.2.2、MyBatis-Pl…

2026/7/22 6:57:11阅读更多 →
AI 生图水印怎么去除?3 种专业方法步骤详解

AI 生图水印怎么去除?3 种专业方法步骤详解

在使用 AIGC 工具(如豆包、即梦等)生成图片后,导出文件常带有平台标识的水印。这些水印虽然不显眼,但在作为设计素材、办公配图或商用展示时,往往会破坏画面的完整性,显得不够专业。手动裁剪会导致构图变形…

2026/7/22 6:57:11阅读更多 →
校园摄影作品人气评选活动策划与实施指南

校园摄影作品人气评选活动策划与实施指南

校园中处处充满值得记录的瞬间——晨光中的教学楼、运动场上的青春身影、图书馆里的专注侧影……将这些美好定格并分享,是校园文化的重要组成部分。组织一场校园优秀摄影作品人气评选活动,不仅能激发学生的创作热情,还能有效增强校园凝聚力与…

2026/7/22 6:57:11阅读更多 →
Builder模式与注解实现优雅的命令模式

Builder模式与注解实现优雅的命令模式

1. 为什么需要Builder模式注解实现命令模式第一次在电商系统里看到订单状态变更的代码时,我被那几十个if-else分支震惊了。每个状态变更都要检查前置条件、执行操作、记录日志、更新数据库,代码像意大利面条一样纠缠在一起。这就是典型的命令模式应用场景…

2026/7/22 6:57:11阅读更多 →
最好用的AI文献综述工具推荐:高效助力科研写作的实用工具盘点

最好用的AI文献综述工具推荐:高效助力科研写作的实用工具盘点

链接链接刚进实验室,你可能认为找文献就是打开知网或Google Scholar,输入关键词,然后一篇篇下载、阅读。如果这是你主要的科研方式,那么一个隐形的天花板已经形成:你的认知深度和广度,将被你使用的工具牢牢…

2026/7/22 6:55:11阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 0:53:59阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 0:53:59阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 0:53:59阅读更多 →
中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业做小程序,最常见的矛盾是预算有限,但又不希望功能太单薄;没有技术团队,但又希望后续能自己运营;想快速上线,又担心隐性收费和售后失联。选型时如果只看“低价套餐”或“案例数量”,很容…

2026/7/22 0:01:17阅读更多 →
GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

企业做营销,最怕钱花完了,资产没有留下。 效果广告能带来一段时间的曝光,但预算停止后,流量往往也随之停止。短视频内容可能在几天内冲高,也可能很快沉下去。AI搜索时代,企业需要重新思考一个问题&#xff…

2026/7/22 0:01:17阅读更多 →
Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复 一、你的 Agent 在"再想想"的循环里绕了 12 轮,用户已经关窗口了 Agent 与人最大的区别是:人知道什么时候该停下来给答案,Agent 会一直"想"下去。你给 Agent 接…

2026/7/22 0:01:17阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/21 22:53:50阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/21 18:53:30阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/21 18:53:30阅读更多 →