前后端分离武理多媒体信息共享平台系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程
高校里的多媒体资源管理,说简单也简单,说复杂也复杂。说简单,无非是上传、存储、播放、下载这几件事;说复杂,则是权限、分类、检索、防滥用、多端兼容这些细节叠加在一起之后的工程问题。我前后做过好几个类似的信息共享平台项目,这套基于 SpringBoot + Vue + MyBatis + MySQL 的前后端分离架构,算是在校园场景里跑得最稳、改动成本最低的一套组合。
这套系统解决的典型痛点很明确:同一个院系甚至同一个班级内部,课件、实验视频、参考资料散落在各个群文件和网盘链接里,没有统一的入口,没有检索能力,也没有访问控制。而一个前后端分离的多媒体共享平台,恰好能把“资源集中管理”和“权限分层控制”这两件事一次讲清楚。无论是课程设计、毕业设计,还是实验室里确实需要一个内网资源库,这套代码和部署思路都可以直接拿去用。
接下来我会从项目设计思路、核心模块拆解、部署实操、常见坑排查这四个方面完整过一遍,保证你拿到源码之后不是只会跑起来,而是真正能改、能调、能上线。
1. 项目整体设计与思路拆解
1.1 为什么一定要用前后端分离
先说结论:校园多媒体共享平台这种项目,前后端分离不是赶时髦,而是业务形态决定的。
这类系统的使用场景很有特点:PC 端管理 + 移动端浏览。老师和管理员要上传资源、审核内容、管理用户,这是在电脑上完成的;学生下课掏出手机想看课件和实验视频,这是在移动端完成的。如果你用传统的服务端渲染模板(比如 JSP、Thymeleaf),那你在手机端看到的内容形态,基本被后端模板锁死了,想做一个适合小屏幕的交互界面,就得重新写一套模板,维护成本成倍增加。
前后端分离之后,后端只提供 JSON 接口,前端不管是 Vue 的 Web 页面,还是以后要套一个小程序,都只需要对接同一套后端 API。这个项目里我用 Vue 2 + Element UI 做管理后台和用户端,接口统一走 RESTful 风格,返回 JSON 数据。Vue 负责渲染和交互,SpringBoot 负责业务逻辑和数据读写,互不干扰,出了问题也容易定位。
另一个实际原因是团队协作。做毕设或者课程设计时,前端一个人、后端一个人(或者你自己一个人分阶段做)是常态。前后端分离意味着你可以先定义好接口文档,然后前端用 mock 数据开发页面,后端用 Postman 调试接口,两边并行推进,不需要等某一方完全做好。这一点在赶工期的场景下价值非常大。
1.2 技术选型背后的理由
这套系统的技术栈乍一看是 Java Web 的“标准答案”:SpringBoot + Vue + MyBatis + MySQL。但标准答案之所以成为标准答案,恰恰是因为它在解决这一类问题上经过了大量验证。
- SpringBoot:选它而不是传统的 SSM 逐项配置,核心原因是 SpringBoot 的自动装配机制把 Spring、SpringMVC 的繁琐配置大幅简化。版本选择上,我用的是 SpringBoot 2.7.x。这里要特别提醒一句:不要一上来就追 SpringBoot 3.x,因为 3.x 强制要求 JDK 17,并且 Jakarta EE 命名空间变化会导致部分老教程和依赖不兼容。在学习型和校园型项目中,稳定压倒一切。
- Vue 2 + Element UI:Vue 3 已经很成熟了,但这个项目用 Vue 2 不是我保守,而是 Element UI 和现有的成熟组件生态在 Vue 2 下最省心。如果你是从零学起,Vue 2 的教程资料最多,遇到问题搜解决方案最容易。当然,如果你对 Vue 3 更熟,改成 Vue 3 + Element Plus 在架构上完全没问题。
- MyBatis:为什么要用 MyBatis 而不是 JPA?因为多媒体信息共享平台的查询场景高度定制化。比如“按资源类型 + 上传时间 + 关键词模糊搜索 + 分页”这种组合条件,MyBatis 的 XML 动态 SQL 写起来非常直观,你能精确控制每一条 SQL 的执行行为。JPA 虽然写基础 CRUD 快,但一旦涉及多表关联和动态条件,调试成本明显上升。
- MySQL:这个不用多说,校园项目的绝对主力。需要强调的一点是,MySQL 的字符集和排序规则在建库那一刻就要定好。推荐使用
utf8mb4+utf8mb4_general_ci,因为资源标题和描述里随时可能出现表情符号或者特殊字符,utf8mb4才能完整支持。
1.3 整体架构与表结构设计
这套系统的架构分为三层:前端展示层(Vue 页面)、后端接口层(Controller + Service + Mapper)、数据存储层(MySQL + 本地文件存储)。
数据库表的设计是整套系统的地基。我总共设计了 6 张核心表:
| 表名 | 用途 | 关键字段 |
|---|---|---|
user | 用户信息 | id, username, password, role, avatar, create_time |
category | 资源分类 | id, name, parent_id, sort_order |
resource | 资源信息 | id, title, type, url, cover, summary, uploader_id, category_id, status, download_count, create_time |
resource_comment | 资源评论 | id, resource_id, user_id, content, create_time |
resource_like | 资源点赞 | id, resource_id, user_id, create_time |
operation_log | 操作日志 | id, user_id, action, target_id, detail, create_time |
资源表是整个系统的核心,几个关键字段我要单独解释一下。
type字段区分资源类型:1 表示视频,2 表示文档,3 表示图片,4 表示压缩包。这个字段决定了前端渲染方式——视频走播放器组件,文档走预览组件,图片直接展示缩略图。
status字段是审核状态:0 待审核、1 已发布、2 已驳回。我在设计时要求所有用户上传的资源必须先经过管理员审核才能公开显示。这个设计能有效避免有人上传不合适的内容,在校园网环境里,这个模块的存在很重要。
关于文件本身的存储,我选择了本地磁盘存储 + 数据库记录元数据的方案,而不是直接把文件存进 MySQL 的 BLOB 字段。原因很简单:文件存数据库会导致表体积膨胀极快,备份和查询都会变得很慢;本地存储目录结构清晰,比如按日期分目录(/upload/2025/05/),配合 Nginx 做静态资源映射,访问效率高,实现也简单。文件路径存在 resource 表的url字段里,前端通过拼接得到完整访问地址。
2. 核心功能模块拆解与实现要点
2.1 用户登录与权限控制
用户体系分为三个角色:管理员(admin)、上传者(teacher)、普通用户(student)。登录认证用的是 JWT(JSON Web Token),没有用 Session。
JWT 方案在前后端分离场景下有几个天然优势:无状态、跨域友好、适合移动端复用。用户登录成功后,后端签发一个有效期为 24 小时的 token 返回给前端;前端存储在 localStorage 中,每次请求在请求头里带上Authorization: Bearer <token>;后端通过拦截器解析 token,识别用户身份和角色。
具体实现上,后端会用 SpringBoot 的拦截器(HandlerInterceptor)统一处理 token 校验,同时配合一个自定义注解@RequireRole来做细粒度的权限控制。比如管理员审核资源的接口方法上标注@RequireRole("admin"),上传者调用上传接口标注@RequireRole("teacher"),普通用户就只能访问公开的资源列表和详情。
这里有一个非常容易出错的细节:token 失效后的前端处理。如果后端接口返回 401,前端 axios 的响应拦截器里必须统一跳转登录页并清除本地缓存,否则用户会看到一个报错页面之后系统就“假死”了。我在这个项目里的做法是:
// axios 响应拦截器 service.interceptors.response.use( response => { return response }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') localStorage.removeItem('userInfo') router.push('/login') } return Promise.reject(error) } )密码存储用的是 BCrypt 加密,Spring Security 框架里的BCryptPasswordEncoder可以直接拿来用。记住一个原则:任何情况下都不要自己写加密算法,数据库里更不能存明文密码。BCrypt 每次生成的哈希值不同,但校验结果是稳定的,这让暴力破解的成本大幅提升。
2.2 资源上传与多媒体处理
上传功能是整个系统里最“硬件”的部分。前端我用的是 Element UI 的el-upload组件,通过action属性指向后端上传接口。上传的同时,前端会把文件类型、标题、简介、所属分类这些信息一起通过表单提交。
后端上传接口的处理逻辑要重点讲一下,因为这里涉及文件大小限制和存储路径的组织。首先在 SpringBoot 配置文件中设置上传限制:
spring: servlet: multipart: max-file-size: 500MB max-request-size: 550MB这里是第一次踩坑的地方。SpringBoot 默认的上传文件大小限制只有1MB。如果你不调整这两个配置,上传稍大一点的实验视频就直接报 413 错误。而多媒体平台上 50MB 以上的视频非常常见,所以我把单个文件限制放宽到 500MB,这个值在大多数校园网场景下够用了。
接收文件后,后端会把 MultipartFile 保存到本地的指定目录,目录按日期组织,避免单个目录下文件过多导致 IO 变慢:
String dateFolder = LocalDate.now().toString().replace("-", "/"); String realPath = uploadDir + "/" + dateFolder; File dir = new File(realPath); if (!dir.exists()) dir.mkdirs(); String fileName = UUID.randomUUID().toString().replace("-", "") + "-" + originalFilename; File dest = new File(dir, fileName); file.transferTo(dest);这里我故意用 UUID 重命名文件,而不是保留原始文件名。原因有两个:一是避免中文文件名和特殊字符在 URL 访问时产生编码问题;二是防止同名文件互相覆盖。原始文件名会被存入数据库的另一个字段,在下载时通过接口返回给前端做展示。
视频上传后还有一个加分项:封面图生成。如果是视频文件,我会调用ffmpeg命令截取第一帧作为默认封面,然后存入数据库的cover字段。这个操作虽然只多了十几行代码,但用户体验提升非常明显——资源列表页不再是清一色的灰底图标,而是真实的视频画面。
2.3 资源检索与分类筛选
检索模块是用户感知系统“好不好用”的第一道关口。我的实现方式是:分类树 + 关键字搜索 + 分页查询三件套。
后端查询接口通过 MyBatis 的动态 SQL 实现组合条件查询。核心 mapper 文件里这么写:
<select id="searchResources" resultType="com.example.entity.Resource"> SELECT * FROM resource <where> <if test="keyword != null and keyword != ''"> AND (title LIKE CONCAT('%', #{keyword}, '%') OR summary LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="categoryId != null and categoryId != 0"> AND category_id = #{categoryId} </if> <if test="type != null and type != 0"> AND type = #{type} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY create_time DESC </select>LIKE 模糊查询在数据量小的时候性能没有问题,当资源量超过几万条后可以考虑引入 Elasticsearch,但校园场景下 MySQL 完全扛得住。
这里要提醒一个 MySQL 排序的常见问题:ORDER BY create_time 在数据量大时无法走索引,因为create_time默认是 DATETIME 类型,你需要给create_time字段加一个普通索引,并且如果有 WHERE 条件,优先保证 WHERE 条件涉及的字段有索引。不建议在一张还在迭代的表上直接建联合索引,等字段稳定了再优化不迟。
前端分类筛选我做了两级分类:一级分类是“学科大类”(如计算机、电子、机械),二级分类是“资源类型”(课件、视频、文献、软件)。这样用户可以按学科树逐级下钻,也可以直接用顶部的搜索框全局搜索。两种方式互补,覆盖了“浏览型”和“目标型”两类用户需求。
2.4 视频播放与下载权限
视频播放是本平台最核心的功能。前端我用video.js组件来做播放器,支持 mp4 格式的直接流式播放。HTML5 的 video 标签天生支持Range请求,也就是拖拽播放进度。这是浏览器和 Nginx 自动完成的,不需要额外写代码。
这里有一个可以分享的冷门技术点:如果你需要播放 m3u8 格式的视频流(比如老师上传的录课视频转成了 HLS 流),需要在前端引入hls.js库才能让浏览器播放。安装方式很简单:
npm install hls.js在 Vue 组件里这样接入:
import Hls from 'hls.js' playM3u8(url) { if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(url) hls.attachMedia(this.$refs.video) } }下载权限的控制我用的是一个简单但实用的方案:登录用户才有下载权限,下载操作在数据库中记录日志。用户点击下载按钮时,前端请求后端获取真实文件地址,后端校验 JWT 后返回一个临时授权地址(拼接一个短暂有效的签名参数),而不是直接暴露静态资源目录的完整路径。这样既能控制未登录用户的访问,也能在后续统计“哪个资源最受欢迎”时拿到真实数据。
3. 本地实战:从零搭建与部署全流程
3.1 环境准备与版本对应关系
项目排坑的第一步,就是要确保环境版本匹配,这大概是我被问过最多的问题。先给出我验证过的版本组合:
| 软件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 不要用 17,除非你上 SpringBoot 3.x |
| Maven | 3.6.3 或 3.8.x | 3.9 以上需要 JDK 11+ |
| Node.js | 14.x ~ 16.x | Vue 2 项目用这个范围最稳 |
| MySQL | 5.7 或 8.0 | 推荐 8.0,字符集选 utf8mb4 |
| Nginx | 1.20+ | 用于部署前端静态文件和代理后端接口 |
很多人忽略的是 Node.js 的版本问题。Vue 2 项目如果装了 node-sass 依赖,而你的 Node.js 版本过高(比如 18+),node-sass 编译非常容易失败。我试过的方案里,Node 14 + npm 6 搭配 Vue 2 + Element UI 是最省心的组合。如果项目用的是 dart-sass,那版本要求会宽松一些,但为了不折腾,还是建议直接用 14。
3.2 数据库初始化与导入
拿到源码后第一步不是改代码,而是先建库。项目里一般会附带一个sql目录,里面有初始化脚本。命令行方式执行:
mysql -u root -p < init.sql如果你的 MySQL 装在 Windows 上没有配好环境变量,可以用 Navicat 这一类图形化工具,新建数据库后右键“运行 SQL 文件”即可。
关于 MySQL 安装,我多说一句踩过的坑:MySQL 8.0 默认认证插件是caching_sha2_password,而一些老版本的驱动(比如 5.1.x 的 mysql-connector-java)不兼容。如果你的项目启动时报连接错误,要么在 pom.xml 里升级驱动版本到 8.0.x,要么在创建用户时指定老认证插件:
CREATE USER 'appuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';强烈建议直接用最新版驱动,因为后续省事很多。
3.3 后端启动配置与常见报错解决
后端项目导入 IDEA 后,Maven 会自动下载依赖。这里要等一会儿,如果网络状况不好,建议把 Maven 镜像换成阿里云镜像,在settings.xml里加一段:
<mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror>然后修改application.yml中的数据库连接配置:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/media_share?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4 username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true file: upload-dir: /data/media_uploadmap-underscore-to-camel-case: true这个配置非常重要。它让数据库里的create_time字段自动映射到 Java 实体类的createTime属性,不用你手动写繁琐的 resultMap。如果你发现查询返回的字段全是 null,八成是这个配置没开。
启动类直接运行main方法,后端就起来了。验证接口是否通,浏览器访问http://localhost:8080/api/health,如果能返回{"status":"ok"}就说明环境没问题。
如果遇到端口被占用的情况(比如 8080 被其他程序占用了),两种方案:改application.yml里的server.port,或者启动时用命令行指定:
java -jar app.jar --server.port=80813.4 前端环境配置与跨域处理
前端项目在 idea 或者 VSCode 里打开,先安装依赖:
npm install这里经常出问题的是依赖下载超时或者某个包安装失败。解决办法就是把 npm 源切换到国内镜像:
npm config set registry https://registry.npmmirror.com安装完成后启动开发服务器:
npm run dev正常情况下终端会显示App running at: http://localhost:8081。但是此时前端页面里的接口请求地址还是后端的 8080 端口,这就涉及跨域问题(CORS)。
我在开发环境里有两个处理方案,两个都用上了:
方案一:在 Vue 项目的vue.config.js里配置代理。这是开发环境的标配方案:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }方案二:在后端配置全局跨域。这个方案的价值是让前端也可以直接访问后端接口,特别是在你临时调试或者开发环境没有代理的情况下:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意:如果设置了
allowCredentials(true),就不能用allowedOrigins("*"),必须用allowedOriginPatterns("*")。这是 Spring 的一个校验规则,直接写allowedOrigins("*")会启动报错。
3.5 生产环境发布:打包与 Nginx 部署
开发环境跑通之后,真正的挑战在部署。前后端分离项目在生产环境的标配是:前端打包成静态文件扔给 Nginx,后端打成 Jar 包挂在服务器上。
后端打包前,注意确认application.yml里的数据库链接已经是生产库地址、上传目录是服务器上的真实路径。然后执行:
mvn clean package -DskipTests在target目录下会生成xxx.jar。在服务器上的启动方式:
nohup java -jar media-share-1.0.jar > app.log 2>&1 &用nohup让进程在关闭终端后继续运行,日志输出到app.log,方便排查问题。
前端打包:
npm run build生成dist目录,把dist目录里的所有文件上传到服务器任意一个目录(比如/var/www/media_share),然后在 Nginx 的配置文件里做静态资源映射和后端 API 反向代理:
server { listen 80; server_name your-domain.com; # 前端静态资源 root /var/www/media_share; index index.html; # 解决 Vue Router 的 history 模式刷新 404 问题 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传文件的静态访问路径 location /files/ { alias /data/media_upload/; add_header Access-Control-Allow-Origin *; } }这里有一个我最初部署时漏掉的关键项:Vue Router 如果不使用 hash 模式(即 URL 里没有#),刷新页面时会向 Nginx 发送一个真实的路径请求,比如访问/resource/12,Nginx 找不到对应的静态文件就会返回 404。try_files $uri $uri/ /index.html;这行配置把所有前端路由的请求都回归到 index.html,由 Vue Router 接管,这个坑如果没踩过,上线后绝对会慌。
4. 常见问题与排查技巧实录
4.1 后端启动报错对照表
在跑这个项目的过程中,大多数报错是环境问题,而不是代码问题。我把遇到过的典型问题整理成了一张速查表:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
Access denied for user 'root'@'localhost' | 数据库密码错误或用户权限不足 | 检查 application.yml 的密码;确认 MySQL 用户允许本地登录 |
Unknown database 'media_share' | 数据库还没有创建 | 先执行 init.sql 或手动建库 |
Port 8080 was already in use | 端口占用 | 换端口启动,或找到占用进程杀掉 |
Failed to configure a DataSource | 数据库连接配置缺失 | 检查 spring.datasource 配置是否完整 |
Cause: com.mysql.cj.jdbc.exceptions.CommunicationsException | MySQL 未启动或端口不是 3306 | 先确认 MySQL 服务是否在运行 |
Invalid bound statement (not found) | MyBatis 的 mapper XML 路径配置不对 | 检查mapper-locations是否匹配实际 XML 存放位置 |
Failed to parse configuration class | SpringBoot 版本与 JDK 版本不匹配 | 确认 JDK 版本是否符合 SpringBoot 要求 |
其中Invalid bound statement这个报错特别容易踩,新手经常把 mapper XML 文件放在 Java 包里,但 Maven 默认不会把src/main/java目录下的 XML 文件打包到 classpath。解决方法是在 pom.xml 的 build 节点中加:
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources>或者干脆把 XML 文件放到src/main/resources/mapper目录下,然后配置mapper-locations: classpath:mapper/*.xml。我用的是后者,惯例做法最不容易出问题。
4.2 MyBatis 查询条件不生效的经典排雷
MyBatis 动态 SQL 有一个非常经典的坑:XML 里的<if>判断,参数明明是空的,但条件就是不生效。
我见过最多的场景是这样的:
<if test="categoryId != null and categoryId != ''"> AND category_id = #{categoryId} </if>当categoryId是 Integer 类型时,categoryId != ''这个判断会报错或者导致条件失效。因为 Integer 不应该和 String 比较,MyBatis 在 OGNL 表达式里对这个比较的处理很微妙。正确写法是:
<if test="categoryId != null"> AND category_id = #{categoryId} </if>这类问题的排查思路是:在 MyBatis 配置里打印 SQL,看最终生成的 SQL 是什么样的。
mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这个配置项是我日常排雷的第一步。开了之后,控制台会打印完整的 SQL 句子和参数列表,你一眼就能看出是参数没传进来、还是 SQL 拼接逻辑有问题。这个技巧对 MyBatis 定位问题来说,比我见过的大部分 debug 方式都直接。
4.3 上传功能失灵的排查清单
上传功能一旦失败,现象五花八门:有的报FileUploadException,有的是 413,有的是 500,有的前端直接提示跨域。
我自己的排查顺序是:
- 先看控制台日志——这一步能区分是文件太大被拦截,还是 SpringMVC 处理过程中出了异常。
- 确认上传请求头——前端上传时是否带上了 JWT token?如果后端上传接口拦截了未登录请求,而 axios 拦截器没有覆盖
el-upload的自定义请求头,就会返回 401。 - 检查磁盘空间和目录权限——
file.transferTo(dest)本质是文件 IO,如果上传目录不存在或者没有写权限,这里会异常。在 Linux 上部署时尤其要注意,/data/media_upload目录的属主要和运行 Jar 包的用户一致。 - 确认 Nginx 上传大小限制——部署模式下的 413 报错,不一定是 SpringBoot 的限制,也可能是 Nginx 的默认限制。必须在 Nginx 配置里加上:
client_max_body_size 500M;这个坑非常隐蔽,因为开发环境走 Vue devServer 代理不会触发 Nginx 限制,只有生产环境部署才会暴露。
4.4 Vue 打包后放到 SpringBoot 的思路差异
网上经常有人问“Vue 打包之后能不能放进 SpringBoot 的 static 目录里运行”。可以,但不建议在前后端分离的项目里这么做。
放进 SpringBoot 的方式本质上是把前后端揉回了一个部署单元,好处是部署简单(只跑一个端口),坏处是失去了前后端分离的扩展能力——以后要是新增一个移动端或多个前端,就得再维护一套部署逻辑。而且如果你用的 Nginx 不只是做静态文件服务,还承担了负载均衡、HTTPS 证书、静态资源缓存这些任务,那 Vue 放到 SpringBoot 里面就是给自己找麻烦。
我的建议很明确:按原生的前后端分离方式部署,前端 Nginx + 后端 Jar 包。如果只是在学校服务器上临时演示,也可以合并运行,但别把它当常规方案。
4.5 MySQL 数据同步与备份的小建议
项目跑起来之后,数据备份就是第一优先级。我的习惯是每天凌晨用 crontab 做一次全量备份,备份命令很简单:
mysqldump -u root -p你的密码 media_share > /backup/media_share_$(date +%Y%m%d).sql恢复时执行:
mysql -u root -p media_share < backup.sql额外提醒一点:上传的文件目录也要一起备份,数据库只是文件路径的记录者,文件实物丢了数据库记录再完整也恢复不了。这两个备份最好存放在不同磁盘位置,防止硬件故障导致全丢。
5. 源码结构解读与二次开发思路
5.1 后端目录结构与分层思想
源码到手后,先别急着跑,花十分钟看一下包结构。我的后端包结构是这样的:
com.example.mediashare ├── controller # 接口层,接收请求、返回响应 ├── service # 业务层,处理核心逻辑 ├── mapper # 数据访问层,MyBatis 接口 ├── entity # 实体类 ├── config # 配置类(跨域、拦截器、WebMvc) ├── common # 通用类(统一返回结果、异常处理) ├── interceptor # JWT 拦截器 └── utils # 工具类controller层只负责参数接收、调用 service、返回结果,不写任何 SQL 相关代码;service层专注业务逻辑,比如上传资源时的文件名生成、路径组装、状态默认值设置;mapper层就是纯粹的接口和 XML。这样的分层让每个文件的职责都很清晰,改起来不会牵一发而动全身。
统一返回结果是我在所有后端项目里都坚持做的。前端的每个请求都会返回一个固定的结构:
{ "code": 200, "message": "操作成功", "data": { } }code是 200 表示成功,400 表示参数错误,401 表示未登录,500 表示服务器异常。前端 axios 统一拦截这个结构体,识别code之后做对应提示,而不是让前端去判断 HTTP 状态码。这个设计的价值在于,HTTP 状态码在跨域和网关场景下可能会被改写,而业务响应里的 code 永远是我们自己说了算。
5.2 前端路由与权限菜单的动态生成
Vue 端用得比较顺手的设计是:路由分为公共路由和权限路由两部分。公共路由只有登录页和首页,权限路由根据登录用户的角色动态添加。
具体说,用户在登录成功后,后端接口会返回一个字段role(admin / teacher / student),前端根据这个角色拼接对应的菜单项。比如管理员能看到“用户管理”和“审核管理”,上传者只能看到“资源管理”和“我的上传”,学生则只有“浏览资源”和“我的收藏”。
Vue Router 的动态添加路由方式是:
// 根据角色添加路由 router.addRoutes(dynamicRoutes[role])动态路由这套逻辑,在用户刷新页面时有一个需要注意的地方:Vuex 里的用户状态是保存在内存里的,刷新后丢失。所以刷新页面时,要么把用户信息和角色存到 localStorage 中,在路由初始化时读取;要么在根组件的 created 钩子里重新调用一次“获取用户信息”接口。我用的是后者,因为更安全——用户信息不落盘,token 丢了就得重新登录。
5.3 后续扩展方向:日志、统计与搜索
如果把这套系统作为毕设或者实验室项目,我建议在现有基础上朝这几个方向扩展:
操作日志采集。目前的 operation_log 表已经记录了用户的行为,但只是被动记录。你可以做一个简单的后台统计面板,按用户、按时间、按资源类型做聚合展示,用高性能的 SELECT 聚合查询就可以实现,不需要引入额外的重量级组件。
访问量统计与资源排行。在资源表中加一个view_count字段,每次进入详情页时异步调用一个自增接口。这个方案要注意接口的防刷,最简单的办法是在后端限制同一个 IP 在一分钟内的重复提交次数。
接入更专业的搜索。当资源量突破一定规模,且你的机器配置允许,可以考虑把 Elasticsearch 引入进来。用 Logstash 或者其他同步工具把 MySQL 的数据同步到 ES,然后前端搜索接口走 ES。在校园网环境下,MySQL LIKE 查询在数据量几十万级别时其实已经够用,所以这个扩展不着急,等卡到不可接受了再上。
5.4 代码阅读顺序建议
如果你是第一次接触这个项目的源码,我建议按照这个顺序读:
- 先看数据库表结构(
init.sql),理解业务原型。 - 看
entity包下的实体类,对比数据库字段,确认映射关系。 - 看
controller包,了解对外提供的所有接口。 - 挑一个完整链路(比如“用户上传资源”),从 controller → service → mapper 走一遍。
- 再看前端对应的页面,理解前后端是如何通过接口配合的。
跳着读是读源码的大忌,尤其是前后端分离项目,如果不知道某个接口对应哪个页面,整个代码在你眼里就是散落的孤岛。
最后再分享一点个人经验
做完这一整套基于 SpringBoot + Vue + MyBatis + MySQL 的多媒体信息共享平台,我最大的感受是:这类系统真正的复杂度不在技术栈本身,而在“资源生命周期”的完整闭环——从上传、存储、检索、浏览、播放到审核和下线,每一个环节都有大量容易被忽略的细节。
比如视频播放的 Range 请求兼容、文件上传大小的三层限制(SpringBoot、Nginx、浏览器)、Vue Router 的 history fallback、MyBatis 动态 SQL 的类型比较陷阱,这些都是在踩过坑之后才真正理解的。写这篇实战记录,也是希望后来的人能少走几天弯路。
如果你在实际部署中遇到了我上面没提到的问题,可以按照“看日志 → 查配置 → 搜资料 → 拆链路”这个顺序来排查,大多数问题都能在这个流程里找到答案。这套系统的完整源码和部署文档整理之后,后续我还会补充一个 Docker 化的部署方案,让整体环境搭建可以一键完成。