简介:面向计算机专业毕业设计及小程序开发学习者的微信小程序移动学习平台完整项目,配合源码、数据库和论文,可直接作为毕设或课程设计的参考工程。平台基于SSM后端架构与微信小程序前端,涵盖在线课程、课后测试、学习资料下载等模块,并带有学习行为数据采集与分析功能,便于做个性化推荐和教学辅助。压缩包共1311个资源文件,大小约18.68MB,主要包含Java后端逻辑与MyBatis映射、vue页面组件、js脚本、小程序wxml/wxss界面、png/svg图标,以及sql初始化脚本和doc论文说明,目录层级清晰、类型搭配完整。已有103人浏览学习,适合需要快速理解小程序+SSM全栈项目结构,并希望能对照源码、数据库设计文档进行二次开发的学习者。
1. 微信小程序移动学习平台,源码数据库和论文一起交付意味着什么
如果你在数据库课程设计或毕业设计的搜索框里敲下“微信小程序移动学习平台源码数据库论文”,大概率是想要一套能直接运行、能写进文档、能应付答辩的完整闭环。很多人误以为这只是一份代码压缩包,实际上它应该包含三层东西:可运行的小程序前端、合理的后端数据库设计、以及能解释“为什么这么设计”的论文正文。对于计算机相关专业的学生或刚入行的开发人员来说,最痛苦的不是写代码,而是把业务逻辑和数据表结构对应起来,再把这些对应关系写进论文的第三章和第四章。本文就顺着这个标题,拆开讲一套最常见的移动学习平台是怎么从零搭起来的,数据库表怎么建、小程序端怎么调接口、论文里哪些部分可以用图表直接撑起来。不依赖任何虚构的项目实录,只讲这个领域里最通用、最可靠的实现路径。
2. 移动学习平台的功能边界与数据库设计先行
2.1 学习平台必须包含哪些核心模块
在写第一行代码之前,先把业务模块画清楚。一个能在课程设计中拿得出手的微信小程序移动学习平台,至少要覆盖四类角色和五个功能域。四类角色是学生、教师、管理员和访客,五个功能域是课程展示、学习记录、测验评价、互动交流和系统管理。不要一上来就想着做视频直播或社区发帖,那会把数据库设计和论文篇幅撑爆,答辩时也讲不清楚。
具体到页面层面,学生端常见的小程序页面有课程列表页、课程详情页、章节学习页、测验答题页、成绩查看页和个人中心页。教师端可以做成同一个小程序内的不同角色视图,也可以单独做一个简易的管理后台网页。管理员负责用户管理和内容审核,通常不需要在小程序里出现。访客只能浏览课程简介,不能进入学习页面。
这些模块对应的核心流程是:学生登录小程序、浏览课程列表、进入章节学习、记录学习进度、参加章节测验、查看测验成绩。教师登录后台、上传课程资料、维护章节内容、发布测验题目、查看学生成绩统计。整个流程里,数据流是单向且清晰的,非常适合用 MySQL 加 RESTful API 来实现。
2.2 数据库表结构如何支撑业务闭环
数据库设计是整个项目的骨架。常见的做法是设计六到八张核心表,不要贪多。第一张是用户表,字段包括用户ID、微信OpenID、昵称、头像、角色(学生或教师)、手机号、创建时间。OpenID 是小程序登录的唯一凭证,必须设置唯一索引,这是后续所有业务查询的基础。
第二张是课程表,字段包括课程ID、课程名称、课程简介、封面图URL、教师ID(外键关联用户表)、创建时间、上下架状态。第三张是章节表,字段包括章节ID、课程ID(外键)、章节标题、章节序号、视频URL或富文本内容、预计学习时长。第四张是学习记录表,这是整个平台最核心的表,字段包括记录ID、学生ID、章节ID、学习状态(已完成或进行中)、学习时长、最后学习时间、完成时间,并且要为“学生ID+章节ID”建联合唯一索引,防止重复记录。
第五张是测验表,字段包括测验ID、章节ID、测验标题、总分、及格分、题目数量、发布时间。第六张是题目表,字段包括题目ID、测验ID(外键)、题干、选项(用JSON存)、正确答案、解析、题型(单选或多选)。第七张是答题记录表,字段包括记录ID、测验ID、学生ID、得分、答题详情(用JSON存每道题的对错)、提交时间。第八张是评论表,字段包括评论ID、课程ID、用户ID、评论内容、点赞数、评论时间。
2.2.1 字段类型和索引怎么选更合理
对于移动学习平台这种中小型项目,字段类型的选择有固定套路。用户表的OpenID用VARCHAR(64),昵称用VARCHAR(50)或VARCHAR(100),手机号用VARCHAR(20)。课程表的封面图URL用VARCHAR(200)就够。章节表的富文本内容用TEXT类型,因为会存大于255字节的HTML或带格式的文本。学习记录表的学习时长用INT存秒数,不要用DATETIME存时长,否则计算累计学习时间时要写一堆日期函数。得分字段用DECIMAL(5,2)或直接INT,看是否需要保留小数。
索引方面,学习记录表除了联合唯一索引外,还要给“最后学习时间”建普通索引,因为论文里常用的“统计最近一周活跃人数”就要按这个字段分组查询。答题记录表要给学生ID建索引,否则查“某个学生的所有测验成绩”时会全表扫描。评论表要同时给课程ID和用户ID建索引,方便详情页按时间倒序拉取评论列表。
2.2.2 数据库初始化脚本的关键SQL语句
CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL, `nickname` VARCHAR(100) NULL, `avatar_url` VARCHAR(200) NULL, `role` TINYINT NOT NULL DEFAULT 2 COMMENT '1-教师 2-学生', `phone` VARCHAR(20) NULL, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE INDEX `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `course` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `intro` TEXT NULL, `cover_url` VARCHAR(200) NULL, `teacher_id` INT NOT NULL, `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1-上架 0-下架', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), INDEX `idx_teacher` (`teacher_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `study_record` ( `id` INT NOT NULL AUTO_INCREMENT, `student_id` INT NOT NULL, `chapter_id` INT NOT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0-进行中 1-已完成', `duration_seconds` INT NOT NULL DEFAULT 0, `last_study_time` DATETIME NULL, `finish_time` DATETIME NULL, PRIMARY KEY (`id`), UNIQUE INDEX `uk_student_chapter` (`student_id`, `chapter_id`), INDEX `idx_last_study` (`last_study_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;上述SQL只展示了最核心的三张表,其余章节表、测验表、答题记录表的建表逻辑完全一致。每张表的主键都设置成INT自增,InnoDB引擎保证事务支持,utf8mb4字符集保证中文和表情符号都能正常存储。外键在实际项目中建议不用物理外键约束,而是在应用层做逻辑关联,原因是课程设计中经常要删除测试数据,物理外键会导致删除顺序各种受限。重点理解study_record表的联合唯一索引是防重设计的关键,学生在同一章节下的学习记录只能有一条,后续做“继续学习”功能时直接按这个索引做插入或更新。
2.3 数据表之间的关系和ER图的画法
论文中必须有数据库ER图,这是评审老师必看的内容。实体关系很简单:一个用户(教师)可以发布多门课程,一门课程包含多个章节,一个学生可以对多个章节产生多条学习记录,一个章节可以有一个测验,一个测验包含多个题目,一个学生可以做多次测验并产生多条答题记录。这些关系画成ER图,就是几种常见的一对多关系。
注意一个容易被问倒的细节:课程和教师是多对一还是多对多。如果平台允许讲师团队共同维护一门课,那就是多对多,需要中间表。但在课程设计这个规模下,做成多对一即可,论文里也能少画一张表。如果答辩时老师问为什么不做多对多,可以回答“当前平台定位是个人教师发布课程,待后续版本扩展讲师团队后再引入课程-教师关联表”。
ER图画好后,还需要在论文里写一段文字说明每张表的职责和主要字段。这段文字可以直接复用建表语句的注释,加上一段“本章节设计了X张数据表,其中study_record表通过联合唯一索引保证了同一学生学习同一章节的数据唯一性,answer_record表通过JSON字段存储答题明细以降低表数量”之类的描述。
3. 小程序前端的搭建与核心页面实现要点
3.1 用HBuilderX创建项目还是用微信开发者工具
微信小程序移动学习平台的开发起点是选工具。两种主流方式:直接用微信开发者工具创建原生小程序项目,或用HBuilderX创建uniapp项目再编译到微信小程序。对于这份毕设或课设来说,原生小程序和uniapp各有优劣。原生小程序的特点是文档全、调试方便、微信新功能支持最快,适合一个人从零写。uniapp的特点是可以用Vue语法写一套代码,将来还能编译到H5和App,适合有Vue基础的人。
搜索热词里“uniapp微信小程序”出现频率很高,说明很多人选的是第二条路。如果走uniapp,项目的目录结构会多一层src目录,编译后生成dist/dev/mp-weixin再用微信开发者工具打开。这套模式的优点是页面组件化更顺手,缺点是多一层编译链,报错时堆栈信息比较绕。我个人建议没有Vue经验的人直接用原生小程序,少踩一层工具的坑。
3.2 小程序登录流程与用户身份绑定
微信小程序的登录流程几乎是固定套路。首先在小程序端调用wx.login获取临时code,然后把code发送到后端,后端拿着code到微信接口服务换取openid和session_key,再用openid去数据库查用户是否存在,不存在则自动创建一条新用户记录,最后返回一个自定义登录态token给前端。前端把token存入wx.setStorageSync,后续所有请求的header里带上这个token,后端拿token解析出用户ID再处理业务。
关键代码在后端,前端反而简单。以下是小程序端的登录调用片段:
wx.login({ success: res => { if (res.code) { wx.request({ url: 'https://your-domain.com/api/auth/login', method: 'POST', data: { code: res.code }, success: res => { const { token, userInfo } = res.data.data; wx.setStorageSync('token', token); wx.setStorageSync('userInfo', userInfo); // 登录成功后跳转到首页 wx.switchTab({ url: '/pages/index/index' }); } }); } } });后端拿到code后向微信接口发起请求的伪代码如下:
const result = await axios.get('https://api.weixin.qq.com/sns/jscode2session', { params: { appid: '你的小程序AppID', secret: '你的小程序AppSecret', js_code: code, grant_type: 'authorization_code' } }); const openid = result.data.openid; const user = await db.findUserByOpenid(openid); if (!user) { const newUser = await db.createUser({ openid, role: 2 }); } const token = jwt.sign({ userId: user.id }, 'your-secret-key', { expiresIn: '7d' });前端登录代码的关键是不要自己处理openid的解析,openid必须由后端从微信接口获取,这是微信官方安全模型的要求。后端的token建议用JWT实现,而不是自己搞随机字符串放Redis,因为JWT无状态且自带过期时间,方便在论文的“系统设计”章节说明实现思路。
3.3 加载页面与课程列表的页面生命周期处理
热词里有“修改刚进入的加载页面”,这在移动学习平台的首页开发中就是课程列表的加载体验问题。原生小程序的页面生命周期onLoad只触发一次,onShow每次进入页面都会触发。课程列表页的数据请求建议放在onLoad里做一次,然后加一个下拉刷新机制弥补数据新鲜度的问题。如果用onShow发请求,从详情页返回列表页时会重复加载,体验并不好。
加载状态需要分三层:首次进入的整页Loading、下拉刷新的导航栏Loading、上拉加载更多的底部Loading。首次加载时用wx.showLoading配合wx.hideLoading控制,或者在页面里放一个wx-loading组件。现在更推荐的做法是页面骨架屏加wx.request的complete回调里关闭Loading,数据返回后再setData渲染列表。注意setData不要传太大对象,小程序对单次setData有性能限制,课程列表一次传20条就够了。
3.3.1 课程列表的分页加载参数
课程列表的接口一般设计成GET /api/course?page=1&pageSize=10。返回的数据结构包含三块:当前页列表、总条数、是否还有下一页。前端维护一个page变量,初始为1,每次触底时page+1再去请求,拿到返回数据后做数组拼接。后端分页用MySQL的LIMIT offset, size即可。
以下是小程序端的触底加载代码:
onReachBottom() { if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true, page: this.data.page + 1 }); this.fetchCourses(); }, fetchCourses() { const { page, pageSize, courses } = this.data; wx.request({ url: `https://your-domain.com/api/course?page=${page}&pageSize=${pageSize}`, method: 'GET', header: { Authorization: wx.getStorageSync('token') }, success: res => { const newList = res.data.data.list; this.setData({ courses: [...courses, ...newList], hasMore: res.data.data.hasMore, loading: false }); } }); }onReachBottom是小程序页面自带的触底回调,不需要额外引入滚动组件。核心逻辑是hasMore和loading双重锁,防止重复请求。page和pageSize分开传递,方便后端做参数校验,也方便论文中描述“后端接口支持分页参数,前端采用滚动加载策略”。
3.4 章节学习页的视频播放与进度上报
章节学习页是整个移动学习平台功能最重的页面。多数课程内容以视频为主,小程序播放视频最稳妥的方式是使用video组件加src属性指向后端返回的视频URL。开发阶段可以用腾讯云点播或阿里云OSS的临时链接,但毕设阶段放在自己服务器上直接用Nginx提供视频文件访问也能跑通。
进度上报是学习记录表发挥作用的地方。常见做法是监听video组件的timeupdate事件,这个事件会高频触发(大约每秒4次),如果每次触发都发请求上报进度,后端会被打爆。正确的做法是前端做节流,每15秒或30秒向后端上报一次当前播放位置,后端更新study_record表中的duration_seconds字段。
<video id="chapterVideo" src="{{videoUrl}}" bindplay="onVideoPlay" bindtimeupdate="onTimeUpdate" bindended="onVideoEnded"></video> onTimeUpdate(event) { const currentTime = Math.floor(event.detail.currentTime); const lastReported = this.data.lastReported || 0; if (currentTime - lastReported >= 15) { this.reportProgress(currentTime); this.setData({ lastReported: currentTime }); } }, reportProgress(currentTime) { wx.request({ url: 'https://your-domain.com/api/study/progress', method: 'POST', data: { chapterId: this.data.chapterId, currentTime, duration: this.data.videoDuration }, header: { Authorization: wx.getStorageSync('token') } }); }这段代码里的节流间隔是一个可调参数,15秒是常见经验值。.reportProgress发送的数据里必须包含chapterId,因为后端要根据student_id + chapter_id找到唯一那条学习记录再更新字段。如果这条记录还不存在则要用INSERT ... ON DUPLICATE KEY UPDATE的写法先创建再更新,这正好呼应了数据库设计章节里的联合唯一索引。
3.4.1 视频播放结束与章节完成判定
bindended事件表示视频播放结束,这时要向后端上报“章节已完成”的状态。后端收到请求后把study_record表的status字段置为1,并写入完成时间。这里有一个逻辑坑:很多学生只看了一半就划走,后端点“完成”按钮时如果把status直接置为1,就会造成虚假学习进度。更严谨的判定方式是后端校验前端上报的累计观看时长是否达到视频总时长的80%以上,达标才置为完成。
onVideoEnded() { wx.request({ url: 'https://your-domain.com/api/study/complete', method: 'POST', data: { chapterId: this.data.chapterId }, header: { Authorization: wx.getStorageSync('token') } }); }这里的“80%阈值”可以写进论文的方法部分,作为系统的一个严谨性设计。答辩时被问到“如何防止刷课”,这就是一个具体的回答点。
4. 后端API接口设计与管理员功能落点
4.1 后端框架选择与接口路由规划
移动学习平台的后端,在课程设计级别用什么框架区别不大。Java的Spring Boot、Python的Flask或Django、Node.js的Express都能胜任。选择依据是你的论文技术栈和答辩老师的偏好。如果论文里写了“基于Java Spring Boot的XXX系统”,那接口实现就用Spring Boot;如果整篇论文的语言风格更偏轻量,用Python写会更快。
接口路由规划采用RESTful风格。以学习记录相关接口为例:POST /api/auth/login完成登录,GET /api/course获取课程列表,GET /api/course/{id}获取课程详情含章节列表,GET /api/chapter/{id}获取章节内容,POST /api/study/progress上报进度,POST /api/study/complete标记完成,POST /api/exam/submit提交测验答案,GET /api/statistic/my获取个人学习统计。
后端接口实现时最容易忽略请求参数校验。比如上报进度接口必须校验chapterId是否为正整数,currentTime是否大于等于0。很多毕设的系统能跑通但答辩被问“如果前端传负数怎么办”就卡住。加一段简单的参数校验代码,会显著提升代码质量观感。
@PostMapping("/api/study/progress") public Result reportProgress(@RequestBody ProgressRequest request) { if (request.getChapterId() == null || request.getChapterId() <= 0) { return Result.error("章节ID不合法"); } if (request.getCurrentTime() == null || request.getCurrentTime() < 0) { return Result.error("播放进度不合法"); } // 业务逻辑 }这段Java代码展示了最基础的校验思路,放在Controller入口处或Service层开头都行。关键是让评审老师看到你的系统有防御式编程的意识,这是加分项。
4.2 教师端课程管理接口的“增删改查”具体实现
热词里“数据库增删改查”是学生搜索的高频词,对应到本系统就是教师端管理后台的四个基本操作。课程管理的新增课程接口是POST /api/course,修改课程是PUT /api/course/{id},删除课程是DELETE /api/course/{id},查询课程列表是GET /api/course?page=1&pageSize=10。
删除课程时有一个关键设计:如果课程下面已有关联的章节和学习记录,物理删除会导致外键悬空。常见做法是逻辑删除,给course表加一个deleted字段,删除时把值置为1,查询列表时统一加WHERE deleted = 0过滤条件。这段逻辑在论文里可以重点写,说明你理解了软删除解决数据一致性问题。
以下是在Spring Boot里实现逻辑删除的典型Service:
public boolean deleteCourse(Long courseId) { Course course = courseMapper.selectById(courseId); if (course == null) { return false; } course.setDeleted(1); return courseMapper.updateById(course) > 0; }selectById配合setDeleted(1)和updateById是MyBatis Plus风格的写法,比手写SQL更简洁。如果没有用MyBatis Plus,换成两行JPA代码或原生SQL也可以。重点不是框架本身,而是这套“查询后修改再做条件更新”的操作模式,可以避免直接用DELETE造成的连带问题。
4.3 管理员端统计报表与MySQL聚合查询
管理员或教师端看数据报表是不可或缺的功能,也是论文里体现数据库查询能力的关键章节。常用统计有三个:课程总学习人数、章节完成率、每日活跃学习人数。它们分别对应三条SQL,都是用聚合函数实现的。
-- 课程总学习人数(假设一门课的所有章节被学习过即视为学习过该课程) SELECT COUNT(DISTINCT student_id) AS learn_count FROM study_record WHERE chapter_id IN (SELECT id FROM chapter WHERE course_id = 1); -- 章节完成率 SELECT COUNT(*) AS total, SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS finished FROM study_record WHERE chapter_id = 10; -- 每日活跃学习人数 SELECT DATE(last_study_time) AS study_date, COUNT(DISTINCT student_id) AS active_users FROM study_record WHERE last_study_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(last_study_time);这三条SQL的注解作用:第一条用DISTINCT去重统计学生,第二条用CASE WHEN做条件计数判断完成率,第三条用DATE函数和GROUP BY按天聚合活跃人数。写进论文时把这三种查询模式分别对应到不同报表功能上,评审老师一眼就能看出你对SQL聚合查询的掌握程度。需要特别注意DATE_SUB(CURDATE(), INTERVAL 7 DAY)这行代码用于限定最近七天范围,是活跃统计类报表里最常见的条件写法和时间窗口设置方式。
5. 论文结构排布与源码数据库配套使用策略
5.1 论文目录和源码数据库如何对应
论文的章节排布通常遵循传统结构:第一章绪论写背景和意义,第二章相关技术介绍,第三章需求分析与总体设计,第四章系统详细设计与实现,第五章系统测试,第六章总结与展望。源码数据库与论文的对应关系是:数据库脚本对应第三章的概念结构设计和逻辑结构设计,后端代码对应第四章的接口实现,前端代码对应第四章的页面实现,测试数据对应第五章的测试用例设计。
其中最容易出问题的是第三章的“数据库设计”部分。很多论文只贴建表语句没有ER图,这是明显的扣分点。正确做法是先用PowerDesigner或draw.io把ER图画出来贴进论文,再写一段设计说明表,表格的列建议是:表名、字段名、类型、是否主键、是否外键、字段说明。把核心表的关键字段整理进表格,剩下不重要的字段可以略写。
5.2 论文中技术描述的常见套路与逐段写法
论文第二章的技术介绍部分要避免一个个技术名词孤立地堆概念。正确写法是结合项目说明选型理由,例如:为什么选微信小程序作为客户端,因为其免安装和微信内置入口适合碎片化学习场景;为什么选MySQL,因为数据量在中小规模下MySQL表现稳定且学习成本低。同样的套路应用到框架选型上,至少能写出三到四段有逻辑的文字。
第四章的“系统实现”部分,每个模块建议用“功能描述 + 关键代码 + 运行截图”三段式展开。代码不要超过15行,截图一定要清晰标注。学习记录模块可以贴上报进度的代码,测验模块可以贴解析判断题答案的代码,用户模块可以贴JWT登录的代码。这部分的核心是代码和文字配合起来讲清楚“做了什么”和“怎么做的”。
5.3 数据库脚本和演示数据如何调试更顺
拿到一份配套的数据库脚本,第一件事不是直接导入数据库,而是先整体浏览一遍SQL文件里的建表顺序。遇到外键关联的表,如果脚本里已经按依赖关系排好序,导入时一次就能成功;如果没有排序,导入时报错就要通过手动拆分SQL或加SET FOREIGN_KEY_CHECKS=0;来绕过限制。正确步骤是先备份自己的现有数据库,再关掉外键检查,导入完成后重新开启。
导入成功后不要急着启动后端,先检查几条关键数据。查询用户表中是否有测试账号,查询课程表中是否有课程记录,查询学习记录表中是否有样例进度数据。如果这三张表都有数据,说明演示环境和论文里的截图能对上,答辩前调试的阻力会小很多。
mysql -uroot -p your_database < study_platform.sql在执行这条命令时,your_database要先创建好。导入后再执行SELECT * FROM user LIMIT 5;看看能否查出数据,能查出来就说明数据库这关已过。
6. 移动学习平台的几种高级玩法与可扩展方向
一个微信小程序移动学习平台做完基础功能,如果拿到的源码质量和数据量都不错,可以继续做几个低成本高回报的扩展点。第一个是给学习记录增加连续打卡统计,这只需要在study_record表的基础上写一条SQL查最近七天每天是否有学习记录,前端画一个七连击的日历格子;第二个是给测验模块增加错题本功能,答错过的题目自动进错题集合,再次答题时优先从错题里抽;第三个是给教师端增加简单可视化大屏,用ECharts读后端的统计接口把数据画成仪表盘和折线图,这部分截图放进论文能明显提升视觉档次。
一个更实用的小技巧是如何“修改刚进入的加载页面”来提升答辩观感。默认小程序的启动页是首页,但如果改成课程列表页加一个自定义的欢迎语动画,答辩现场打开手机演示时的第一眼效果会好很多。具体改动方法是编辑app.json里的pages数组顺序,把页面路径顺序调换成目标页在前,再在目标页onLoad里加一段欢迎文案的渐显动画。要注意小程序冷启动时会先加载app.onLaunch里的登录逻辑,如果登录未完成前跳转会白屏,所以更稳妥的做法是保留首页为启动页,在首页onLoad里做一次wx.redirectTo跳转到课程列表页。
最后验证整个平台是否完备,可以用一条链路自测:新用户登录(自动注册)→ 浏览课程列表 → 进入课程详情 → 播放视频章节 → 上报进度(等15秒后看后台durations字段变化)→ 完成章节(看status字段变为1)→ 参加测验(提交答案后看得分是否按规则计算)→ 查看个人统计页的折线图数据。任何一步不通就往对应接口的日志或数据库记录里排查,这一步跑通,整个项目的可用性和论文的完整性就都坐实了。
本文还有配套的精品资源,点击获取