移动端学习 App 技术实现:从跨端交付到可观测学习闭环
移动端学习产品的难点不在于“把页面做出来”,而在于同一套学习状态要跨越 App、小程序和 H5,同时经受弱网、系统切后台、播放器生命周期和高频进度事件的考验。织码在线教育系统以UniApp + Vue 3 + uview-plus为基础,把平台差异收敛到适配层,把课程学习过程抽象为可恢复、可重试、可观测的状态链路。
一、架构边界:跨端框架解决交付,业务层解决一致性
建议将移动端拆成四个边界:页面与组件层、状态与缓存层、平台适配层、统一请求层。课程页不直接调用原生能力,播放器不直接修改全局业务状态,所有跨端差异通过组件或工具函数暴露稳定接口。
| 边界 | 主要职责 | 关键约束 |
|---|---|---|
| 页面组件 | 组合课程卡片、播放器、进度条 | 不写平台判断 |
| 状态缓存 | 用户、课程、播放进度 | 细粒度更新,避免全页刷新 |
| 平台适配 | 视频、全屏、后台音频、网络 | 条件编译集中管理 |
| 请求服务 | 鉴权、超时、重试、错误码 | 写操作具备幂等语义 |
二、请求层:移动网络下的失败必须可解释
统一请求封装至少要处理四类结果:HTTP 成功且业务成功、登录态失效、业务错误、网络失败。对于 GET 请求可以有限重试;对于进度保存、考试提交等写请求,应携带幂等键,不能简单重放。
constRETRYABLE=['ETIMEDOUT','NETWORK_ERROR']exportasyncfunctionrequest(options){constrequestId=options.method==='POST'?uuid():undefinedconstres=awaituni.request({url:BASE_URL+options.url,method:options.method||'GET',data:options.data,timeout:8000,header:{token:userStore.token||'','X-Request-Id':requestId}})if(res.statusCode===401||res.data.code===401){awaituserStore.refreshOrLogout()}if(res.statusCode!==200||res.data.code!==200)thrownormalizeError(res)returnres.data}接口错误需要统一映射为可展示文案,同时保留 traceId 上报日志。这样用户看到“请重试”时,研发仍能在服务端定位具体请求。
三、平台能力适配:条件编译不等于复制三份页面
#ifdef APP-PLUS、#ifdef MP-WEIXIN和#ifdef H5适合包住平台属性或原生调用,不适合在每个业务页面复制完整模板。可以定义播放器适配接口:
exportconstplayerAdapter={play:()=>videoContext?.play(),pause:()=>videoContext?.pause(),seek:(seconds)=>videoContext?.seek(seconds),fullscreen:()=>videoContext?.requestFullScreen()}App 侧可接入后台音频和原生权限,小程序侧重点是网络变化与组件限制,H5 侧需要处理自动播放策略、浏览器缓存和全屏 API。统一接口让课程业务只关心“播放、暂停、跳转、错误”这些领域事件。
四、播放状态机:恢复、上报和退出要有明确时序
播放器可以抽象为INIT -> LOADING -> PLAYING -> PAUSED/ENDED,任意阶段都可能进入ERROR。进入课程页先获取播放地址和历史进度,只有两者准备完成后才初始化initialTime,否则容易出现先从 0 秒播放、随后突然跳转的闪动。
@TransactionalpublicvoidsaveProgress(LonguserId,ProgressCommandcmd){checkChapterBelongsToCourse(cmd.getChapterId(),cmd.getCourseId());Progressold=progressMapper.find(userId,cmd.getChapterId());if(old==null||cmd.getCurrentTime()>=old.getCurrentTime()){progressMapper.upsert(userId,cmd.getChapterId(),Math.min(cmd.getCurrentTime(),cmd.getDuration()),cmd.getRequestId());}}客户端按时间节流上报,暂停、退出和结束时立即 flush;服务端以章节为粒度做最大值更新,并校验currentTime <= duration。这可以避免弱网重试导致进度回退,也能降低频繁写库的压力。
五、性能与可观测性:从“感觉流畅”变成可量化
性能优化要拆成网络、渲染、媒体和状态四层:接口分页与缓存课程元数据;列表触底加载、图片懒加载和分段渲染;视频先显示封面,进入可视区后再创建播放器;Pinia 按字段更新,避免进度变化触发整页重绘。
可观测指标建议包括:首屏渲染时间、接口 P95、视频起播耗时、播放错误率、进度上报成功率、页面崩溃率。指标按端、系统版本、机型和网络类型切分,才能识别“某一类设备在弱网下起播失败”的真实问题。
六、安全与发布:移动端只是可信边界之外
课程视频地址不应硬编码在前端。服务端鉴权后返回短时效签名 URL,接口仍需校验用户是否拥有课程权限;客户端 token 放在安全存储中,日志禁止打印 token 和完整播放地址。发布前还应检查隐私权限说明、异常上报脱敏和不同端的资源包大小。
七、总结
一套跨端代码的价值,最终取决于是否建立了清晰的工程边界。UniApp 负责多端交付,适配层负责平台差异,请求层负责失败语义,播放器状态机负责断点续播,进度接口负责幂等与一致性,指标体系负责持续改进。
如需私有化部署报价、远程产品演示,可访问官网 https://www.weavecodes.com/ ,私信作者领取企业落地案例。
断点续播,进度接口负责幂等与一致性,指标体系负责持续改进。