ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue大创管理系统实战:状态机、权限控制与部署踩坑全记录

SpringBoot+Vue大创管理系统实战:状态机、权限控制与部署踩坑全记录 前阵子接了套本科生大创管理系统的小项目技术栈锁死是 SpringBoot Vue Java MySQL MyBatis前端还得用现成模板改前后端分离部署。做完以后复盘了一下发现这套东西虽然业务不复杂但涉及的知识点非常典型从数据库建模、MyBatis 动态 SQL、权限控制到前端 Vue 路由守卫、文件上传、前后端联调几乎把 JavaWeb 项目开发的主干流程全走了一遍。这篇文章就把整个项目的设计思路、核心模块实现、踩坑记录完整梳理一遍给正在做类似管理系统或者准备做毕设、课设的读者一个可参考的蓝本。先说明一下这个项目到底是干嘛的大创管理系统核心是“大学生创新创业训练计划”的全流程管理包含学生申报项目、指导教师审核、学院/学校管理员评审、立项公示、中期检查、结题验收这些环节。说白了就是让原本靠 Excel 和纸质材料流转的行政事务变成线上可追踪、可留痕、可统计的流程系统。这套设计思路可以直接平移到大创、挑战杯、互联网、甚至一般的科研项目管理系统属于典型的“业务状态机 角色权限 CRUD 报表”组合体。如果你正准备用 SpringBoot Vue 写一套完整管理系统这篇内容可以帮你少走很多弯路。我不只讲代码怎么拼还会解释每一步为什么这么设计哪个位置暗藏坑以及实际测试时遇到的诡异问题。1. 项目背景与整体设计思路1.1 大创管理系统到底要管什么做系统之前最重要的是先把业务理顺。大创项目的生命周期大概可以分成六个阶段学生申报、指导教师审核、学院管理员初审、学校管理员组织评审、立项后中期检查、结题验收。每个阶段又有不同的角色参与常见角色包括学生、指导老师、学院管理员、校级管理员包括评审专家。最初的需求方只提了个轮廓“要能在线申报、评审、查看进度”。但真正动工时你会发现核心难点不在写代码而在把流程状态理清楚。一个项目在任意时刻必须处于一个明确的状态比如“待导师审核”“导师已通过”“学院初审中”“评审中”“已立项”“中期检查中”“已结题”“已终止”等等。每个状态下允许执行什么操作操作以后流转到什么状态这是整个系统的心脏。我设计了如下状态机用文字描述不画图学生保存草稿后提交申报状态变为“待导师审核”。导师审核同意后 - “学院初审中”导师退回则回到“待修改”。学院管理员通过 - “学校评审中”不通过则直接“已驳回”。学校管理员发起专家评审录入评审结果后综合得分达标则“已立项”否则“未通过立项”。已立项的项目在指定时间节点可以提交中期报告状态变为“待中期检查”由指导老师或学院管理员审核。结题同理提交结题报告后进入“待结题审核”最终变为“已结题”。这样的状态机有两个好处第一后端接口可以做成“动作”而不是“改字段”比如/api/project/apply、/api/project/teacherReview、/api/project/collegeReview参数里带项目ID和审批意见服务端统一校验当前状态是否允许这个操作第二前端可以依据状态码渲染不同的按钮和流程进度条用户界面不需要写死逻辑。我见过很多新手直接给项目表加一个status字段然后所有接口都能随意修改它这样三个月后你就会收到各种“状态错乱”的bug反馈。正确做法是把状态变更收敛到service层用枚举定义状态和允许的操作至少要做一层守卫校验。这是整套系统里最值得投入精力的地方。1.2 技术选型为什么是 SpringBoot Vue MySQL MyBatis这个技术栈被无数课设和毕设项目使用已经成了“管理系统全家桶”的标准答案但很多人只是跟风选型并不知道其他选项的优劣。我结合这个项目说一下选型理由。SpringBoot的优势是开箱即用、内置Tomcat、自动配置。用SSHSpring MVC Hibernate那套老框架要写大量XMLSpringBoot直接一个spring-boot-starter-web完事。对于这种小型管理系统没必要上微服务单体应用就是最合理的选择。Vue选择基于版本考虑现在主流是Vue 2或Vue 3配合Element UI或Element Plus组件库。选Vue的核心原因是它足够轻量、学习曲线平缓而且做管理后台效率极高。如果你的前端基础薄Vue的单文件组件和双向绑定能让你很快写出可交互的页面。真正项目的路由设计、状态管理、组件通信都能用到不是纯玩具。MySQL就不多说了开源、免费、稳定绝大多数高校项目都跑在MySQL上。表结构不复杂的话单机部署完全够用。MyBatis在这一点上争议比较大。Spring Boot 官方推荐的是 Spring Data JPA而国内企业很大一部分用的是 MyBatis。MyBatis 的优势在于 SQL 完全可控尤其是大创这种业务里有大量多表关联查询、动态条件筛选比如按学院、按评审状态、按项目类型的场景用动态SQL可以写得很灵活。JPA 那种“拿对象当数据库”的思路在小项目里效率高但遇到复杂报表和统计查询时MyBatis 的 mapper XML 反而更直白。所以如果项目偏数据管理、报表定制选 MyBatis 通常不会错。1.3 数据库设计与表关系数据库设计决定了后面写代码是顺畅还是痛苦。我花了整整半天来建表最后定了8张核心表外加2张辅助表。第一类是基础信息表users用户表字段包括id、username、password、real_name、role_id、college_id、phone、email等。这里注意用户与角色不要直接写死字符串而是用role_id关联角色表角色表roles里放角色编码比如STUDENT、TEACHER、COLLEGE_ADMIN、SYSTEM_ADMIN。第二类是业务核心表projects项目表这是全系统最重要的表。字段除了基本信息项目名称、项目类型、负责人、指导老师、所属学院、立项年份、预算金额、项目简介等还有一个current_status字段和一个current_step字段。前者记录整个流程的状态后者记录当前处于哪个流程节点申报/评审/中期/结题。这里我一开始只设计了current_status后来发现状态流转和历史追踪容易混乱于是增加了process_logs表流程日志表专门记录每个项目每次操作的时间、操作人、动作类型、备注说明。这对“留痕”要求非常有帮助。第三类是评审相关表reviews评审表保存专家对项目的打分、评审意见和评审轮次attachments附件表保存学生上传的申报书、中期报告、结题报告的元数据比如文件名、存储路径、上传者、关联业务类型和业务ID。第四类是项目成员表project_members一个项目可以有多名成员学生成员与项目是多对多关系所以单独建表。这里分享一个心得因为你最终要生成“完整源码”所以建表语句、初始化数据一定要写在sql/init.sql脚本里并配上注释。不要只丢一个数据库导出文件那种文件导入起来很痛苦尤其是包含中文注释时容易乱码。我习惯用Navicat或DataGrip逆向生成SQL再手工调整字段注释和索引保证别人拿到后可以一条命令初始化。2. 后端核心模块设计与实现要点2.1 项目分层与包结构我采用经典的五层结构controller、service、mapperdao、entity、vo/dto。但有一些细节要特别注意。Controller 层不要写业务逻辑它只做参数接收、调用service、统一封装返回结果。这个项目里我封装了一个Result类包含code、message、data三个字段所有接口都返回这个结构。前端配合axios拦截器可以统一处理错误码和跳转登录页。Service 层最关键是业务核心。比如“项目申报”这个动作service里做了几件事校验用户是否有申报权限、校验是否存在进行中的项目、组装实体、插入项目表和成员表、写入流程日志、更新用户的项目列表版本号如果有缓存。这里强烈建议使用Transactional确保系统异常时数据不会只写一半。Mapper 层用 MyBatis 的时候我习惯把简单的单表 CRUD 用注解实现复杂查询写 XML。为什么注解写起来快但动态SQL一多注解里的script标签非常难看还没法复用。XML 可以写sql片段可以组织where、foreach团队协作时也更清晰。所以我的Mapper都是接口XML成对出现。如果你用MyBatis生成器注意生成的XML默认可能不包含where动态条件需要自己加。Entity 和 VO 的区别也要分清Entity 对应数据库字段VO 是给前端展示用的对象。比如Project实体里有一个userId但它需要展示“负责人姓名”“学院名称”我就建一个ProjectDetailVO在Service里组装好。不要直接在Entity里加冗余字段后面改表的时候你会很崩溃。2.2 基于 MyBatis 的数据访问层实践MyBatis 在这个项目中最大的价值是动态SQL。举一个典型例子综合查询“项目列表”时前端会传多个筛选条件项目名称模糊搜索、所属学院、状态、申报年份、项目类型。如果不用动态SQL你至少要写5个if组合查询或者写一个包含所有可能条件的大SQL用where 11拼。MyBatis 的where标签会自动去掉第一个多余的and非常实用。下面是一段我在ProjectMapper.xml里写的关键片段select idselectProjectList resultTypecom.example.entity.Project SELECT p.*, u.real_name AS leaderName, c.college_name AS collegeName FROM projects p LEFT JOIN users u ON p.leader_id u.id LEFT JOIN colleges c ON p.college_id c.id where if testkeyword ! null and keyword ! AND p.project_name LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null and status ! AND p.current_status #{status} /if if testcollegeId ! null and collegeId ! AND p.college_id #{collegeId} /if if testprojectType ! null and projectType ! AND p.project_type #{projectType} /if /where ORDER BY p.create_time DESC if testoffset ! null and limit ! null LIMIT #{offset}, #{limit} /if /select注意两个坑。第一个是模糊查询#{keyword}是预编译参数不能把%拼在参数里传%%应该用CONCAT函数拼接也可以用%||#{keyword}||%但Oracle和MySQL语法不同。第二个坑是排序字段如果允许前端传入必须做白名单校验否则会有SQL注入风险。比如前端传orderBycreate_time你要检查这个字段是否在允许列表里。再说分页我没用PageHelper插件而是手动传入offset、limit计算。为什么要这样因为PageHelper有一个版本兼容问题不同MyBatis版本下偶尔会出现作用域偏差比如查第二页却执行的是上一页的count查询。在小项目里手写分页反而最可控。配合前端Vue的el-pagination计算方式就是offset (currentPage - 1) * pageSize。如果你的查询结果需要关联多张表手写分页时可以把count和list分成两条SQL一条select count(*)一条limit查询这样PV高时也更方便分开做缓存优化。2.3 登录鉴权与角色权限控制这个小项目的权限控制我选择了最轻量但足够用的方案基于Session的登录态 拦截器做接口鉴权而不是引入Spring Security。为什么如果你做过Spring Security就知道它的过滤器链和配置很繁琐为了一个管理后台的简单权限性价比不高。但如果你把项目拓展到接口级细粒度权限再上Spring Security也不迟。具体实现用户登录成功后把用户对象不含密码存到HttpSession中同时更新最后登录时间。写一个AuthInterceptor实现HandlerInterceptor在preHandle中判断当前请求的URI是否是公开接口如登录、验证码、学生注册否则检查Session是否为空。如果为空直接返回401状态码。角色权限用自定义注解RequireRole(ADMIN)标注在Controller方法上拦截器里通过HandlerMethod获取该注解再比对当前用户的角色编码。这样代码非常干净。这是核心代码框架示意非完整源码public class AuthInterceptor implements HandlerInterceptor { Autowired private UserMapper userMapper; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String uri request.getRequestURI(); if (uri.startsWith(/api/auth/)) { return true; } HttpSession session request.getSession(); User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { response.setStatus(401); return false; } if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod (HandlerMethod) handler; RequireRole requireRole handlerMethod.getMethodAnnotation(RequireRole.class); if (requireRole ! null) { if (!currentUser.getRoleCode().equals(requireRole.value())) { response.setStatus(403); return false; } } } return true; } }这里有个容易漏掉的坑前后端分离时浏览器每次请求都会带上Cookie但如果你给前端用的域名或端口不一致比如前端localhost:8081后端localhost:8080Session里的JSessionId是无法跨域写回的。我用了两种方案配合开发环境让Vue的devServer代理/api到后端避免跨域如果线上必须分开部署则统一走Nginx反向代理保证前端请求的域名和当前页面域名一致Session Cookie就生效了。记住后端响应头不要随意设置Access-Control-Allow-Origin: *配合allowCredentialstrue时必须指定具体Origin否则浏览器会拦截。2.4 业务核心项目申报与评审流程的状态机设计这个环节是整个项目里技术含量最高、也是最容易出bug的部分。我的做法是建立一个ProjectStatusHandler它是一个普通的Spring组件内部维护了一张状态转移表。Map的key是“当前状态”value是一个列表列表元素包含“目标状态”和“操作类型”。在发起操作时先校验当前状态是否在允许的转移集合中再进行数据库更新并插入流程日志。比如“导师审核通过”这个动作要判断项目当前状态必须是PENDING_TEACHER并且当前用户必须是该项目的指导教师注意权限校验不只是角色还要校验数据归属。之后将项目状态改为PENDING_COLLEGE同时把导师意见写入process_logs表。这样设计的好处是以后增加“终止”“退回修改”等操作只需要在转移表里增加条目不会破坏已有逻辑。我在Controller里提供的是动作化接口/review、/submit等而不是暴露/updateStatus之类的直接改状态接口。从安全角度来说也防止了前端通过改写status字段来跳过不合法流程。另一个重要点是并发问题。假设管理员同时点了“通过”学生又点了“撤回”状态可能冲突。我的方案是在projects表中加version字段乐观锁。更新状态时SQL里带上WHERE version #{oldVersion}如果更新行数为0则抛出异常“当前项目已被他人操作请刷新后再试”。SpringBoot中配置Version注解或手动在mapper中实现都可以。这个小改动在答辩演示时很加分能说明你考虑到并发场景。3. 前端Vue设计与交互实现3.1 前端工程化与路由设计前端用Vue CLI或Vite搭建都可以。Vite启动速度快但如果你后续要兼容旧浏览器或依赖特定webpack插件用Vue CLI更稳妥。我用的是Vue 3 Vite Element Plus。路由设计上我采用了“静态路由 动态路由”结合的方式。静态路由只有两个登录页/login和首页/dashboard。动态路由根据登录用户的角色动态生成比如学生登录后能看到“项目申报”“我的项目”“材料提交”等菜单管理员登录后能看到“项目评审”“用户管理”“学院管理”。这样既控制了前端权限也避免了将大量路由写在打包文件里导致安全暴露。实现动态路由的核心是在路由守卫里做判断router.beforeEach((to, from, next) { const token sessionStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else { if (store.state.menus.length 0) { // 拉取用户信息根据角色生成动态路由 store.dispatch(generateRoutes).then(menus { menus.forEach(route router.addRoute(route)) next({ ...to, replace: true }) }) } else { next() } } })特别注意路由刷新时会重新执行守卫如果动态路由没有再次注入用户直接访问深层地址会白屏。要确保刷新后能重新拉取菜单、重新addRoute。这就是为什么不能用纯前端写死全部路由来模拟权限刷新后权限会丢失。3.2 核心页面拆解申报表单、评审打分、进度跟踪申报表单是大创管理系统中信息密度最高的页面。学生需要填写的内容包括项目名称、项目类型创新训练/创业训练/创业实践、项目级别校级/省级/国家级、指导教师姓名、团队成员信息、项目经费预算、项目内容简介、预期成果等。我用的是Element Plus的el-form配合rules做校验。这里有几个提高效率的小技巧通过el-select的remote-method实现远程搜索指导教师。因为你不可能把所有老师一次性渲染出来输入关键字后端返回匹配结果。团队成员使用动态表单点击“添加成员”可以插入一行新的表单绑定数组并渲染编号。使用v-for时注意添加key为唯一的memberId。项目类型决定预算上限前端可以通过监听el-radio改变时动态调整el-input-number的max属性同时后端也要再次校验不能信任前端约束。评审打分页面是管理员的重点页面。每个专家在一个评审批次里对一个项目分别打分创新性、可行性、团队能力、预期成果最后自动生成总分。前端使用el-rate组件做星级评价或者用el-slider做百分制评分。提交评审意见时调用POST接口要求不能重复提交同一轮次。进度跟踪页面比较有特点。我用了一个垂直的el-steps组件把项目状态机映射到步骤上。当状态码改变时自动高亮当前步骤并在下方展示流程日志的时间线el-timeline。这样学生和老师都能清晰看到材料流转到哪个环节、哪个人手上。3.3 与后端接口联调的经验前后端分离后接口对接是一个高频痛点。我总结了几个让联调少走弯路的方法第一定义好统一的返回结构。后端Result的code字段我约定200成功、401未登录、403无权限、500业务异常。前端axios响应拦截器统一处理service.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { router.push(/login) } Message.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error { Message.error(网络异常请稍后重试) return Promise.reject(error) } )第二接口文档用Swagger或Apifox维护。我项目里直接引入了springdoc-openapi集成简单写注释就能生成接口文档前端照着文档联调比口口相传强太多了。第三联调前先跑通一条最简单的链路比如先实现GET /api/user/info前端登录拿到用户信息并渲染在界面再逐步展开其他模块。不要所有接口完成后才联调不然一堆问题堆在一起很难定位。4. 完整部署与源码落地4.1 环境准备JDK、MySQL、Node.js 的坑很多读者拿到的源码在自己电脑上跑不起来一大半原因是环境问题。我整理一下关键点JDK版本这个项目基于Spring Boot 2.7推荐使用JDK 8或JDK 11。如果你用JDK 17部分老版本的Lombok和Spring Boot 2.7也能跑但如果你用的是JDK 21最好换成Spring Boot 3.x否则会有兼容性报错。MySQL建议使用5.7或8.0。注意8.0以上版本的认证插件默认是caching_sha2_password而老驱动不支持会导致连接报错Public Key Retrieval is not allowed。解决方法是在JDBC连接串中加allowPublicKeyRetrievaltrueuseSSLfalse。这个问题我遇到太多次了几乎每套用MySQL 8的项目都会踩一次。Node.jsVue CLI建议使用Node 14~16Vite 4建议Node 16以上。太新的Node比如18跑Vue 3 Vite 4基本没问题但跑Vue CLI 4.5可能会报错误。建议直接看项目的package.json里engines字段或者在启动失败时看日志里的提示。Maven如果你的pom.xml里用了spring-boot-starter-parent尽量使用Maven 3.6以上版本否则可能出现解析失败。4.2 数据库初始化与MyBatis配置数据库初始化我用了一个init.sql脚本包含建库、建表、插入初始化数据。脚本最前面要加一句CREATE DATABASE IF NOT EXISTS dachuang DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE dachuang;用utf8mb4而不是utf8原因很简单utf8在MySQL里最多存3字节一些生僻字和emoji会变成乱码utf8mb4是完整的UTF-8支持。用户表初始密码我用的是123456但存到数据库的应该是BCrypt加密后的字符串。如果你用的是BCryptPasswordEncoder可以先把加密后的值写在init.sql里也可以写一个启动时若检测到空数据就自动初始化数据的CommandLineRunner。MyBatis的核心配置在application.yml中有几项对排查问题很有帮助mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case开启了从create_time到createTime的自动映射否则你ResultMap里要写一堆映射关系。StdOutImpl能把你每次执行的SQL打印到控制台这是调试MyBatis最直接的利器。生产环境记得关掉日志或者改为Slf4jImpl。如果你设置了allowPublicKeyRetrievaltrue在链接串里也要加上serverTimezoneAsia/Shanghai否则可能会因为时区问题导致时间字段值差8小时。这个坑在MySQL 8里很典型。4.3 项目打包与前后端联调部署后端打包用mvn clean package -DskipTests生成target/*.jar。如果我把前端打包后的静态文件直接放到src/main/resources/static目录Spring Boot就可以同时提供静态资源访问实现“单jar部署”这在演示环境非常方便。做法是前端执行npm run build把生成的dist里的内容复制到静态目录。注意复制之前一定要清理掉旧的静态文件否则会残留一些过期的js/css引起缓存问题。如果前后端分开部署代理配置就很关键。开发环境中我建议在Vue的vue.config.js里配置devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端页面里的请求地址就写/api/xxx不写绝对路径联调时不会有端口冲突。生产环境用Nginx把前端静态文件和后端接口统一到一个域名下location /api/ { proxy_pass http://localhost:8080; }还要注意前端路由的历史模式history: createWebHistory()会依赖Nginx的try_files配置否则用户直接访问/system/project-list刷新会报404。Nginx配置中加一句try_files $uri $uri/ /index.html;就能解决。5. 常见问题与排查技巧实录5.1 MyBatis 常见报错与缓存问题项目开发过程中MyBatis相关的坑占了很大比例。第一个是“Invalid bound statement (not found)”错误。通常原因是mapper接口和XML文件没有映射成功。检查三点application.yml中的mapper-locations路径是否与实际的XML目录一致XML文件的namespace是否与接口的全限定名一致接口文件名和XML文件名是否一致。很多小白把XML放在src/main/java下而没做资源处理导致打包时XML根本没有被复制到classpath。最稳妥的做法是把XML放在src/main/resources/mapper下并且不要启用手动扫描。第二个是“Parameter xxx not found”的错误。当Mapper接口方法有多个参数时必须使用Param(xxx)标注否则MyBatis无法提取参数名。例如ListProject selectByStatusAndCollege(Param(status) String status, Param(collegeId) Integer collegeId);第三MyBatis的一级缓存和二级缓存。一级缓存是Session级别的同一个SqlSession内查询同一SQL会直接返回缓存结果这在Service方法中可能会造成数据不一致比如多次查询返回的是同一个对象的引用你如果修改了对象的属性下次查询得到的还是这个被修改过的对象实际上Spring集成时每次Mapper操作都重新获取SqlSession一级缓存默认关闭了。二级缓存如果全局开启可能会带来脏数据风险。我的建议是管理系统这类项目中除非SQL确实非常频繁且数据更新不敏感否则不要开二级缓存。用本地缓存Cacheable或Redis处理热点数据更可控。5.2 前后端跨域与Session/Cookie问题我在2.3节提到了Session跨域的问题。实际联调中出现过的现象是登录接口请求成功后续获取用户信息的接口却返回401而且浏览器Application中看不到JSessionId。排查步骤确认后端是否设置了Access-Control-Allow-Credentialstrue前端axios是否配置了withCredentials: true后端是否设置了允许的Origin不能是*。如果你用SpringBoot的CrossOrigin注解在allowCredentials为true时origins必须明确指定。为了避免这些麻烦最干净的方式是把前端构建产物交给SpringBoot托管像4.3节说的那样单jar启动天然同源。不要被“前后端分离”四个字限制住分离指的是开发期和代码层面的分离部署时合在一起完全合法也省去很多运维麻烦。5.3 Vue路由刷新404问题前端使用history模式时刷新二级路由页面会404这是Nginx/Apache的配置问题不是路由本身的问题。Apache需要在.htaccess里配置RewriteRuleNginx用try_files我已经在上面给出了。如果你没有Nginx配置权限也可以退而求其次改用createWebHashHistory地址栏会多一个#但不影响功能而且部署时不存在404问题。对于课设和毕设用hash模式省心很多。5.4 MySQL连接与SSL问题有读者遇到过启动时提示SSL connection error或Communications link failure。这个大多与JDBC URL配置有关。我常用的一根比较稳的JDBC串url: jdbc:mysql://localhost:3306/dachuang?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruezeroDateTimeBehaviorconvertToNulluseSSLfalse是直接关掉SSL握手开发环境无所谓如果企业安全要求开启SSL那需要证书配置不是我们这个项目的重点。另外MySQL 8.0的时区问题很常见如果连接参数中没有serverTimezoneAsia/Shanghai可能会报The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized。记住了上海时区。还有一个小坑是初始化SQL里出现了DEFAULT CHARSETutf8mb4但你本地MySQL是5.5就会报错。如果是MySQL 5.5需要改成utf8。现在主流都已经到8.0了一般来说问题不大。5.5 实际测试中的心得笔记最后记录几个我在真机测试中发现的小问题供参考。第一文件上传和下载。学生上传申报书PDF时前端用的el-upload组件发送multipart/form-data后端用MultipartFile接收。但Nginx默认限制client_max_body_size 1m超过1MB的PDF会上传失败返回413。需要在Nginx的http或server块中设置client_max_body_size 20m。SpringBoot本身的配置是spring.servlet.multipart.max-file-size20MB两边都要调。第二日期插件问题。前端用el-date-picker提交的日期格式是YYYY-MM-DD但后端实体里用的是java.util.DateJackson默认会把Date序列化为时间戳或者带毫秒的字符串前端拿到的值可能会出现“Sun Mar 06 00:00:00 CST 2025”这样的难解析格式。我统一在全局配置了spring.jackson.date-formatyyyy-MM-dd HH:mm:ss spring.jackson.time-zoneGMT8这样前后端日期传递就很干净。第三Excel导出功能。大创项目评审结束后管理员需要导出项目汇总表。我用的是Apache POI但生产环境不建议直接操作内存中的Workbook写到HttpServletResponse因为一旦数据量大比如数千条内存会飙升。更稳的方式是先用模板生成Excel文件到临时目录再以流形式下载。这个系统里数据量不大所以直接输出流也可以但我还是写了下载完成后删除临时文件的逻辑。6. 源码结构建议与扩展方向如果你打算把这套大创管理系统做成完整源码交付目录结构一定要整洁。我的推荐结构如下dachuang-system/ ├── src │ ├── main │ │ ├── java/com/example/dachuang │ │ │ ├── config │ │ │ ├── controller │ │ │ ├── service │ │ │ ├── mapper │ │ │ ├── entity │ │ │ ├── vo │ │ │ ├── interceptor │ │ │ └── utils │ │ └── resources │ │ ├── mapper │ │ ├── static (前端构建目录) │ │ └── application.yml │ └── test ├── sql │ └── init.sql ├── frontend │ ├── src │ └── package.json ├── pom.xml └── README.mdREADME中必须写清楚环境要求、数据库初始化步骤、后端启动命令、前端安装依赖和启动命令。很多源码作者惜字如金导致用户连怎么跑都不知道。我建议写一个比较仔细的README把端口、默认账号密码例如admin/admin123、测试数据都列出来这对阅读体验是质的提升。从功能扩展上来说这套系统还可以增加几个模块数据统计看板用ECharts展示各学院申报数量柱状图和评审通过率饼图、消息通知当状态流转时发送站内信或邮件、导出PDF结题材料。这些扩展点对于毕业设计答辩中的“创新点”讲解很有帮助。如果时间充裕还可以引入 Redis 缓存用户登录状态、接入Minio做附件存储这些都是可以讲亮的点。我个人在实际开发中的体会是这种管理系统最大的难点永远不是代码能力而是业务流程的精确建模。把状态机理清楚把权限边界划明白数据库设计合理哪怕代码写得粗糙一点整个系统都很难出大问题。反过来如果业务状态乱成一团再牛的框架也救不了。所以如果你正准备做类似项目先从梳理流程开始不要急着写代码。这一步花的时间之后会十倍百倍地还回来。
返回列表