做过的学生项目里,交流培养平台这类题目算是毕业设计和课程设计中的常青树。原因很简单——它业务上既有“交流”的社交互动属性,又有“培养管理”的过程监控属性,技术上前后端分离一套走完,SpringBoot、Vue、MyBatis、MySQL这些该碰的组件一个不少,从数据库设计到接口开发,从前端联调到生产部署,整条链路都能完整覆盖。所以我最近完整做了一遍这个“本科生交流培养管理平台”,从零写源码到最终部署上线,中间踩了不少坑也积累了不少经验。这篇文章就把我的整套思路、选型逻辑、核心代码和部署过程全部分享出来,不管是正在做这个题目的同学,还是想练手前后端分离项目的开发者,都可以直接参考。
1. 项目背景与需求拆解:一个交流培养管理平台到底要解决什么问题
1.1 本科生培养场景中的信息断层
现在很多高校在推行本科生导师制或者学业导师制,本意是让本科生从大一进校开始就有固定教师带着做科研、参加竞赛、做职业规划。这个制度本身没问题,但真实落地的时候,我见过不少学院是这样的状态:导师和学生之间只有一份Excel名单,平时沟通靠微信群,临时通知靠口头转达,导师想了解学生这周进展如何,翻聊天记录都翻不到。学院教学办想统计每个导师带了多少学生、指导频率多高、学生完成了几个阶段任务,只能发通知让各班班长收Excel再手动合并。过程资料没有统一归档,学生做了什么、老师反馈了什么全凭自觉,出了问题也追溯不到。
这种场景正是一个交流培养平台要解决的问题。“交流培养管理平台”拆开看,其实是两条业务线:一条是“交流”,偏向内容社区,解决师生之间、同学之间信息互通和经验分享的问题;另一条是“培养管理”,偏向教务过程管理,解决培养计划制定、阶段任务下发、材料提交审核、进度追踪的问题。两条线有交集但目标完全不同,做需求分析的时候必须先拆清楚,否则后面数据库设计和功能开发很容易做成一锅粥。
1.2 从标题关键词倒推功能清单
“交流”这条线对应的是内容与互动:公告发布、经验分享帖、讨论区、评论、回复、点赞、收藏、消息通知。它有明显的社区属性,核心要素是用户生成内容的发布和互动。“培养管理”这条线对应的是任务与过程:师生结对关系配置、培养计划制定、阶段任务下发、学生提交材料、教师审核评估、进度跟踪、统计报表,更像一个轻量级的教务系统。加上平台管理能力,例如用户管理、角色权限、内容审核、数据看板,就构成了完整的功能矩阵。
把这些需求列成功能清单大概是这个样子:
- 交流模块:公告列表、帖子发布与浏览、评论与回复、点赞收藏、我的消息
- 培养模块:培养计划、阶段任务、材料提交、审核反馈、进度跟踪
- 平台管理:用户管理、角色分配、专业与年级维护、内容删除、数据统计
- 个人中心:基本信息、我的帖子、我的培养进度、消息通知
注意我特意没有把“交流”做成很重的社区,也没有把“培养”做得很复杂,因为这是在校园场景里跑的系统,用户是本科生和教师,功能太杂反而没人用。先把核心流程跑通,比堆功能重要得多。
1.3 三类角色与核心业务流程
系统的使用者只有三类角色:学生、教师、管理员。每个角色的操作边界完全不同。
| 角色 | 核心操作 | 关注点 |
|---|---|---|
| 学生 | 登录、浏览公告、发布交流帖、查看培养计划、提交阶段材料、查看教师反馈 | 流程是否顺畅、提醒是否及时 |
| 教师 | 管理指导学生、发布培养计划与阶段任务、审核学生材料、回复交流区问题 | 批改效率、进度可视化 |
| 管理员 | 用户导入、角色分配、内容监管、学期配置、统计报表 | 数据准确、操作可追溯 |
核心业务流程可以概括成一条主线:管理员初始化学期数据,导入学生和教师账号;教师创建培养计划,拆分成若干阶段任务,指派给名下学生;学生按阶段提交材料,教师在线审核并给出评语;学院负责人通过统计报表查看整体完成情况。整个过程中交流区作为非正式的沟通渠道,学生可以提问,教师可以解答,内容沉淀下来也是培养过程中的一部分。
2. 技术选型分析:SpringBoot+Vue+MyBatis+MySQL这套组合为什么稳妥
这个题目的技术栈几乎已经被固定了,缘因不是这套组合多么前端时尚,而是在校园项目这种场景下,它是公认最稳妥、资源最多、最容易排查的一整套方案。
2.1 为什么坚持前后端分离
“前后端分离”现在基本成为项目标配,核心是把界面展示和业务逻辑解耦。前端通过接口取数渲染,后端专注处理数据与业务规则,两边可以用不同团队、不同节奏开发,部署上也可以各自独立。对我来说最重要的实际收益是调试方便:前端报错单独看浏览器控制台,后端接口报错单独看服务端日志,问题定位比单体JSP项目清晰得多。而且Vue这种SPA应用打包后只是纯静态文件,可以扔到Nginx里跑,也可以塞进SpringBoot的静态资源目录,部署方式很灵活。
2.2 SpringBoot:让后端开发回归业务本身
SpringBoot的核心理念是约定大于配置,它对Spring生态做了一层自动装配封装。做一个web项目,引入spring-boot-starter-web之后,内嵌Tomcat,不需要额外装容器,一个main方法就能启动服务。相比传统SSH或SSM项目里那一堆XML配置,SpringBoot用application.yml就能搞定大部分设置。当然它本质上还是Spring,依赖注入、AOP、事务管理这些底层能力一个没少。我的习惯是用SpringBoot做项目骨架,Controller负责接收请求,Service层放业务逻辑,Mapper层对接数据库,层次清晰,后期加功能也不会乱。
我用这个平台项目验证过一件事:相同功能量的后端,如果用传统Spring MVC加XML配置,光配置文件就要写半天,SpringBoot只需要一个启动类和很少的配置就能跑起来,开发效率差距非常明显。
2.3 MyBatis:灵活SQL在复杂查询中的价值
选MyBatis而不是JPA/Hibernate,是我在这个项目里比较坚持的一点。培养管理场景里有大量多条件组合查询,例如按年级、专业、导师姓名、阶段状态筛选学生列表,这种需求用MyBatis的XML动态SQL写起来非常顺手。 加 的结构可以精确控制SQL拼接,条件全选的写法也几乎不会出错。此外,SQL由自己掌控,跨过框架层面去排查慢查询、优化索引时可以直接定位,不用去猜框架帮你生成的SQL是什么样。
有人会质疑,JPA写代码更简洁,但在这个题目的场景下,可控制的SQL比省那几行代码更能让我安心。而且MyBatis在国内的普及度极高,网上资料和面试题一大堆,出了问题搜一下就有答案,学习成本也低。
2.4 Vue:组件化开发带来的前端效率提升
Vue在前端这块的优势体现在组件化和响应式上。每个页面可以拆成多个逻辑独立的组件,像是帖子卡片组件、评论列表组件、进度条组件,写好后直接复用,改动一个组件所有引用处同步生效。响应式数据绑定意味着我不需要手动操作DOM,数据变化视图自动更新,写起来像在操作一个状态层,比以前jQuery时代轻松太多。
如果配合Vue Router做前端路由,整个应用就是单页体验,页面切换不刷新,感觉像桌面软件。而且Vue的上手曲线在主流框架里算平缓的,一个从后端转前端的人,花一两天看文档就能上手写页面。这对校园项目来说特别现实,因为你的开发周期就那么长,没时间从头啃一个重框架。
2.5 MySQL在数据量级和运维成本上的现实考量
MySQL是这套组合里最没有争议的一块。高校内部系统的数据量级,绝大多数场景就是几千用户、几万条帖子、几十万条提交记录,MySQL完全撑得住。成本上它免费开源,部署简单,Linux上一行命令的事;生态上Navicat、DataGrip这些客户端工具都很成熟。相比PostgreSQL或者MongoDB,MySQL对国内开发者来说熟悉度最高,遇到问题找解决方案也最容易。我还专门配合MyBatis做了实践,动态SQL在MySQL下的写法、索引优化方式都有成熟路径,容错空间很大。
3. 数据库设计实战:从业务实体到MySQL表结构的完整落地
到了数据库设计这一步,是最容易决定项目生死的环节。很多同学拿到题目直接建几张表就开始写代码,等到功能做起来才发现缺字段、表关系不清,再加字段改结构,痛不欲生。我的建议是先画实体关系图,把用户、帖子、培养计划、提交材料这些核心实体之间的关系理清,再落到建表SQL上。
3.1 用户-角色-权限表设计
我的用户表设计非常直接,没有刻意做强权限框架:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键自增 |
| username | varchar(50) | 登录名,学号或工号 |
| password | varchar(100) | BCrypt加密后的密码 |
| real_name | varchar(50) | 真实姓名 |
| role | tinyint | 1学生 2教师 3管理员 |
| major | varchar(50) | 专业(学生用) |
| grade | varchar(10) | 年级,如2022级 |
| teacher_id | bigint | 学生绑定的指导教师 |
| phone | varchar(20) | 联系方式 |
| status | tinyint | 账号状态,1正常 0禁用 |
| create_time | datetime | 创建时间 |
这里把教师和学生直接做成一张用户表,用role字段区分,好处是登录认证逻辑统一,不用为不同角色建多张表。学生和教师的结对关系用teacher_id字段直接指向教师的用户ID,简单直观。如果你对权限有更复杂的要求,可以再加一张角色表和用户角色关联表,但在这个平台里,三个角色用一个小字段就能覆盖,不必过度设计。
3.2 交流模块的表设计
交流功能我拆成了帖子表、评论表、回复表、点赞记录表四张表:
- post表:id、user_id(发帖人)、category_id、title、content、view_count、like_count、comment_count、status、create_time
- comment表:id、post_id、user_id、content、create_time
- reply表:id、comment_id、from_user_id、to_user_id、content、create_time
- like_record表:id、post_id、user_id、type(0帖子 1评论)、create_time
这里面有几个细节要注意。点赞记录表加唯一索引(post_id, user_id, type),防止一个用户重复点赞,这个索引也是高并发下防重的主要手段。评论表和回复表分开,是为了在页面上区分二级评论和追评的展示逻辑,如果合在一起查询起来会很别扭。帖子表用冗余字段comment_count和like_count来存计数,虽然不满足第三范式,但查询列表的时候不用连表count,性能好很多,数据一致性可以通过事务回调维护。
3.3 培养管理模块的表结构设计
这部分是整个系统业务逻辑最重的区域,我设计了四张核心表:
- mentor_student表:id、mentor_id(教师ID)、student_id(学生ID)、semester(学期)、status(结对状态)、create_time
- training_plan表:id、mentor_id、semester、title、description、create_time、update_time
- stage_task表:id、plan_id、title、content、deadline、sort_order、create_time
- stage_submission表:id、task_id、student_id、content、file_url、submit_time、score、evaluation、evaluate_time、status
阶段任务挂在培养计划下面,一份计划拆成若干个阶段任务,每个任务学生提交一次材料。status字段在提交表里用来标记待审核、已通过、已打回三种状态。教师在审核的时候可以填评语和打分,学生端就能实时看到进度。这里要特别注意外键关系:stage_submission的task_id关联stage_task,stage_task的plan_id关联training_plan,training_plan的mentor_id关联用户表,层级关系必须清晰,删除操作也要按这个层级来约束。
3.4 初始化数据与Navicat实操
建表我直接用Navicat可视化操作,把SQL跑一遍然后把表关系图生成出来检查一遍,逻辑没问题再开始写代码。初始化数据这个环节我建议一次到位:默认管理员账号、几个测试教师账号、一批学生账号、几条培养计划和阶段任务、若干条测试帖子。有数据的情况下,前端页面开发不会对着空表盲写,联调时也能直接看到效果。
注意:学生密码统一用BCrypt加密后的同一串密文,方便测试登录,上线前再提醒用户修改。明文密码入库是低级错误,千万别干。
4. 后端实现:SpringBoot+MyBatis的关键代码与核心逻辑
4.1 项目分层与初始化配置
后端的包结构我用的是经典的controller-service-mapper分层,再加上config、entity、dto、common几个包。Controller只做参数接收和响应包装,业务逻辑全部下沉到Service,Mapper用MyBatis的XML方式管理SQL。
application.yml里的关键配置大概长这样:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/train_platform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.train.entity configuration: map-underscore-to-camel-case: true数据库连接串里的三个参数要特别注意:useUnicode和characterEncoding=utf8保证中文不乱码,serverTimezone指定时区否则高版本MySQL驱动会报时区错误。MyBatis开启mapUnderscoreToCamelCase之后,数据库字段grade_college自动映射到实体类的gradeCollege属性,省去一大堆resultMap。
4.2 JWT登录认证与拦截器设计
登录接口用的JWT方案,用户登录成功后签发一个token,之后每次请求前端把token放在Authorization头里,后端拦截器解析token确认用户身份。这个方案的优点是不需要在服务端保存会话状态,非常适合前后端分离场景。
拦截器核心逻辑:
@Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token != null && jwtUtil.validateToken(token)) { Integer userId = jwtUtil.getUserIdFromToken(token); request.setAttribute("userId", userId); return true; } response.setStatus(401); return false; } }注册拦截器时要注意放行登录接口、注册接口和静态资源,否则前端没拿到token之前什么都访问不了。我实际开发中在WebMvcConfigurer里通过addPathPatterns和excludePathPatterns配置,把这种放行列表集中管理,后面加白名单很省事。
这个平台里我还加了基于拦截器的角色权限校验,比如只有管理员能访问用户管理接口,只有教师能调用审核接口。做法是再写一个角色判断的工具方法,在需要校验的接口上配合注解使用,没有引入Spring Security那种重框架,功能上完全够用。
4.3 统一返回结果与全局异常处理
前后端对接时最怕的就是每个接口返回值结构不一样,前端解析起来全靠猜。我设计了统一的返回体类Result,包含code、message、data三个字段,所有接口都返回这个结构。
public class Result<T> { private Integer code; private String message; private T data; // 静态方法 success/error 构建返回结果 }对应的全局异常处理器用@RestControllerAdvice接管业务异常和系统异常,保证后端抛异常时前端拿到的也是标准结构,而不是一堆堆栈信息。这样前端axios响应拦截器里只需要判断code是不是200,就能统一处理错误弹窗。
4.4 MyBatis动态SQL在条件查询里的应用
培养进度列表是典型的多条件筛选场景:按学期、按专业、按导师、按状态。这种查询用MyBatis动态SQL写起来是真正的舒服:
<select id="selectProgressList" resultType="com.example.train.entity.ProgressVO"> SELECT s.real_name AS studentName, m.real_name AS mentorName, p.title AS planTitle, task.title AS taskTitle, sub.status, sub.score FROM stage_task task LEFT JOIN training_plan p ON task.plan_id = p.id LEFT JOIN mentor_student ms ON p.mentor_id = ms.mentor_id LEFT JOIN user s ON ms.student_id = s.id LEFT JOIN user m ON p.mentor_id = m.id LEFT JOIN stage_submission sub ON sub.task_id = task.id AND sub.student_id = s.id <where> <if test="semester != null and semester != ''"> AND p.semester = #{semester} </if> <if test="major != null and major != ''"> AND s.major = #{major} </if> <if test="mentorId != null"> AND p.mentor_id = #{mentorId} </if> <if test="status != null"> AND sub.status = #{status} </if> </where> ORDER BY task.sort_order </select>这里 标签会自动处理条件前多余的AND, 标签根据参数有无动态拼接SQL。我当时在实际操作时遇到过一个问题:如果某个学生还没提交材料,LEFT JOIN的sub表会出现NULL值,此时status筛选的AND条件会把这个学生过滤掉。这个坑的解决办法是在写联查条件时把这个筛选放到HAVING或者子查询里去判断“未提交”这种状态,而不是简单地用status IS NULL。
4.5 事务与级联删除的坑
删除一个培养计划的时候,要连带删除计划下的阶段任务和提交记录,这个操作必须加事务。我第一版写的时候直接写了三个delete方法然后按顺序调用,结果删完计划之后,阶段任务删除失败,数据库里出现一堆孤儿数据。原因就是没有加@Transactional注解,三个操作不在一个事务里,失败了也不回滚。
@Transactional(rollbackFor = Exception.class) public void deletePlan(Long planId) { List<Long> taskIds = stageTaskMapper.selectTaskIdsByPlanId(planId); stageSubmissionMapper.deleteByTaskIds(taskIds); stageTaskMapper.deleteByPlanId(planId); trainingPlanMapper.deleteById(planId); }这里最容易被忽略的是删除顺序:先删关联的子表数据,再删主表数据,否则外键约束会直接报错。如果你在设计表时没有加外键约束只靠逻辑关联,那你要在代码里保证删除顺序,这也是我为什么说数据库设计的逻辑关系必须清晰的原因。
5. 前端Vue项目构建与联调细节
5.1 项目创建与目录划分
前端用Vue 2加Vue CLI为例,创建命令就是vue create web-frontend。进入项目后按功能划分目录:
src/ api/ # 接口请求模块 assets/ # 静态资源 router/ # 路由配置 store/ # Vuex状态管理 views/ # 页面组件 login/ # 登录页 admin/ # 管理后台 mentor/ # 教师端 student/ # 学生端 components/ # 公共组件 utils/ # 工具类:axios封装、token存取我习惯把接口请求按模块拆分,user.js、post.js、plan.js独立成文件,每个文件里只放对应模块的接口调用函数,代码量大了以后一眼能找到入口。页面组件里就不会出现一堆axios调用散落各处,维护成本低。
5.2 路由与权限控制
路由我采用静态路由加动态菜单的方案。页面路由本身是固定注册的,不需要从后端动态生成,因为页面数量有限。但菜单是根据角色动态显示的,登录后根据用户role字段控制菜单渲染,学生看到的是“我的培养计划-提交材料”这类菜单,教师看到的是“我的学生-审核任务”,管理员看到的是“用户管理-数据统计”。
路由守卫用来处理未登录跳转:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path !== '/login' && !token) { next('/login'); } else { next(); } });5.3 axios请求封装与拦截器
axios如果不封装,每个组件里都要写一遍请求地址、错误处理、loading显示,重复代码多且风格不统一。我的封装思路是创建一个实例,设置baseURL,然后添加请求拦截器和响应拦截器。请求拦截器统一加token,响应拦截器里判断result.code,401跳登录,业务错误统一提示。
const service = axios.create({ baseURL: '/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = token; } return config; }); service.interceptors.response.use(response => { const res = response.data; if (res.code === 401) { localStorage.removeItem('token'); window.location.href = '/login'; return Promise.reject(new Error(res.message)); } return res; }, error => { return Promise.reject(error); });注意axios封装里最常见的坑是响应拦截器return的是res,前端拿到的对象结构和实际返回体不匹配,所以我在封装里把res.data直接抛出去,组件里调用result.code就对了。
5.4 跨域代理与联调
开发环境联调时最大的问题就是跨域。前端跑在8081端口,后端跑在8080端口,浏览器直接请求接口会报CORS错误。解决办法有两种:一种是后端写CORS过滤器允许跨域,另一种是前端用Vue CLI的proxy代理。我推荐用proxy,因为生产环境部署时前端的请求本来就会经过Nginx同域转发,用proxy模拟这个环境更合理。
vue.config.js里的配置:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };这样前端代码里请求地址写/api/user/login,开发环境会被代理转发到后端8080端口,生产环境交给Nginx做同样的转发。联调的时候还要注意一个问题:后端接口如果访问不到,不要只盯前端,先用浏览器直接访问http://localhost:8080/api/user/login看看能不能返回,能排除是后端没启动还是代理配置不对。
5.5 前端构建与后端集成
开发完打包用npm run build,产物在dist目录,是纯静态文件。部署时可以扔到Nginx里,也可以拷贝到SpringBoot的src/main/resources/static目录下,然后通过后端端口直接访问。放到后端的做法省一台Nginx,但不太利于前端独立迭代,我的建议还是单独用Nginx部署静态资源,把后端反向代理出来,这样前端后端互不影响,发布时不冲突。
6. 从零到一完整部署流程与排坑实录
6.1 环境准备与关键安装点
部署到Linux服务器前,本机开发环境至少要装好这几样:
- JDK 1.8以上,推荐JDK 8或JDK 11,构建和运行都需要
- Maven 3.6以上,用来打后端jar包
- Node.js 14以上和npm,用来构建前端
- MySQL 5.7或8.0,作为数据库
- Nginx,作为前端静态服务器和反向代理
MySQL安装这里我多说一句。如果你在Linux上用rpm方式安装MySQL 8.0,装完默认root密码只在日志里生成一次,很容易错过。我的习惯是用rpm安装完成后,用grep 'temporary password' /var/log/mysqld.log找到临时密码,然后立刻登录修改root密码。注意MySQL 8.0的密码策略默认要求密码包含大小写字母、数字和特殊符号,太简单的密码过不了检查,可在init文件中配置降低策略等级。
6.2 后端jar包打包与启动
在项目根目录执行mvn clean package,Maven会用短短几十秒到几分钟时间完成编译和打包,最终在target目录下生成一个train-platform-0.0.1-SNAPSHOT.jar文件。启动命令最简单的方式是:
java -jar train-platform-0.0.1-SNAPSHOT.jar但如果想要后台运行并且把日志写出来,要这样操作:
nohup java -jar train-platform-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > logs/app.log 2>&1 &这里用--spring.profiles.active=prod指定生产环境的配置文件,因为我准备了application-dev.yml和application-prod.yml两套配置。生产环境的数据库地址、密码都放到prod配置里,避免开发配置泄露。这一步在多人协作项目中非常关键,我曾经见过别人把生产库密码写在application.yml里直接提交到代码仓库,这是典型的安全事故。
6.3 前端Nginx部署配置
前端打包后的dist目录上传到服务器的/var/www/train-platform目录下,然后配置Nginx。我的Nginx站点配置大概长这样:
server { listen 80; server_name your-domain.com; root /var/www/train-platform; index index.html; location / { try_files $uri $uri/ /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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }location /里的try_files是最关键的配置,vue-router使用history模式时,刷新页面如果找不到对应路径就会404,这里把请求回退到index.html由前端路由接管。location /api/把前端API请求反向代理到后端8080端口,前后端共用一个域名,彻底解决跨域。
6.4 数据库迁移与初始化
部署时数据库这块我踩过一个比较惨的坑:第一次上线时直接本地导出的SQL文件往外传,结果线上的MySQL版本比本地高,导出的默认字符集设置不一样,导入后中文全部变成问号。后来养成习惯,导出SQL前明确指定导出内容为utf8mb4编码,在命令行导入前先执行一句SET NAMES utf8mb4确认客户端编码。
初始化流程是:登录线上MySQL,create database train_platform字符集设为utf8mb4,然后source导入SQL文件,再把默认管理员账号的BCrypt密文写入用户表。跑通之后检查一遍基础数据数量,确认没问题再启动后端。
6.5 部署中我实际遇到的5个坑
第一个坑是端口被占用。服务器上别的服务占了8080,SpringBoot应用启动直接报端口冲突,排查半天。后来我在启动命令里加了--server.port=8081临时换个端口验证,确认问题后改回规划端口并腾出占用资源。
第二个坑是数据库连接串没加serverTimezone,新版MySQL驱动启动时报错Cannot create PoolableConnectionFactory。这个在开发环境可能一直没暴露,因为本机MySQL时区设置碰巧没问题,部署到服务器就翻车。建议统一在连接串里加上serverTimezone=Asia/Shanghai。
第三个坑是Nginx上传文件大小限制。学生提交培养材料时上传附件,如果超过Nginx默认的1M限制,会直接返回413 Request Entity Too Large。所以需要把client_max_body_size调到合适的值,我在这边设置的是100m。
第四个坑是前端路由history模式的404。第一次部署时访问首页没问题,点击页面内跳转也正常,但我刷新了一个子路由页面就404了。排查后发现就是上面说的try_files配置缺失,这是vue-router history模式部署时的经典问题。
第五个坑是后端日志看不到SQL。排查线上问题时发现MyBatis的SQL执行日志不输出,定位问题全靠猜。原因是生产配置文件里没有配置日志级别,我加上logging.level.com.example.train.mapper=debug之后,SQL和参数都能输出,排查效率翻倍。
最后分享一点个人体会
这个项目从需求梳理到上线,我最大的收获不是哪段代码写得漂亮,而是把整个流程串起来的经验。数据库表设计时多想一步关系,后面写SQL就少改一次;前端跨域问题在开发环境靠代理解决,生产环境靠Nginx转发解决,两条路径都想清楚就不会被卡住。如果你也在做交流培养管理平台这类前后端分离项目,我建议你按这个顺序推进:先把表结构设计好,再做后端接口并保证每个接口能用浏览器直接访问,最后再做前端页面。只要这个顺序不乱,整个开发过程会顺畅非常多。还有一点,部署一定尽早做,不要等代码全写完再部署,早部署早发现问题,很多坑都是上线前才暴露的,提前踩总比验收时手忙脚乱强。