ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot2+Vue3高校实习管理系统源码深度拆解与部署指南

SpringBoot2+Vue3高校实习管理系统源码深度拆解与部署指南 这套“Java Web 高校实习管理系统”的源码技术栈正好打在SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这个组合上前后端分离还配了一套完整文档。我拿到手之后第一时间就是启动、拆包、过源码把里面值得学习的东西捋了一遍。今天这篇东西就是完整的复盘记录包括选型逻辑、模块拆解、数据库设计思路、前后端核心实现、部署过程以及我在跑通项目过程中踩过的一堆坑。如果你是准备做毕业设计、正在学前后端分离项目或者公司内部要快速搭一套流程管理后台这篇内容可以让你少走不少弯路。这套系统解决的其实是高校实习管理中的老问题实习信息分散在Excel、微信群里学生找不到合适岗位教师不清楚到底有哪些人在自己名下实习管理员导出统计报表全靠手工。系统的核心价值就是把“学生选岗、企业发布岗位、教师审核指导、管理员汇总统计”这条链路放到同一个平台上。批量角色分离权限清晰每一步流程都有状态记录数据还能自动汇总成报表属于典型的教学管理信息化场景。1. 项目概览与技术选型思路1.1 这套系统到底是做什么的先说功能全景方便你对号入座。系统从使用者的角度分成四种角色分别是学生、教师、企业和管理员每个角色的操作边界不一样学生端浏览实习岗位投递简历或实习申请查看审核进度提交周记、月报、实习报告和实习材料附件。教师端查看名下指导的学生审核学生的实习申请批阅周记和报告给学生实习表现打分。企业端发布实习岗位查看学生投递情况确认实习意向后回写录用状态。管理员端维护学生、教师、企业的账号信息配置基础数据字典审核岗位发布查看全学院或全专业的实习统计报表包括参与率、岗位覆盖率、成绩分布等。流程核心是“实习申请—审批—记录—材料提交—评分”这条链路。学生提交申请后由指导老师或企业进行审批审批通过后形成一条实习记录后续所有周记、报告都挂在这条记录下面。管理员可以通过数据看板看到整个学院目前的实习完成情况哪些人还没落实单位哪些部门审核积压严重一眼就能看到。这套流程建模属于典型的工作流加数据采集场景。对于做毕业设计的同学来说最大的参考价值在于它把RBAC权限模型、业务流转状态机、附件上传、数据统计独立成模块每一块都是可以单独拿出来当面试题讲的。对于公司内部做管理后台的开发者它的表格列表、弹窗表单、状态标签、审批操作这类通用交互也可以直接借鉴。1.2 为什么是这套技术组合先说SpringBoot2。这里有人会问现在SpringBoot3都出来了为什么还要用2.x这套技术栈本身就是奔着“稳妥、文档多、踩坑成本低”去的。SpringBoot2.7.x已经是2代版本的长期维护终点稳定性经过了大量验证社区资料多到你想找不到都难。另外SpringBoot3强制要求JDK17而SpringBoot2.7对JDK8和JDK11都有很好的兼容高校教学环境里绝大多数机器还停留在JDK8项目跑不起来是最大的灾难所以选SpringBoot2是保证“拿过来就能跑”的最现实选择。Vue3作为前端框架也是同样的逻辑。Composition API把过去那一大堆mixin、散落各处的data和methods统一收敛到setup里复用逻辑时可以抽成自定义hook结构比Vue2时代干净得多。配合Vite做开发服务器冷启动和热更新速度比webpack时代舒服太多。系统里面的角色后台页面其实是一个非常标准的Vue3后台管理场景登录页、动态路由、菜单权限、列表页、表单页、状态标签、弹窗确认这些模块在Vue3生态里都已经有非常成熟的写法。再来看MyBatis-Plus。用过原生MyBatis的人都能体会那种“每张表都要写Mapper接口、XML映射、通用CRUD”的重复感。MyBatis-Plus的核心思路是“只增强不改变”它提供了BaseMapper和IService把单表CRUD、条件构造、分页查询这些高频操作全部通用化拿到一张新表基本不用写XML。尤其适合这种业务表比较多、字段变动频繁的管理类系统能减少大量样板代码。再加上它内置的逻辑删除、自动填充、乐观锁插件都是后台管理系统刚需。最后是MySQL8.0。8.0之前utf8mb4字符集虽然已经存在但要显式配置8.0从2018年开始就把它设为默认中文存储乱码概率大幅下降。窗口函数、公共表表达式这些分析型SQL能力在这种系统做统计报表时非常有用。比如统计每个指导老师名下的学生人数排名用ROW_NUMBER()一行就能实现而在5.7里你可能要用临时表加自连接绕来绕去。关于这套技术组合我的结论很明确如果你不是为了追新、不是做技术预研而是想稳定交付一个管理类系统SpringBoot2 Vue3 MyBatis-Plus MySQL8.0就是目前“性价比最高”的标配。三者各自的选型逻辑可以用一个表格总结组件选择理由替代方案存在的风险SpringBoot2.7JDK8兼容生态成熟资料多SpringBoot3强制JDK17教学环境普遍不支持Vue3 ViteComposition API开发效率高构建快Vue2已停止维护Webpack冷启动慢MyBatis-Plus单表CRUD零SQL逻辑删除分页开箱即用原生MyBatis代码量大查询条件拼接容易出错MySQL8.0utf8mb4默认性能优化器提升明显5.7已过生命周期大量新语法不支持1.3 拿到源码第一步目录结构先看明白这套项目的目录结构很规整而且前后端放在两个独立目录里分的很清楚。拿到源码后千万别直接点开Application就启动花五分钟先把目录结构过一遍后面能省很多排查时间。后端部分对应的是SpringBoot的标准分包结构controller、service、mapper、entity、config、common、utils各司其职。我建议你重点关注common目录里面通常放的是统一返回结果Result类、全局异常处理器、状态码枚举和常量类。这些是整个项目的“地基”先搞懂它们的约定再看具体的业务代码就容易多了。前端部分是一个标准的Vite工程src下面有api、router、store、views、components、utils这些目录。其中api目录里的每个js文件对应后端的一份controller接口比如student.js、teacher.js、admin.js这个设计很好前端接口调用按业务域隔离改动一个模块时不会影响其他模块。src/utils下面一般有request.js里面封装了axios实例和token注入逻辑这是我每看一个项目都会首先打开的文件因为很多前后端联调问题都出在这层封装里。2. 业务模块与数据库设计拆解2.1 角色权限与核心业务流程权限模型是这套系统的骨架采用的是最常见的RBAC设计。用户表、角色表、用户角色关联表三联表再加上菜单权限表管理员可以给不同用户分配不同角色每个角色对应一批可访问的菜单和按钮级权限。实现方式上后端在登录成功时根据用户角色生成菜单树返回给前端前端在路由守卫里对路由做动态添加后端在接口层用拦截器或注解做二次校验。这样既保证了前端菜单不出现无权访问的入口也保证了后端接口不会被绕过两层都堵住才是完整方案。核心业务流程可以拆解成下面这个闭环管理员导入师生账号或由用户自助注册并等待审核。企业或个人发布实习岗位提交后进入待审核状态。管理员审核岗位审核通过后岗位对全体学生可见。学生浏览岗位并投递实习申请填写期望实习时间等信息。教师或企业查看申请审批通过或驳回。审批通过后生成正式实习记录学生在实习期间可以提交周记、月报、实习报告。教师批阅周记与报告在实习结束时评分。管理员端根据所有数据生成统计报表。每一步都以状态字段驱动比如申请表的status字段有草稿、待审核、已通过、已驳回、已撤回等多个值每个角色只能看到自己权限范围内的数据和操作按钮。状态机设计是这类业务系统的灵魂所有角色之间的信息异步流转都靠它完成。2.2 核心表设计一览数据库是整个项目最先要读懂的部分。这套系统的主要表在设计上非常贴近实际业务核心表整理如下表名用途关键字段sys_user用户表所有登录账号统一存放username, password, role, statussys_role角色表role_code, role_namesys_user_role用户角色关联表user_id, role_idbiz_enterprise企业信息表name, credit_code, contact_name, contact_phonebiz_position实习岗位表enterprise_id, title, description, headcount, statusbiz_apply实习申请表/投递记录student_id, position_id, status, audit_remarkbiz_internship实习记录表student_id, teacher_id, position_id, start_date, end_datebiz_daily周记/日报表internship_id, content, attachment, create_timebiz_report实习报告表internship_id, title, content, score, commentsys_dictionary数据字典表dict_type, dict_label, dict_value字段设计上有几个细节值得学习。所有业务表都带了create_time、update_time和deleted字段create_time和update_time用MyBatis-Plus的MetaObjectHandler自动填充deleted字段用TableLogic注解配合全局配置做逻辑删除。用户表中的password字段存的是BCrypt加密后的密文绝对不是明文。企业表的信用代码字段设置了逻辑唯一索引防止同一家企业被重复录入。状态字段统一用int类型加枚举类管理不直接在代码里写死魔法值。2.3 最容易忽略的几个设计细节第一个是业务表主键策略。系统统一用MyBatis-Plus的ASSIGN_ID算法生成雪花ID而不是数据库自增主键。这么做的好处是前端在新增数据时可以提前拿到主键值方便做图片上传时的路径命名和子表关联而且数据量大了以后做分库分表不用重建主键体系。第二个是逻辑删除与唯一索引的组合拳。比如企业信息表如果直接对信用代码建唯一索引那么删除记录时就不能物理delete否则再次录入同一家企业就会主键冲突。系统的做法是把deleted字段也拼进唯一约束里逻辑删除时deleted变为原ID值既保留了历史数据也不会阻塞重新录入。这是一个非常实用的设计技巧很多人做逻辑删除时容易在上面踩坑。第三个是状态字段的字典化。状态、类型这类字段的值在数据库里存的是数字编码比如0待审核、1已通过、2已驳回但在前端下拉框、标签显示时对应的是“待审核”“已通过”“已驳回”这样的中文文本。系统通过sys_dictionary字典表和前端字典工具函数做映射新增一种状态时不用改代码只要在数据库里加一条字典记录就行。这个设计在前端中通常体现为DictTag组件传入状态值自动渲染对应颜色的标签新增业务模块时非常顺手。3. 后端核心实现从配置到业务代码3.1 项目配置与基础框架搭建拿到项目后第一步是确认配置文件这套系统可以在application.yml里直接看到核心技术点。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/internship_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里重点说明几个关键配置。url里的serverTimezone和allowPublicKeyRetrieval是MySQL8.0的刚需前者解决时区报错后者解决MySQL8.0默认认证插件在第一次连接时不允许获取公钥导致连接失败的问题。multipart配置把上传上限调到了20MB因为学生上传的实习材料经常是扫描件PDF默认1MB肯定不够用。MyBatis-Plus的map-underscore-to-camel-case让数据库字段的snake_case自动映射成Java的驼峰属性不需要在实体类上写一堆TableField。分页插件是必须手动注册的这也是MyBatis-Plus最常见的遗漏点。在config目录里加入以下配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个配置类不注册的话调用Page方法时虽然不报错但会查出全表数据而且total数为0。我记得第一次跑项目时发现列表接口返回的数据量不对排查了半天才发现是漏了分页拦截器。3.2 统一返回结果和全局异常处理看这种管理系统的代码先看它的Result类怎么写比先看业务接口要有价值得多。系统的Result类结构大致是public class ResultT { private Integer code; private String message; private T data; public static T ResultT success() { ... } public static T ResultT success(T data) { ... } public static T ResultT error(String message) { ... } }业务层代码只需要返回查询结果或实体对象Controller只负责把它们包装进Result.success()里不需要在每一处手写返回结构加上全局异常处理器之后所有业务异常和系统异常都会被统一拦截不会出现后端一报错就返回一堆乱糟糟的堆栈信息给前端的情况。使用心得是这样的如果项目里抛出了代码为500的Result还是带着异常堆栈返回给前端的那不是这套系统的设计问题而是没有用好全局异常处理器。强制业务代码只在异常时抛BizException全局处理器负责把BizException转换为Result.error这样前端在处理错误时只看code就能决定是弹消息还是跳登录页联调效率高出几个档次。3.3 权限认证设计这套系统用的是JWT加拦截器的方案没有引入Spring Security那套重量级框架。用户登录成功后后端签发一个带用户ID、角色信息和过期时间的token返回给前端。前端把token存在localStorage里之后每次请求在拦截器中加上Authorization头。后端实现一个LoginInterceptor在preHandle里解析token、校验有效性、把用户ID存入ThreadLocal然后放行请求。没有token、token过期、token被篡改这些情况在拦截器里直接返回401前端response拦截器里看到401就清除本地登录态并跳转到登录页。与其纠结Security和JWT拦截器谁更好不如关注这个项目给我们的一种更适合中小系统的设计思路。对于实习管理系统这种单机部署、用户量不大的后台引入Security全家桶反而增加了配置学习成本。一个拦截器加一个JWT工具类就能解决的问题没必要事情复杂化。如果哪天系统要接入OAuth2、细粒度到数据权限再升级到Spring Security框架也不迟。3.4 文件上传与静态资源映射学生提交实习报告、教师上传成绩单附件都是文件上传场景。系统在后端实现一个FileController接收MultipartFile类型参数按日期分目录存储到服务器本地。文件存储路径通过配置项可调保存成功后返回文件的URL地址。为了能让前端直接通过URL访问到文件系统配置了静态资源映射把本地文件路径映射到/upload/**访问路径上Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourcePattern(file: fileUploadPath /); } }这里有一个生产环境必须注意的问题本地存储虽然开发调试方便但服务重启或换机器后文件会丢失而且前端通过服务器地址拼接URL上传下载走的是同一台机器一旦机器磁盘满了就麻烦了。更进一步的做法是接OSS或者云存储不过这套系统本地存储的策略对毕业设计和中小型业务完全够用。3.5 通用列表查询的实现套路管理系统最密集的接口就是列表查询这套系统的写法可以作为范本。Controller里接收当前页码、每页大小、查询条件参数Service层用LambdaQueryWrapper拼装查询条件再交给MyBatis-Plus的Page方法返回分页结构。public PageApplyVO pageApply(ApplyQuery query) { PageApply page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperApply wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getStudentName()), Apply::getStudentName, query.getStudentName()) .eq(query.getStatus() ! null, Apply::getStatus, query.getStatus()) .orderByDesc(Apply::getCreateTime); PageApply applyPage applyMapper.selectPage(page, wrapper); // 转换为包含学生姓名、岗位名称、企业名称等冗余字段的VO return convertToVO(applyPage); }这里有人可能会疑惑为什么Apply实体里不直接存学生姓名、岗位名称这些字段而是只存外键ID查询后再去关联表查一次。原因是为了保证数据一致性避免用户改名后旧数据出现错误冗余。而在SQL层面性能不足时可以再使用连表查询一次性查出来。这套代码在实体设计上保证了基础的规范化又在VO转换层做了输出定制算是比较成熟的折中方案。4. 前端工程Vue3 Vite的实战写法4.1 前端目录规划前端采用了Vue3 Vite Pinia Vue Router Element Plus的组合src目录结构非常标准。api目录按后端controller的命名一一对应views目录按角色子目录划分。components目录放公共组件比如UploadFile、DictTag、StatusSelect这些在多个页面都会用到的组件抽出来统一维护可以避免复制粘贴导致的分歧。开发时有一个很重要的体验Vite的按需加载机制让页面访问很快但刚启动项目第一次打开页面时会稍慢之后就非常流畅。这是因为Vite在冷启动时只编译当前需要的模块按需编译的代价就是第一次访问某个路由时需要现场编译。如果你发现页面首次加载很慢不要马上怀疑代码有问题让Vite多编译几十秒第二次再访问就好了。4.2 Axios封装与Token无感刷新request.js是前后端联调的关键我的建议是每拿到一个前后端分离项目先翻它。系统的request.js封装了以下几点创建axios实例设置baseURL和后端响应超时时间。请求拦截器从localStorage取出token加到Authorization头。响应拦截器统一处理HTTP状态码和业务codecode为200时直接返回data非200时用Element Plus的Message组件弹出错误提示。遇到401时清空本地用户信息跳转登录页。通过响应头或请求配置控制是否需要显示全局Loading。这段代码踩过最大的坑是如果axios的baseURL写成了绝对路径开发环境走代理时就不生效了。正确做法是baseURL写相对路径/api然后通过Vite的proxy把/api开头的请求代理到后端地址。4.3 路由守卫与动态菜单路由设计分为静态路由和动态路由两部分。静态路由只有登录页、404页两个其余路由全部在登录成功后根据用户角色动态添加。这种做法的好处是不同角色看到的菜单天然就是不同的不会出现学生账号菜单里露出管理端入口的情况。router.beforeEach是前端权限控制的核心。逻辑是判断本地是否已登录。未登录一律跳转到登录页。已登录但本地store中没有菜单信息说明是刷新页面需要重新请求后端获取用户信息和动态菜单然后addRoute注册动态路由。已登录且菜单已存在直接放行。这里经常出现的一个问题是用户刷新页面后动态路由还没注册完就被放行了导致目标路由找不到直接跳404。解决办法是确保用户信息加载完成后next并return注意使用next({ ...to, replace: true })的方式重新进一次导航而不是直接next()。4.4 列表页与分页组件对接前端做列表页时主流的写法是el-table el-pagination的组合。系统里的列表页结构清晰我摘一下核心逻辑const queryParams reactive({ pageNum: 1, pageSize: 10, studentName: , status: null }) const pageData ref({ records: [], total: 0 }) async function loadData() { const res await getApplyPage(queryParams) pageData.value res }这里的要点是查询参数要绑定到reactive对象上确保后续修改查询条件后重新请求时参数不会丢。el-table本身支持loading属性可以配合Loading组件做加载动画。表格的操作列一般有编辑、删除、审核这些按钮通过click绑定对应事件事件里弹出Dialog或进行二次确认这一套交互从后台管理系统里几乎完全固定了下来代码可复用性很高。4.5 前后端联调的关键配置开发环境联调的核心是Vite的proxy配置。在vite.config.js中配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端在开发环境下请求/api/login实际会被转发到http://localhost:8080/api/login绕开了跨域限制。生产环境下则通过Nginx把前端静态资源和后端接口反向代理到同一个域名下也不存在跨域问题。我还特地看了一下前端对接口返回结构的处理axios响应拦截器已经统一把data字段取出所以页面里写代码时直接拿到业务数据不需要反复解包、判空。只要后端Result格式不变前端业务代码就保持在最简单状态。5. 环境搭建、数据库初始化和部署实录5.1 环境准备避坑清单这个项目的运行环境所需的工具基本是Java Web开发者的标配但我在安装过程中还是踩过几个坑记下来免得后人再踩。软件版本要求注意事项JDK1.8及以上建议直接用JDK8兼容性最佳Maven3.6.3及以上注意配置阿里云镜像加速MySQL8.0.x安装后记得设置utf8mb4字符集Node.js16及以上Vite3要求Node16以上npm/yarn/pnpm仓库自带锁文件优先有pnpm-lock就用pnpm installMySQL8.0装好之后记得确认认证插件是caching_sha2_password而Java驱动用的是com.mysql.cj.jdbc.Driver两者匹配就没问题。如果驱动版本是mysql-connector-java 5.x连8.0会报Authentication plugin错误解决办法是升级驱动到8.x或者在url里配置useSSLfalse和allowPublicKeyRetrievaltrue。5.2 MySQL8.0安装与数据初始化如果你在Windows上装MySQL8.0可以直接用安装包或者前辈总结的免安装zip方式。Linux环境最省事的方式是直接用docker一条命令搞定docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e TZAsia/Shanghai \ -v /opt/mysql/data:/var/lib/mysql \ mysql:8.0容器启动后执行数据初始化脚本一般在项目的sql目录下文件名类似internship_db.sql。执行命令是docker exec -i mysql8 mysql -uroot -p123456 sql/internship_db.sql注意导入前确认脚本里是否包含CREATE DATABASE语句如果包含就需要先确保权限允许执行创建数据库如果不包含需要自己先建库再切库导入。建议先打开sql文件确认开头几行是建库还是直接建表再决定导入方式。5.3 后端启动步骤后端启动的流程很简单用IDE打开后端项目目录或者用命令行进入目录。确认Maven配置有效IDEA右侧Maven面板能正常刷新依赖命令行执行mvn spring-boot:run也可以。修改application.yml中的数据库连接信息改成你自己的用户名和密码。运行Application主类的main方法。启动时如果看到控制台打印出SpringBoot的启动banner和Tomcat started on port 8080说明启动成功。但这里有个隐藏问题如果项目里配置了数据库初始化逻辑比如Spring的schema.sql或者MyBatis-Plus的初始化脚本只要有一点字段不兼容启动过程就会报错。所以建议先把控制台日志级别调成DEBUG或至少INFO确认启动过程中没有SQL异常。5.4 前端启动步骤前端启动相对简单cd frontend npm install npm run devnpm install如果遇到依赖下载慢可以设置npm的镜像源为国内镜像npm config set registry https://registry.npmmirror.com依赖安装完成后npm run dev启动Vite开发服务器终端会显示一个本地访问地址默认一般是http://localhost:5173。浏览器打开这个地址能看到登录页说明前后端已经成功打通。开发环境接入后端接口时要注意本地服务和后端接口不要占用同一个端口否则前端页面会被后端服务的404页面覆盖。Vite默认端口5173和SpringBoot的8080互不冲突但如果后端被改成了8081或其他端口记得同步修改vite.config.js的proxy target。5.5 生产环境Nginx部署生产部署我强烈建议用Nginx托管前端静态资源再反向代理后端接口。打包时先执行npm run build构建完成后前端产物在dist目录下。把这个目录放到Nginx的html目录里再配置如下server块server { listen 80; server_name your-domain.com; location / { root /opt/dist; index index.html; try_files $uri $uri/ /index.html; } 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; } }这里有个必须写上的配置是try_files $uri $uri/ /index.html。Vue3默认用的是HTML5 History模式路由是纯前端控制的路径。如果不配置try_files用户直接访问http://your-domain.com/apply/list时Nginx会去找服务器上的/apply/list目录或文件找不到就返回404只有加上try_files把请求全部落到index.html上让前端路由接管路径解析刷新页面才不会白屏。后端接口用java -jar方式启动nohup java -jar internship-system.jar logs/app.log 21 这样生产环境的网络架构就是浏览器访问Nginx 80端口静态资源由Nginx直接返回接口请求带/api前缀的转发到后端8080端口。前后端同域不跨域性能和安全也都有保障。6. 这套系统手把手避坑手册6.1 高频报错速查表我在跑通这套系统过程中记录下来一些典型的报错和处理方法整理成速查表后面遇到类似问题可以直接对号入座。报错信息产生原因解决办法Public Key Retrieval is not allowedMySQL8.0默认认证插件机制JDBC URL加allowPublicKeyRetrievaltrueuseSSLfalseThe server time zone value is unrecognized数据库时区和驱动不匹配URL设置serverTimezoneAsia/ShanghaiRequired request body is missingaxios POST JSON格式没设置对确认请求头Content-Type为application/jsonbody是JSON对象Total pages 0 / 分页查全部MyBatis-Plus分页插件未注册在配置类注册PaginationInnerInterceptorjava.sql.SQLSyntaxErrorException表名或字段名用了MySQL关键字给表名加反引号或改名避免type、order等关键词Invalid bound statementMapper XML路径不对检查Mapper接口和XML文件的namespace及方法名刷新404 / 页面白屏前端History路由刷新后Nginx找不到文件Nginx配置try_files $uri $uri/ /index.htmlUnauthorized 401反复弹token过期或未正确传递检查后端拦截器放行路径检查前端请求头注入6.2 逻辑删除“查不出数据”的真相MyBatis-Plus的逻辑删除是自动在SQL上追加del_flag0条件的这是方便。但它的副作用是自定义SQL很容易漏掉这个条件。如果项目里出现“数据明明有列表却查不出来”的情况大概率是手写的SQL没有带上del_flag判断或者多个表JOIN时条件拼接位置不对。解决办法是两个方向一是自定义SQL时自己补齐逻辑删除条件二是全局配置逻辑删除后在所有Mapper方法中都注意使用MyBatis-Plus提供的封装方法避免大量原生SQL。这两种方式没有谁更好关键是团队里必须约定统一规范否则有人用封装方法、有人写原生SQL排查起来会非常痛苦。6.3 文件上传失败排查链路上传接口在管理类系统中总是容易出问题排错顺序建议从外到里先看前端是否真的把文件发到了请求体里用浏览器开发者工具看Network面板。再确认后端配置的上传大小限制默认1MB会让稍大一点的附件直接失败需要在application.yml里调到合适的值。最后检查文件存储目录是否存在且有写权限。Linux服务器上如果Nginx或服务以www用户运行而上传目录是root创建的也会导致上传失败。这套系统里上传目录路径可以通过配置项指定上线前收敛到一个固定目录并给这个目录设置好统一权限能省掉很多运行时权限报错。6.4 联调时常见的跨域与代理问题开发环境出现跨域基本都是因为前端没有通过代理访问后端。打开浏览器开发者工具如果看到网络请求的地址是http://localhost:5173/api/xxx但返回CORS错误说明代理没有生效检查vite.config.js中proxy配置是否真的匹配了前端请求路径。如果前端请求的baseURL写的是完整后端地址那么代理永远不会生效因为浏览器已经直连后端了配置时要保证前端只发出相对路径。生产环境出现跨域则多半是Nginx没有正确反向代理接口或者前端静态文件地址和后端接口地址不在同一个域。最简单的判断方法是右键查看页面请求看地址栏里的接口URL域名和页面域名是否一致不一致就是代理没配好。如果系统里也有一些接口需要在同域下用相对路径访问建议在开发环境统一用代理转发生产环境统一用Nginx转发不要在代码里写死某个开发环境的IP不然换台电脑联调时所有请求全部失效排查起来真要掉一层头发。6.5 其他值得复用的设计思路这套源头虽是毕业设计但代码里有些思路是真正做过项目才会采用的值得单独拎出来讲。确实好用的是用注解全局处理器统一处理分页参数。比如PageQuery注解打在Controller层参数上全局处理器自动把pageNum、pageSize转换成Page对象这样Service里不必每次手写new Page代码更干净。对于前端传参不规范的情况比如pageSize传成字符串“10”或负数全局处理器直接拦截并给默认值避免系统被脏参数干扰。另外这套系统的代码生成器配置也值得复用。项目里预置了一个代码生成器入口根据数据库表一键生成entity、mapper、service、controller和vue页面模板。新加一张表时开发效率能提升十倍。代码生成器的核心就是MyBatis-Plus的AutoGenerator只需要配置表名、包名、模板路径就能把重复性极高的CRUD代码自动生成后面再根据业务做手工调整。虽然网上的生成器教程不少但真正把这个工具集成到项目流程里的团队反而不多你能在源码里看到这个算是意外之喜。7. 这套源码的文档价值以及我的一点扩展建议标题里专门标注了【含文档】这点其实很加分。我收到项目后发现文档不是那种随便写两页的凑数材料而是从需求分析、数据库设计说明、接口文档到部署手册都比较齐全对毕设答辩和团队内部交接帮助非常大。你可以直接把这套文档当范本用以后自己写项目建设书或者系统说明书时目录结构、详略程度都能照着参考。尤其是数据库设计文档里的表关系说明和字段注释放到答辩PPT里是很好的素材。而把文档和代码对照着看你还会发现系统在数据库设计阶段就已经把角色权限、业务流程、数据字典这些关系想清楚了而不是写代码时随机演进。这个“先设计再编码”的思路比任何一门视频课都更有教学价值。如果后面你想基于这套系统继续做二次开发我有几个具体方向在学生端增加实习签到功能基于定位或拍照上传和实习记录表关联这个场景经常被企业方和高校看重。把报表导出从后端接口扩展到定时任务每周自动给指导老师推送学生实习进度汇总邮件这个功能在真实管理场景中会很受欢迎。增加消息通知模块学生提交申请、教师审核完成后自动发送站内信或短信提醒把流程的闭环体验补全。如果数据量大可以引入Redis缓存热点岗位列表和字典数据减少数据库压力。我自己在实际复现和改造这套系统的过程中最大的体会是这类管理系统的技术难点从来不在某个独立技术点上而在“把明确的小需求按规范落成一个完整闭环”的过程里。比如列表页的筛选条件、状态流转、权限控制、文件上传单看每一个都很简单但组合在一起就构成了合格项目的门槛。这套SpringBoot2 Vue3 MyBatis-Plus MySQL8.0的组合恰好把这些环节都串得很顺源码可读性也在线作为入行练手项目或毕业设计我认为比那些只堆技术名词、复杂度失控的项目要好得多。最后分享一个我自己习惯的做法拿到类似项目源码以后不要急着启动去点页面先把数据库表结构和ER图过一遍再对照文档看核心流程最后才动手改代码。这套顺序看起来慢实际是真正把项目吃透的最快路径。你看完这篇拆解后也可以用这个顺序再去过一遍项目相信会有和我一样的感受技术上没有特别高深的东西但每一个细节都处理得很踏实这种“稳”正是工程化开发最需要的东西。
返回列表