
前段时间接了个活儿给学校的信息学科部做一个官网。这种院系级别的网站大家应该都不陌生——门户首页、新闻通知、师资队伍、专业介绍外加学生最常用的下载中心。表面看需求不复杂真做起来才发现细节一堆新闻图片要能传上去、通知要按权限发布、活动视频要能在线播放还得同时兼容电脑和手机。我最终落地的方案就是标题里这套 Spring Boot Vue 前后端分离架构配合 MySQL、MinIO 这些配套组件也就是 springboot信息学科部校园网站3249q-vue 整个项目的核心。这篇文章把完整过程、选型逻辑、踩过的坑全部梳理一遍给后面要接校园网站、内容管理系统这类项目的朋友做个参考。1. 项目整体设计与技术选型思路1.1 为什么锁定Spring Boot Vue这套组合先说选型。接到需求时我的第一个念头就是别把简单事情搞复杂。信息学科部的校园网站本质是一个内容管理系统加信息展示平台核心动作是发布内容和展示内容没有特别复杂的高并发、高实时性场景。但也不能拿纯静态页面去糊弄后面肯定要加新闻管理、文件上传、权限控制这些动态功能所以最终确定 Spring Boot Vue 这套组合。选 Spring Boot理由是省心。它是构建后端服务最快的方案之一自动装配机制帮我省掉了大量 XML 配置Maven 依赖拉下来就能跑内嵌 Tomcat 又不用单独装服务器。对校园网站这种业务来说Spring Boot 的成熟生态和稳定性能完全够用而且招人维护时会 Spring Boot 的开发者远比会老旧 SSH 框架的多。前端选 Vue是因为它足够轻、组件化思路清晰而且在国内高校和企业里的普及率极高。Vue 的单文件组件SFC把一个页面拆成模板 脚本 样式写起来直观维护起来也友好。再加上 Element Plus 这类现成组件库做后台管理页面基本是拼积木。这套组合还有一个隐性优势前后端分离以后前端只需要关注页面渲染和数据交互后端只需要把接口设计好两边可以并行开发不需要互相等待。1.2 信息学科部官网的功能模块拆解在动工之前我先把需求拆成了两个端前台门户和后台管理。前台门户主要面向师生和外部访客核心模块有这么几块门户首页展示学科部简介、新闻动态轮播图、通知公告、快速入口。新闻通知按分类展示学院新闻、教务通知、学工动态支持搜索和分页。师资队伍教师列表、教师详情研究方向、成果、联系方式。专业介绍各本科/专科专业的信息展示。下载中心教学资料、表格模板等文件下载。招生就业面向考生的招生宣传内容。后台管理端主要面向管理员和部门干事设计成角色化权限管理内容管理新闻、通知的发布、编辑、下架、置顶。文件管理下载中心的上传、分类、删除。用户管理管理员账号维护、角色分配。基础配置轮播图设置、网站参数配置。这个拆法基本沿用了我做类似项目的经验前台管看后台管发中间通过一套接口桥接起来。权限大体分成三类超级管理员管所有模块、部门管理员管自己负责的栏目、普通编辑只能发文章。权限模型不搞太复杂校园官网的角色贵在够用过度设计后面反而是负担。1.3 页面骨架与权限模型设计页面骨架我采用的是前台走官网风格、后台走管理风格的双模板策略。前台用响应式布局重点照顾手机上浏览新闻的体验后台则用侧边栏加顶栏的管理框架方便管理员快速切换功能。这里有一个值得说的点权限模型的设计决定了后面所有菜单和接口的编写方式。我没有用太重的 RBAC 表结构而是采用用户—角色—菜单三层角色对应一组菜单编码后端返回该角色允许访问的菜单树前端根据菜单树动态生成侧边栏。这样做的直接好处是后端接口通过过滤器统一校验权限前端菜单也是同一套数据驱动生成权限改动只需要在数据库里调整角色和菜单的关联不用同时改前端代码和后端拦截逻辑。对一个课程设计阶段或部门自用系统来说这是性价比非常高的方案。2. Spring Boot后端从零搭建到核心接口落地2.1 环境准备与项目初始化要点开发环境我用的 JDK 1.8 Maven 3.6.3Spring Boot 版本选了 2.7.x。很多朋友迷信最新版本我这次特意没有上 Spring Boot 3.x原因很简单3.x 强制 JDK 17而且大量第三方依赖把 javax 命名空间切换成了 jakarta很多旧教程和老依赖会直接报错。对一个要快速交付、稳定上线的项目来说2.7.x 的生态反馈最快坑最少。这个选择也提醒各位一句——技术选型永远是为交付服务的不是为简历服务的。项目创建我用的是 Spring Initializr也可以直接用 IDEA 的 Spring Initializr 窗口。关键依赖我加了这些Spring Web、Spring Data JPA 或 MyBatis-Plus我用的 MyBatis-Plus、MySQL Driver、Lombok、Spring Security、JWT 相关工具包。MyBatis-Plus 在这里帮了大忙单表 CRUD 几乎不用写 SQL自带分页插件、逻辑删除、字段填充非常适合内容管理这种以单表查询为主的业务。初始化之后第一件事是配置 application.yml。除了常规的端口、数据库连接要特别注意两个地方一是 MySQL 的时区参数二是上传文件大小限制。时区不配置查询出的日期经常会差 8 个小时这是新手最容易忽略的上传限制不放大新闻配图稍微大一点就直接报错。我的配置大概是server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/site_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: xxxxxx servlet: multipart: max-file-size: 50MB max-request-size: 100MB这里要补充一个容易踩坑的细节MySQL 连接串里加上 allowPublicKeyRetrievaltrue 也很重要尤其使用 MySQL 8.x 时某些客户端连接会报Public Key Retrieval is not allowed。服务能跑起来只是第一步这些底层依赖问题越早配置越省心。2.2 统一响应结构与全局异常处理后端接口设计我坚持一个原则所有接口返回统一格式。前后端分离之后前端最怕的就是接口返回格式五花八门——一会儿返回字符串一会儿返回对象一会儿抛异常直接返回错误页。我在这个项目里定义了一个 Result 类固定包含 code、message、data 三个字段public class ResultT { private Integer code; private String message; private T data; }所有 Controller 的返回值都包成 Result成功 code 是 200业务失败用 500 或自定义业务码。前端拿到响应后统一在 axios 拦截器里判断 code等于 200 就正常返回不等于就弹提示。这一套下来前后端联调时几乎没有出现过格式对不上的情况。与之配套的是全局异常处理。我用 RestControllerAdvice 写了一个全局异常捕获器把 RuntimeException、参数校验异常、文件上传异常等都统一转换成 Result 返回。这一步的价值在于后端不把堆栈信息直接抛给前端避免暴露内部细节也让前端错误提示永远保持统一风格。2.3 登录认证与权限控制为什么我用了JWT拦截器关于登录认证我一开始其实犹豫过要不要上 Spring Security 全家桶。后来走访了一圈发现自己做过的大部分同类项目用 Spring Security 时配置复杂、放行规则容易写错反而拖慢进度。所以我最后采用了一个更轻的方案JWT 令牌 自定义拦截器。具体逻辑是这样的用户登录成功后后端生成一个 JWT 令牌返回给前端令牌里只携带用户 id 和角色编码有效期设置为 24 小时。前端请求头带上 Authorization: Bearer 后端写一个拦截器统一解析解析成功就把用户信息放进 ThreadLocal解析失败返回 401 状态码。首页、新闻列表、教师展示这些公开接口通过白名单配置直接放行不经过拦截器。字符串这种实现简单直接对校园官网完全够用。但要注意几个关键点JWT 密钥千万不要硬编码在业务代码里我放到了配置文件拦截器里要排除登录接口和静态资源路径否则项目一启动就 401前端要在 axios 响应拦截器里统一处理 401让用户跳回登录页而不是弹一个莫名其妙的报错。2.4 文件上传与m3u8视频播放支持这个项目让我最费工夫的功能是视频处理。学校希望官网能在学院风采栏目里放活动视频但浏览器对普通 mp4 大文件在线播放兼容性并不好尤其要拖动进度条时体验很差。最终我采用了 HLS 方案把上传的 mp4 用 FFmpeg 转成 m3u8 索引文件加 ts 切片前端用 video.js 或者 hls.js 播放。文件存储我用了 MinIO。MinIO 是兼容 S3 协议的对象存储服务用 Docker 就能起一个实例相比直接存服务器本地磁盘它的好处是文件访问走 HTTP管理界面直观扩展也容易。Spring Boot 集成 MinIO 的核心步骤就三步引入 minio 依赖配置服务地址和账号密钥封装一个 upload 方法返回访问 URL。视频转 m3u8 我走的是异步流程视频上传到 MinIO 后立即返回上传成功转码中的状态给前端后端用线程池执行 FFmpeg 命令转码完成后更新数据库状态。这样做的好处是前端不用一直等待后台也可以集中处理耗时的转码任务。FFmpeg 核心命令大致如下ffmpeg -i input.mp4 -codec copy -start_number 0 -hls_time 10 -hls_list_size 0 -f hls output.m3u8这里有个实操细节如果只是转 HLS-codec copy 可以直接复制流不重新编码速度非常快但它的前提是原视频编码格式兼容H.264/MPEG-4。如果上传的视频编码不标准保险起见还是要加 H.264 编码参数保证兼容性。3. Vue前端环境、路由与组件化实践3.1 Vue环境配置与项目创建的版本选择前端我把 Vue 版本定在了 Vue 3配套 Vite 构建工具和 Element Plus 组件库。Vue 3 用组合式 API 写起来更灵活Vite 的开发服务器启动速度比老一代 Webpack 快得不是一点半点改动代码热更新几乎是秒级这对调试体验提升很大。环境配置方面我用 Node 18 LTS 版本。当时极简流程是这样先安装 Node.jsnpm 随自带用 vite 模板创建项目命令是 npm create vitelatest交互界面里选择 vue 即可安装依赖 npm install启动开发环境 npm run dev。这里要强调一个经常被人忽略的问题npm 源。默认官方源在国内拉依赖时慢到怀疑人生建议尽早切换为国内镜像源命令是 npm config set registry https://registry.npmjs.org/ 改成镜像即可。这不算什么高级操作但能省出的时间非常可观。Vue 项目创建完成后我按模块拆分目录views 放页面components 放组件router 放路由store 放全局状态api 放接口请求封装。分层清晰的好处后面加功能、改 bug 时体会特别明显。3.2 动态路由与菜单权限的落地姿势Vue 侧的这个项目里动态路由是我重点设计的一个环节。刚才说了后端返回菜单树前端要根据菜单生成侧边栏并注册路由。这里我采用的方案是前端定义一份路由映射表把后端返回的菜单编码映射到具体的组件路径。具体做法是登录接口返回用户信息的同时带上 menuList前端把这份菜单存到 Pinia 或 Vuex 里然后通过 router.addRoute 动态注册路由。举个例子后端返回一个菜单项是新闻管理对应前端路由路径 /admin/news映射到 views/admin/NewsList.vue 组件前端在路由守卫里判断该用户是否已经注册过动态路由没有的话先 addRoute 再放行。这套方案最核心的价值菜单权限和路由权限来自同一份数据不会出现菜单看到了、点进去却 404的尴尬。我在项目里给 Vue Router 设了一个约定——页面文件名和路径保持一致动态映射时通过 import.meta.glob 一次性加载所有页面组件省去手动 import 的麻烦。3.3 组件化拆分通用列表页、插槽与复用做后台管理最烦的就是重复写列表页搜索框、表格、分页器、编辑弹窗每个模块都长得很像。我用组件化的方式解决这个问题抽了一个 CommonTable 组件把搜索表单、数据表格、分页封装成一个整体父组件只需要传入接口请求函数和列配置就能生成一个完整列表页。这里体现的是 Vue 插槽slot的强大之处。我在 CommonTable 组件里预留了几个插槽位置搜索区域插槽、表格操作列插槽、弹窗内容插槽。不同的业务模块只需要往插槽里塞不同的内容列表结构和分页逻辑全部复用。比如新闻管理需要置顶按钮就在操作列插槽里写一个按钮绑定对应的请求方法下载中心需要复制链接按钮也同样在插槽里追加。这种组件化改造一开始会花点时间但项目做到第四、五个功能模块时效率优势极其明显。写代码第一版我是直接从新闻列表复制改成通知列表改到一半发现到处都是冗余代码才痛下决心抽象组件。如果大家下次做类似项目建议从第一个列表页就开始用通用组件思路不要走我这种先复制再重构的老路。3.4 前端播放器集成m3u8播放免转码方案视频播放这里我前端用的是 video.js 加 hls.js 的组合。Vue 项目里安装视频播放器的依赖方式很简单npm install video.js但要注意video.js 原生默认不直接支持 m3u8 直播流的播放通常还需要引入 hls.js 作为它的解析器或者直接使用 video.js 自带的 contrib-hls 插件。我这里选了 hls.js 做底层流解析video.js 做 UI 框架封装了一个 VideoPlayer 组件。这个组件接受一个视频地址如果是 .m3u8 后缀就走 hls 播放逻辑是普通 mp4 就直接喂给 video 标签。封装完以后前台页面使用这个组件时只需要传视频 URL其他全部内部处理。有个经验要分享m3u8 播放时浏览器跨域问题容易导致加载失败。如果 m3u8 文件里的 ts 切片地址是相对路径而播放器页面域名和存储服务域名不一致就会出问题。最稳妥的做法是让 FFmpeg 转码时输出绝对路径的 m3u8或者在 MinIO 桶策略里配置好访问域名确保所有切片 URL 都能被正常访问。这种底层兼容问题排查起来非常费劲我光在这个坑里就花了小半天。4. 前后端联调、打包与部署实录4.1 联调阶段的跨域与代理问题前后端联调第一个拦路虎就是跨域。前端开发服务器跑在 5173 端口后端接口跑在 8080 端口浏览器默认会拦截跨域请求。解决思路有两个后端开启 CORS或者前端配置代理。我在开发环境用的方案是 Vite 代理。在 vite.config.js 里配置一个 proxy把请求 /api 开头的接口转发到 localhost:8080前端代码里请求地址只写 /api/xxx这样浏览器端不存在跨域问题。后端不需要额外写 CORS 配置开发环境清净很多。但要注意部署到服务器后如果前端走 Nginx 提供静态服务、后端接口仍然由 Spring Boot 提供跨域问题会重新出现。这时候就要用 Nginx 反向代理统一路径或者在后端配置文件里配置 CORS 允许的域名白名单。我偏好的是 Nginx 反代方案它能同时解决静态资源服务和接口转发两个问题而且不用改代码。4.2 Vue打包放进Spring Boot的两种方式这个项目里我实际上测试了两种部署方式都在不同场景下用得上。第一种是最省事的单应用部署前端执行 npm run build 后生成 dist 目录把 dist 里的文件整个复制到 Spring Boot 项目的 src/main/resources/static 下然后重新打包 Spring Boot jar。这样整个系统只有一个应用一个端口部署时只需跑一个 jar 包非常方便。缺点是前后端代码耦合在一起每次改前端界面都要重新打包后端不够灵活。第二种是标准的前后端分离部署前端 dist 交给 Nginx 托管后端 Spring Boot 以 jar 独立运行。这种方式上线后前端文件更新只需替换 dist 并刷新 Nginx后端接口迭代单独重启 jar互不影响也更贴近生产环境。我当时交付给学校的是第二种因为后续部门老师可能要频繁改页面内容如果每次都要重新打一个 jar维护成本太高。但如果遇到内网测试环境不方便装 Nginx 的情况第一种方案就是最快捷的兜底。4.3 部署到服务器端口、静态资源与Nginx反代部署到服务器时我的操作流程大致如下后端项目执行 Maven 打包命令 mvn clean package -DskipTests生成 jar 文件前端执行 npm run build生成 dist 文件夹把 jar 和 dist 上传到服务器指定目录后端通过 nohup java -jar site-server.jar 方式启动日志重定向到文件服务器安装 Nginx配置一个 server 块root 指向 distlocation /api/ 反代到本机 8080 端口。Nginx 配置核心片段长这样server { listen 80; server_name your-domain.com; root /opt/site/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有个部署细节Vue Router 如果开了 history 模式刷新页面时 Nginx 会找不到对应的静态资源返回 404。需要在 Nginx 配置里加一个 try_files $uri $uri/ /index.html;把所有未命中的路径都回退到 index.html。这个坑我当年第一次部署 Vue 项目时就遇到得刻骨铭心这次提前写进配置一次通过。端口问题也要提一嘴Spring Boot 默认端口 8080很多服务器上这个端口已经被占用了启动会直接报错。我习惯在配置里额外看一眼端口占用情况用 netstat -tlnp 查一下如果是云服务器还要在安全组里放行对应端口否则外部依然无法访问。防火墙和安全组是两套体系漏掉任何一个都可能导致服务起来了却访问不了。5. 常见问题排查与避坑指南5.1 典型问题与解决方案速查表我把这次项目中踩过、以及同事们经常遇到的典型问题整理成一个速查表按问题现象、原因定位、解决方案三列列出后面的项目直接对照查就行问题现象原因定位解决方案后端启动报 mysql 时区异常连接串缺少 serverTimezone连接串加 serverTimezoneAsia/Shanghai前端 npm install 极慢npm 默认使用官方源切换到国内镜像源上传图片超过 1MB 报错Spring Boot 默认限制 1MB在配置中调整 max-file-sizeVue 打包部署后刷新 404history 模式缺少回退规则Nginx 配置 try_files 回退到 index.html前端请求接口 502后端服务未启动或端口未放行检查 jar 进程、查看日志、配置安全组登录接口被拦截器拦截白名单未配置在拦截器排除登录接口和静态资源m3u8 视频黑屏ts 切片地址跨域或未生成配置 MinIO 桶策略确保切片可访问日期显示相差 8 小时MySQL 时区和 Jackson 序列化时区不一致统一配置 GMT8这个表在交付时我还单独打印了一份给学校的运维同事方便他们后面遇到问题先自查。实用价值比甩一份开发文档高得多这也是做项目时一个很讨巧的技巧——把最容易出问题的东西总结成速查表比写一百页说明文档都好使。5.2 写了这么多次项目后的5条实战经验做这类校园网站和内容管理系统我梳理出的几条实战经验比较核心写在这里第一数据库表设计不要过度设计但发布时间、更新时间、排序字段、状态字段这四个必须带上。任何内容型网站的核心操作都是查列表、看详情、改状态字段一次留够后面不用反复加列。第二前端状态管理库不要滥用。我这个项目里用 Pinia 存的东西其实不超过五个最核心的就是用户信息和菜单列表。很多人写项目上来就建一堆 store反而把数据源搞乱了调试时不知道数据是从接口来的还是从缓存来的。第三日志一定要打全。后端每个关键接口的开始和结束都要打日志尤其登录、文件上传、视频转码这类操作。校园网站出问题时往往都是老师打电话说传不上去了、发不出来了这时候有日志能立刻定位。第四视频转码这类耗时任务务必异步化。用户上传视频后如果前端一直等后台转码完成再响应用户体验非常差。我当时用线程池解决简单可靠任务多时也可以换消息队列。第五上线前至少留两天做兼容性测试。校园网里什么浏览器都有老版 Edge、360、火狐还有各种手机浏览器。Vue 3 项目在低版本浏览器上偶尔会有兼容问题提前用 babel 或者 browserslist 处理一下免得上线后被人当客服。5.3 维护期意外发现的配置细节项目交付后我陆续做了两轮小维护期间又发现几个配置层面容易被忽略的细节。一个是 Spring Boot 的控制台日志编码问题。服务器上默认编码如果不是 UTF-8中文日志会显示乱码排查问题时非常头疼。后来我在启动脚本里加了 -Dfile.encodingUTF-8 参数日志恢复正常。另一个是 MinIO 文件上传的大小限制其实不只是 Spring Boot 要限制MinIO 服务端也要注意。如果视频文件特别大还可能遇到访问超时的问题这时可以在 Nginx 层增大 proxy_read_timeout。这些问题不真正跑到生产环境基本发现不了好在它们修复成本不高但每一条都能在关键时刻救场。这套项目整体做下来结构并不复杂但每一层都有值得打磨的细节。如果后面学校提出更多需求比如成绩查询、在线选课这套 Spring Boot Vue 的骨架可以直接扩展接口和页面都在只是新增业务模块而已。我个人负责这种校园网站项目最深的一点体会是方案不需要炫技稳定、好维护、别人接手时看得懂才是真正的价值。技术栈选成熟组合代码写清晰文档留到位这个项目就成功了大半。哪怕以后不做校园网站转去做企业官网、内部管理系统这套思路依然通用。