
最近在整理手头这套前后端分离的Web本科生交流培养管理平台系统从最开始梳理业务模型到用SpringBootVueMyBatisMySQL这套技术栈把项目完整落地再到后期部署和排错整个过程差不多持续了一个多月。现在源码完整、部署流程也验证过好几轮了我把整个项目的设计思路、核心实现和部署细节整理成这篇文字方便正在做类似毕设或课程设计以及想系统学习前后端分离项目实战的开发者直接参考。这套交流培养管理平台面向本科生培养过程中最日常的几个场景培养计划发布、导师双选、学术交流答疑、课题申报和成果归档。系统里分学生、教师、管理员三类角色前端用Vue负责页面与交互后端用SpringBoot提供RESTful接口MyBatis作为持久层框架操作MySQL数据库。如果你已经掌握了一些Java和Vue的基础但缺少一个完整的项目串联经验这篇文章值得一步步跟下来。1. 为什么需要这样一套交流培养管理平台业务痛点和系统定位1.1 本科生培养过程的真实管理困境很多高校的本科生培养管理实际推进过程中非常依赖人工。教学秘书用Excel收集培养计划导师分配靠纸质志愿表线下统计学生遇到学术问题只能课下找老师或在年级大群里问有价值的问答内容没有沉淀下届学生照样重复踩坑。课题申报、结题验收、成果登记这些环节更是散落在各个系统甚至微信聊天记录里。这些碎片化操作的直接后果是管理成本高、追踪困难、数据不连贯。比如导师带的学生有几次指导记录、学生提交过哪些培养材料、某个课题的申报进度到了哪一步信息散落各处就无法快速给出结论。我在设计这套系统时核心出发点就是把这些分散的流程收拢到一个平台上让培养过程从无记录变成有迹可循从靠人催变成线上流转。1.2 学生、教师、管理员三类角色的业务闭环交流培养管理平台天然是一个多角色协作系统我梳理下来核心角色和诉求如下表所示角色核心诉求高频操作学生看清培养要求、找到合适导师、获得有效指导查看培养计划、提交导师志愿、发帖提问、申报课题、登记成果教师把指导精力集中在有效的事务上发布或审核培养计划、确认指导学生、回复答疑、审核课题与成果管理员保障平台流程规范和运行稳定维护用户与角色、配置基础数据、监控流程节点、查看统计报表整个业务闭环的逻辑是管理员和教师先行维护培养计划与课题资源学生进入系统后查看计划、选择导师、参与学术交流同时在平台上申报课题、提交成果教师对学生的申请和成果进行审核反馈管理员通过后台数据掌握整体运行状态。这个闭环覆盖了本科生从入学到毕业过程中最常接触的管理环节也是我后面拆解模块和设计数据库的依据。1.3 功能模块如何划分根据上面的业务闭环我把系统拆成了六个主要模块用户认证与权限管理登录、注册、JWT鉴权、基于角色的菜单权限控制。培养计划管理教师或管理员发布学期培养计划学生按学期查看计划明细。导师双选管理教师发布招生名额学生提交志愿教师确认或拒绝。学术交流社区学生和教师发帖、回帖、分类浏览支持关键词检索。课题申报与成果管理教师发布课题学生申报结题后登记论文、竞赛等成果。数据统计与消息中心首页展示培养概况站内消息推送关键审核状态变化。模块之间的数据是共享的比如用户模块的ID贯穿所有业务表课题申报完成后自动生成一条待审核消息成果登记后又可以反哺统计报表。这个逻辑理顺了后面无论是建表还是写接口思路都会清楚很多。2. 技术选型思路为什么偏偏是SpringBootVueMyBatisMySQL这一套2.1 前后端分离结构的核心收益把前端和后端拆成两个独立工程很多人第一反应是部署变复杂了。但从实际开发体验看前后端分离带来的收益远大于成本。首先是职责边界清晰。前端团队或开发者只需要关注页面结构、交互逻辑和接口调用后端只需把接口契约定清楚不需要操心页面渲染。其次是开发调试效率高前端用Vue dev server跑在本地后端用Spring Boot内嵌Tomcat跑在另一个端口两者通过HTTP通信前端数据异常可以直接定位是接口问题还是渲染问题。这套系统里我使用的就是标准前后端分离结构前端工程只做页面展示和状态管理后端只提供JSON数据接口不包含任何视图页面。2.2 后端选型SpringBoot快速搭建MyBatis控制SQLSpringBoot是目前Java后端开发绕不开的框架。选它不是因为流行而是因为它确实能减少大量基础设施配置。内嵌Tomcat、自动配置注入、Starter简化依赖管理意味着一个新项目搭起来只需要一个启动类加少量配置。对于这种管理系统SpringBoot的生态也非常成熟集成MyBatis、JWT、文件上传等都有一大堆现成方案可以少走弯路。MyBatis在这套系统里承担的是持久层访问。它和SpringBoot的整合非常顺滑mybatis-spring-boot-starter加进去后只需要定义Mapper接口和XML文件。相比JPA的全自动映射我更喜欢MyBatis的点在于SQL完全可控尤其是多表关联查询、分页SQL、动态条件拼接这些场景自己写SQL心里更有底。另外系统里有一些复杂统计SQL比如按导师维度统计指导学生数量、按学期统计计划完成率这种场景MyBatis的优势非常明显。2.3 前端选型Vue的渐进式开发体验和组件生态前端我选的是Vue配合Vue Router做路由、Vuex做状态管理、Axios做HTTP请求。Vue的学习曲线相对平缓模板语法直观特别适合逻辑并不复杂的后台管理系统。组件化开发带来的好处是把页面拆成一个个可复用组件比如用户列表、分页器、表单弹窗写成组件后不同页面直接引用代码量能少不少。对这套系统来说我在页面布局上引入了Element UI组件库表格、表单、对话框、消息提示这些基础控件不用自己造轮子。选择Vue 2版本是从稳定性和生态兼容性考虑的因为Element UI对Vue 2的支持最成熟网上资料也最多遇到问题基本都能搜到解决方案。如果你已经在用Vue 3和Element Plus核心思路完全一样只是API细节上有些区别。2.4 MySQL为什么够用且稳定MySQL在这套系统里承担所有结构化数据的持久化存储。本科生培养管理系统的数据量级别在单表几万到十几万条的规模下MySQL配合合理索引和分页查询完全可以轻松应对。MySQL 5.7和8.0都支持事务、存储过程、视图等能力更重要的是它对Linux和Windows环境都友好安装和运维成本低。实际部署时我建议数据库字符集使用utf8mb4因为交流社区里学生可能发表情符号utf8mb4才能完整支持。这套技术栈组合在一起最终效果就是一个典型的低门槛、高稳定性、资料丰富的项目体系。对做毕设或课程设计的同学来说这套组合在答辩时也非常容易讲清楚SpringBoot负责什么、Vue负责什么、MyBatis如何访问MySQL每个环节都边界清晰评委问起来完全不慌。3. 数据库设计把培养管理业务翻译成表结构3.1 核心表结构和职责说明这套系统的数据库我设计为一张用户中心表加六类业务表。下面先列一个整体清单表名职责关键字段sys_user用户信息包含学生和教师id、username、password、real_name、role_type、student_no、dept_name、phone、email、statussys_role角色定义id、role_name、role_keytraining_plan培养计划id、semester、course_name、credits、teacher_id、category、description、statustutor_selection导师双选id、student_id、teacher_id、apply_reason、status、apply_time、confirm_timetopic_post交流帖子id、user_id、title、content、category、view_count、reply_count、statustopic_reply帖子回复id、post_id、user_id、content、create_timeproject_apply课题申报id、project_name、publisher_id、student_id、description、status、apply_time、audit_timeachievement_record成果记录id、student_id、project_id、achievement_type、description、file_url、statussys_notice消息通知id、user_id、title、content、is_read、create_timesys_user是所有业务表的锚点通过user_id或student_id字段把操作者和数据关联起来。比如学生发帖topic_post.user_id指向sys_user.id学生申请导师tutor_selection.student_id和teacher_id都指向sys_user.id通过角色类型区分身份。这种设计的好处是用户体系单一权限控制维度清晰出现问题也容易追溯。3.2 表间关系设计的几个关键取舍在设计表关系时我专门并列了几个关键点逻辑外键优于物理外键。业务表里虽然存在大量关联字段比如tutor_selection.student_id参照sys_user.id但我没有在数据库层面加物理外键约束。原因是业务系统里删除用户、批量导入数据时物理外键的强约束会导致很多操作无法灵活执行。维护逻辑外键配合代码层校验实际使用中更稳妥。状态字段贯穿所有流程表。训练计划、导师双选、课题申报这些表都有status字段用数字表示不同状态0待处理、1通过、2驳回所有状态流转都通过后端接口控制禁止前端直接改状态。时间字段统一格式。所有表的创建时间和更新时间统一用datetime类型业务上的时间节点比如选导师截止时间单独用date或datetime字段存储避免语义混乱。大字段拆分。交流帖子的正文content用text类型存储列表展示时只查询摘要和标题详情页再加载完整内容。这样列表SQL的IO压力明显更小。3.3 设计中容易被忽略的索引和初始化数据建表时我的习惯是为所有外键字段、状态字段和查询频率高的字段建立索引。比如topic_post的category、training_plan的semester、tutor_selection的teacher_id和status这些字段经常作为WHERE条件或GROUP BY字段出现没有索引的话数据量上来之后分页查询会明显变慢。另外mysql的联合索引在设计中也用上了比如tutor_selection表建了(student_id, status)的联合索引查询某个学生的申请记录只需要走一个索引。初始化数据是另一个容易被忽略的点。上线前我把管理员账号、默认角色数据、几个测试教师和学生账号直接写进了初始化SQL脚本。管理员账号固定为admin初始密码经过BCrypt加密存入首次登录后可以修改。开发调试阶段直接使用这些账号省去注册流程部署阶段也方便快速验证系统是否正常。4. SpringBoot后端核心实现分层架构与关键代码4.1 工程结构与分层设计后端工程我采用的是经典的三层架构Controller接收请求并做参数校验Service处理业务逻辑Mapper负责数据访问。实际目录结构如下src/main/java/com/example/cultivation/ ├── controller/ # 接口层 ├── service/ │ ├── impl/ # 业务实现 ├── mapper/ # MyBatis Mapper接口 ├── entity/ # 数据库实体类 ├── dto/ # 请求与响应对象 ├── vo/ # 视图对象 ├── config/ # 配置类CORS、JWT拦截器、MyBatis配置 ├── common/ # 统一响应、异常处理、工具类 ├── utils/ # JWT、日期等工具 └── CultivationApplication.java这个分层的必要性在维护阶段体会特别深。比如要给导师双选功能增加一个不能重复申请的校验只需要在Service层加一段逻辑Controller和Mapper完全不用动。不同层各司其职项目规模变大后依然不会乱。4.2 统一响应封装与全局异常处理前后端分离项目最重要的一点是接口返回结构一致。我这里封装了一个Result类所有接口都返回同一结构Data public class ResultT { private Integer code; // 200成功500业务异常401未登录 private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } public static T ResultT unauthorized(String message) { ResultT result new Result(); result.setCode(401); result.setMessage(message); return result; } }配合全局异常处理器把校验异常、业务异常和兜底异常统一转换为Result返回。这样前端Axios拦截器只需要判断code是否为200够简洁。关键接口格式统一后前端封装Ajax请求也变得非常舒服后续新页面接入接口就只是一次简单的Post调用。4.3 JWT登录认证与角色权限控制登录认证我使用的是JWT方案。用户传入用户名密码后后端校验BCrypt加密后的密码是否匹配匹配则使用JJWT生成一个tokentoken中携带userId、username、roleType和过期时间响应给前端。前端在请求头带上Authorization字段// JWT工具类核心方法 public String generateToken(Integer userId, String username, String roleType) { Date now new Date(); Date expireDate new Date(now.getTime() 7 * 24 * 60 * 60 * 1000); return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(roleType, roleType) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }在后端使用拦截器统一验证token并解析出用户信息放入ThreadLocal供Service层调用。没有token或token过期的请求直接返回401。角色权限上我在拦截器里做了一个简单的注解控制思路管理员专用的接口检查当前用户角色是否为admin普通用户接口只判断是否登录。这套轻量级鉴权对毕设和中小型系统已经足够不需要引入Spring Security那种重框架。4.4 MyBatis Mapper与XML中的关键写法MyBatis是本系统的数据访问核心实际开发中我大量使用了动态SQL和关联查询。下面这段是交流帖子分页查询的Mapper XML展示了动态条件拼接的典型写法select idselectPostPage resultTypecom.example.cultivation.vo.TopicPostVO SELECT tp.id, tp.title, tp.category, tp.view_count, tp.reply_count, tp.create_time, su.real_name AS authorName FROM topic_post tp LEFT JOIN sys_user su ON tp.user_id su.id where if testcategory ! null and category ! AND tp.category #{category} /if if testkeyword ! null and keyword ! AND (tp.title LIKE CONCAT(%, #{keyword}, %) OR tp.content LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY tp.create_time DESC LIMIT #{offset}, #{pageSize} /select动态SQL配合LIKE查询完成帖子的分类过滤和关键词搜索LEFT JOIN一次性把发帖人的真实姓名带出来避免前台二次查询。这里有一个我强调过的细节MyBatis的驼峰映射要在application.yml里开启map-underscore-to-camel-casetrue不然数据库的下划线字段无法自动映射到Java实体类的驼峰属性。4.5 核心业务接口清单与一个完整接口流程我整理了这套系统最核心的几个接口方便你对照理解后端职责模块接口路径方法说明认证/api/auth/loginPOST登录并返回JWT认证/api/auth/infoGET获取当前用户信息培养计划/api/training/plan/pageGET分页查询计划列表培养计划/api/training/plan/addPOST新增培养计划教师导师双选/api/tutor/applyPOST学生提交导师申请导师双选/api/tutor/confirmPOST教师确认学生交流社区/api/topic/pageGET帖子分页查询交流社区/api/topic/publishPOST发布帖子交流社区/api/topic/replyPOST回复帖子课题申报/api/project/applyPOST学生申报课题课题申报/api/project/auditPOST教师审核申报统计面板/api/statistics/dashboardGET首页统计数据以导师双选为例完整流程是这样的学生提交apply请求Controller校验参数后调用ServiceService先查tutor_selection表判断该学生是否已经申请过防重复然后插入一条status为0的申请记录同时写一条通知给对应的教师教师端看到申请后可confirm或reject。整个流程涉及两张表写入和一次状态更新这里我用Transactional保证了事务一致性。这块是后端业务逻辑里最有代表性的部分也是答辩时最值得展开讲的地方。5. Vue前端实现路由、请求封装和核心页面5.1 前端工程目录规划前端工程基于Vue CLI搭建目录规划我尽量贴近真实项目习惯。views目录按模块分文件夹api目录统一存放接口调用文件router目录单独管理路由表src/ ├── api/ # 所有接口请求封装 ├── router/index.js # 路由配置 ├── store/ # Vuex状态管理 ├── views/ │ ├── login/ # 登录页面 │ ├── dashboard/ # 首页仪表盘 │ ├── training/ # 培养计划 │ ├── tutor/ # 导师双选 │ ├── community/ # 学术交流 │ ├── project/ # 课题申报 │ ├── achievement/ # 成果管理 │ └── system/ # 后台管理用户、角色 ├── components/ ├── utils/request.js # Axios实例封装 └── layout/ # 整体布局组件这种目录结构把每个业务模块的页面放到同一个文件夹后续维护时定位代码非常快。组件目录下放一些通用组件比如分页组件、富文本编辑器封装、图片上传组件多个页面复用。5.2 路由配置与导航守卫前端路由的核心是区分哪些页面需要登录哪些页面只允许特定角色访问。我在路由配置里给每个页面绑定了meta信息包含requiresAuth和roles数组const routes [ { path: /login, component: Login, meta: { title: 登录 } }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: Dashboard, meta: { title: 首页, requiresAuth: true } }, { path: training, component: TrainingList, meta: { title: 培养计划, requiresAuth: true } }, { path: community, component: CommunityList, meta: { title: 学术交流, requiresAuth: true } }, { path: system/users, component: UserManage, meta: { title: 用户管理, requiresAuth: true, roles: [admin] } } ] } ]导航守卫在路由跳转前检查token是否存在以及角色是否满足页面要求。这个机制保证了用户只能通过登录进入系统管理员页面也不会被普通学生看到前后端双重校验的思路也在这里体现。前端守卫更多是体验优化真正安全的还是后端拦截器。5.3 Axios请求封装与拦截器所有HTTP请求我统一封装在一个request.js中基于Axios创建实例。请求拦截器负责在每次请求前把JWT token拼到请求头响应拦截器统一处理返回码code为200直接返回数据code为401则清除本地token并跳转登录页code为500弹出错误提示。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }, error Promise.reject(error)) 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.data }, error { Message.error(网络异常请稍后再试) return Promise.reject(error) })这个封装看似简单但实用性很强。项目跑起来之后几乎不需要再在每个页面里重复处理错误码和未登录跳转了这也是所有前端页面代码保持清爽的基础。5.4 核心页面实现以学术交流社区为例学术交流模块是整个系统里前端逻辑最复杂的页面之一。列表页使用Element UI的el-table展示帖子标题、分类、作者和回复数顶部加搜索框和分类筛选器。点击标题跳转到详情页详情页加载完整帖子和所有回复列表底部有回复输入框和提交按钮。实现时要注意的一个细节是页面跳转传参方式。列表页跳详情页我用路由参数传帖子的idthis.$router.push({ path: /community/detail, query: { id: row.id } })详情页在created钩子里通过this.$route.query.id拉取详情接口。帖子发布按钮在表单校验通过后调用publish接口成功后清空表单并重新加载列表。这套交互流程在培养计划、课题申报等模块中几乎可以复用前端页面的开发效率很大程度就是从这些可复用的模式中来的。还有一个体会是Element UI表格组件自带的分页配合后端LIMIT分页接口是固定搭配几乎不需要自己写分页逻辑。6. 从零到一完整部署步骤与联调验证6.1 本地环境准备清单部署这套系统所需的基础环境如下表。我建议严格按版本准备版本差异往往是很多奇怪报错的根源软件推荐版本用途JDK1.8或11编译与运行后端Maven3.6后端依赖管理与构建Node.js14或16前端依赖安装与构建Vue CLI4.x或5.x前端工程运行与打包MySQL5.7或8.0数据存储Navicat或命令行任意数据库管理JDK和Node.js的安装属于常规操作不多展开。Maven建议在settings.xml里配置阿里云镜像不然第一次拉依赖会等得让人怀疑人生。Node依赖安装也建议通过npm或pnpm使用国内镜像节省时间很有效果。6.2 初始化数据库与基础数据首先在MySQL中创建数据库字符集必须指定utf8mb4CREATE DATABASE cultivation_system DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;然后导入项目中的init.sql文件里面包含建表语句和初始数据。导入完成后可以验证一下基础数据是否正确执行SELECT * FROM sys_user应该能看到名为admin的管理员账号以及其他测试账号。在使用MySQL 8.0时有一个容易踩的坑是时区问题连接串上必须加上serverTimezoneAsia/Shanghai参数否则连接会报错。原因很简单MySQL 8.0默认时区设置和国内环境不一致JDBC连接时两边对不上就直接抛异常了。这个问题我后面踩坑章节还会再提。6.3 后端启动步骤后端启动前需要检查application.yml中的数据库连接配置把用户名密码改为自己本地的MySQL账号server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/cultivation_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true如果是在IDEA里运行直接启动CultivationApplication主类即可。如果命令行运行先执行mvn clean package -DskipTests打包成jar再执行java -jar target/cultivation-system-1.0.0.jar。启动日志出现Started CultivationApplication就说明后端成功起来了此时在浏览器访问http://localhost:8080/api/auth/info应该会返回未授权的JSON提示。6.4 前端启动步骤进入前端项目目录先安装依赖npm install依赖安装完成后启动开发服务器。这里有一个关键问题Vue CLI默认端口是8080和后端Tomcat冲突。我的做法是在vue.config.js里把前端端口改为8081同时配置开发代理把所有/api开头的请求代理到后端8080端口module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }配置完成后执行npm run dev浏览器访问http://localhost:8081输入管理员的账号密码即可登录系统。我特意使用代理而不是直接写后端地址是为了避免开发阶段频繁手动修改请求地址而且代理还能顺便解决跨域问题。6.5 前后端联调验证清单系统启动后建议按下面的清单做一轮完整的功能验证使用admin账号登录检查首页统计数据是否正常返回。进入用户管理确认能查询出学生和教师账号列表。使用教师账号创建一个培养计划切换到学生账号查看该计划是否可见。学生账号提交导师申请教师账号确认申请状态流转是否正确。在学术交流模块发帖、回帖贴子里能否正常显示作者姓名。学生账号申报课题教师账号审核通过通知消息是否到达。这一轮验证跑完说明数据库初始化、后端接口、前端页面三条链路已经全部打通整个系统的核心闭环是健康的。7. 实战过程中踩过的坑和我的处理方法7.1 跨域问题开发与部署的不同解法前后端分离项目遇到的第一个问题就是跨域。前端跑在8081后端跑在8080浏览器直接请求后端接口会被CORS策略拦截。我的处理是开发环境使用Vue CLI的proxy代理让浏览器的请求同源到前端由开发服务器转发到后端绕开跨域限制。部署时如果前后端使用不同域名或端口则需要在后端加CORS全局配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(Registry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }一个容易忽略的细节是如果跨域配置里同时设置了allowedOriginPatterns(*)和allowCredentials(true)某些浏览器会拒绝携带凭证的跨域请求需要注意allowedOrigins不能直接使用通配符加credentials的组合这也是网上讨论较多的问题。7.2 MyBatis参数映射和空值处理的坑MyBatis在使用过程中最常见的报错就是参数绑定失败和结果映射不完整。参数绑定失败通常是Mapper接口方法参数和XML里的#{paramName}不一致造成的。解决方案有两个要么在Mapper接口参数使用Param注解明确命名要么在编译参数中加上-parameters参数让编译器保留参数名。我现在统一使用Param注解简单直观。结果映射不完整则多数是驼峰映射未开启。数据库字段create_timeJava实体属性createTime如果不开启map-underscore-to-camel-case返回值里createTime就是null。排查这类问题时最快的办法是把MyBatis的SQL日志打开看SQL执行结果与返回实体是否对得上。7.3 时间字段的时区与格式化问题系统里涉及大量的创建时间和截止时间。MySQL的datetime字段默认不带时区信息如果连接串没有加serverTimezoneAsia/ShanghaiJava侧拿到的时间会和本地时间相差8小时页面上的日期显示就会漂移。更麻烦的是前端提交的日期字符串和后端LocalDateTime之间的格式转换稍有差异就可能出现解析异常。我的统一做法是数据库层用datetime存绝对时间Java实体用LocalDateTime接收前端展示时在后端VO中直接格式化为yyyy-MM-dd HH:mm:ss字符串避免前端重复处理。这个约定在多个模块统一执行后日期相关的Bug就基本消失了。7.4 前端打包发布后的路由404问题本地开发一切正常但把前端打包部署到Nginx后刷新非首页路由就会出现404。原因是Vue Router的history模式在服务器端找不到对应的物理文件路径需要Nginx配置try_files指令把所有路径回退到index.htmllocation / { try_files $uri $uri/ /index.html; }如果你不熟悉Nginx或没有独立服务器也可以把router改成hash模式地址栏会多一个#但部署时不需要任何服务器额外配置对纯静态部署更友好。这两种方案我都验证过实际项目中根据部署环境二选一即可。7.5 文件上传大小限制与富文本问题系统里成果管理模块涉及附件上传我在这里踩过两个小坑。第一个是SpringBoot默认单次上传文件大小限制为1MB超过就直接报错。需要在application.yml里调大限制spring: servlet: multipart: max-file-size: 20MB max-request-size: 100MB第二个是前端上传组件和后端接口的字段名必须一致Element UI的el-upload组件默认上传字段名是file如果后端MultipartFile参数名不叫file就需要在组件里配置name属性。这种问题排查起来不复杂但第一次遇到时确实会浪费不少时间。写在最后这套项目后续可以怎么扩展整个项目跑通之后剩余的优化空间其实还很大。我自己在后续迭代中考虑的方向有两个一是把消息中心从简单的站内信升级为WebSocket实时推送导师确认申请、审核结果同步不用等刷新二是引入更细粒度的权限模型把菜单权限细化到按钮权限级别目前基于角色的一层权限控制虽然够用但不灵活。如果你是在这个项目基础上做二次开发建议优先补上数据导出功能培养计划和成果记录按Excel导出是实际使用中频繁被提到的需求。代码层面可以把公用的分页参数封装成一个通用请求对象配合MyBatis的PageHelper插件分页代码能进一步精简。最后分享一个我在整个开发过程中体会最深的一点这套系统本身的业务并不难真正的价值在于它完整串联了数据库设计、后端接口、前端页面和部署交付的全过程。好好把每一步弄明白比急着堆功能有价值得多。