1. 项目目标与需求分析
1.1 学习过程管理系统到底要解决什么问题
先把这个项目的本质说清楚。很多人听到"在线学习系统",第一反应是做一个课程列表加视频播放页面,再搭一个后台管理课程,然后就完事了。但题目里有个很关键的词——"过程管理"。这四个字才是整个毕业设计的灵魂,也是答辩时最能体现你思考深度的地方。
传统在线学习平台的核心逻辑是"结果导向":学生看完视频、做完作业、考完试,系统给一个分数就结束了。但"过程管理"要解决的是另一个问题:学习过程本身的数据追踪与分析。比如这个学生今天学了多长时间、重点卡在哪个章节、作业是按时提交还是拖延提交、考前突击还是持续学习,这些过程性数据比一纸成绩单更能反映真实学习状态。
放到毕业设计的场景里,这个系统就可以拆成两条主线:一条是学生的日常学习闭环——选课、看视频、记笔记、做作业、参加考试、看成绩;另一条是教师和管理员的过程监控闭环——发布课程内容、布置作业、查看班级学习进度分布、识别学习困难学生。两条线最终汇总到数据统计模块,形成"学习过程报表"。
1.2 三类角色与核心业务流程
系统按权限划分为三种角色,对应三个不同的操作台。
学生端:登录后进入个人学习空间,可以浏览可选课程、查看已选课程、进入课程学习页面观看章节视频、填写学习笔记、记录学习进度;在作业模块中查看待完成作业、提交作业附件、查看批改结果;在考试模块中参加在线考试、查看考试成绩和答案解析;在统计页面查看自己的学习时长趋势、章节完成度、历史成绩曲线。
教师端:负责内容生产和过程监管。可以创建课程、维护章节信息、上传教学视频和课件;布置作业并设置截止时间;创建考试试卷、设置题目和分值;查看所授课程的学生列表和每个人的学习进度;对作业进行批改打分;查看班级整体学习数据分布。
管理员端:做系统层面的管理。用户管理(禁用/启用账号)、课程审核、公告发布、系统参数配置、全站数据总览。
业务流程里最核心的一条是学习进度闭环:
学生进入课程章节页 → 前端定时上报学习状态 → 后端记录学习时长和最后学习位置 → 章节完成后更新课程总进度 → 教师端实时可见
这条链路是体现"过程管理"的关键,后面我会详细说实现细节。
1.3 功能清单与边界划分
在开始写代码之前,一定要把功能边界划清楚。很多毕业设计翻车都是因为在"我要做一个大而全的系统"的心态里迷失了方向。我的建议是,核心功能做深,边缘功能做简。
核心功能(必须做扎实):
- 用户认证与角色权限控制
- 课程管理(增删改查、上下架)
- 课程章节与视频资源管理
- 学习过程记录(核心亮点)
- 作业发布、提交、批改
- 在线考试与自动判分
- 学习数据统计报表
- 个人中心与学习档案
边缘功能(可以做简版或不做):
- 支付与订单(毕业设计可以不涉及)
- 实时音视频互动(太复杂)
- 社区论坛/问答
- 消息推送(可以用简单的站内信代替)
功能边界想清楚了,后面建表、写接口、做页面都会轻松很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与工程架构
2.1 为什么选SpringBoot,版本怎么定
先聊技术选型。在线学习系统的主语言用Java,框架用SpringBoot,这是最稳妥的选择。SpringBoot最大的价值不是某个单独的特性,而是它把Spring生态里那些繁琐的配置全部自动化了——内嵌Tomcat、自动配置、Starter机制让项目从零到能跑只需几分钟。对毕业设计而言,这意味着你可以把有限的精力放在业务逻辑上,而不是浪费在配置XML上。
版本选择上,我推荐一个经过验证的组合,如果你不是对某个新特性有硬需求,就老老实实用这套:
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 稳定,兼容性最好,别盲目上17/21 |
| SpringBoot | 2.7.18 | 2.x系列最后版本,资料多,踩坑少 |
| MyBatis-Plus | 3.5.x | 单表CRUD不用写SQL,分页插件好用 |
| MySQL | 5.7/8.0 | 8.0更好,但5.7也能跑 |
| Redis | 可选 | 做缓存和验证码存储,非必须 |
| JWT | jjwt 0.11.x | 无状态登录认证 |
| Vue | 3.x + Element Plus | 前端主流方案 |
注意:SpringBoot 3.x 已经发布很久了,但如果你用JDK8,就老老实实选SpringBoot 2.7.x。SpringBoot 3.x强制要求JDK17+,很多老教程和老依赖都会出问题。我见过太多人因为版本问题浪费了一两周时间。
2.2 后端分层结构与核心依赖
项目结构上,我建议采用经典的四层架构,这也是答辩时最容易被认可的结构,简单清晰,面试官/评委一眼就能看懂:
code复制com.example.learningsystem
├── controller # 接口层:接收请求,参数校验,返回结果
├── service # 业务层:核心业务逻辑
│ └── impl
├── mapper # 数据访问层:MyBatis-Plus的Mapper接口
├── entity # 实体类:对应数据库表
├── dto # 数据传输对象:前端传参封装
├── vo # 视图对象:返回给前端的数据封装
├── config # 配置类:跨域、拦截器、上传配置等
├── common # 通用类:统一返回结果、异常处理、常量
└── utils # 工具类:JWT工具、时间处理等
为什么强调这种分层?因为毕业设计的评分标准里,代码工程化能力是明确的一档。你写了多层结构,说明你理解了"关注点分离"这件事。即使逻辑简单,也要把层次分开,不要所有的代码都堆在Controller里,那种"全堆Controller"的写法在答辩时会被问到很尴尬的问题。
核心依赖(pom.xml里的关键部分):
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.3 认证与权限设计
登录认证这块,我推荐用JWT + 拦截器的方案,而不是传统的Session。理由有三:一是前后端分离架构下JWT天然契合;二是Redis存Session的方案对毕业设计来说引入的复杂度大于收益;三是JWT无状态、易扩展,答辩时你能解释的东西更多。
实现思路:
- 用户登录成功后,后端用用户ID和角色生成JWT token返回给前端
- 前端把token存在localStorage里,每次请求在Header里带上
Authorization: Bearer <token> - 后端写一个拦截器,拦截所有需要认证的接口,解析token,把用户信息放到ThreadLocal里
- 对于需要特定角色的接口,用自定义注解
@RequireRole("teacher")或者直接简单地在拦截器里按URL前缀判断
代码核心是这样一个拦截器:
java复制public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
throw new BusinessException(401, "未登录");
}
// 解析token,校验合法性
Claims claims = JwtUtil.parseToken(token.replace("Bearer ", ""));
UserContext.set(claims); // 放到ThreadLocal
return true;
}
}
在WebMvcConfig里注册这个拦截器,并放行登录、注册、验证码等接口。
2.4 数据库表结构设计思路
表结构是整个系统能不能立住的关键。我强烈建议你在写任何代码之前,先把表设计好。我根据这个项目的核心需求,设计了一套表结构,你可以直接参考。
核心表有8张:
- user:用户表,字段包括id、username、password(BCrypt加密)、real_name、role(0学生/1教师/2管理员)、avatar、status、create_time
- course:课程表,字段包括id、course_name、course_code、teacher_id(关联教师)、description、cover_url、status(0下架/1上架)、create_time
- course_section:课程章节表,字段包括id、course_id、section_name、video_url、video_duration(视频总时长,秒)、sort_order、create_time
- learning_record:学习记录表,字段包括id、student_id、section_id、course_id、study_duration(本次学习时长)、last_position(最后播放位置)、is_finished(是否学完)、create_time、update_time
- homework:作业表,字段包括id、course_id、teacher_id、title、content、deadline、create_time
- homework_submission:作业提交表,字段包括id、homework_id、student_id、submit_content、file_url、score、status(0未批改/1已批改)、submit_time
- exam:考试表,字段包括id、course_id、exam_name、start_time、end_time、duration、total_score、status
- exam_question:试题表,字段包括id、exam_id、question_type(0单选/1多选/2判断题)、question_content、options(JSON格式存储选项)、answer、score、sort_order
- exam_record:考试记录表,字段包括id、exam_id、student_id、answers(JSON格式存储学生答案)、score、submit_time
设计要点:learning_record表是这张表里最核心的,它记录的是每次学习行为产生的数据,而不是只存一个"总进度"。通过这个设计,你可以统计学生今天学了多久、某一章学了多久、视频看到哪里停了。这些数据是"过程管理"的地基。总进度可以实时按记录聚合算出来,不需要单独维护字段。
关于表关联,我的建议是尽量用逻辑关联(代码里管理),而不是数据库外键。MyBatis-Plus体系下,外键除了带来麻烦没有太多好处,查询性能也不如代码里按需查两次。这也是企业开发的主流做法。
3. 核心功能模块的设计与实操
3.1 学习过程跟踪模块——这个项目的亮点
现在说到这个系统最有含金量的模块:学习过程跟踪。把这块写好了,你的毕业设计基本成功了一半。
先讲清楚业务逻辑。学生进入某个课程章节页面观看教学视频,前端播放器需要做两件事:
- 定时上报:每隔15秒向后端发送一次心跳请求,带上当前章节ID和已观看的时长增量
- 离开上报:页面关闭或切换时,发送最终时长和最后播放位置
后端的处理接口长这样:
java复制@PostMapping("/learning/record")
public Result<Void> record(@RequestBody LearningRecordDTO dto) {
// dto:courseId, sectionId, duration(本次累计学习秒数), position(播放位置秒数)
Long studentId = UserContext.getUserId();
// 保存学习记录
LearningRecord record = new LearningRecord();
record.setStudentId(studentId);
record.setCourseId(dto.getCourseId());
record.setSectionId(dto.getSectionId());
record.setStudyDuration(dto.getDuration());
record.setLastPosition(dto.getPosition());
// 判断是否学完:当播放位置 >= 视频时长 * 0.9 时标记完成
CourseSection section = courseSectionMapper.selectById(dto.getSectionId());
if (dto.getPosition() >= section.getVideoDuration() * 0.9) {
record.setIsFinished(1);
}
learningRecordMapper.insert(record);
return Result.success();
}
这里有三个细节值得注意,也是答辩时的高光点:
防刷策略:如果只在前端上报时长,那学生完全可以开着页面挂机。一个简单的防刷策略是,后端校验两次上报的时间间隔:每条记录自带上一次上报时间,如果间隔大于45秒(前端每15秒上报,允许一定网络延迟),就按45秒计算有效时长。这样即使前端伪造间隔,也无法人为拉长有效学习时长。
进度聚合算法:课程的总体进度如何计算?不能简单地把所有章节进度取平均,因为各章节视频时长不同。正确算法是:
code复制课程总进度 = 所有已学完章节的视频时长总和 / 该课程所有章节视频时长总和 × 100%
比如一门课程有10个章节,每个章节30分钟,学生学完了其中6章,总进度就是60%。这个算法用一段SQL就能聚合出来,但要在答辩时能讲清楚"为什么这样算"。
数据量控制:一个学生每15秒上报一次,一天的请求量不小。设计表结构时,学习记录表可以按 is_finished 做索引,统计时用MyBatis-Plus的聚合查询,数据量大时再考虑按天归档。毕业设计规模下完全够用。
3.2 课程与作业模块的实现要点
课程管理这块相对常规,但有一个细节容易踩坑:教师上传视频文件的存储方案。
毕业设计不推荐用分布式文件系统,最简单靠谱的方案是两种:
- 本地磁盘存储:在application.yml里配置一个上传目录,文件存到服务器,数据库存访问URL
- 对象存储(如阿里云OSS):有免费额度,但需要配置密钥,联网环境可用
本地磁盘存储的配置示例:
yaml复制file:
upload-path: D:/upload/
access-path: /upload/**
配合SpringBoot的资源映射配置:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceHandler("file:" + fileUploadPath);
}
}
作业模块要注意的是截止时间判断。学生提交作业时,后端要判断当前时间是否超过了作业的deadline,超过就不能提交。前端虽然也要做判断,但真正要卡住的是后端,前端校验只是用户体验,后端校验才是保证公平的底线。类似这样:
java复制if (homework.getDeadline().isBefore(LocalDateTime.now())) {
throw new BusinessException("作业已截止,无法提交");
}
3.3 在线考试与自动判分
在线考试模块,我建议只做客观题(单选题、多选题、判断题),自动判分逻辑简单,也够完整。如果你想把主观题也加进去,可以做"学生提交文字答案,教师后台手动批分",这能体现出你的设计考虑全面,但会增加不少工作量。
客观题自动判分的核心逻辑:
java复制public Integer calculateScore(Exam exam, List<QuestionAnswerDTO> answers) {
int score = 0;
for (QuestionAnswerDTO answer : answers) {
ExamQuestion question = questionMapper.selectById(answer.getQuestionId());
if (question.getQuestionType() == 1) { // 单选题
if (question.getAnswer().equals(answer.getAnswer())) {
score += question.getScore();
}
} else if (question.getQuestionType() == 2) { // 多选题
// 多选题:完全匹配才给分
String[] correct = question.getAnswer().split(",");
String[] submitted = answer.getAnswer().split(",");
Arrays.sort(correct);
Arrays.sort(submitted);
if (Arrays.equals(correct, submitted)) {
score += question.getScore();
}
}
}
return score;
}
这里有一个策略选择:多选题怎么判分?有的系统是"选对但不全给一半分",有的系统是"只要有多选或漏选就是0分",我建议用后者实现更简单,答辩时也能解释清楚。考试结束后,系统自动生成成绩单,学生可以查看每道题的作答情况和正确答案。
3.4 数据统计报表的设计
数据统计是体现"过程管理"的一个出口。如果只是做了增删改查,你的系统就只是一个信息管理系统;有了数据统计分析,才算真正呼应了"在线学习过程管理"这个题目。
我实现了三个维度的统计报表:
学生个人维度:
- 学习总时长趋势(近7天/30天)
- 各课程章节完成进度条
- 作业提交及时率(按时提交数/总作业数)
- 历史考试成绩曲线
教师班级维度:
- 每门课程的学生人数和学习进度分布图
- 完成率最高的章节和最低的章节
- 未交作业学生名单
管理员全局维度:
- 全站活跃用户数
- 课程学习热门排行
- 每日新增用户与学习时长趋势
这些图表前端用ECharts展示,后端提供聚合查询接口。以"近7天学习时长趋势"为例:
java复制@GetMapping("/statistics/duration-trend")
public Result<List<DurationItemVO>> getDurationTrend() {
Long studentId = UserContext.getUserId();
// 查询最近7天的日期
List<String> dateList = DateUtils.getLastDays(7);
// 按天分组统计学习时长
List<DurationItemVO> result = dateList.stream().map(date -> {
DurationItemVO vo = new DurationItemVO();
vo.setDate(date);
vo.setTotalSeconds(
learningRecordMapper.selectTotalDurationByDate(studentId, date)
);
return vo;
}).collect(Collectors.toList());
return Result.success(result);
}
对应的SQL(用MyBatis-Plus的QueryWrapper实现):
java复制QueryWrapper<LearningRecord> wrapper = new QueryWrapper<>();
wrapper.eq("student_id", studentId)
.between("create_time", date + " 00:00:00", date + " 23:59:59");
return learningRecordMapper.selectList(wrapper).stream()
.mapToInt(LearningRecord::getStudyDuration).sum();
统计口径要注意:一个学生一节课重复学了3次,学习总时长是3次累加,但课程进度只算一次完成。这两个指标是独立的,不要混在一起。
4. 从零搭建到部署的完整实操
4.1 环境准备与初始工程搭建
我把完整流程做一个清单,你照着操作就行:
- 安装JDK 1.8,配置环境变量
JAVA_HOME - 安装MySQL 8.0,创建数据库
online_learning,UTF-8mb4编码,创建用户并授权 - 安装Maven 3.6+,配置阿里云镜像加速依赖下载
- 下载IDEA(推荐使用IDEA,社区版就够),安装Lombok插件
- 使用Spring Initializr创建工程,如果网络不好,也可以去start.spring.io生成压缩包再导入
创建工程时勾选依赖:Spring Web、MySQL Driver,其他的依赖后续手动加到pom里就行。
需要注意的坑:如果IDEA里创建SpringBoot项目时找不到Spring Initializr,通常是网络问题,手动去start.spring.io下载包,然后Import Project。
4.2 配置文件的正确写法
这是新手最容易卡壳的地方,application.yml的配置看起来简单,但细节不少。我贴一份我实际用过的配置,你对照着改:
yaml复制server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/online_learning?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 你的密码
servlet:
multipart:
max-file-size: 200MB # 视频文件较大,必须调大
max-request-size: 200MB
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 数据库下划线自动转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志,开发调试用
global-config:
db-config:
logic-delete-field: deleted # 逻辑删除字段
logic-delete-value: 1
logic-not-delete-value: 0
file:
upload-path: D:/upload/
注意:
spring.datasource.url里的serverTimezone=Asia/Shanghai不能省,不然MySQL 8.0会报时区相关的异常。max-file-size务必调大到200MB,否则上传教学视频会直接报MaxUploadSizeExceededException。这两个是我见过最频繁的新手报错。
4.3 前端联调与代理配置
前端用Vue3 + Vite + Element Plus,创建项目:
bash复制npm create vite@latest frontend -- --template vue
cd frontend
npm install
npm install element-plus axios pinia vue-router
联调时有一个关键配置:开发环境跨域代理。不配置的话,前端访问 http://localhost:8080 的接口会被浏览器跨域拦截。Vite的配置文件 vite.config.js 里加:
js复制export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
注意两个习惯:一是统一在axios里封装BaseURL为 /api,所有请求自动走代理;二是后端的Controller统一加上 @RequestMapping("/api/xxx") 前缀。这样开发环境和生产环境都不会乱。
4.4 项目打包与部署
部署有两种常见方式,任选其一:
方式一:Maven打包成Jar直接运行
bash复制mvn clean package -DskipTests
java -jar target/online-learning-0.0.1-SNAPSHOT.jar
这种方式部署最简单,但要处理前端静态资源的合并。我推荐把前端构建后的 dist 目录拷贝到后端的 src/main/resources/static 下面,这样打包出来的Jar自带前端页面,一个Jar搞定全栈。
方式二:前后端分离部署
后端用上面方式打包运行,前端 npm run build 生成dist,用Nginx托管:
nginx复制server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://localhost:8080/api/;
}
}
毕业设计演示时,方式一更省事,答辩现场不容易出问题。方式二更能体现你对工程化的理解,但现场环境问题要多一重。
5. 常见问题排查与经验总结
5.1 高频坑位速查表
我在开发这个系统的过程中踩了不少坑,也帮很多同学排查过类似问题,整理成下表,遇到报错优先对照自查:
| 现象 | 大概率原因 | 解决办法 |
|---|---|---|
启动报 Access denied for user 'root'@'localhost' |
数据库密码错或用户无权限 | 检查application.yml里的用户名密码;GRANT ALL ON *.* TO 'root'@'localhost' |
启动报 Unknown database 'online_learning' |
数据库没创建 | 先执行 CREATE DATABASE online_learning |
| 访问接口报404 | 路径或前缀不对 | 检查Controller的RequestMapping,验证axios的BaseURL |
上传文件报 MaxUploadSizeExceededException |
multipart大小没调 | 在application.yml设置max-file-size和max-request-size |
| 前端页面白屏,控制台报跨域错误 | Vite代理没配置或后端CORS没开 | 配置Vite proxy;后端配置CorsFilter |
| 时间差8小时 | 时区没设置 | 数据库连接串加 serverTimezone=Asia/Shanghai |
JWT解析报 JWT signature does not match |
签名密钥不一致 | 确认生成和解析用的是同一个密钥 |
| 查询结果中文乱码 | 数据库编码不是UTF-8 | 建库时指定 CHARACTER SET utf8mb4 |
| Controller接口能通,但返回的JSON字段值全是null | 实体类没加 @Data 注解 |
检查Lombok插件是否安装、注解是否加了 |
5.2 答辩时一定会被问到的技术难点
毕业设计答辩和平时开发不一样,评委老师问的不是"你做了什么",而是"你为什么这样做"和"如果遇到XX情况怎么办"。我在系统里做的几个关键决策,对应的解释逻辑也可以提前准备好:
为什么使用JWT而不是Session? JWT无状态,适合前后端分离,服务器不需要保存会话信息,扩容方便。缺点是无法主动让token失效,所以我们可以让前端存储过期时间,后端在做敏感操作时直接抛"登录已过期"。
学习记录防刷怎么做的? 后端校验上报间隔时间,两次上报之间小于45秒视为正常,超过则无效;同时检测同一章节在同一天内是否重复观看,学习时长上限设为一个合理的值(比如视频总时长的1.5倍)。
事务失效场景遇到过吗? 这是个高频问题。我在提交作业并更新作业状态时,把两个操作放在同一个Service方法里,用了 @Transactional 注解。但要注意:注解要加在public方法上,并且方法不能从内部调用,否则Spring的AOP代理不会生效,事务就失效了。还有一点就是 @Transactional 默认只有RuntimeException才回滚,如果方法里catch了异常不抛出,事务也会失效。
数据库表为什么不分表? 如果被问到数据量大怎么办,可以答:当前设计已支持通过时间索引查询学习记录,如果数据量进一步增长,可以按月份对learning_record表做分区,或者引入分库分表组件。但毕业设计规模下,单表加索引完全够用,不要自己给自己挖坑。
5.3 一些让项目"加分"的小细节
最后分享几个我实测下来性价比很高的小功能,代码量不大,但展示效果直接拉满:
第一个是验证码登录。登录时加入图形验证码,用Hutool工具类生成图片,Redis存值,2分钟过期。这一项瞬间把你的系统从一个"学生作品"提升到"有安全意识的产品",答辩老师非常吃这一套。
第二个是学习笔记功能。学生在视频页下方可以随时记录笔记,笔记可以导出为文本文件下载。这个功能实现简单,但在"过程管理"的定位下非常自洽——学习过程中的碎片想法,本身就是过程数据的一部分。
第三个是公告推送。管理员发布公告后,学生端登录时拉取最新的3条公告展示在首页。用一张公告表加一个查询接口就搞定,但很多同学都只做"后台发布"忘了"前台展示",你做了这个闭环就能超出预期。
还有一点,代码提交前养成一个好习惯:写注释。不是每个方法都写,而是在关键逻辑处写清"为什么"。比如防刷策略后面的注释是"防止前端挂机刷时长",总进度聚合算法的注释是"按视频时长加权而非简单平均"。你代码里留下的思考痕迹,答辩时如果你忘了,照着注释也能想起来。
这个项目做完之后我最大的感受是,技术本身并不复杂,SpringBoot帮你把框架层面的复杂度都屏蔽掉了,真正拉开差距的是你对业务的理解深度。同样的系统,有的人做成了课程管理CRUD,有的人做出了学习过程的数据闭环。后者才是"设计"这两个字的价值所在。如果你正在做这个题目,建议把重点放在学习记录和行为数据分析上,这一块的想象空间非常大,也最能让评委看到你是在做设计,而不是在抄代码。
