我前阵子刚交付完一个成人教育机构的课程学习考试系统,技术栈正好就是标题里这对组合:后端用php,前端用uniapp,同时出APP和微信小程序。整个项目从需求梳理、数据库设计、考试判分逻辑,到双端兼容、上架审核,踩了不少坑也总结了不少经验。这篇就把这个系统从0到1的完整思路写出来,重点放在那些光看文档根本不会告诉你的细节上,给同样在选型或正在开发这类系统的朋友做个参考。
1. 为什么是php+uniapp:这套组合恰好踩中成人教育项目的软肋
1.1 先盘业务:成人教育的学习考试系统到底要哪些东西
成人教育和K12或者职业培训最大的区别在于:学员的时间碎片化严重,很多人是边工作边学历提升,所以系统首先要解决“随时打开就能学”的问题。APP和小程序双端不是锦上添花,而是刚需——小程序负责轻量入口和社交裂变,APP负责更稳定的考试环境和离线能力。
业务模块拆下来其实就三大块:课程学习、在线考试、后台管理。
课程学习不只是放几个视频,成人教育特别看重“学习进度可追踪”。教务老师要能导出每个学员学了哪些章节、学了多少时长、有没有刷课嫌疑。考试模块更麻烦,要支持题库管理、自动组卷、限时答题、自动判分,还得考虑防作弊——成人教育的考试虽然不像高考那么严格,但机构需要成绩有公信力。后台管理则是给教务和财务用的,课程上架、学员管理、题库录入、成绩统计,全是刚需。
这些需求决定了技术选型的几个硬指标:开发周期要短、多端覆盖要快、后期维护成本要低。机构老板不会给你半年时间慢慢磨,最好一两个月能看见能用的东西。
1.2 php做服务端到底行不行:别被“php不行”的偏见带偏
先说结论:这类业务系统用php开发,完全够用,而且有它的独特优势。
很多人一听到php就想到老项目、想到“性能差”,其实那是历史包袱。php 7以后的性能相比之前是翻倍级别的提升,配合 opcache 扩展,处理这种中低并发的业务系统绰绰有余。我这里用的是 ThinkPHP 8,选它不是因为它多先进,而是因为:
- 生态成熟,市面上能找到的管理后台模板和扩展包非常多,自己能省大量重复造轮子的时间
- ORM和查询构造器写起来直观,尤其处理课程、题库这类结构化数据非常顺手
- 部署简单,一个nginx加php-fpm就能跑,机构自己的服务器或者云主机随便都能搞定
- 招人容易,php的开发者基数大,后期甲方想自己找人维护也不会被绑定
成人教育系统的并发量说实话不会特别高。一个机构几千名学员,真正同时在线学习的可能就几百人,考期集中的时候接口压力会大一些,但通过数据库索引优化、查询缓存、接口限流完全顶得住。没必要为了“看起来高级”去上Java微服务全家捅,那是给自己找麻烦。
1.3 uniapp一套代码双端跑:成熟方案但也有认知盲区
uniapp在这类项目里的价值不用多吹,一套vue代码编译出iOS APP、Android APP、微信小程序,开发效率确实高。但我要提醒几个很容易忽略的点:
| 对比维度 | 微信小程序端 | APP端 |
|---|---|---|
| 运行环境 | 微信浏览器内核,能力受限 | 系统WebView + 原生渲染,能力更强 |
| 登录授权 | uni.login获取code,后端换openid | 需手动集成微信/苹果/手机号登录 |
| 文件存储 | 本地缓存上限10MB,超出要清理 | 本地存储空间大得多 |
| 更新机制 | 审核通过即发布,基本实时生效 | 需重新打包上传应用市场,审核周期长 |
| 用户粘性 | 适合学习入口和分享传播 | 适合高频考试场景,体验更沉浸 |
这里有个核心认知:uniapp跨端不等于“写得一份代码就完事”,条件编译是必须用好的。比如考试倒计时、定时器这类逻辑,在微信小程序里页面切到后台定时器会被挂起,而APP端就不一样,这两端的行为差异,后面第4节我会专门展开讲。
2. 课程学习模块的数据模型与学习进度上报设计
2.1 课程表结构怎么拆:从专业、课程到课时
课程学习这个模块,数据模型设计得好不好,直接决定后面开发顺不顺畅。我拆成五张核心表:
- 专业表:一个机构下有多个专业方向,比如“工商管理本科”“会计专科”,成人教育很常见
- 课程表:关联到某个专业下,一门课程有名称、封面、简介、任课老师
- 章节表:课程下的章,比如“第一章 绪论”
- 小节表:章节下的课时,每个课时对应一个课件视频或图文内容
- 课件表:小节内容区,区分视频、文档、音频、图文链接等类型
这样拆的好处是树形结构清晰,左边小程序端可以做出层级目录,右边后台管理可以按专业、按课程批量维护。简单给个建表参考:
CREATE TABLE `course_chapter` ( `id` int(11) NOT NULL AUTO_INCREMENT, `course_id` int(11) NOT NULL COMMENT '所属课程ID', `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父级ID,0为章', `title` varchar(255) NOT NULL COMMENT '章节标题', `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序号', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_course_sort` (`course_id`, `sort_order`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程章节表';注意parent_id这个字段,让章节表同时承载章和小节,用parent_id区分层级。这样用一条sql就能查出整个课程目录树,接口返回也灵活。
2.2 学习进度上报:防刷时长和断点续学的实现思路
学习进度是这套系统的核心数据,教务老师盯得最紧的就是这个。一开始我图省事,设计成“学员点进视频,播放结束就上报100%”。结果上线第一周就发现有人挂机刷课,进度数据全废了。
后来重构成四层校验:
- 上报接口带视频ID、小节ID、已播放位置、本次连续观看时长
- 服务端校验上次上报的时间戳,间隔低于60秒的视为无效重复上报
- APP端每10秒心跳上报一次,小程序端因为生命周期限制,在onHide时上报一次,onShow时再补一次
- 服务端记录累计有效时长,只有超过该小节课时的80%才标记为“已完成”
这里的关键是APP和小程序的上报策略不能一模一样。小程序在页面隐藏时定时器会停,如果依赖定时器上报必然丢数据,所以在onHide和onUnload里强制补一次上报,永远不会丢进度。APP端则可以在全局逻辑层做心跳,哪怕用户切去看别的应用,回到APP后也能恢复上次位置。
断点续学也依赖这个上报机制,服务端存住“已播放位置”,用户下次进入小节时,前端拿到这个位置直接跳到对应时间点继续播放。实际做下来用户体验提升非常明显,尤其是视频课多的专业。现在市面上的视频时长统计服务很多,但如果项目预算有限、不想接第三方,这套自研方案完全够用。
2.3 配合小程序的分享裂变:onShareAppMessage的配置细节
成人教育机构的获客渠道里有很大一部分是学员转介绍,所以分享功能不能只是“有”,必须做得巧。uniapp的onShareAppMessage在小程序端是原生支持的,但有几个细节:
// 页面内定义分享 onShareAppMessage() { return { title: '我正在学《管理学原理》,你也来试试', path: '/pages/course/detail?id=' + this.courseId + '&from=share_' + this.userId, imageUrl: this.shareCover } } // 分享到朋友圈 onShareTimeline() { return { title: '每日一课,提升学历就现在', query: 'courseId=' + this.courseId } }注意几个点:
- path里的query带着userId,就可以在课程详情页识别分享人,实现“邀请有礼”的发券逻辑
- imageUrl不传的话默认截取页面截图,效果通常很丑,我建议专门做一张分享卡片图传到CDN
- 分享标题别写“快来上课”这种,成人教育转化率高的是“学什么有用”“我在学XX”这种社交背书式文案
APP端的话,分享逻辑就完全不一样了,安卓/iOS需要集成各自的分享SDK,个人开发者的APP分享到微信还要申请开放平台账号,审核周期一周左右,这个时间成本要提前算进项目计划里。
3. 考试系统的几个硬骨头:组卷、判分、防作弊一起说清楚
3.1 智能组卷:按章节抽题、难度配比是怎么算的
考试模块是整个系统技术含量最高的部分,没有之一。成人教育的考试一般有两种场景:章节测验和期末统考。章节测验相对简单,从当前章节题库随机抽题;期末统考则需要“组卷”,要满足不同章节的分值占比、难度分布。
我这里设计的组卷逻辑是:
- 后台创建试卷时配置规则:总分、题型数量、各章节题数占比、难度占比
- 组卷接口根据规则,从题库里按章节分组,用数据库随机取数
- 组成试卷后,把试卷和题目快照存起来,避免题库后来改动导致试卷内容变化
具体实现时,随机抽取我用的是MySQL的ORDER BY RAND()配合LIMIT。题库量小的时候没问题,题库过万了会有点性能压力,但是考试系统这种低频操作可以忍,真扛不住再改成先查ID列表再随机取ID。
难点在于“难度配比”。我题库每道题都有一个难度字段,取值1到5。组卷时按规则指定每个难度段的题数,比如难度3的题占比40%、难度4占比30%、难度5占比30%。抽题时就是按难度和章节两个维度的交集条件来抽取:
$where = [ 'chapter_id' => $chapterId, 'difficulty' => $difficultyLevel, 'status' => 1 ]; $ids = Db::name('question') ->where($where) ->orderRaw('RAND()') ->limit($count) ->column('id');组卷完再做一次总分校验,检查抽出来的题分值加起来是否等于试卷设定总分,不等就自动换题补齐。这个校验特别重要,否则就会出现考试界面上总分100但实际题目总分是95的尴尬事故,我就犯过这个错。
3.2 提交判分:客观题的比对逻辑和坑
成人教育的考试大部分是客观题:单选、多选、判断。判分逻辑听起来简单,但多选题是个容易翻车的地方。
单选和判断题好办,答案字段存一个字符串,直接比对就好。多选题麻烦在:全选对才得分,多选、少选、错选都不得分或部分得分。不同机构的规则还不一样,有的要全对才给分,有的少选按比例给分。我的做法是把“判分规则”做成试卷表的配置字段,判分时按规则走。
// 多选题判分示例 $correctAnswers = explode(',', $question['answer']); // 正确答案 $userAnswers = explode(',', $userAnswer); // 用户答案 sort($correctAnswers); sort($userAnswers); if ($correctAnswers === $userAnswers) { $score = $question['score']; // 全对 } elseif ($rule === 'partial') { // 少选给一半分 $intersect = array_intersect($correctAnswers, $userAnswers); if (count($intersect) === count($userAnswers)) { $score = $question['score'] / 2; } else { $score = 0; } } else { $score = 0; }另一个坑是选项顺序的问题。前面做防作弊时我会把选项顺序打乱,做题页显示的顺序和题库里存的不一样。这就意味着前端提交答案时不能传“第几个选项”,必须传“选项的内容值”。我用的是ABCD加上答案正文的方式存储,页面展示时随机打乱ABCD的映射关系,但内容值不变,判分就不受影响。
判分完成后,成绩要实时回写。前端交卷接口一次性提交全部答案,后端全部判完再返回总分和每题明细。注意接口要做幂等处理,防止用户重复提交导致成绩被覆盖成第二次的0分。我在考试成绩表里加了一个unique索引,约束“学员ID+考试记录ID”唯一,重复提交直接返回第一次的成绩。
3.3 防作弊:切屏检测、乱序选项和答题时间校验
成人教育考试防作弊不能做得像监考那么严,但至少要拦住明显的作弊行为,让成绩有说服力。我从三个层面做了设计。
第一层是页面级防切屏。小程序端监听onHide事件,一旦检测到用户切出考试页面超过3次,就弹窗警告并记录到后台。APP端同样用原生生命周期事件来监听,前端记录切屏次数,交卷时把次数一起提交,后台看到切屏次数异常的考试,成绩会打上标记由教务人工复核。
第二层是选项乱序和题目乱序。同一份试卷给不同学员的题目顺序、选项顺序都打乱,这就直接把“对答案”这种作弊方式的成本抬高了一大截。具体做法是:进入考试时在后端生成一份乱序配置,前端按照配置渲染。
第三层是时间校验。学员进入考试时服务端下发开考时间和考试时长,交卷时服务端会校验时长是否合理。比如一场60分钟的考试,学员如果40秒就交卷且全对,这明显有问题。校验逻辑是这样的:如果用时低于总时长的10%,考试成绩标记为“疑似异常”,提醒教务人工核查。
3.4 交卷中断的恢复处理
考试最怕的就是答到一半手机没电、小程序被系统杀掉、APP闪退,学员辛辛苦苦答的题全没了,那客服电话能被家长和学员打爆。这个问题必须提前设计好。
我的方案是本地自动保存+服务端草稿箱。前端每答完一题,就把当前答题状态写入本地缓存;每隔30秒自动向后端草稿接口提交一次。学员重新进入考试页面时,前端先检查本地缓存有没有未提交的答题记录,有就恢复,同时向后端拉取最新草稿,以较新的那份为准。
服务端草稿箱的表设计也简单,就是考试记录表加几个字段:draft_data(json格式的答题数据)、update_time、is_submitted。考试中途异常退出后,再进来时先查有没有未提交的草稿,有就提示用户“检测到未提交的答卷,是否继续作答”,用户确认后前端用草稿数据恢复答题界面。
这个功能看着不起眼,但在实际运营中用户好评度极高,成人教育学员很多是用手机在通勤路上考试的,网络和环境都不稳定,这份兜底方案能实打实减少投诉。
4. uniapp双端开发绕不开的兼容性坑位清单
4.1 登录态、缓存和定时器:三座最容易翻车的大山
uniapp做双端最浪费时间的地方就是“同代码不同行为”。表面看都是uniapp语法,跑起来才知道差别有多大。
登录态是第一个坑。微信小程序登录走的是uni.login拿code,然后后端拿着code去微信接口换openid和session_key。而APP端呢?如果是单纯手机号登录,走的是短信验证码流程;如果想支持微信登录,APP要去开放平台申请移动应用,审核下来又得等。我做的时候干脆分成了两条登录链路,小程序走微信授权,APP端走手机号验证码登录,后期再补微信登录。
缓存是第二个坑。uni.setStorageSync在小程序端有10MB的总大小限制,成人教育课程里如果有很多图文资料,学员看几门课缓存就满了。满之后写入会静默失败,表现就是“内容加载不出来”。后来我在设置页面做了“清理缓存”功能,并限制视频课件不允许缓存到本地,只缓存课程信息和学习进度这类轻量数据。
定时器是第三个坑,前面也提过。微信小程序的定时器在页面切到后台后会节流甚至冻结,考试倒计时如果依赖前端setInterval,切后台再回来时间就错了。我的解法是:倒计时不靠前端累加秒数,而是靠服务端时间戳,前端每秒计算一下“当前时间戳减开考时间戳”,并把每次切后台的时间点上报,回来时用服务端当前时间重新校准。这样哪怕小程序被冻结,回来后的剩余时间也依然是准确的。
4.2 manifest配置与打包上架:有些坑不在代码里
uniapp的manifest.json这个文件,大部分新手都不会认真看,但打包上架时大部分问题都出在这里。
APP端安卓打包一定要搞清楚证书。用HBuilderX的云打包,需要一个Android数字证书,证书的别名、密码、有效期这些信息要记牢。上架安卓应用市场,不同市场对安装包的要求还不一样:华为、小米、OPPO、vivo这些主流市场都要软著或授权证明,对targetSdkVersion有硬性要求,低了根本不给过。我是提前把targetSdkVersion配置到最新的稳定版本,避免打包出来被市场中心弹回。
隐私政策也是一个不能漏的环节。安卓应用市场现在强制要求:APP首次启动时要弹窗展示隐私政策,用户同意后才能开始采集数据。uniapp的manifest里也专门有这项配置,要在“App常用其它设置”里勾选“使用摇一摇或传感器”等权限声明,不然审核可能在隐私权限这一关卡你半个月。
另外要注意uniapp和uniappx的区别。很多人以为uniappx是uniapp的升级版,可以无缝迁移,其实这是两套东西。uniappx的性能更强,用的是自己的编译器,但生态和三方插件不兼容,老项目迁移成本很高。成人教育系统这种偏业务型、依赖大量现有插件的项目,用成熟的uniapp更稳妥。
4.3 自定义分享和页面标题这类小细节也别忽视
分享功能上面讲过onShareAppMessage,这里补充一个易踩的坑:分享出去的页面,如果没配置条件编译,在APP端可能会报错。因为APP端没有onShareAppMessage这个生命周期,需要在同一个文件里用#ifdef MP-WEIXIN和#ifndef MP-WEIXIN做条件编译,把APP端的分享逻辑分开处理。
页面标题也是常见的小细节。小程序端的导航栏标题默认取pages.json里配置的navigationBarTitleText,但如果页面里通过uni.setNavigationBarTitle动态改标题,要注意某些机型上标题长度超过十几个字会被截断成省略号。成人教育课程名字通常很长,比如“2024年成人高考专升本《高等数学》基础精讲班”,建议前端做字符串截断处理,核心课程信息保留在前面,后面加“...”就行。
4.4 考试倒计时和canvas导出的隐蔽问题
我在开发里遇到过两个比较偏的问题,顺手说一下。
第一个是考试倒计时的显示精度。微信小程序里用setInterval做每秒刷新,如果页面里有复杂渲染,定时器会丢帧,显示出来的倒计时偶尔会跳秒。解决办法是:定时器里不做任何复杂计算,只执行一个“更新时间戳”的动作,秒数变化交给vue的computed计算属性去驱动视图更新,把渲染压力从定时器里拆出去。
第二个是canvas导出白图,这个在iOS的safari里特别容易触发。uniapp做成绩单分享海报时,我用了canvas绘制图片,安卓端没问题,iOS端导出的图片有时是空白的。核心原因是在绘制时,图片还没加载完成就执行了uni.canvasToTempFilePath。解决方式是把海报绘制放到图片的onload回调里,或者先uni.getImageInfo获取到图片完整信息后再开始绘制。这个东西我排查了一整天才定位到,说出来给大家避坑。
5. php后台管理的实现重点,不止是增删改查
5.1 题库管理和Excel批量导入
成人教育机构的题库动辄几千上万道题,让管理员一道一道在后台录,是不现实的。Excel批量导入是刚需,这个功能做得好不好,直接决定教务老师愿不愿意用这套系统。
我用的phpoffice/phpspreadsheet这个库来解析Excel。流程是:上传Excel文件,读取内容,逐行校验,错误列表逐行返回,让管理员下载一个错误报告去修正。
关键点是校验一定不能省。Excel里常出现的坑有:公式单元格读取到的是公式字符串而不是计算结果、日期字段读出来变成一串数字、多选答案用顿号分隔但系统里存的是逗号分隔、题目选项里带有换行符等等。我的校验逻辑是这样写的:
// 逐行校验示例 foreach ($rows as $index => $row) { $errors = []; $questionType = trim($row[0]); $questionContent = trim($row[1]); if (empty($questionContent)) { $errors[] = '题目内容不能为空'; } if (!in_array($questionType, ['single', 'multiple', 'judge'])) { $errors[] = '题型不合法'; } $answerRaw = trim($row[6]); // 统一答案分隔符,兼容中英文逗号、顿号 $answer = str_replace([',', '、'], ',', $answerRaw); // 多选答案必须包含至少2个选项 if ($questionType === 'multiple' && count(explode(',', $answer)) < 2) { $errors[] = '多选题答案不能少于2个'; } if (!empty($errors)) { $errorRows[] = ['row' => $index + 1, 'errors' => implode(';', $errors)]; } }还有一个隐蔽的流程设计问题:导入题库是一次性全量导入,还是增量导入?我的建议是增量导入,文件里带一个“科目ID”字段,后台先选科目再上传Excel,导入的题目自动归属这个科目。这样既不会误覆盖已有题库,也能做到分科目管理。
5.2 成绩统计接口和导出
成绩统计这部分,教务老师要的不是花哨的图表,而是几个硬指标:每个学员学了多少课时、考试平均分、通过率、补考名单、每个章节的平均得分率。
课程学习统计的SQL要重点注意,因为涉及多表关联,不加索引会非常慢。我的课程学习记录表给user_id、section_id、course_id都建了索引。统计学员某门课的学习进度时,单条SQL就能跑出来:
$progress = Db::name('study_progress') ->where('user_id', $userId) ->where('course_id', $courseId) ->count('DISTINCT section_id'); $totalSections = Db::name('course_section') ->where('course_id', $courseId) ->count('id');导出功能我用的是常用方案:先按条件查询出数据集合,然后用PHP生成CSV。这里有个Excel兼容坑:直接用Excel打开UTF-8编码的CSV会出现中文乱码,需要给CSV文件加\xEF\xBB\xBF这个UTF-8 BOM头,或者在导出时用GBK编码。我在函数开头就打上BOM,然后直接输出CSV内容,省得每次转换编码。
5.3 管理员操作日志
成人教育系统里管理员不止一个,教务、财务、客服都可能有后台权限。万一有管理员误删了课程或者改了成绩,没有操作日志根本查不出来。我加了一个简单的操作日志表,记录管理员ID、操作模块、操作类型、操作内容、IP、时间。
实现不难,就是定义一个公共的日志方法,在所有增删改的controller里调用一下。刚开始做的时候觉得烦琐,但上线运行两个月后,有次一个课程被误下架,教务和财务互相推,我直接查日志,两分钟定位到是哪位管理员几点几分操作的,瞬间解决纠纷。这个功能强烈建议大家做,成本极低,价值极高。
6. 上线后的几个高频问题处理经验
6.1 小程序年审、备案和类目审核,每个都卡过
小程序上线后不是一劳永逸的,微信小程序每年要年审,不年审会被暂停服务。年审收费30块,但要注意审核期间服务是不中断的,只是新版本不能发布。所以时间上要打好提前量,别赶在考试季快到了才想起来年审。
还有一件很多人不知道的事:自2023年9月起,新注册的微信小程序必须完成备案才能上架,老的小程序也陆续要求补备案。备案需要提供营业执照、法人身份证等资料,走的是腾讯云或阿里云的备案通道,一般要7到20天。这绝对会影响上线时间,一定要把备案周期算进整个项目排期里。
类目审核这块,成人教育类小程序最好选择“教育 > 在线视频课程”或“教育 > 教育信息服务”这个类目。选择类目时需要上传对应的资质证明,有的类目要求提供办学许可证或ICP备案号。如果机构资质不全,可以先选择比较宽泛的“教育信息服务”,但功能说明里要尽量避免出现“培训”“办学”这类敏感字眼。这个不是教你钻空子,是让你按规则选对最匹配你现有资质的类目,避免审核反复。
6.2 接口慢和并发超时的排查套路
系统上线初期一切正常,一到考试高峰期就频繁出现接口超时,这种问题我在项目上线第三周就遇到过。排查下来三级台阶走完就定位了。
第一步,查慢SQL。开启MySQL的慢查询日志,把执行时间超过1秒的SQL捞出来。我当时发现最严重的一条是成绩排名接口,要统计全机构考试排行,写法是ORDER BY score DESC直接全表排序,没加索引,几千条记录就慢得不行。加上考试记录表的score索引后,快了很多。
第二步,查接口层有没有该加缓存的地方。课程列表、首页banner这类更新频率低但访问频率高的接口,直接用Redis缓存,设置5到10分钟的过期时间。考试题库和试卷草稿这类要求实时准确的,不走缓存。
第三步,调整php-fpm配置。默认情况下php-fpm的pm.max_children设置偏保守,并发一上来就出现504。我根据服务器内存和单个php进程的平均内存占用(大约30到50MB)算了一下,给2核4G的云主机配置了pm.max_children = 40,pm.start_servers = 10,pm.min_spare_servers = 5,pm.max_spare_servers = 20,高峰期基本够用。
下面是实际用的调整参考,结合服务器内存配置微调:
pm = dynamic pm.max_children = 40 pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 pm.max_requests = 1000pm.max_requests = 1000这个设置很多人会忽略,它的作用是让每个php进程在处理完1000个请求后自动重启,防止内存泄漏堆积。
6.3 线上问题的日志追踪法
最后分享一个很朴素但好用的经验:无论开发时考虑多周全,线上一定会出问题,所以日志系统从一开始就要建好。
我的做法是三个层级的日志:thinkphp自带的运行日志记录所有SQL和异常;nginx access log记录所有接口请求和响应状态码;前端在考试异常时主动调用一个上报接口,把报错信息、机型、小程序版本号、网络状态发到后端。这样线上出了诡异问题,三段日志一对照,基本十分钟就能定位。
比如有一次有学员反馈“考试中途闪退”,我查了前端上报日志发现是某个机型的内存溢出,再查nginx日志确认接口响应正常,很快就判断是前端渲染层的问题,然后针对性做了页面销毁时的清理工作。如果没有这套日志体系,这种偶发性问题可能得靠用户口述猜上一周。
实际开发成人教育课程学习考试系统,除了技术本身,还有很多跟业务方沟通确认的地方,比如判分规则、防作弊强度、学习时长的认定标准,这些都会直接影响技术实现的上限。我的体会是先把这些业务规则用文字列出来跟机构确认清楚,再动工写代码,能少走一半弯路。