带了三届毕设,带过十几组做类似“课程学习系统”的学生,三分之一的人栽在同一个地方:手里拿着完整源码,却不知道答辩时怎么把项目讲清楚、怎么回答老师那句“这个表为什么这么设计”。今天借这套基于Spring Boot + Android + 微信小程序的计算机精品课程学习系统,把整个项目从表结构到部署、从踩坑到答辩要点完整拆一遍,当作一份可直接照做的毕设实战参考。
这套系统的核心业务其实很朴素:学生端看课程视频、做练习、记录学习进度;管理端上传课程、管理用户、查看统计数据。难的不是业务,而是“三端怎么分工”“接口怎么设计”“上传视频这么大文件怎么处理”“小程序和Android端共用一套后端时有哪些坑”这类实际问题。这篇文章会把这些问题一个个摊开,适合正在做同类毕设、或者拿到这套源码但不知道怎么讲清楚的人。
1. 项目整体拆解:三端一体的课程学习系统到底怎么做
1.1 需求梳理与技术选型
每次拿到一个毕设项目,我习惯先画一张需求脑图,把“谁能用什么功能”列清楚。这套课程学习系统按角色拆,大致是:
- 学生用户:注册登录、浏览课程列表、查看课程详情、在线观看视频、提交练习、查看学习进度、收藏课程。
- 管理员:登录后台、课程分类管理、课程信息管理、视频上传、用户管理、学习数据统计。
功能点看起来不多,但它是典型的“管理端 + 用户端”结构,天生适合拆成前后端分离。选型上,很多学生第一反应是用“网页后台 + 网页前台”,但你这套题目明确要求了Android和微信小程序两个移动端,那就要提前规划好共用后端的问题。
我见过不少学生一上来就写两套后端,一套给Android、一套给小程序,这是最大的弯路。核心业务完全是同一套,数据模型也一样,多写一套后端等于把毕业设计的工作量直接翻倍,还容易导致两边数据不一致。正确做法是:做一个Spring Boot后端统一提供RESTful接口,Android端和小程序端都通过HTTP请求访问同一套接口。后端只写一次,前端管好各自的表现层就行。
1.2 为什么选Spring Boot而不是SSH或者Servlet
现在还有学生在纠结“Spring Boot是不是太难了,用SSH行不行”。我的态度很明确:能用Spring Boot就别碰那些老框架,毕设的加分项恰恰在于你用了一个主流、规范、容易讲清楚的技术栈。
Spring Boot在毕设里的优势有几个:
- 内置Tomcat,不用单独配置服务器,
mvn spring-boot:run就能起服务,部署时打一个jar包扔服务器上就能跑。 - 约定优于配置,减少了大量XML配置,代码量少,写起来快。
- 生态完整,MyBatis-Plus、JPA、Spring Security这些常用组件都能无缝接入,答辩时讲到“集成安全框架”也有素材。
- 面试时这也是主流技能,做毕设的同时把技能点练了。
但这套项目里我一般建议用Spring Boot 2.x,而不是最新的3.x。原因很现实:很多教程、插件、云服务商的一键部署镜像还停留在2.x生态,3.x要求JDK 17,部分老版本的MyBatis-Plus、代码生成器等兼容性要踩坑。对毕设来说,稳定压倒一切,别拿新技术冒险。网上那些“springboot版本太高”的吐槽,十有八九是把这个坑踩了个结实。
1.3 三端如何分工协作
- Spring Boot后端:只做数据管理和业务逻辑,提供JSON接口,管理MySQL里的数据,处理文件上传。
- Android端(原生):负责学生用户的核心学习体验,包括课程列表、视频播放、学习进度记录。原生的好处是视频播放器(如JieCaoVideoPlayer或ExoPlayer)集成方便,体验比WebView好很多。
- 微信小程序端:功能与Android端基本对齐,但更注重轻量化和快速体验。小程序有自己的一套登录机制(wx.login),不能照搬Android的账号密码登录逻辑。
- 管理后台:这里我给学生的建议是做个Web后台,用Vue或者直接用Thymeleaf服务端渲染都行。如果前端底子弱,直接用Spring Boot + Thymeleaf做管理端最简单,一个项目搞定,不用再单开前端工程。
2. 数据库设计与后端核心模块实现
2.1 数据库表结构设计(核心表)
毕业设计答辩时,老师最爱问的就是数据库设计。这套系统的表不算多,我通常按模块拆成以下几类:
用户模块
user表:用户ID、用户名、密码(BCrypt加密存储)、昵称、头像、手机号、角色(student/admin)、创建时间。- 注意一点:密码一定不能明文存。用Spring Security的BCryptPasswordEncoder加密,答辩时还能顺带讲一下“密码安全存储”的考虑。
课程模块
course_category表:分类ID、分类名称、排序。course表:课程ID、标题、封面图URL、简介、所属分类ID、讲师、价格(或免费标识)、创建时间。
学习模块
course_video表:视频ID、所属课程ID、视频标题、视频URL、视频时长、排序号。learning_progress表:进度ID、用户ID、课程ID、视频ID、当前播放位置(秒数)、是否完成、更新时间。course_favorite表:收藏ID、用户ID、课程ID、收藏时间。
练习模块
exercise_question表:题目ID、所属课程ID、题目内容、选项A/B/C/D(JSON存储也可以)、正确答案、解析。exercise_record表:记录ID、用户ID、题目ID、用户答案、是否正确、作答时间。
统计模块
- 统计类的数据不需要单独建表,直接用SQL聚合查询。比如“某个课程的总学习人数”,
SELECT COUNT(DISTINCT user_id) FROM learning_progress WHERE course_id = ?就行。
表与表之间要画清楚外键关系,但MySQL里的外键约束我建议不加,逻辑上关联就行,用MyBatis-Plus的@TableId和@TableField处理映射。原因很简单:加了外键约束,批量删除课程时要先删子表数据,删除顺序很容易出问题,出错后报错信息也不友好,对毕设来说纯属增加调试成本。
2.2 后端分层结构与接口设计
后端代码我习惯按经典三层结构组织:Controller(接口层)、Service(业务层)、Mapper(数据访问层),再加一个entity包放实体类、一个config包放配置。
com.example.course ├── controller │ ├── AuthController.java │ ├── CourseController.java │ ├── VideoController.java │ ├── ProgressController.java │ ├── ExerciseController.java │ └── AdminController.java ├── service │ ├── UserService.java │ ├── CourseService.java │ ├── VideoService.java │ ├── ProgressService.java │ └── FileService.java ├── mapper │ ├── UserMapper.java │ ├── CourseMapper.java │ └── ... ├── entity │ ├── User.java │ ├── Course.java │ └── ... ├── config │ ├── MybatisPlusConfig.java │ ├── WebMvcConfig.java(拦截器/跨域配置) │ └── FileUploadConfig.java ├── common │ ├── Result.java(统一返回结果) │ └── GlobalExceptionHandler.java └── CourseApplication.java接口设计的关键是统一返回格式。我让所有接口都返回Result对象,结构固定为:
{ "code": 200, "message": "success", "data": {} }好处是Android端和小程序端解析JSON时只需要写一套解析逻辑,code为200就取data,否则弹错误提示。很多学生不做统一封装,每个接口返回结构都不一样,前端解析时写一堆if-else,纯属给自己挖坑。
2.3 文件上传与课程视频处理的坑
课程视频是这套系统里最特殊的资源——一个视频几十上百MB,和普通表单提交完全是两回事。我让学生的处理方案是:
- 后端使用Spring Boot的
MultipartFile接收文件,上传到服务器本地目录(如/www/uploads/video/),Nginx或宝塔面板把这个目录映射为静态资源路径。 - 上传时校验文件类型和大小:文件后缀白名单(mp4、mov、avi),大小限制在500MB以内。在
application.yml里配置:
spring: servlet: multipart: max-file-size: 500MB max-request-size: 512MB- 文件名要重命名,别让用户上传什么名就存什么名。我习惯用
UUID + 原始扩展名,避免中文名和特殊字符导致访问404。
这里有个典型的坑:如果用默认配置,Tomcat对POST请求体大小有限制,视频传一半就报413 Request Entity Too Large。上面这个配置就是干这个的。还有如果用了Nginx反向代理,Nginx的client_max_body_size默认只有1MB,也必须改,否则后端配置再大也被Nginx挡回去了。这两个地方漏掉任何一个,视频上传都会失败,而且报错信息还不一样,排查起来很费劲。
3. Android端实现要点与细节
3.1 Android端的技术选型:原生还是框架
我建议Android端用原生开发,Java或Kotlin都行,配上常用的开源库。理由很简单:毕设考察的是你对Android开发的基本功,用Flutter/uni-app这种跨端框架虽然省事,但答辩时老师很可能会问“为什么不直接用原生”,如果你对框架底层一知半解,反而容易露怯。
原生开发推荐的库组合很固定:
- 网络请求:Retrofit2 + OkHttp3
- 图片加载:Glide
- JSON解析:Gson 或 FastJson
- 视频播放:JieCaoVideoPlayer(简单易用)或ExoPlayer(性能更好,推荐用这个,Google官方维护)
现在的关键问题是Android Studio版本更新太快,Gradle版本和插件版本如果不匹配,会直接导致项目同步失败。那套基于最新Android Studio的工程里,gradle-wrapper.properties里的Gradle版本和build.gradle里的com.android.tools.build:gradle版本必须对应上,新版本Android Studio打开老工程时常常自动升级Gradle版本,一升级就有各种编译报错。我踩过的坑是:AGP 7.x对应Gradle 7.x,AGP 8.x对应Gradle 8.x,版本错一个都不行。学生拿到源码后如果本地环境是新版Android Studio,最省事的方式是把项目里的Gradle版本改成和老师给的教程一致,不是用AS里的提示去随便升级。
3.2 视频播放、进度上报与本地缓存
Android端的学习进度记录是个容易被忽视但答辩常问的点。实现逻辑是:
- 用户点击某个视频开始播放,在
onResume里通知后端创建一条learning_progress记录。 - 播放过程中,每隔15秒上报一次
currentPosition,用videoView.getCurrentPosition()取出位置,调用进度更新接口。 - 视频播完(
onCompletion回调触发)时,把进度标记为完成。 - 再次进入时,从后端拉取这个视频的播放位置,用
seekTo(position)跳到上次位置继续播放。
这个“断点续播”功能看起来简单,却是整个系统里最能体现“学习”属性的功能,答辩一定要主动讲。
还有一个常见问题是Android访问本地文件时的路径异常。很多学生喜欢把测试视频放在手机的Download目录里写死路径,比如/storage/emulated/0/Download/demo.mp4,结果一换手机就找不到。原因和Android 10以后的分区存储机制有关。建议的稳妥做法是:把测试视频通过Android Studio的Device Explorer上传到/sdcard/Android/data/包名/files/目录下,这样应用自己可以合法读写;如果是用户购买课程后想本地缓存视频,就存到应用专属目录getExternalFilesDir()下,不要往公共目录塞。在真机调试时遇到“文件不存在”或者“Permission denied”这类问题,十有八九是存储目录和权限出了岔子。
3.3 Android侧接口联调与跨域配置
Android端用Retrofit访问后端接口,有一个基础配置必须提前做好。如果Android模拟器访问电脑本地的后端服务,不能用localhost或127.0.0.1,得用10.0.2.2。这个地址是Android模拟器专门保留给宿主机用的“特殊IP”。很多新手在这里卡很久,用localhost调接口一直超时,换10.0.2.2立刻就好了。
用真机调试的话,则需要电脑和手机连同一个局域网,后端地址填电脑的局域网IP,比如http://192.168.1.100:8080。还有两个隐藏问题:
- Android 9及以上默认禁止明文HTTP请求,需要在
AndroidManifest.xml里配置android:usesCleartextTraffic="true",或者配置网络安全策略,否则所有HTTP请求直接报错。 - 后端要配置跨域,否则从Android端发过来的请求(Origin不同)也会被拦截。Spring Boot里可以写一个
WebMvcConfigurer,重写addCorsMappings方法统一放行。
这两处如果不配置,联调阶段就会无限报错。项目带的部署说明文档里都写了,但很多学生不看,出了问题才回头翻。
4. 微信小程序端实现要点
4.1 小程序登录与手机号授权的差异处理
微信小程序端的登录逻辑和Android端完全不一样,这是最容易出错的地方。小程序不能像Android端那样直接用手机号+密码登录,必须走微信授权体系:
- 小程序端调用
wx.login()获取一个临时code。 - 把这个code传给后端,后端拿code加上AppID和AppSecret,通过微信接口换取
openid和session_key。 - 后端用openid在
user表里查用户,查不到就以该openid为新用户自动注册(“静默注册”)。 - 后端生成一个自定义登录态token返回给小程序,后续请求带上token即可。
关于“获取手机号”这个功能,微信官方要求很严格,现在必须通过“手机号快速验证组件”(<button open-type="getPhoneNumber">)来触发,而且需要已认证的小程序账号,个人开发者的未认证小程序无法使用这个能力。毕设场景下我建议不要在这个功能上死磕,直接用微信授权登录即可,答辩时如果老师问“为什么不用手机号登录”,就可以说“手机号快速验证需要企业认证,个人主体的开发环境下以微信openid作为唯一标识已经可以满足业务需求”。这个回答既诚实又合理。
4.2 顶部导航栏适配与下拉刷新
小程序界面的导航栏适配也是个让新手头疼的细节。不同型号的手机屏幕尺寸、刘海屏、胶囊按钮位置都不一样。小程序里wx.getSystemInfoSync()可以拿到状态栏高度,wx.getMenuButtonBoundingClientRect()可以拿到右上角胶囊按钮的位置。动态计算导航栏高度的方法大致是:
const menu = wx.getMenuButtonBoundingClientRect() const statusBarHeight = wx.getSystemInfoSync().statusBarHeight const navHeight = (menu.top - statusBarHeight) * 2 + menu.height把导航栏高度算出来之后,页面里的自定义导航栏组件才能适配各种机型。很多模板项目用的是“自定义导航栏”(navigationStyle: custom),这时候如果不处理高度适配,就会出现导航栏遮挡内容或者胶囊按钮挤在一起的情况。
列表页面建议开启enablePullDownRefresh或者加一个onReachBottom分页加载。课程列表数据可能比较多,小程序端拉取接口时分页参数(pageNum和pageSize)是必备的,后端接口设计时要把分页支持做进去,用户滑到底部自动加载下一页。
4.3 小程序与后端接口的兼容性细节
小程序端的网络请求不直接用axios这类库,用的是微信自带的wx.request。有几个细节要注意:
wx.request请求的header里的Content-Type默认是application/json,后端如果用@RequestBody接收,就没问题。- 如果后端接口要求携带token,
wx.request的header里要加上Authorization字段,小程序没有浏览器那样的cookie机制,一切都得手动传。 - 小程序对WebSocket、上传下载都有独立的API,比如
wx.uploadFile()上传文件、wx.downloadFile()下载文件,这两个API和wx.request的写法也不一样,不是同一个封装。
另外我提醒学生在做小程序端时,要把事务的接口文档写清楚。后端接口那么多字段,Android端和小程序端都要用,如果后端跟前端开发没有契约,后端改个字段名前端就全线报错。最简单的方式是后端用Swagger(springdoc-openapi或springfox)自动生成接口文档,调试时页面直接可以看到每个接口的参数和返回示例,两个前端的同学对着同一份文档开发,后期联调省一大半时间。
5. 部署上线:从本地到云服务器的完整流程
5.1 本地部署与源码运行环境准备
不管最终要不要买服务器,本地先把项目跑起来是第一关。整套源码的开发环境建议是:
- JDK 1.8(对应Spring Boot 2.x)
- Maven 3.6+
- MySQL 5.7或8.0
- Android Studio(版本要看工程对应的Gradle版本)
- 微信开发者工具(稳定版)
- Redis(如果项目用了Redis做缓存;没用到就不是必须的,注意看部署说明)
前端小程序导入工程时,需要在微信开发者工具里把AppID改成测试号(如果用个人账号),否则部分API不让调用。Android端打包APK则要检查签名配置,代码里build.gradle的applicationId和正式签名的ApplicationId要一致,不一致安装时会报“应用未安装”错误。
数据库初始化有两种常见方式:部署说明里通常会附带一个course.sql文件,导入MySQL后建好库表;另一种是后端配置了spring.datasource的initialization-mode: always,启动时自动执行schema.sql。我见到的多数毕设源码是前一种方式,导入SQL后还要检查application.yml里的数据库账号密码是否和本地一致。
5.2 云服务器部署(宝塔面板 + Docker两种路径)
学生到答辩阶段,最担心的往往是“我的系统能不能在老师电脑上跑起来”。这里我给两种部署路径。
路径一:宝塔面板一键部署(推荐给不想折腾的学生)
买一台2核4G的云服务器(带宽建议3M以上,因为要在线播放视频),装好宝塔面板。在宝塔里:
- 安装Nginx、MySQL 5.7、JDK。
- 创建数据库,导入
course.sql。 - 上传Spring Boot的jar包到服务器,用
nohup java -jar course-system.jar &启动,端口默认8080。 - Nginx配置一个站点作为反向代理,把
/api/开头的请求转发到http://127.0.0.1:8080,同时把视频上传目录(如/www/wwwroot/upload/video)映射成静态资源访问路径。
这种部署方式的好处是视频文件直接存在服务器磁盘,安装配置简单。缺点是一旦服务器重启,jar包需要手动重新启动(也可以用宝塔的进程守护插件自动拉起)。
路径二:Docker容器化部署(加分项)
如果毕设题目要求“容器化部署”或者你想在答辩时展示Docker能力,就用docker-compose把MySQL、后端服务、Nginx全部编排起来。大致结构:
version: '3' services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: yourpassword ports: - "3306:3306" app: build: . depends_on: - mysql ports: - "8080:8080" nginx: image: nginx ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - /www/uploads:/www/uploads用Docker部署时,后端application.yml里的数据库地址要写成容器名mysql而不是localhost,这是新手最容易卡壳的地方。两种方式我推荐宝塔,对毕设够用还省时间;Docker可以作为论文里的“系统部署”章节的亮点来写。
5.3 演示视频拍摄与论文(LW)编写的配合
题目标签里提到“演示视频”和“LW”(毕业论文),这两样东西在毕业设计里往往是决定成绩上限的环节,但也是最容易被忽视的。
演示视频我的建议是:按功能模块分段录制,每段30秒以内,全程展示操作过程,不要只放静态截图。顺序可以是:学生登录 → 浏览课程分类 → 点开一门课程看详情 → 播放视频 → 中断播放再进,展示断点续播 → 做练习题 → 查看学习记录 → 切到后台 → 登录管理员 → 新增课程 → 上传视频 → 查看统计。整个视频控制在5-8分钟,文件导出MP4格式,720p以上即可。
论文部分,重点不是堆页面截图,而是把“设计思路”说清楚。论文大纲我建议:
- 第一章 绪论:背景与意义、国内外研究现状(找几篇课程平台相关论文引用)。
- 第二章 相关技术介绍:Spring Boot、Android、微信小程序、MySQL、RESTful API。
- 第三章 系统分析:可行性分析、功能需求分析、用例图。
- 第四章 系统设计:总体架构、功能模块设计、数据库设计、接口设计。
- 第五章 系统实现:分模块贴关键代码和界面截图。
- 第六章 系统测试:功能测试(写几组测试用例表)、性能测试、兼容性测试。
论文里出现代码时要注意:贴的是核心业务代码片段,不是整个类全贴。视频上传和进度上报这部分逻辑是我建议学生在论文中重点讲的,因为这两个功能最能体现系统“学习”属性的价值,也好画时序图。
6. 常见问题排查与避坑指南
6.1 Spring Boot版本踩坑记录
前面说过Spring Boot用2.x别用3.x,这里再补充几个版本相关的实操问题,都是学生真踩过的:
- JDK版本不匹配:Spring Boot 2.7要求JDK 8-17,但MyBatis-Plus老版本在JDK 11以上有反射异常风险。所以环境配置最稳的组合是“JDK 8 + Spring Boot 2.7 + MyBatis-Plus 3.5.x”。
- Spring Boot 2.6之后
spring.mvc.pathmatch策略变化:Swagger在Spring Boot 2.6以上版本会报NPE(空指针异常),需要在配置里加上spring.mvc.pathmatch.matching-strategy=ant_path_matcher。 - Maven依赖下载慢或失败:把Maven的
settings.xml里的中央仓库镜像改成阿里云镜像。这一步能解决80%的“依赖红一片”问题。
6.2 Android真机调试常见问题
Android端联调时最常碰到三个问题:
- 接口地址问题:真机用局域网IP,模拟器用
10.0.2.2,别用localhost。 - 明文HTTP拦截问题:低于Android 9的版本默认不限制,Android 9以上默认限制明文流量,manifest里加
android:usesCleartextTraffic="true"。 - 网络库报EOFException或Connection reset:多数是因为后端接口抛了异常但前端没有全局处理。我在后端加了一个
GlobalExceptionHandler,捕获所有异常并返回Result格式的错误信息,前端解析时就稳定了。
还有一个我自己踩过好几遍的坑:Android端使用ViewPager2作为课程轮播图容器时,内嵌视频播放会导致滑动冲突。后来在页面里加入“点击轮播图时才请求视频URL,滑动时释放播放器”的交互控制,问题才解决。这类滑动与播放的冲突细节,在纯功能演示时不太容易暴露,但真实体验时非常明显。
6.3 微信小程序常见问题速查表
| 问题现象 | 常见原因 | 解决方法 |
|---|---|---|
请求接口报url not in domain list | 小程序后台未配置合法域名,或用了IP地址 | 开发阶段在开发者工具里勾选“不校验合法域名”,部署后配置HTTPS域名 |
| 视频加载失败 | 视频地址不是HTTPS,或服务器带宽不够 | 视频地址改为HTTPS;上传压缩过的视频;演示时用本地短链 |
登录接口报code无效 | code一次性使用,或AppID/Secret不匹配 | 检查小程序后台的AppID,确保后端配置一致 |
| 部分安卓机型上播放器黑屏 | 视频编码格式不兼容 | 转码为H.264编码的MP4 |
| 上传图片无反应 | 后端接口未配置multipart配置,或Nginx限制 | 检查Spring Boot的max-file-size和Nginx的client_max_body_size |
小程序端还有一个常见坑是**wx.request里的Content-Type**——如果你把header里的Content-Type设成了application/x-www-form-urlencoded,而后端用@RequestBody接收,那边直接收不到参数。前后端联调时先对齐格式,这种最基础的问题反而最耽误时间。
6.4 答辩前必须准备的两个亮点
答辩时间一般只有10-15分钟,老师没时间看完整演示。我建议学生把两个功能作为亮点重点讲:
第一个亮点是断点续播 + 学习进度统计。这个是“课程学习系统”区别于“视频网站”的关键功能,体现你理解业务,而不是只会写CRUD。可以从learning_progress表的设计切入,讲清楚“位置字段为什么要用秒数而不是浮点秒数”“课程完成的判定逻辑是什么”。
第二个亮点是三端共用后端的设计思想。Android端和小程序端虽然UI和交互方式不同,但业务层完全复用同一套REST API。这个“一套后端、多端适配”的思想,不仅是一个毕业设计级别的合理设计,也体现了工程化的全局观,老师对这个回答通常很感兴趣。
写在最后
我始终觉得,毕业设计不等于代码堆量,关键是把自己做的东西讲出道理。这套基于Spring Boot + Android + 微信小程序的计算机精品课程学习系统,业务不算复杂,但麻雀虽小、五脏俱全——有典型的用户体系、有文件上传这类容易被忽视的工程细节、有跨端的接口设计、有部署运维的实际操作,做完一遍,相当于把一个商业级项目的核心链路完整走通了。
最后再分享一个我反复跟学生强调的小技巧:拿到源码后,第一件事不要急着跑功能,先把README或部署说明文档完整读一遍,把整个项目的环境依赖、启动顺序、账号密码记在纸上。大多数毕设翻车都不是代码太复杂,而是环境没配好、启动顺序搞反了。先把这些基础捋顺,后面的进度会顺利很多。