1. 项目概述:这套“武理多媒体信息共享平台”到底做了什么
毕业设计年年都有做“XX管理系统”“XX平台”的,但真正能让人眼前一亮、答辩不翻车的,往往是那种把技术栈选扎实、把业务逻辑讲清楚的项目。这套基于 SpringBoot + Vue + MySQL 的多媒体信息共享平台,走的正是这条务实路线——它本质上是一个面向高校场景的资源分享与展示系统,用户可以在上面上传视频、图片、文档等多媒体资源,浏览和检索他人分享的内容,管理员则负责资源审核、用户管理等后台操作。核心价值在于:用一个完整的前后端分离项目,把学生时代最常被问到的那几个技术问题——权限控制怎么做、文件上传怎么存、跨域怎么解决——全部用一套可运行、可演示的方案回答清楚。
我推荐有 Java Web 基础、想拿一个“能跑、能讲、能改”的毕设项目的同学认真看一遍这套东西。它不只是给你一堆源码,而是把从数据库设计到部署上线的整条链路都打通了,论文、部署文档齐全,属于典型的“拿来就能用、改改就能答辩”的完整交付物。下面我结合自己做类似项目的实际经验,把它的设计思路、核心实现、部署避坑一条条拆开讲,希望能帮你省掉几周的无头苍蝇式摸索。
2. 核心方案选型与技术剖析
系统采用前后端分离架构,前端 Vue + 后端 Spring Boot + 数据库 MySQL,这个组合在毕业设计场景里几乎是“标准答案”,但标准答案也有标准答案的讲究。选型不是越新越好,而是要权衡你自己的基础、环境、资料丰富度和答辩展示需求。
2.1 为什么是 Spring Boot 而不是 SSM
很多同学的课程设计用的是 SSM(Spring + SpringMVC + MyBatis),到了毕业设计就纠结要不要换。我的建议是直接换 Spring Boot,理由很实在:
- 配置简化:SSM 里那堆 XML 配置(数据源、事务、扫描包、视图解析器)在 Spring Boot 里全部自动化,一个
application.yml搞定大部分事。毕设时间就那么多,省下配置时间去做业务不香吗? - 内嵌容器:Spring Boot 自带 Tomcat,打包成 jar 直接跑,不用单独装 Tomcat、配置 server.xml。部署文档里少写一大段环境配置说明,答辩演示也方便。
- 生态成熟:Spring Boot 整合 MyBatis-Plus、Spring Security、Redis 都有现成 starter,查资料容易,遇到问题搜得到。
- 版本选择:考虑到大部分同学本机环境是 JDK 8,推荐 Spring Boot 2.7.x 系列(比如 2.7.18),不要一上来就追 Spring Boot 3.x。Spring Boot 3 要求 JDK 17,如果你电脑上装了多个 JDK 或者学校机房环境是 JDK 8,版本冲突会让你在环境配置上浪费大量时间。这一点下面详细说。
2.2 前端架构:Vue 2 + Element UI 还是 Vue 3 + Element Plus
前端框架选择直接影响开发体验和后期维护,这里给两个方案对比:
| 对比维度 | Vue 2 + Element UI | Vue 3 + Element Plus |
|---|---|---|
| 上手难度 | 低,教程多 | 中,需理解 Composition API |
| 组件生态 | 成熟稳定 | 持续更新 |
| 资料丰富度 | 极高,几乎任何问题都能搜到 | 较多,新项目为主 |
| 推荐指数 | ★★★★★ | ★★★★ |
对于毕业设计,我的建议是:如果你 Vue 基础薄弱,选 Vue 2 + Element UI,稳;如果你想在答辩时多一个亮点(“我们使用了最新主流框架”),选 Vue 3 + Element Plus,前提是你愿意花时间学 Composition API。
我自己的项目用的是 Vue 2 + Element UI,原因很简单:资料多、坑少、我熟。技术选型不是越新越好,而是越稳越好。毕设的核心目标是顺利通过答辩,不是展示你用了多少新技术。
2.3 数据库设计:4 张核心表撑起整个系统
MySQL 数据库设计是毕设的重头戏,表结构设计得好不好,直接影响后期编码效率。我设计的数据库名为media_share,核心表结构如下(简化版,完整建表 SQL 请看项目内doc/sql目录):
用户表(user)
CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名', `password` VARCHAR(100) NOT NULL COMMENT '密码', `avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像地址', `role` TINYINT DEFAULT 0 COMMENT '角色 0-普通用户 1-管理员', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `status` TINYINT DEFAULT 1 COMMENT '状态 1-正常 0-禁用' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;资源表(resource)
CREATE TABLE `resource` ( `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '资源ID', `title` VARCHAR(200) NOT NULL COMMENT '资源标题', `type` VARCHAR(20) NOT NULL COMMENT '资源类型:video/image/doc', `url` VARCHAR(500) DEFAULT NULL COMMENT '资源存储地址', `cover` VARCHAR(255) DEFAULT NULL COMMENT '封面图', `description` TEXT COMMENT '资源描述', `uploader_id` INT NOT NULL COMMENT '上传者ID', `category_id` INT DEFAULT NULL COMMENT '分类ID', `download_count` INT DEFAULT 0 COMMENT '下载次数', `view_count` INT DEFAULT 0 COMMENT '浏览次数', `status` TINYINT DEFAULT 1 COMMENT '状态:1-审核通过 0-待审核 2-下架', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;分类表(category)和评论表(comment)相对简单:分类表存资源分类(视频/图片/文档等),评论表存用户对资源的评论内容、评论时间、评论人 ID。具体字段这里不展开,项目 SQL 文件里都有。
设计要点:
- 外键不建物理关联,建逻辑关联。比如
resource.uploader_id关联user.id,我只在代码层面做校验,没有在数据库层面加外键约束。这样后期如果要删数据、加批量操作,不会被外键绊住。 - 状态字段用 TINYINT 而非 VARCHAR。开发时你会频繁做状态筛选,用数字类型效率更高,且不容易出现大小写问题。
- 大字段用 TEXT。
description用TEXT类型,避免 VARCHAR(255) 存不下长描述导致插入失败。 - 统一使用 utf8mb4。不要用 utf8,mysql utf8 在存储 emoji 时会报错,utf8mb4 才是真正的“全量字符集”。
3. 后端核心实现:权限、登录与文件上传
后端是整个系统的“大脑”,我按模块拆分讲,重点说权限控制、登录认证、文件上传三个模块,因为这三个地方是毕设里最容易出问题、也最容易被老师追问的。
3.1 JWT 登录认证:状态管理不再依赖 Session
传统的 Session 登录在前后端分离架构下不好使——前端是独立部署的,Session 存在后端服务器上,小程序、App、网页端各自维护 Session 太麻烦。这里我用 JWT(JSON Web Token)做登录认证,一句话概括:服务器不存状态,客户端自己保管凭证。
流程是这样的:
- 用户提交用户名密码到
/api/auth/login - 后端校验通过后,生成一个 JWT Token 返回给前端
- 前端把 Token 存在 localStorage 或 Cookie 里
- 后续每次请求在请求头加
Authorization: Bearer <token> - 后端写一个拦截器(Interceptor)统一校验 Token,非法请求直接返回 401
JWT 的核心代码如下(简化版):
// JwtUtil.java 核心方法 public String generateToken(String username, Long userId) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) // 24小时过期 .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } // 解析Token public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); }这里有一个特别容易踩的坑:JWT 的 secretKey 必须在配置文件中管理,不能写死在代码里。我之前为了省事,直接在类里写了一个固定字符串,后来想换 key 得重新编译代码,非常被动。正确做法是放在application.yml里,读取用@Value("${jwt.secret}")注入。
另外,JWT 过期时间建议设置 24 小时,配合前端的“记住我”功能做成 7 天。如果时间太短,用户频繁重新登录,体验很差;如果太长,安全性下降。24 小时是折中方案。
3.2 基于拦截器的权限控制:管理员和普通用户分开
权限控制如果每个接口都写一遍判断逻辑,代码会非常冗余,而且容易漏。我的做法是用 Spring Boot 拦截器统一处理:
// AuthInterceptor.java @Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录、注册等公开接口 if (request.getRequestURI().startsWith("/api/auth")) { return true; } String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { String realToken = token.substring(7); try { Claims claims = jwtUtil.parseToken(realToken); request.setAttribute("userId", claims.get("userId")); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; } }拦截器写完后,需要在配置类里注册,并且设置拦截路径。这里我犯过一个低级错误:拦截器注册顺序和路径匹配规则搞混,导致静态资源也被拦截。后来在配置类里明确排除了/assets/**、/static/**等静态资源路径才解决。
对于管理员接口,我额外加了一个注解@RequireAdmin,在拦截器里解析 token 里的 role 字段,如果是 0 就返回 403。这样写的好处是:
- 普通接口只验证是否登录
- 管理接口额外验证角色权限
- 新增接口时只需加注解,不用改拦截器
3.3 文件上传:本地存储方案详解
多媒体资源系统最核心的功能就是文件上传。这里的文件包括图片、视频、文档,我采用的方案是本地磁盘存储:上传的文件保存在服务器的/upload目录,数据库里只存文件的相对路径。
关键代码如下:
// FileController.java @PostMapping("/api/upload") public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("type") String type) { if (file.isEmpty()) { return Result.error("文件不能为空"); } // 原文件名处理,防止非法字符 String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); // 生成新文件名:时间戳 + 随机数,避免重名 String newFileName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + ext; // 按日期分目录存储,如 /upload/2025/06/ String datePath = new SimpleDateFormat("yyyy/MM").format(new Date()); File dir = new File(uploadPath + "/" + datePath); if (!dir.exists()) { dir.mkdirs(); } File dest = new File(dir, newFileName); file.transferTo(dest); // 返回可访问路径 String url = "/api/files/" + datePath + "/" + newFileName; return Result.success(url); }这段代码虽然简单,但有几个细节值得注意:
- 文件名必须重命名:直接用用户上传的文件名存磁盘,一是可能重名,二是可能包含特殊字符(如中文路径在某些环境会乱码),三是路径穿越攻击风险。用
时间戳 + 随机数 + 扩展名的重命名方案,三防齐了。 - 按日期分目录:如果所有文件都堆在同一个目录,文件数量到几万个时,目录性能明显下降,而且手动查找文件极其痛苦。按
yyyy/MM分目录,既方便管理,又方便按月份清理。 - 前端展示跨域问题:前端访问
/api/files/**时,如果前端和后端不在同一个域名下,会跨域。这一点在 Vue 开发环境通过 proxy 代理解决,生产环境用 Nginx 反向代理解决,下面部署章节会说。
还有一个经验:上传接口要限制文件大小和类型。Spring Boot 默认上传文件大小上限是 1MB,这显然不够用,你需要在application.yml里调整:
spring: servlet: multipart: max-file-size: 100MB max-request-size: 200MB这里也别无脑开大,100MB 够用了。如果有同学硬要上传几个 GB 的大视频,建议走对象存储(比如阿里云 OSS、七牛云),本地存储扛不住。本地存储适合做毕设演示、中小规模系统,这是它的定位,用起来心里有数就行。
4. 前端核心实现:页面路由、组件通信与接口对接
前端我用 Vue 2 + Element UI 开发,整体结构清晰,模块划分合理。这一章讲三个关键点:路由配置、组件通信、接口封装。
4.1 路由配置:页面跳转与权限控制
Vue Router 是前端页面的“导航系统”,我设计的路由表如下(简化版):
// router/index.js const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { title: '登录', public: true } }, { path: '/', component: () => import('@/layout/Layout.vue'), children: [ { path: '', name: 'Home', component: () => import('@/views/Home.vue') }, { path: 'resources', name: 'ResourceList', component: () => import('@/views/ResourceList.vue') }, { path: 'upload', name: 'Upload', component: () => import('@/views/Upload.vue') } ] } ]路由配置里有一个容易被忽略的点:页面标题(meta.title)一定要配。毕业设计答辩时,老师会切换页面看效果,如果浏览器标签页一直显示同一个 title,观感很差,而且显得不专业。我通过全局路由守卫动态设置 document.title:
router.beforeEach((to, from, next) => { document.title = to.meta.title ? `${to.meta.title} - 武理多媒体分享平台` : '武理多媒体分享平台' // 未登录跳转登录页 const token = localStorage.getItem('token') if (!to.meta.public && !token) { next('/login') return } next() })这个守卫函数同时承担了两个职责:设置页面标题 + 登录拦截。个人体会:路由守卫是前端权限控制的核心,不要把所有验证都放在后端,前端至少要拦住“未登录用户”这一层,否则用户直接在地址栏输入内部页面链接,能看到一堆报错信息,体验远不如优雅跳转登录页。
4.2 组件通信:父子传值、事件总线与状态管理
Vue 组件通信是前端面试高频考点,在毕设里同样绕不开。我总结了三种方式的使用场景:
1. 父子组件传值(props / emit)
这是最基础的通信方式,父组件向子组件传数据用 props,子组件向父组件抛事件用 emit。比如资源列表页ResourceList和分页组件Pagination之间:父组件把总数、当前页作为 props 传给分页组件,分页组件切换页码时通过 emit 抛出新页码。
2. 事件总线(EventBus)
适合跨组件通信,比如登录成功后通知多个组件刷新用户信息。代码很简单:
// 新建 eventBus.js import Vue from 'vue' export const eventBus = new Vue() // 组件A中触发 import { eventBus } from '@/utils/eventBus' eventBus.$emit('refreshUser') // 组件B中监听 eventBus.$on('refreshUser', () => { this.loadUserInfo() })3. Vuex 状态管理
当多个组件都要读取同一个用户信息、同一份全局配置时,用 Vuex 最合适。不过毕设程度的需求,大部分用前两种方式就够,不用为了用 Vuex 而用。
4.3 接口封装:axios 统一处理,省掉 90% 的重复代码
前端调接口最忌讳每个页面各写各的 axios 请求。我做了统一封装,所有请求都走一个模块:
// request.js import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { Message.error(res.message || '请求失败') if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(new Error(res.message || '请求失败')) } return res }, error => { Message.error('网络异常,请稍后重试') return Promise.reject(error) } ) export default service这段代码的价值在于:所有接口的 token 携带、错误提示、401 跳转都集中在这一个文件里,其他地方只管调接口、用数据。页面代码里写service.get('/api/resources')就行,不用关心 token 怎么带、报错怎么弹。
5. 项目联调与功能测试
前后端都写完之后,要做的第一件事不是写论文,而是把系统完整跑起来,做整体联调。这个阶段最容易暴露接口对接问题、跨域问题、数据格式不一致问题。
5.1 联调前准备:统一接口返回格式
我提前定好了一个统一的接口返回格式,前后端都严格遵守:
{ "code": 200, "message": "操作成功", "data": {} }code:业务状态码,200 成功,400 参数错误,401 未登录,403 无权限,500 服务器错误message:给前端提示的文字信息data:实际返回的数据对象或数组
为什么要统一格式?因为前端 axios 拦截器只需要解析这个固定结构,就可以做全局错误提示和 token 过期处理。如果每个接口各写各的格式,前端代码会非常臃肿,而且出错了不好排查。
5.2 功能测试清单:边测边改别等最后
很多同学喜欢把所有功能写完再一起测,结果一测全是 bug,来回改非常痛苦。我的习惯是每个模块写完就立刻自测。下面是我整理的功能测试清单:
| 模块 | 测试项 | 预期结果 |
|---|---|---|
| 用户登录 | 正确账号密码登录 | 登录成功,跳转首页 |
| 用户登录 | 错误密码登录 | 提示“用户名或密码错误” |
| 用户注册 | 用户名重复 | 提示“用户名已存在” |
| 资源上传 | 上传图片/视频/文档 | 上传成功,列表出现新资源 |
| 资源上传 | 上传超过大小限制文件 | 提示“文件过大” |
| 资源浏览 | 游客访问资源详情 | 跳转登录页 |
| 资源搜索 | 按关键词搜索 | 返回匹配资源列表 |
| 管理员 | 删除他人资源 | 删除成功,资源列表消失 |
| 管理员 | 管理用户状态 | 禁用后该用户无法登录 |
每一行都要实际跑一遍,不要“觉得没问题”就跳过。实测下来,登录和上传两个模块最容易出问题,因为涉及 token、跨域、文件路径,多个环节串在一起,任何一个环节错位都会导致功能异常。
5.3 跨域问题:开发环境与生产环境的两种解法
跨域是前后端分离开发绕不开的问题,我分两种情况处理:
开发环境:Vue CLI 的 proxy 代理
在vue.config.js里配置代理,让前端开发服务器的/api请求转发到后端地址:
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }这样前端代码里写axios.post('/api/login', data),实际请求会被代理到http://localhost:8080/api/login,浏览器层面不存在跨域(因为请求发到了同源的前端服务器)。
生产环境:Nginx 反向代理
部署到服务器后,用 Nginx 把/api请求代理到后端端口:
location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这个配置同时解决了静态资源托管和跨域问题:Nginx 负责托管前端编译后的 dist 目录,同时把/api转发给后端。
个人体会:跨域问题一定要在开发初期就建立正确认知,不要等到联调才处理。很多同学前两个星期写代码一切顺利,一到联调发现所有请求都被拦截,心态直接崩了。正确的做法是开发第一天就把 proxy 配好,后续所有接口调用都是“同源”的,注意力才能集中在业务逻辑上。
6. 数据库设计与项目部署
数据库设计是系统的地基,部署是系统的“最后一公里”,这两块做好了,项目才算真正完整。
6.1 数据库设计实操:从需求到建表
前面 2.3 节已经展示了核心表的建表 SQL,这里补充设计思路。
一个多媒体共享平台的数据库设计,核心是回答三个问题:
- 有哪些实体?用户、资源、分类、评论,这四个实体缺一不可
- 实体之间有什么关系?用户上传资源(一对多),资源属于分类(多对一),用户评论资源(一对多)
- 哪些字段是必须的?每个实体的关键属性要列全,比如用户要有用户名、密码、头像、角色;资源要有标题、类型、URL、上传者、分类、状态、时间
用 ER 图的思路整理一遍,表结构就清晰了。设计的时候记住几条原则:
- 字段类型宁大勿小:状态用 TINYINT 没问题,但是数量、次数这些可能增长的字段,建议直接用 INT,别用 TINYINT 后来再改。
- 时间字段统一:所有表都用
create_time(创建时间)、update_time(更新时间)两个字段,不要有的表叫 createTime、有的叫 created_at,前后端对接时字段名统一能省很多事。 - 冗余字段按需添加:比如资源表的
view_count(浏览数)和download_count(下载数),其实可以通过统计表算出来,但直接冗余在资源表里,查询更快、代码更简单。毕设场景完全够用。
6.2 后端部署:从 IDEA 到服务器
后端的部署方式我推荐用mvn package打成 jar 包,然后用java -jar直接跑。具体步骤:
- 在项目根目录执行
mvn clean package -DskipTests,生成target/*.jar - 把 jar 包上传到服务器(可以用 scp 命令或者宝塔面板的文件管理器)
- 服务器上执行
nohup java -jar xxxx.jar > app.log 2>&1 &让服务后台运行 - 查看日志用
tail -f app.log,确认启动成功无报错
这里有几个细节:
- 端口号:默认后端端口是 8080,如果你的服务器上 8080 被占了(常见的是别的 Java 服务占用了),用
--server.port=8081指定新端口。 - 数据库连接:MySQL 数据库如果也在同一台服务器,建议创建专门的数据库账号(比如
media_user),不要直接用 root。用 root 一是安全风险,二是以后迁移服务器、改连接串都麻烦。 - 配置外置:
application.yml里的数据库密码、JWT secret 等敏感信息,建议打包时排除,部署时用--spring.config.location指定外部配置文件。这一步是加分项,答辩老师问到“安全性”时,你有话说。
6.3 前端部署:编译与 Nginx 托管
前端部署分两步:编译 + 托管。
第一步:编译
在项目根目录执行:
npm run build编译产物在dist目录。如果编译报错,先看是不是依赖没装全:执行npm install再重新编译。
第二步:托管
把dist目录里的文件上传到服务器,然后用 Nginx 托管:
server { listen 80; server_name your_domain.com; root /var/www/media-share/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080; } }这里的try_files $uri $uri/ /index.html;很关键——Vue 是单页应用,路由是前端路由(history 模式),刷新页面时如果服务器找不到对应路径的文件,就会 404。加上这一行后,所有未匹配到的路径都回退到 index.html,由前端路由接管。
常见问题:刷新 404
这是 Vue 部署最常见的坑,首次部署 vue-router history 模式必踩。解决的思路就是上面加try_files。如果你的路由是 hash 模式(URL 里有 #),这个问题不会出现,但 URL 不好看。
6.4 部署文档:怎么写才能拿高分
最后说说部署文档。很多同学项目做完了,部署文档随便写两页,结果答辩时老师照着文档操作发现根本跑不起来,印象分大打折扣。
我的部署文档固定包含这些章节:
- 环境要求:JDK 版本、Node 版本、MySQL 版本、Nginx 版本
- 1. 安装 JDK、MySQL、Node(附安装教程链接和验证命令)
- 2. 初始化数据库:导入 SQL 文件的具体命令,
mysql -u root -p < media_share.sql - 3. 后端部署:jar 包使用说明、启动命令、查看日志命令
- 4. 前端部署:npm install、npm run build、Nginx 配置内容
- 5. 常见问题:端口被占用、数据库连接失败、刷新 404 的解决思路
写部署文档的核心原则:把自己当成一个什么都不会的新手,每一步都写清楚,每个命令都验证过。比如“MySQL 安装教程”,不要只写一句“自行安装”,而要写清楚下载地址、安装步骤、设置 root 密码的方法、创建数据库的命令。这不仅是文档,也是你答辩时的“提词器”。
7. 常见问题与避坑经验
写代码的过程就是踩坑和填坑的过程。这一章把我实际遇到的高频问题整理出来,帮你少走弯路。
7.1 Spring Boot 版本陷阱:降版本比配环境快得多
最常见的坑就是Spring Boot 版本选择不当。很多同学从网上找教程,直接复制最新版本号(比如 3.x),结果发现 JDK 不兼容、依赖下载失败、配置写法变了,整套环境搭了三天还没跑起来。
我的建议是:
- JDK 8 + Spring Boot 2.7.x,这是最稳定的组合,网上资料最多,遇到问题基本都能搜到解决方案
- 如果你本机已经装了 JDK 17,可以选 Spring Boot 3.x,但要注意
javax.servlet改成jakarta.servlet等 API 差异 - 千万别在毕设前折腾“最新版本”,你不是在做技术预研,你是在用最稳妥的方式完成学业任务
如果实在遇到版本不兼容的问题,最快的方法不是去查文档,而是直接去 Maven 仓库(mvnrepository.com)查这个版本对应的依赖写法,一步到位。
7.2 视频播放 m3u8:别在格式转换上浪费生命
做过视频类平台的同学应该都遇到过 m3u8 播放问题。m3u8 是 HLS 协议的视频切片格式,需要专门的播放器支持。
如果在网页里要播放 m3u8 格式的视频,推荐用video.js配合videojs-contrib-hls插件,或者直接用hls.js。核心代码:
// 使用 hls.js 播放 m3u8 if (Hls.isSupported()) { const video = document.getElementById('video') const hls = new Hls() hls.loadSource('/static/sample.m3u8') hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play() }) }需要注意的是:m3u8 播放可能存在跨域问题,因为视频切片请求的是.ts文件,这些请求也要走 CORS。如果你用 Nginx 托管视频文件,记得在 Nginx 配置里加上跨域头。
7.3 MySQL 安装与字符集:utf8mb4 是底线
MySQL 安装教程网上非常多,但有几个细节容易被忽略:
- Windows 安装时选择 UTF-8 字符集,不要用默认的 latin1,否则后期中文乱码会把你逼疯
- MySQL 8.x 和 5.7 的驱动配置不一样:8.x 用
com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver。Spring Boot 2.7 大多配合 MySQL 5.7 或 8.0,连接串要写成jdbc:mysql://localhost:3306/media_share?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai - 数据库名不要用中文:有的人为了“好看”把数据库名起成中文,结果 SQL 导入时各种报错,纯属自找麻烦。
7.4 Vue 安装与环境配置:Node 版本别太新
Vue 项目开发环境配置,最容易翻车的是 Node 版本。Vue CLI 对 Node 版本有一定要求:
- Node 14/16 都稳定,配 Vue CLI 4/5 都没问题
- Node 18+ 不是不行,但某些老项目的依赖会有兼容问题
- 如果
npm install报错,先试试npm install --registry=https://registry.npmmirror.com(国内镜像源),大概率是网络问题
另外,npm install报错时不要慌,先看报错信息里的关键词:是网络超时、依赖版本冲突、还是权限问题。多数情况下换镜像源、升级 Node 版本、删掉node_modules重新安装就能解决。
7.5 其他高频坑
- 后端返回的日期格式:Java 的
LocalDateTime默认序列化格式是2023-06-01T10:30:00,不是2023-06-01 10:30:00。前端显示会很难看,需要在后端加配置或注解统一格式。 - 前端静态资源 404:图片、CSS、JS 加载不出来,先检查 Nginx 配置里的 root 路径对不对,再检查文件权限(
chmod -R 755)。 - 数据库连接字符串里的时区问题:连接串加
serverTimezone=Asia/Shanghai可以解决“The server time zone value ... is unrecognized”报错。 - 部署后日志时间不对:服务器系统时区如果不是中国时区,日志时间和你本地对不上。解决方案是启动命令加
-Duser.timezone=Asia/Shanghai参数。
最后再分享一个我自己写这类毕设项目的经验:动手之前先把数据库表设计好、接口返回格式定好、前后端联调的 proxy 配好,这三件事做好,后面至少省一半时间。遇到版本问题、环境问题不要死磕,优先换一个更稳的版本组合,毕设是用来展示你掌握知识的能力的,不是用来证明你有多能折腾环境的。项目能跑、逻辑能讲、坑能说清楚,答辩基本就稳了。