资讯动态

从零搭建前后端分离的高校办公室行政管理系统:SpringBoot+Vue实战记录

发布时间:2026/10/22 10:10:30 来源:尧图企业网站定制
把一套前后端分离的高校办公室行政事务管理系统从零搭起来再完整部署上线整个过程我前前后后做了不下三回。这套系统用的技术组合很老实后端SpringBootMyBatisMySQL前端Vue整体遵循MVC分层思路。今天这篇就是完整记录从技术选型到表结构设计从核心接口到Nginx部署顺便把我在开发中踩过的坑一起写在里面适合正在做课设、毕设或者准备接手类似管理系统项目的同学参考。如果只挑一句话来说这套系统的价值在于把“行政事务”从纸质审批和Excel表格里解放出来。公告、会议室预约、用章申请、公务用车、值班安排这些场景以前靠人工登记现在全部在线流转权限清楚、记录可查。对刚接触前后端分离项目的人来说它又是一个信息量非常合适的样本不算特别复杂但MVC分层、RBAC权限、JWT认证、接口对接、前后端打包部署这些关键环节一个不少照着源码走一遍基本上能把主流的开发流程打通。1. 项目定位与整体设计思路1.1 为什么选择前后端分离架构我最早接触这类管理系统时用的是单体模板引擎页面是服务端渲染改一个按钮样式都得重启服务加接口还要同时碰页面代码。后来接手一套前后端分离的代码才发现两边解耦之后舒服很多前端只管渲染和交互后端只管出数据只要接口约定好两边可以同时开工。特别是做高校办公室这类内部系统需求经常变前端改个表单不用动后端后端调整审批逻辑也不影响页面迭代速度快很多。有人会觉得前后端分离增加了部署成本确实算上Nginx反向代理、跨域配置、构建流程前期要比单体项目麻烦一点。但它换来的是结构清爽和后续扩展的可能性比如未来要加移动端H5、小程序后端接口可以直接复用不需要重新做一套。对于毕设和课设来说前后端分离这种架构也比较容易在答辩时展开讲MVC的思想在后端三层里体现得很清楚前端配合Vue组件化整个项目一眼就能看出分层关系。1.2 角色与业务模块怎么切系统设计之初我把用户分成三类系统管理员、办公室工作人员、部门负责人。管理员维护基础数据和用户权限办公室工作人员负责日常业务录入和发起申请部门负责人做审批确认。业务模块围绕高校办公室的高频行政场景来设计包括公告管理、会议室预约、用章申请、公务用车申请、值班排班、文件收发登记。每个模块都离不开一个核心状态待提交、待审批、已通过、已驳回、已归档。这个状态字段是整套系统的命脉设计的时候我统一命名为status后端用整数表示前端通过字典翻译成标签。比如用章申请工作人员填完申请表之后status是0负责人看到待审批列表点击通过后变成1驳回则变成2归档后变成3。状态统一之后前端可以复用同一套流程标签组件后端也只需要约定几个常量开发效率提升非常明显。角色主要权限对应模块管理员用户管理、角色分配、数据字典、系统日志系统管理工作人员发布公告、发起申请、登记文件、维护日程行政事务部门负责人审批各类申请、导出报表、查看统计审批中心还有一个设计重点审批链路不能写死。项目初期我只做了一层审批结果需求方反馈有些流程要二级审批有些只要登记备案不需要审核。后来我把流程类型抽成了一个配置字段申请记录里保存当前步骤和流程标识每次流转只处理“当前节点”该做的事。这个调整在数据库层面只是多了两列却让系统适用范围扩大了不少会议室预约可以是一级审批用章申请可以变成两级审批增加流程时不用改太多代码。2. 技术选型与项目结构2.1 后端为什么用SpringBootMyBatis后端选择SpringBoot看中的是它的低配置启动和内嵌Tomcat一个jar包就能跑起来部署时不需要单独装Tomcat。MyBatis则是因为我对SQL有很强的掌控欲尤其在多表关联、条件动态拼接、分页统计这些场景写XML比用JPA隐式生成的SQL要直观得多。项目里经常出现“按日期范围、申请类型、当前状态三个条件组合查询”的情况MyBatis的动态SQL用if标签就能轻松处理而JPA在这种场景下要么写复杂的Specification要么回退到原生SQL反而绕。MyBatis还有一个优势是方便SQL调优。曾经有一个统计页面加载很慢我用MyBatis打印出来的SQL去EXPLAIN发现一张表缺索引加完索引响应时间从两秒降到几十毫秒。这种排查过程在JPA自动生成SQL时就没有那么直观。如果项目里的查询比较简单用Spring Data JPA也不是不行但这套系统里有大量按状态筛选和关联统计需求MyBatis显然更合适。2.2 前端为什么用Vue Element UI前端选Vue是因为它的组件化开发和渐进式引入方式很适合后台管理系统。Element UI提供了现成的表格、表单、弹窗、分页、日期选择器后台页面长得都差不多用组件拼装比从零写DOM高效太多。我在这套系统里把所有列表页抽象成一套通用模板顶部搜索区、中间表格区、底部分页区、右侧操作按钮再加一个表单弹窗。每个业务模块只需要提供字段配置渲染逻辑基本复用。请求层用Axios统一封装我把POST、GET、上传、下载都包了一遍所有请求自动带上token后端返回非200状态码时统一弹错误提示。路由守卫也踩过坑最初只在菜单上做了权限过滤结果有人直接输入URL就能访问没权限的页面后来在路由守卫里增加了动态权限判断没有权限的路径统一跳转到403页面。这一套组合下来新增一个模块的工作量大概只有后端的三分之一。2.3 项目目录结构与分层规范后端采用标准的Controller-Service-Mapper三层结构额外加一层DTO用于接口参数传递避免直接用实体类接收前端字段。Controller层只做参数接收和结果包装Service层处理业务逻辑和事务边界Mapper层负责SQL读写。这样的好处是职责单一后续接单元测试也方便替换实现不会影响上层接口。office-admin ├── src/main/java/com/office/admin │ ├── controller # 接口层 │ ├── service # 业务层 │ ├── mapper # MyBatis Mapper接口 │ ├── entity # 数据库实体 │ ├── dto # 前端交互对象 │ ├── config # 安全、跨域、MyBatis配置 │ ├── common # 结果封装、常量、异常处理 │ └── utils # JWT、日期等工具类 ├── src/main/resources │ ├── mapper # Mapper XML │ ├── application.yml # 主配置 │ └── application-prod.yml └── sql └── office_admin.sql # 建库建表与初始化数据前端目录也同样做了分层src下面按照api、router、store、views、utils、layout这些职责划分。api层集中放接口定义views每个模块一个文件夹store只放用户信息和权限菜单这种全局状态不把所有数据都塞到Vuex里避免刷新后状态丢失。这个结构看起来简单但维护起来非常舒服新成员接手时能很快找到代码位置。3. 数据库设计与核心业务实现3.1 数据表设计与关键字段数据库的命名我统一用sys_开头表示系统管理表biz_开头表示业务表。系统管理部分是一套经典的RBAC模型sys_user用户表、sys_role角色表、sys_user_role用户角色关联表、sys_menu菜单表、sys_role_menu角色菜单关联表。业务部分包括biz_notice公告表、biz_meeting_reservation会议室预约表、biz_seal_apply用章申请表、biz_car_apply公务用车申请表、biz_document文件收发登记表、biz_flow_record流程记录表。每张表里都加了create_by、create_time、update_by、update_time、deleted这几个通用字段。deleted用逻辑删除而不是物理删除是因为行政记录经常要追溯历史物理删了数据就彻底没了。逻辑删除用0和1区分所有查询默认过滤deleted0再由MyBatis拦截器统一处理减少漏写的概率。以用章申请表为例核心字段包括申请人ID、申请事由、印章类型、使用日期、文件份数、当前审批节点、状态、备注。审批节点current_node很重要因为允许二级审批后同一个表里要记录当前轮到谁审前端才能正确显示按钮。流程记录表biz_flow_record则把每一次审批动作都保存下来包括操作人、操作意见、操作结果、时间这样即使状态被改错也能从记录表里还原整个完整的历史链。3.2 登录认证与权限控制这样实现登录流程用的是JWT而不是传统Session。原因很简单前后端分离之后后端接口可能被多个客户端调用Session在跨域和分布式环境下都不好处理。用户登录成功后后端用一个密钥把用户ID、用户名、角色信息生成token返回前端把它存到localStorage后续每个请求在请求头里带上Authorization字段。后端有个拦截器统一从请求头解析token解析成功后把用户信息放到ThreadLocal里Controller和Service随时能拿到当前用户。这里有一个非常重要的安全细节密码不能用MD5直接存。MD5虽然快但撞库太容易了现在网上随便一查都有海量MD5彩虹表。项目里用BCrypt加密每次校验的时候用BCrypt的check方法同一个密码每次加密结果都不一样数据库泄露了也不容易反向破解。JWT的密钥我放在配置文件里部署时通过环境变量覆盖绝对不写死在代码里。权限校验除了登录拦截还有一个按钮级权限问题。前端根据角色动态生成菜单但后端接口也必须校验权限。管理员接口、审批接口、普通查询接口都加了权限标识比如审批用章申请需要hasPermi(seal:approve)。即使有人绕过前端直接调接口后端没有权限直接拒绝。这个思路虽然简单但能挡住大多数越权问题。3.3 会议室预约接口与前端配合实例拿会议室预约这个模块说一下前后端怎么配合。前端预约页面包含会议室下拉框、日期、开始时间、结束时间、事由、参与人数提交时向后端发送POST请求。后端Controller先把DTO校验一遍再交给Service保存同时检查这个会议室在当前时间段是否已经被占用。如果冲突直接抛出业务异常前端弹窗提示“该时间段已被预约”。关键接口大致是这样PostMapping(/reservation) public Result add(RequestBody Valid MeetingReservationDTO dto) { meetingReservationService.addReservation(dto); return Result.success(); }Service里第一件事是查重SQL用重叠条件判断新预约开始时间小于已有预约结束时间且新预约结束时间大于已有预约开始时间。这比单纯比较“开始时间是否重复”要准确得多。查重通过后再查一次会议室状态然后插入预约记录状态设为待审批。审批接口则校验当前操作人是否有权限再把状态改成已通过或已驳回同时写入一条流程记录。前端这边Axios封装后的调用大概长这样export function createReservation(data) { return request({ url: /reservation, method: post, data }) }roomOptions从会议室字典接口拉取提交按钮loading状态防止重复点击提交成功后刷新列表。列表页的审批按钮根据当前状态动态渲染状态为待审批时显示通过和驳回已通过时显示归档已驳回时不显示可操作按钮。这样的交互逻辑看着不复杂但真正做好状态和按钮的联动需要前后端一起约定枚举值和字段名否则很容易出现类型对不上、状态差一位的bug。4. 完整部署流程与上线配置4.1 环境准备与版本建议部署之前先把环境列清楚。后端需要JDK1.8或11、Maven3.6以上、MySQL5.7以上前端需要Node.js 14或16生产服务器一般还要装Nginx。如果你用的是云服务器建议先开一台Linux机器CentOS或者Ubuntu都行内存至少2G因为后端jar包加MySQL容易把1G内存吃满。依赖版本建议备注JDK1.8 / 11推荐使用供应商的OpenJDK发行版Maven3.6打包后端jarNode.js14 / 16构建前端资源MySQL5.7 / 8.0数据库使用utf8mb4Nginx1.18静态资源与反向代理数据库初始化我习惯用命令行导入。把项目里的sql/office_admin.sql上传到服务器然后执行source命令。建库语句里要指定utf8mb4字符集默认排序规则用utf8mb4_general_ci或者utf8mb4_unicode_ci都可以。初始化数据除了表结构之外我还会塞一个默认管理员账号和基础菜单方便部署完成后直接登录验证。4.2 后端打包与启动后端在打包之前要改两个配置数据库连接地址和JWT密钥。本地开发连localhost服务器上要改成实际数据库地址。如果MySQL和jar包在同一台服务器用127.0.0.1就行。JWT密钥需要设一个足够复杂的随机字符串长度为32字节以上别用默认值上线。application-prod.yml里可以用环境变量引用的方式来配置例如spring: datasource: url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: ${DB_USER} password: ${DB_PASSWORD}打包命令很简单在项目根目录执行mvn clean package -DskipTests如果Maven下载依赖很慢可以配置国内镜像这个属于常规加速手段。打包完成后target目录下会生成office-admin.jar用nohup命令后台启动nohup java -jar office-admin.jar --spring.profiles.activeprod app.log 21 启动之后不要急着测接口先看日志。日志出现Started Application并且没有报错再用curl测一下健康检查接口比如curl http://127.0.0.1:8080/api/health。返回正常之后再接Nginx。4.3 前端构建与Nginx配置前端构建项目根目录下执行npm install npm run build构建完成后生成dist目录把dist目录整个上传到服务器比如放在/opt/office/dist。Nginx配置的核心是两点一是静态资源指向dist目录二是把API请求反向代理到后端8080端口。如果前端路由用history模式还要加上try_files配置否则刷新页面会出现404。一个可以参考的server块配置如下server { listen 80; server_name your-domain-or-ip; root /opt/office/dist; 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; } }修改完配置用nginx -t检查语法然后nginx -s reload生效。这里要特别注意proxy_pass后面有没有斜杠。如果写成proxy_pass http://127.0.0.1:8080;请求路径会原样转发比如/api/login会到后端的/api/login这是对的。如果写成http://127.0.0.1:8080/;/api/前缀会被去掉变成/login前后端对不上就会404。这个细节我踩过一次花了不少时间才定位。4.4 上线自检清单部署完成之后我建议按下面的顺序做一遍自检避免遗漏关键环节。检查防火墙是否放行80端口云服务器安全组也要同步配置。后端日志无异常数据库连接正常。浏览器访问域名或IP能看到登录页。使用默认管理员账号登录验证菜单是否正常加载。发起一条用章申请走一遍审批流程确认状态流转正确。刷新前端页面确认不会出现404。修改默认管理员密码并确认JWT密钥已经替换。验证上传文件的目录是否有写权限如果系统包含文件上传功能。这八项如果全部通过基本可以宣布上线。线上运行之后还会遇到各种零碎问题比如定时任务没触发、日志把磁盘写满、数据库连接池爆掉这些问题在下一条会专门展开。5. 常见问题与排查技巧实录5.1 跨域与代理配置开发环境下前端跑在8080后端跑在8081端口不一样必然产生跨域。解决办法有两种一种是在后端配置CORS另一种是在前端开发服务器配置代理。我用的是代理方式因为CORS在生产环境通常用不上Nginx已经做了同源代理。前端在vite.config.js或vue.config.js里配置module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端请求/api/login时开发服务器会把它转发到后端浏览器看到的请求地址还是前端自己的地址不会触发跨域。生产环境只要按照上一节的Nginx配置反向代理跨域问题也自然解决。有时候后端接口已经通了但前端报跨域要优先检查是不是代理配置没生效特别是改了配置之后忘了重启前端开发服务。5.2 MySQL连接时区与中文乱码MySQL经常出现的两个恶心问题都是连接串参数引起的。第一个是时区问题报错说The server time zone value解决办法是在数据库连接URL后面加serverTimezoneAsia/Shanghai。第二个是中文乱码页面提交的中文保存到数据库变成问号八成是连接URL少了characterEncodingutf8或者建表时字符集不是utf8mb4。我的统一连接串是jdbc:mysql://127.0.0.1:3306/office_admin?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse注意useSSL参数也要显式写清楚MySQL8.0在某些环境下默认尝试SSL连接没有配置证书会一直握手超时。另外建库的时候执行CREATE DATABASE office_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;表里的varchar字段也尽量统一utf8mb4这样表情符号、中文生僻字都能正常存不会出现部分字符变成问号的情况。5.3 页面刷新404与静态资源路径前端用Vue Router的history模式后直接访问http://域名/meeting/list会请求服务器上的真实路径Nginx找不到这个文件就返回404。解决办法就是配置try_files把所有非文件路径的请求都重写到index.html由前端路由接管location / { try_files $uri $uri/ /index.html; }配置完之后刷新页面、收藏夹直接打开深层链接都不会再404。还有一个相关问题是静态资源加载失败如果页面能打开但js/css文件报404多半是打包时的publicPath配置不对。资源部署在域名根路径时publicPath设置为/如果部署在子路径比如/office/就要设置成/office/。这个路径和Nginx的root、location要完全对应否则页面白屏。5.4 部署后接口返回异常排查顺序我遇到过一种情况本地联调正常部署到服务器之后部分接口报500。遇到这种问题先别急着改代码按顺序做第一步看后端日志日志里一般会有异常堆栈第二步确认数据库连接是否正常特别是数据库地址是否写成了localhost但应用在容器里第三步用Postman或curl直接调后端接口绕过Nginx定位是哪一层出的问题第四步如果接口偶尔超时看数据库慢查询日志和连接池配置。还有一个经验是上线前把MyBatis的SQL日志打开生产环境调试完再关掉。在application-prod.yml里配置logging: level: com.office.admin.mapper: DEBUG这样每个SQL都会打印出来接口报错时能看到实际执行的SQL和参数排查效率提升不少。等确认稳定了再把日志级别调回INFO避免过多的日志刷盘影响性能。6. 开发到上线的实战经验与扩展方向6.1 几个容易翻车的细节写这套系统的时候我在几个地方栽过跟头总结出来给后面的人提个醒。第一个是事务失效只在Controller的方法上加Transactional是没用的Spring事务是通过代理对象生效的必须放在Service层而且同一个Service内部方法自调用也不会触发事务。整个申请流程里包含查重、插入预约记录、写入流程记录三个操作必须放在一个事务里任何一个失败都要整体回滚否则会出现会议室占用但预约单没生成的情况。第二个是MyBatis的实体字段映射不要依赖自动驼峰转换。数据库字段是create_time实体类是createTimeMyBatis的mapUnderscoreToCamelCase开启后可以自动映射但多表联查时如果有重名字段一定要用resultMap显式映射。我一开始图省事查询统计报表时发现某一个字段总是取不到值后来才发现是两张表都有一列叫nameMyBatis不知道取哪个换成带别名的resultMap就解决了。第三个是前端按钮权限不能只靠v-if。虽然菜单和按钮都按角色渲染了但后端接口必须再校验一遍。有人直接在浏览器控制台改掉localStorage里的角色然后调审批接口如果后端不加权限拦截就能越权操作。这个我做了双层校验之后才放心。第四个是日期字段不要用字符串拼接。数据库时间字段统一用datetime或timestampJava侧用LocalDateTime前端用日期组件处理。对接的时候传输用时间戳或标准格式字符串后端解析成LocalDateTime这样排序、比较、时区转换都不容易出错。用字符串存日期一开始看起来方便后面做范围查询和报表统计会非常痛苦。6.2 后续功能扩展建议系统上线之后如果还想要继续完善可以考虑几个方向。第一个是接入企业微信或钉钉机器人审批通过或者驳回时自动发送通知这个比邮件提醒更及时。实现上不需要改动现有业务表只要在流程记录写入后发一条MQ消息或者异步HTTP请求就行。第二个是引入工作流引擎比如Flowable或Activiti把现在手写的状态机替换成真正的流程定义。如果流程经常要动态调整工作流引擎会更省心但这会让项目复杂度上升不少小项目不一定划算。第三个是增加数据导出功能管理后台经常要把列表导出成Excel。集成EasyExcel在Controller加一个导出接口把查询结果分页流式写入内存占用控制得比较好。第四个是文件存储目前文件上传到本地磁盘如果服务器磁盘不够或者要做负载均衡需要换成对象存储或MinIO前端上传时先用后端签名拿到直传地址上传完成再通知后端记录文件信息。第五个是前端技术栈可以往Vue3和Vite上迁移组件库可以继续用Element Plus代码写法改动不算特别大但构建速度和生态会好一些。我个人在实际操作中的体会是这种管理系统的难点从来不在某个高深的算法而在于把状态流转、权限边界和部署细节想清楚。项目做完后把部署脚本和常用命令也整理进文档下次再接手类似系统会省掉很多沟通成本。如果你照着这篇流程走最后卡在某个报错上多半是环境变量或者路径问题先看日志再逐层排查基本都能解决。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑