尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Vue+MyBatis+MySQL评奖评优系统源码拆解:权限、审批流与状态机设计

SpringBoot+Vue+MyBatis+MySQL评奖评优系统源码拆解:权限、审批流与状态机设计 直接说结论SpringBoot Vue MyBatis MySQL 这条技术组合放到现在依然是国内中小型企业管理系统的绝对主流选型。评奖评优这类系统看起来是个“学生管理”的小场景但拆开之后你会发现它几乎涵盖了业务系统开发的所有核心命题权限模型、流程审批、批量导入、条件查询、数据统计、前端动态路由、后端事务一致性。把这套源码吃透不只是学会一个项目而是把企业级开发的通用骨架摸了一遍。所以这篇不是给你贴代码的而是把我自己拆解这套源码时的思路、关键设计、数据库建模逻辑、前后端联调时容易踩的坑以及部署上线时的注意事项完整梳理一遍。目标很明确让你拿到源码后知道先看什么、重点学什么、改的时候怎么下手。1. 整体设计与技术选型这套架构为什么这么搭1.1 评奖评优系统的业务本质先别急着打开IDE第一步是理解业务。评奖评优系统本质上是一个带有审批流的资源分配系统——奖项是资源学生是申请者辅导员和评审委员会是审批者。这和请假系统、报销系统、工单系统在底层逻辑上几乎同构。核心业务链路是这样的学校发布评奖通知 → 学生在线申报填写成绩、综合测评、获奖经历等 → 辅导员初审 → 学院评审 → 学校终审 → 公示 → 归档。整个链路里涉及的角色至少四种学生、辅导员、院系管理员、校级管理员。每种角色看到的界面不同能操作的数据不同这就是权限控制的直接需求来源。这套系统用 SpringBoot Vue MyBatis MySQL 来实现恰好命中这个业务模型的每一个关键点。SpringBoot 负责快速搭起稳定的后端服务和接口层Vue 负责前端页面的组件化交互MyBatis 负责灵活高效的 SQL 操作——尤其适合评奖评分这类需要大量动态条件拼接的查询场景MySQL 则提供可靠的事务保障和数据存储。1.2 选型背后的具体逻辑SpringBoot 解决的是“快速落地 生态成熟”的问题。评比系统的开发周期通常很紧张学校一般要求在奖学金评定季之前上线。SpringBoot 的自动配置、内嵌 Tomcat、Starter 机制让项目从零到能跑只需要几分钟不需要像传统 SSM 那样手动配置一堆 XML。而且 Spring Security 或 Shiro 可以很方便地整合进来实现基于角色的访问控制。Vue 解决的是“多角色界面复用”的问题。学生端、教师端、管理员端虽然业务不同但很多组件是共通的——比如学生信息表格、奖项列表、审批弹窗。Vue 的组件化特性把这些公共部分抽离出来一套代码多处使用。配合 Vue Router 的动态路由可以实现“不同角色登录后看到不同菜单”的效果这对多角色系统来说是刚需。MyBatis 解决的是“复杂查询不设限”的问题。评比系统的查询条件极其多变按院系查、按年级查、按奖项类型查、按成绩区间查、按综合测评排名查……如果用 JPA 这类全自动 ORM写动态条件反而别扭。MyBatis 的if标签可以像拼积木一样组合条件SQL 牢牢掌握在自己手里。特别是多表关联统计“某个奖项下各院系的申报人数”这种查询手写 SQL 的效率和可控性远高于自动生成。MySQL 解决的是“数据一致性和团队熟悉度”的问题。评比数据涉及奖学金发放一分钱都不能错。MySQL 的 InnoDB 引擎提供事务支持——申报时同时更新学生申请表和奖项名额表要么都成功要么都失败这在高并发填报时特别重要。而且 MySQL 是国内团队最熟悉的数据库招人成本低运维资料多学校的机房管理员也能接手。这套选型不是最炫的但一定是最稳的。评比系统最怕的不是性能不够而是上线后没人敢改、没人会接。SpringBoot Vue MyBatis MySQL 的组合恰好拥有最大的开发者基数后续维护不愁没有人力。1.3 企业级体现在哪里很多人一看到“企业级”就以为是分布式、微服务、高并发。实际上对学生评奖评优这类系统来说企业级更多体现在这几个方面规范的目录结构。后端按 controller / service / mapper / entity 分层前端按 views / components / router / store 组织各司其职。拿到源码后你会发现找任何一个功能点从请求入口到 SQL 语句三层就能翻完不会出现一个 3000 行的上帝类。完整的权限体系。不是前端藏个按钮就叫权限控制而是后端接口层也要做拦截校验。这套系统里学生只能操作自己的申报记录辅导员只能审核自己管辖的学生校级管理员才拥有奖项配置和终审权限每一层都不能越权。数据看板与统计汇总。评比工作最繁琐的是汇总——各班多少人申请、各奖项申报率如何、历年对比怎样。这套系统把核心统计做成了可视化面板管理员登录后一眼就能看到申报总数、待审数量、通过率等关键指标。可追溯的操作日志。谁在什么时间审核了谁的申请改了什么内容都有记录。这不是功能需求而是合规需求——奖学金评定是敏感事务出了问题要能回溯。2. 数据库设计拆解评奖评优的第一道地基2.1 核心表结构与业务含义数据库设计是一套系统的灵魂代码可以重写表结构一旦定错后期改造的代价极其高昂。我拆这套源码时先翻的就是 SQL 脚本它暴露了整个系统的设计水平。评奖评优系统的表结构大致围绕五条主线展开用户与权限线sys_user用户表、sys_role角色表、sys_user_role用户角色关联表。用户表里一般会有学号、姓名、所属院系、年级等字段角色表区分学生、辅导员、管理员。这里注意一个细节师生的基本信息可能会来自学校的统一身份认证平台所以表里通常会预留external_id之类的外部关联字段方便后续对接。奖项配置线award_type奖项类型表、award_plan评选计划表。奖项类型表定义“国家奖学金”“校级一等奖学金”“优秀学生干部”这些固定奖项以及每个奖项的名额数量、奖金额度、申请条件模板。评选计划表则对应一次具体的评选活动包含评选批次、开始时间、结束时间、适用院系范围、申请人资格要求。这两个表分开的好处是同一奖项可以多次发起评选比如 2024 年秋和 2025 年春各评一次配置互不干扰。申报与审批线application申报表、approval_record审批记录表。申报表是核心业务表记录学生申报了哪个奖项、综合测评排名、成绩绩点、申请理由、附件路径等。审批记录表每一次审核操作都会写入一条字段包括审批人、审批动作通过/退回/拒绝、审批意见、审批时间——这就是操作日志和审批历史的来源。评分线score_detail评分明细表。有的系统评选流程里有量化打分环节比如“思想品德 20% 学业成绩 50% 社会实践 30%”每一项得分都要落库这样最终总分才能让评审人心服口服。这个表放在后面细说。通知公示线notice通知公告表、publicity公示记录表。评选结束后公示阶段要发布公示内容通常包含公示起止时间、公示材料附件、异议反馈渠道。这里有个硬性需求公示期内一般不允许随意修改申报数据所以系统里往往加一个状态开关公示开始后锁定相关记录。2.2 状态机设计一张申报表走完生命周期评奖评优系统最值得学的是申报表的状态管理。它不只是一个字段而是一个状态机DRAFT草稿 →SUBMITTED已提交 →DEPARTMENT_REVIEW院系审核中 →SCHOOL_REVIEW校级审核中 →APPROVED已通过 /REJECTED已拒绝中间还可以有RETURNED已退回让学生补充材料后再提交状态。为什么状态机这么重要因为在审批流里每个状态决定了“谁能操作、能做什么操作”。比如只有SUBMITTED状态下的记录才能被辅导员审核只有RETURNED状态下学生才有权限编辑内容重新提交。如果这个逻辑不提前设计好后期会出现学生连传到一半的漏洞提交后改不了、审核中又显示可编辑、已经公示了还能撤回。这套源码里前后端对状态的处理是统一的。后端在 Service 层判断状态是否合法前端在按钮级别用v-if控制显示。双端校验而不是只靠前端是因为恶意请求可以直接调用接口跳过界面。2.3 关键 SQL 套路动态条件与统计报表MyBatis 在这套系统里最有价值的部分是动态 SQL 拼查询。拿“按条件筛选申报记录”举例学生列表页会有姓名搜索框、院系下拉框、奖项类型下拉框、状态筛选框、申请时间范围选择器。问题来了用户可能只选了院系其他条件全部留空。用 MyBatis 处理这个场景就是whereif的经典组合select idselectApplicationList resultTypecom.example.entity.Application SELECT * FROM application where if teststudentName ! null and studentName ! AND student_name LIKE CONCAT(%, #{studentName}, %) /if if testdepartmentId ! null AND department_id #{departmentId} /if if testawardTypeId ! null AND award_type_id #{awardTypeId} /if if teststatus ! null and status ! AND status #{status} /if if testapplyStartTime ! null AND apply_time gt; #{applyStartTime} /if if testapplyEndTime ! null AND apply_time lt; #{applyEndTime} /if /where ORDER BY create_time DESC /select这段代码的精髓在于如果用户只选了院系其他条件为 nullif就不会拼进去SQL 自动变成WHERE department_id ?不会出现“多条件全空导致查询失效”的问题。这套写法在后台管理系统中几乎是标配值得反复琢磨。统计报表是评奖评优的另一个硬需求。管理员最常问的几个问题是国家级奖项申了多少人通过率多少各院系的覆盖率如何这时候一条 GROUP BY 语句就解决了SELECT department_name, COUNT(*) AS apply_count, SUM(CASE WHEN status APPROVED THEN 1 ELSE 0 END) AS approved_count FROM application a LEFT JOIN sys_department d ON a.department_id d.id WHERE a.award_plan_id #{planId} GROUP BY a.department_id ORDER BY apply_count DESC这种统计 SQL 在 MyBatis 里写起来轻车熟路返回结果直接映射到前端的柱状图和表格上。这也是为什么我说 MyBatis 比 JPA 更适合这类系统——JPA 表达这种多表聚合查询代码会变得很绕而写 SQL 就是顺手的事。3. 核心技术点实拆从登录鉴权到评分规则3.1 认证与权限的落地方式评奖评优系统涉及敏感数据登录鉴权不能只是摆设。这套系统的做法通常是基于 Token 的认证机制用户输入账号密码后后端验证通过返回一个 Token常用 JWT前端把 Token 存在本地每次请求都在 Header 里带上后端通过拦截器校验 Token 并解析出用户身份和角色。JWT 的好处是无状态——后端不用存 session分布式部署时也能直接用。但这里有一个注意事项JWT 一旦签发在有效期内无法主动作废。如果学生的账号密码泄露或者学生毕业离校需要立刻禁用账号只能等 Token 自然过期。所以这套系统里通常会把 Token 的有效期设得比较短比如 2 小时配合前端在 401 响应时自动跳回登录页重新登录算是一种折中方案。权限校验就要落到后端拦截器或 AOP 上了。核心思路是自定义注解RequireRole(TEACHER)标注在需要特定角色的接口上登录拦截器先判断 Token 是否有效权限校验器再检查当前用户角色是否满足注解要求。这样学生直接调“删除申报记录”的接口也会因为角色不符被后端拒绝而不是仅仅靠前端藏起那个删除按钮。3.2 Vue 前端的工程化组织Vue 前端部分这套源码的组织方式是标准的 Vue CLI 或 Vite 工程结构不用毕业设计那种一个.vue文件里手写全局组件的方式。值得关注的是这几个模块的分工Vue Router 动态路由。管理员的菜单和学生看到的菜单不同实现方式是用户登录后后端返回当前用户的角色和可访问的路由表前端用router.addRoutes()动态添加。这样前端路由表不是写死的而是跟随用户权限实时生成。实现上要注意防止刷新页面后路由丢失——通常会把路由信息存到 Vuex 和 localStorage 里刷新时重新拉取。Axios 封装与拦截器。所有前后端交互都通过 Axios 实例统一收发。请求拦截器里自动附加 Token响应拦截器里统一处理 HTTP 401Token 过期跳登录页、业务错误码提示、文件流下载等。这样每个页面不需要重复处理异常逻辑几十行代码省掉后面几十处重复劳动。组件复用。学生信息选择器、奖项下拉框、分页表格、状态标签、审核弹窗这些跨页面复用的组件全部抽到了components/目录下。比如“审核弹窗”接收一个applicationId和当前状态提交时调用审核接口父组件刷新列表这个组件在辅导员端和校级管理员端都能用。3.3 服务端分层Controller、Service、Mapper 各自的本分我拆源码时一定会看的一个点是业务逻辑到底写在哪一层。大部分毕业设计的通病是 Controller 里堆几百行业务代码Service 层形同虚设。这套源码的分层是规范的Controller 层只做参数接收、基础校验、调用 Service、返回结果。一个方法通常撑死十几行。Service 层业务核心。状态流转判断、事务边界控制、业务异常处理都在这层。比如申报时的核心方法applyForAward()先检查该学生是否重复申报、再检查当前时间是否在申报期内、然后插入申报记录、同时更新该奖项的已申报名额这个组合逻辑全部在 Service 里完成且加上了Transactional事务注解。Mapper 层只负责 SQL 和数据库交互不写业务判断。为什么要坚持这个分层因为它决定了系统能不能持续升级。这学期只评奖学金下学季加上“评优评先”改动应该只需要加表和加接口而不是把原有的三层全拆了重写。3.4 评分规则的配置化实现如果评奖评优系统里含有“综合评分”环节那评分规则的配置化设计就很值得学习。不把它写死在代码里的做法是把评分项和权重放在一张配置表里管理员在系统里可以直接调整百分比。评分计算的伪逻辑如下public BigDecimal calculateScore(Application application) { ListScoreItem items scoreConfigService.getItemsByAwardType(application.getAwardTypeId()); BigDecimal total BigDecimal.ZERO; for (ScoreItem item : items) { BigDecimal value getScoreValue(application, item.getFieldName()); total total.add(value.multiply(item.getWeight())); } return total; }这套方案的关键价值在于业务人员自己就能调规则。比如今年学校强调“创新创业加分”就新建一个评分项权重设为 15%代码一行没动评选规则却变了。对学生来讲每一项得分也都有明细可查评审过程更透明。4. 环境搭建与部署实操从源码到跑通的完整过程4.1 本地开发环境准备拿到源码后第一件事是搭建能跑起来的本地环境。需要准备的工具和版本大致如下工具版本建议说明JDK1.8 或 11SpringBoot 2.x 系列对 JDK 版本要求不高1.8 足够稳Maven3.6项目管理与依赖下载Node.js14Vue 前端构建环境MySQL5.7 或 8.05.7 是经典稳定版8.0 功能更新都兼容IDEIDEA 或 VSCode后端推荐 IDEA前端 VSCode 够用需要特别提醒一个常见的坑Node 版本不要盲目追新。如果 Vue 项目用的是 2.x 版本Node 18 以上可能在安装依赖或执行构建时出现兼容问题。建议先用node -v看下当前版本如果太高用nvm切到 14 或 16 再跑前端。4.2 后端启动步骤详解后端启动的完整流程是第一步创建数据库award_system字符集选utf8mb4。这一点别用默认的latin1否则存中文和 Emoji 都会产生乱码问题。第二步导入数据库脚本sql/award_system.sql。执行成功后检查一下核心表是否都有数据尤其是sys_user表里的初始管理员账号和密码。第三步打开application.yml修改数据库连接配置spring: datasource: url: jdbc:mysql://localhost:3306/award_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_passwordserverTimezoneAsia/Shanghai是必须加的否则高版本 MySQL 驱动会因为时区问题直接报错。useSSLfalse是本地开发时关掉 SSL 加密省去证书配置的麻烦。第四步用 IDEA 打开后端项目等待 Maven 自动下载依赖。这一步如果网络状况不好可能在下载 SpringBoot 相关 jar 包时失败。建议在 Maven 配置文件里配置阿里云镜像仓库下载速度会快很多。第五步运行启动类AwardApplication.java。看到日志输出Tomcat started on port(s): 8080就是启动成功了。这时候可以用接口测试工具先调一下登录接口验证后端是否正常。4.3 前端启动与联调配置前端启动相对简单但在联调阶段有一个关键配置必须处理跨域问题。前端的开发服务器跑在 8080 端口Vue CLI 默认端口后端的接口跑在 9090 或 8080两者端口不同浏览器会拦截跨域请求。常用的解决方案是开发环境下配置 Vue CLI 的代理// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:9090, changeOrigin: true } } } }这样一来前端请求/api/login时开发服务器会把请求转发到后端的9090端口浏览器感知不到跨域问题自然消失。如果后端也配置了全局跨域过滤器双重方案并存也没问题。启动前端命令是npm install npm run devnpm install装依赖时容易卡在某个包上常见原因还是网络问题。解决方案是切换到国内 npm 镜像安装速度会提升明显。4.4 前后端联调的排查思路前后端已经分别启动但页面就是调不通接口时我一般按这个顺序排查先看后端日志。如果登录接口返回 500大概率是数据库连接失败或 SQL 语句有误。SpringBoot 的日志会打印完整的异常堆栈照着提示改就行。用接口测试工具直接测后端接口。绕过前端直接用 Postman 之类的工具请求后端接口。如果后端接口正常返回数据问题就锁定在前端如果后端本身报错那就先处理后端。看浏览器 Network 面板。打开 DevTools 的 Network 页签点击“登录”按钮看请求是否发出、状态码是多少、响应体里写了什么。这一步能快速区分是请求没发出去、跨域被拦截、还是后端正常返回了但前端渲染出错。检查前端控制台是否有报错。很多时候前端报错是undefined读取某个属性——通常是接口返回的数据结构和前端期望的不一致字段名对不上之类的问题。这种就对照后端返回的 JSON 和前端定义的模型逐字段核对。5. 二次开发指南往这套系统里加新功能5.1 新增一个奖项类型要改哪些文件如果你理解了这套架构任何新增需求都不会慌。比如学校要新增“校长特别奖学金”需要动的地方非常明确数据库往award_type表插入一条记录配置好奖项名称、名额、奖金金额、申请条件说明。后端一般不需要改代码——因为奖项配置已经做成了数据字典。如果这个新奖项有特殊审批流程比如需要校领导单独终审那才需要在 Service 层加一条分支逻辑。前端如果奖项下拉框的数据是动态加载的同样不需要改代码刷新页面就会出现新选项。这就是数据驱动设计的好处——配置优先代码兜底。5.2 增加一个“班主任评审”环节的改造路径这个改动会稍微复杂因为涉及审批流的变更。可以理解为原来只有“辅导员初审 → 校级终审”两个节点现在变成了“班主任初审 → 辅导员二审 → 校级终审”三个节点。改造方案有两种取决于系统目前的设计方案一如果审批流是代码写死的比如 Service 层里硬编码了状态顺序那就需要修改审批逻辑把状态过渡链拉长新增一个CLASS_TEACHER_REVIEW状态的判断。动作在同层代码里新增一个方法并调整前端的按钮显示逻辑。方案二如果审批流已经配置化了那只需要在配置表里新增一个节点指定审批角色和顺序即可。这是更优雅的方案但要求系统在设计之初就预留了工作流配置表。如果源码支持的话这也是最省事的路径。从这两个方案可以看出设计之初的决策决定了后面改动的成本。这也是我核对源码时重点看的东西审批流是写死的还是有配置化能力。5.3 数据导入导出功能的经验评奖评优系统绕不开 Excel 导入导出。期末评定时管理员手里经常有一份教务处导出的学生成绩 Excel需要批量导入系统评选结束后又要导出最终名单公示。实操中最容易踩的坑是 Excel 解析的兼容性问题。我之前处理过一个项目学生成绩单里的“专业排名”字段格式不一有的填“3/120”有的填“3”还有的填“前 2.5%”。这就要在导入时写一个格式化器把各种形式统一成数值类型保存。导出功能相对简单用 Apache POI 或 EasyExcel 生成 Excel 后通过接口把文件流传给前端下载。注意文件名的编码问题——直接用中文文件名容易出现乱码或下载失败习惯做法是将文件名进行 URL 编码String fileName URLEncoder.encode(2024年国家奖学金名单.xlsx, UTF-8); response.setHeader(Content-Disposition, attachment; filename\ fileName \);5.4 学习这套源码的推荐路线如果你是初学者或者刚接触这套源码建议不要按目录顺序从头看到尾而是按我给顺序来先跑通系统把“学生申报 → 辅导员审核 → 管理员终审”完整走一遍对业务有体感。看数据库的sql脚本把每张表的字段含义和表间关系梳理成一张思维导图。找一个核心功能点比如“学生提交申报”从前端页面出发追到接口、Service、Mapper完整走完一条链路。画一遍状态流转图理清各个状态之间允许哪些转换。最后再去看权限、日志、统计这些横切关注点。这个顺序的核心逻辑是先建立整体认识再深入单点闭环最后覆盖横切能力。一步步来系统就不像一团迷雾而是一条条清晰的线。6. 关键功能实现实录申报、审核、统计一条龙6.1 学生申报功能的完整链路这里拆一个最核心的场景学生提交奖学金申请。从前端点击“申报”按钮到数据落库到底发生了什么。前端页面是一个表单包含奖项类型、申请理由、综合测评成绩、附件上传等字段。学生填写完毕点击“提交”事件监听函数先做一个前端校验比如必填项不能为空然后调用封装好的 Axios 请求this.$http.post(/api/application/submit, this.formData).then(res { if (res.data.code 200) { this.$message.success(申报成功); this.fetchApplicationList(); } });后端 Controller 接收请求后调用 Service 层的submitApplication方法。这个方法内部的逻辑依次是检查申报时间是否在评选计划的有效期内检查当前用户是否是学生身份检查该学生对这个奖项是否已经申报过防止重复提交插入申报记录设置初始状态为SUBMITTED给辅导员生成一条待办提醒通常是插入到待办表里。这些操作被Transactional注解包裹意味着如果中途任何一步抛出异常之前的数据库操作都会回滚。比如插入申报记录成功但生成待办提醒失败了整个申报操作都会被撤销不会出现“学生显示已申报但辅导员那边看不见”的数据不一致情况。6.2 审核操作的并发控制审核操作有一个容易忽略的并发问题两个辅导员同时打开同一个学生的申请一个人点了“通过”另一个人点了“退回”。如果不加控制后提交的操作就会覆盖前面的结果但审批记录表里会残留两条互相矛盾的历史记录。避免这个问题有两种常用手段乐观锁方案在申报表里加一个version字段。更新前先查出version值更新时 SQL 里带上WHERE version #{oldVersion}如果影响行数为 0说明其他线程已经改过这条数据了需要重新加载后再操作。MyBatis 执行这条更新语句时天然支持这种写法也不会有明显性能损耗。状态机强校验方案审核接口在执行前检查当前状态是否等于可审核状态。如果第一条请求已经把状态从DEPARTMENT_REVIEW改成了APPROVED第二条请求到达时发现状态不匹配直接抛出“该申请已审核”的异常。这套源码采用的方案通常是两者结合核心思想是不容忍并发下的静默覆盖。数据在评审阶段出错解释成本极高宁可少一点并发便利也要保住数据正确。6.3 统计面板如何支撑决策管理后台的统计面板不是花架子而是真正帮管理员减少工作量的。典型的统计需求有申报进度监控。评选计划发布后管理员需要知道各个院系的申报进度。这个看板上会显示总申报人数、待院系审核数量、待校级审核数量、已通过数量、被驳回数量。每项数字背后对应一条带状态条件的 COUNT 查询刷新数据时一起更新。覆盖率和获奖分布分析。评选结束后管理员关心的是奖项分配是否合理。比如“国家奖学金”名额是 50 个实际申报 300 人通过率 16.7%。这个数字可以用当前申报状态实时算出来也可以归档后存到一张统计表里供下一年做对比分析。学生成绩与获奖的交叉分析。更高阶的统计是判断“哪些学生拿奖后成绩持续突出哪些奖项的筛选效率更高”这种问题。这类分析通常需要连表查询多个学期的成绩表和获奖记录表SQL 写起来比较复杂但对于学校制定奖学金策略有很实际的参考价值。7. 部署上线与运维避坑真正跑起来才是本事7.1 服务器部署的三种方式本地跑通了不算完部署到服务器上才是真正交付。三种主流方式各有适应场景方式一前后端分离部署。后端打 jar 包直接运行前端构建出静态文件后部署到 Nginx。这是最推荐的方案。Nginx 同时承担静态文件服务和反向代理前端请求/api时自动转发到后端的 8080 端口。优点是前后端可分别升级、可单独排查故障负载能力和灵活性都更好。方式二前后端打包在一起。把前端npm run build生成的dist目录拷贝到 SpringBoot 项目的src/main/resources/static下重新打 jar 包。好处是只需要维护一个进程一个端口部署更简单适合校内的低并发场景。缺点是一旦前端改了整个后端都要重新打包发布迭代效率偏低。方式三Docker 容器化部署。编写Dockerfile把后端打成镜像用docker-compose.yml编排后端和 MySQL 容器。好处是环境隔离、迁移方便换服务器时docker-compose up -d一下全部搞定。对于有 Docker 环境的学校机房这已经是最省心的方案了。7.2 部署时常见的五个坑及对策端口被占用。默认 8080 端口经常被其他服务占用。对策是用netstat -ano找出占用进程杀掉或换端口启动。启动命令可以携带--server.port9090不用改配置文件也能覆盖端口。MySQL 连接被拒。服务器上数据库没开远程访问权限或者防火墙没放行 3306 端口。对策是检查 MySQL 用户表中的host是否是%并确认安全组规则和防火墙策略是否放行端口。文件上传路径不存在。系统里的附件上传功能通常会上传到服务器本地某个目录比如/data/award-system/uploads。如果部署前没创建这个目录运行时会抛 IOException。对策是启动前检查所有配置的本地存储目录是否存在且有写权限。JVM 内存不足。如果你的服务器只有 2GB 内存后端 jar 包启动时默认申请最大堆内存可能直接压垮系统。对策是启动命令限制内存参数java -Xms256m -Xmx512m -jar award-system.jar日志不轮转导致磁盘爆满。评比季系统每天都有大量访问SpringBoot 默认的日志文件会越滚越大。对策是在application.yml里配置logback轮转策略按天归档并设置历史日志保留周期。7.3 上线后的性能优化要点评比系统虽然并发不高但有几个性能瓶颈值得提前预防申报截止日前的高峰并发。很多学生习惯在最后一晚提交申报此时数据库的瞬时压力会集中爆发。对策是给申报表的award_plan_id student_id建立联合唯一索引既防止重复申报又加快查询速度。前端可以加一个提交中的 loading 状态避免用户反复点击造成重复请求。慢查询排查。如果感觉系统变卡第一步不是加服务器配置而是打开 MySQL 慢查询日志找出执行时间超过 1 秒的 SQL。大部分问题出在没有建索引的关联查询上解决了索引问题性能往往直接翻倍。静态资源缓存。前端打包后的 JS、CSS 文件名自带 hash可以放心在 Nginx 中配置强缓存location /static/ { expires 30d; add_header Cache-Control public, immutable; }这样用户二次访问时不需要重复下载静态资源页面的加载速度会有明显提升。8. 常见问题与排查技巧实录8.1 前端页面打不开但后端接口正常这类现象很典型用接口测试工具直接调后端接口能拿到数据但浏览器里访问前端页面就是白屏或显示网络错误。优先排查以下几处前端开发服务器是否正常启动并监听在预期端口npm run dev输出的地址是否正确。如果浏览器开的是localhost:8080但前端实际跑在127.0.0.1:8080有时也会出现奇怪的访问问题。代理配置是否生效。如果请求/api返回 404很可能是代理没配对请求直接发到了前端的 8080 端口而不是被转发到后端。检查vue.config.js中的proxy目标和后端实际端口是否一致。浏览器缓存问题。前端代码改了但只要公共依赖没变浏览器可能继续用旧的缓存文件。开发阶段经常用“无痕窗口”来规避缓存干扰。8.2 登录后页面一直转圈登录接口返回了 Token前端的请求也携带了 Token但页面数据一直加载不出来。我的排查经验是先看控制台报什么错——最常见的是 401 无权限。这个问题的层级很深Token 本身可能有效但后端的权限校验器发现当前用户缺少访问该接口所需的角色。比如学生登录后被要求访问管理员的接口就算 Token 没问题权限校验也会拒绝。另一种情况是 Token 存取的 key 不一致前端存的时候用的localStorage.setItem(token)取的时候可能因为代码里拼错了 key 拿到null。这种问题不细看很难发现但控制台里一般会打出“request failed with status code 401”之类的提示。8.3 图片上传成功但无法显示评审材料里学生会上传获奖证书照片上传环节没报错但图片在页面上显示不出来这是上传类系统的经典问题。最常见的原因是上传到的本地磁盘目录和访问时的 URL 路径不对应。后端把文件存到了/data/uploads/file.jpg但访问地址写的是http://localhost:8080/uploads/file.jpg如果没有配置静态资源映射这个地址在服务器上找不到对应文件。解决方式是在 SpringBoot 里加一个静态资源映射配置把/uploads/**的访问映射到本地磁盘目录Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceHandler(file:/data/uploads/); } }还有一个隐蔽问题是 Windows 和 Linux 的文件路径分隔符差异代码里写死了\在 Linux 上就会报错建议统一用File.separator或/拼接路径。8.4 页面有数据但表格不显示后端接口返回正常响应体里 JSON 结构也正确但前端表格是空的。优先检查字段名是否匹配。很多坑来源于命名规范不一致后端的实体字段如果是snake_case比如studentName对应数据库列名student_name而前端期望的是studentName经过某些序列化配置后字段名可能被转成了其他形式。解决方式是在后端实体上加JsonProperty(studentName)或者在配置里统一设置驼峰命名映射。另外检查一下表格组件的绑定字段是否是data而不是list。很多后端接口包装成统一响应格式后数据放在data字段里但前端组件取数时还停留在res.data.list的值里一个字段没对上整张表格就会空掉。9. 这套源码还能往哪些方向扩展评奖评优系统的价值不只在评奖评优本身它的核心骨架——用户权限、申报审批、状态流转、数据统计——完全可以复用到学校或者企业的很多其他场景。一个直接的扩展方向是评优之外的各类审批系统。比如学生请假系统、勤工助学申请系统、社团活动经费申报系统、实验室设备借用系统。这些系统的核心流程都一样提交申请 → 逐级审批 → 状态跟踪 → 结果通知。换掉业务字段复用权限和审批模块基本就能快速搭出新系统。另一个扩展方向是引入消息通知机制。当前系统里的“待办”可能只是管理后台的一个数字角标如果接入微信服务号或者邮件通知学生提交申报后辅导员能第一时间收到提醒审核结果也能自动推送给学生。这种体验提升对系统的实际使用率有明显帮助。还有一个方向是对接学校统一身份认证。很多高校已经上线了统一身份认证平台学生和教师使用校园账号就能登录所有系统。把当前系统的密码登录改造成 OAuth2 或 CAS 的单点登录对接管理员不用再维护一套独立的账号体系安全性和便捷性都上一个台阶。我个人实际操作中的体会是这套系统既然已经搭好了地基就不要只把它当成“学生评奖评优”这一个点来用。把权限体系、审批流程、数据统计这些通用能力抽出来后续不管接什么业务场景都会发现当初做的基础设计完全够用。真正做二次开发的人赚到的往往不是第一套系统本身而是这套骨架带来的复制能力。
返回列表