Spring Boot Vue企业报销管理系统:从状态机到权限落地的Java毕设实战
发布时间:2026/10/1 10:34:04来源:尧图网络
简介这是一份面向Java毕业设计课题的完整资料包适合正在开发企业报销或办公自动化系统的学生。压缩包共206个文件整体约11.53MB包含17个Java源文件、109个class字节码文件以及论文、PPT模板、SQL Server数据库备份mdf/ldf等在具备Java环境时即可直接运行使用。系统采用C/S模式基于Java语言开发以SQL Server 2000为数据库涵盖个人工作、信息中心、日常工作、流转中心、维护中心五大模块涉及电话簿管理、公告发布、规章制度查询、办公用品申领、公文流转归档、组织机构与权限维护等完整办公流程可直观体现企业报销与行政管理的真实业务场景。目前已有46人浏览学习适合作为毕业设计选题参考或课程设计模板。通过源码可学习Java界面编程、数据库连接、角色权限设计等关键技能论文和PPT则有助于快速梳理设计思路减少撰写文档和答辩准备的时间成本。1. 企业报销管理系统这套毕业设计资源到底值不值得你动手改先说结论这套“Java毕业设计企业报销管理系统源代码论文PPT模板”的包本质上是把一个典型企业级 CRUD 业务场景——员工提交报销单、主管审批、财务打款、费用统计——用 Spring Boot Vue 前后端分离的方式完整落地再配上毕业设计最耗时间的论文和 PPT。对正在做计算机毕业设计、或者想快速启动一个能写进简历的 Java Web 项目的开发者来说它的价值不在“代码能跑”而在于“思路能抄、结构能扩展、论文能改”。我见过太多人拿到这类源码包后的两种极端反应要么原封不动提交结果答辩时被问一句“这个审批流程是怎么设计的”就卡壳要么干脆不用自己从零敲了半个月还卡在权限模型上。这个题目的正确打开方式是——把它当成一份高质量参考实现把核心流程拆出来读懂然后按自己的业务理解改造 23 个模块。这样论文有的写、答辩有的讲、代码也经得起追问。这套系统里最值得研究的不是增删改查而是报销状态机、多级审批逻辑、Excel 导入导出和权限控制这四件事。下面从选型到落地一步步拆。2. 技术选型与项目结构为什么这套方案能跑通毕业设计2.1 Spring Boot MyBatis Plus Vue毕业设计最稳的铁三角企业报销管理系统这个题目绝大多数现成方案都选 Spring Boot 作为后端框架。原因很直接Spring Boot 的自动配置能省掉大量 XML 配置一个spring-boot-starter-web依赖就能把 Web 容器、JSON 序列化、异常处理全部带起来对毕设周期来说是最短路径。前端选择 Vue 2 Element UI 或 Vue 3 Element Plus 的居多因为这套组合的组件生态最成熟表格、表单、弹窗、分页这类报销单管理页面需要的 UI 件基本拿来即用。MyBatis Plus 在这个项目里的地位也很关键。它比原生 MyBatis 多了BaseMapper通用 CRUD 接口写报销单、审批记录、用户的单表操作时连 SQL 都不用手写分页查询有内置的Page对象逻辑删除只需要在实体类字段上标TableLogic。对于毕业设计这种“要快速出功能、又要能讲清楚数据访问层原理”的项目MyBatis Plus 是最合适的中庸选择。JDBC 太原始、JPA 太黑盒子MyBatis Plus 既保留了 SQL 可控性又大量减少样板代码。项目整体分为三个部分拿到源码先按这个结构看不要一头扎进代码里。后端是标准的 controller-service-mapper 三层结构前端是 Vue 单页应用路由按登录、报销管理、审批中心、系统管理四个模块组织。另外还有一个sql目录存放初始化脚本一个docs目录放论文和 PPT 模板。理解这个分层之后你就能快速定位“改哪一层实现什么功能”。2.2 数据库设计五张核心表理清报销业务的主线报销管理系统的数据库是整个项目的骨架表设计好与坏直接影响论文数据模型章节的评分。一套合格的企业报销系统核心表至少包含用户表sys_user、报销单表reimburse、审批记录表approval_record、费用明细表reimburse_item、字典表sys_dict。这五张表的关系一句话能说清一个用户提交一张报销单报销单挂多个费用明细每次审批操作写一条审批记录。表名关键字段用途sys_userid, username, password, role_id, dept_id登录认证与权限判断reimburseid, user_id, amount, status, apply_time报销单主表status 字段控制流转reimburse_itemid, reimburse_id, type, amount, invoice_no报销明细交通/餐饮/住宿等分类approval_recordid, reimburse_id, approver_id, action, comment, create_time审批轨迹答辩常问sys_dictid, type, label, value报销类型、状态等可配置项注意reimburse表里的status字段它是报销状态机的物理载体。一般用整数或短字符串表示0-草稿、1-待主管审批、2-待财务审批、3-已打款、4-已驳回。这个字段设计得清晰审批流程的代码就好写如果这个字段语义混乱后面所有状态判断都会成灾难。设计数据表时有一个容易踩的坑金额字段千万别用float或double。报销金额涉及财务对账必须用decimal(10,2)Java 实体类用BigDecimal。MySQL 的 float 有精度损失答辩时老师如果问“为什么金额不用 float”这是一个加分回答点。另外所有的 create_time、update_time 字段不要省MyBatis Plus 的TableField(fill FieldFill.INSERT)可以自动填充审计字段在答辩中属于“工程规范”加分项。3. 核心业务流程实现报销状态机与多级审批是怎么落地成代码的3.1 用状态机模式写报销单提交接口从草稿到待审批报销状态流转是整个系统的灵魂。最差的实现方式是到处写if (status 1) { ... } else if (status 2) { ... }后期改需求会改到怀疑人生。常见做法是定义一个报销单状态枚举类把每个状态的合法流转条件封装进去这样“草稿能否提交”“已驳回能否重新提交”这种判断就集中在一个地方管理。public enum ReimburseStatus { DRAFT(0, 草稿, Arrays.asList(SUBMITTED)), SUBMITTED(1, 待主管审批, Arrays.asList(APPROVED, REJECTED)), APPROVED(2, 待财务审批, Arrays.asList(PAID, REJECTED)), PAID(3, 已打款, Collections.emptyList()), REJECTED(4, 已驳回, Arrays.asList(SUBMITTED, DRAFT)); private final Integer code; private final String description; private final ListReimburseStatus allowedTransitions; // 判断当前状态能否流转到目标状态 public boolean canTransitionTo(ReimburseStatus target) { return allowedTransitions.contains(target); } }提交报销单的 service 代码就变成了两层校验先校验当前状态是否允许提交再校验业务数据金额是否大于 0、是否有明细。第一层校验走枚举的canTransitionTo拒绝非法状态跳转比如已打款的单子不可能再回到待审批。第二层校验走正常的参数校验逻辑。Override public void submit(ReimburseSubmitRequest request) { Reimburse reimburse getById(request.getReimburseId()); // 状态校验只有草稿和已驳回的单子才能提交 ReimburseStatus current ReimburseStatus.fromCode(reimburse.getStatus()); if (!current.canTransitionTo(ReimburseStatus.SUBMITTED)) { throw new BizException(当前状态不允许提交); } // 业务校验金额与明细不能为空 BigDecimal total reimburseItemMapper.sumAmountByReimburseId(reimburse.getId()); if (total null || total.compareTo(BigDecimal.ZERO) 0) { throw new BizException(报销金额必须大于0); } // 状态流转 记录审批轨迹 reimburse.setStatus(ReimburseStatus.SUBMITTED.getCode()); updateById(reimburse); ApprovalRecord record new ApprovalRecord(); record.setReimburseId(reimburse.getId()); record.setAction(SUBMIT); record.setComment(员工提交报销申请); approvalRecordMapper.insert(record); }代码里的关键在两个 design decision。第一状态流转的合法性判断收敛到枚举内部以后如果要加“已撤回”状态只需要在枚举定义里加一个值和一条流转路径不用去满项目搜 if 判断。第二每次状态变化都同步插入一条approval_record这既保证了审批流程可追溯也让系统管理页面的“审批轨迹”功能有了数据来源。答辩时能主动说清楚这两个设计点比背一百行代码有用得多。3.2 多级审批的两种实现方式硬编码与策略模式报销单从提交到打款要经过“主管审批 → 财务审批”两级这是企业报销的基本盘。最简单的实现就是在 service 层写一个判断当前操作人角色是 MANAGER 则把状态改为 APPROVED是 FINANCE 则改为 PAID。这个写法在流程固定时问题不大但答辩时容易暴露两个软肋角色扩展性差、审批行为无法复用。更好的方案是用策略模式按“动作”而不是“角色”组织代码。定义一个审批动作接口提交、通过、驳回各是一个实现类每个类里只写自己关心的状态迁移和通知逻辑。public interface ApprovalAction { // 执行审批操作返回审批后的新状态 ReimburseStatus execute(Reimburse reimburse, String approverId, String comment); } Component public class ApproveAction implements ApprovalAction { Override public ReimburseStatus execute(Reimburse reimburse, String approverId, String comment) { ReimburseStatus current ReimburseStatus.fromCode(reimburse.getStatus()); // 主管审批通过则进入待财务审批财务审批通过则进入已打款 ReimburseStatus target (current ReimburseStatus.SUBMITTED) ? ReimburseStatus.APPROVED : ReimburseStatus.PAID; if (!current.canTransitionTo(target)) { throw new BizException(当前状态不能执行审批通过操作); } reimburse.setStatus(target.getCode()); // 记录审批人、审批意见 ApprovalRecord record new ApprovalRecord(); record.setReimburseId(reimburse.getId()); record.setApproverId(approverId); record.setAction(APPROVE); record.setComment(comment); approvalRecordMapper.insert(record); return target; } }这个实现把“审批通过”在不同状态下的目标值收敛到了一个方法里。判断当前是SUBMITTED还是APPROVED来决定下一个状态而不是在 controller 里针对角色写两套逻辑。以后要加“部门经理审批”作为第三级只需要改ApproveAction里的目标状态判断或者把各级审批目标状态配到字典表里。另一个值得关注的是驳回动作。驳回不能像通过那样直接跳到固定状态否则员工提交后一旦被驳回再提交审批轨迹就乱套了。常规设计是驳回回到DRAFT还是SUBMITTED取决于业务规则一般驳回回到待修改状态DRAFT员工修改后可再次提交。这个决策也要在代码注释里写清楚答辩时能讲出“为什么驳回后还能再提交而不会被重复审批”这样的业务闭环逻辑。3.3 金额统计与 Excel 导出让财务模块有实用价值报销系统的财务视角是月度费用统计和导出。这里用 MyBatis Plus 的 LambdaQueryWrapper 配合 group by 就能完成按类型、按部门的聚合查询不需要写复杂 SQL。Excel 导出用 Apache POI 是 Java 生态的标准做法对应热词里“java poi word能生成图表吗”的同类能力——POI 不只处理 Word操作 Excel 更是强项。public void exportMonthlyReport(String yearMonth, HttpServletResponse response) { // 查询该月所有已打款的报销单 LambdaQueryWrapperReimburse wrapper new LambdaQueryWrapper(); wrapper.eq(Reimburse::getStatus, ReimburseStatus.PAID.getCode()) .likeRight(Reimburse::getApplyTime, yearMonth); ListReimburse list list(wrapper); // 用 POI 创建工作簿和表头 try (Workbook workbook new XSSFWorkbook(); ByteArrayOutputStream baos new ByteArrayOutputStream()) { Sheet sheet workbook.createSheet(报销明细); String[] headers {报销单号, 申请人, 报销金额, 报销类型, 申请日期}; Row headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } int rowIndex 1; for (Reimburse r : list) { Row row sheet.createRow(rowIndex); row.createCell(0).setCellValue(r.getReimburseNo()); row.createCell(1).setCellValue(r.getUserName()); row.createCell(2).setCellValue(r.getAmount().doubleValue()); row.createCell(3).setCellValue(r.getTypeName()); row.createCell(4).setCellValue(r.getApplyTime().toString()); } workbook.write(baos); // 设置响应头触发浏览器下载 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamereimburse_ yearMonth .xlsx); response.getOutputStream().write(baos.toByteArray()); } catch (IOException e) { log.error(导出报销单失败, e); throw new BizException(导出失败请稍后重试); } }这套导出代码有几个细节要注意。list(wrapper)直接查的是主表如果还要带出用户名、类型名一种做法是实体类里加冗余的userName、typeName字段查询时通过自定义 SQL 联表填充另一种是用 VO 对象接收多表查询结果。毕业设计选前者更省事因为 MyBatis Plus 的实体类允许存在非表字段标上TableField(exist false)就不会报错。响应头里setContentType和setHeader缺一不可否则下载的文件要么打不开要么文件名乱码。内存层面的坑也值得一提。XSSFWorkbook会把整个 Excel 加载进内存月度导出数据量小时没有影响但如果未来做年度导出数据量上到几万行会 OOM。解决路径是换SXSSFWorkbook做流式写入每 100 行刷一次盘。这个优化不看代码规模看的是业务预判写进论文的“系统优化”章节会很有说服力。4. 认证授权与操作权限不要让登录模块拖垮整个项目的答辩印象分4.1 JWT Spring Security无状态登录的正确姿势报销系统不需要像电商那样做复杂的会话管理JWT 方案更契合前后端分离的架构。后端登录接口校验用户名密码成功后签发一个 token前端每次请求在 Authorization 头里带上这个 token后端用拦截器或 Spring Security 过滤器解析鉴权。相比 SessionJWT 不需要在服务器端存储登录态非常适合部署在单台服务器上的毕业设计。Spring Security 在这个项目里是双刃剑。用好了是安全架构的体现用不好光 configure 方法里的 filter chain 就能把人绕晕。常见做法是重写OncePerRequestFilter在 Spring Security 的过滤器链里加入 JWT 解析逻辑。核心配置只有三件事放行登录接口和静态资源、其他接口全部需要认证、异常处理返回 JSON。Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/login, /api/auth/captcha).permitAll() .antMatchers(/api/**).authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); }这段配置里csrf().disable()和STATELESS是关键。前后端分离后没有 Cookie 会话CSRF 防护失去了存在基础不禁用反而会导致 POST 请求全部 403。STATELESS明确告诉 Spring Security 不要创建 Session每个请求都是独立的这样 JWT 才能发挥作用。答辩时这两个配置几乎是老师必问的点能答清楚“为什么前后端分离要关 CSRF”和“无状态会话的好处”安全设计这块就拿到了分数。4.2 RBAC 权限模型让“财务能看到所有人的报销单”不再是 if 判断报销系统里的权限需求很典型员工只能看自己的报销单主管能看到本部门员工的单子财务能看到全公司的单子。如果前端菜单靠 v-if 控制后端接口不做数据权限校验那任何人都可以直接调接口查看他人数据。系统必须做到“接口级鉴权 数据级过滤”。RBAC 模型在代码里落地很直接sys_user表通过dept_id关联部门通过role_id关联角色角色在sys_role表里定义菜单权限通过角色关联的菜单集合控制。查询报销单列表时根据当前登录用户的角色拼接数据范围条件public PageResultReimburseVO pageQuery(ReimburseQuery query, LoginUser loginUser) { LambdaQueryWrapperReimburse wrapper new LambdaQueryWrapper(); // 普通员工只能看自己的单子 if (EMPLOYEE.equals(loginUser.getRoleCode())) { wrapper.eq(Reimburse::getUserId, loginUser.getUserId()); } // 主管能看本部门所有单子 if (MANAGER.equals(loginUser.getRoleCode())) { wrapper.eq(Reimburse::getDeptId, loginUser.getDeptId()); } // 财务不拼数据权限看全部 // 财务只能看已提交和已通过的单子 if (FINANCE.equals(loginUser.getRoleCode())) { wrapper.in(Reimburse::getStatus, Arrays.asList(ReimburseStatus.SUBMITTED.getCode(), ReimburseStatus.APPROVED.getCode())); } return page(new Page(query.getPageNum(), query.getPageSize()), wrapper); }数据权限过滤没有放在 SQL 拦截器层面做而是直接在 service 里基于角色拼接条件胜在直观、易读、好向答辩老师解释。但要注意角色编码是硬编码的EMPLOYEE、MANAGER、FINANCE如果以后要加新角色就必须来改这段代码。真正企业级做法是把数据权限规则配置进数据库做成一个data_scope字段但那个复杂度对毕业设计没有必要——你只需要在论文里提一句“本系统采用基于角色的数据权限控制未来可通过配置化扩展规则”就足够了。5. 避坑手册从数据库连接到部署运行的五条血泪经验拿到源码包后最常见的翻车场景往往是环境问题多于代码问题。以下是我这些年看毕业设计代码、帮人排错时积累的高频故障。每一条按“现象 → 原因 → 解决”的顺序梳理遇到类似问题直接对号入座。坑 1项目启动报数据库连接失败明明 MySQL 已经启动了现象是控制台抛Communications link failure或者Access denied for user rootlocalhost。第一类原因是 MySQL 8 的认证插件是caching_sha2_password而项目里的驱动版本还是 5.x 的旧驱动认不了新插件第二类原因更直接项目application.yml里的密码跟本地数据库不一致。解决建议直接用 MySQL 8.0 并同步升级驱动为mysql-connector-java 8.0.x密码改成自己的。另外检查jdbc:mysql://localhost:3306/reimburse?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai——serverTimezone参数不加高版本 MySQL 驱动也会报时区错误。坑 2前端 npm install 卡死或下载极慢现象是执行npm install后进度条长时间不动最终报ERR! code ETIMEDOUT。原因是 npm 默认源在境外网络波动大。解决执行npm config set registry https://registry.npmmirror.com切换国内镜像后再 install。装完后启动npm run dev如果报Node Sass相关的错误多半是 node-sass 版本和 Node 版本不匹配优先把项目里的 node-sass 换成sassDart Sass或者直接用项目 package.json 里锁定的 Node 版本。坑 3审批列表查不到数据但数据库里明明有记录看似是 Bug实际是逻辑删除过滤器在作祟。MyBatis Plus 配置了全局逻辑删除之后所有查询会自动拼接WHERE deleted 0。如果你在 SQL 脚本里初始化数据时deleted字段没有给默认值 0那老数据就会被全部过滤掉。解决检查sys_user和reimburse表的deleted字段是否都有默认值0或者在初始化脚本里执行UPDATE reimburse SET deleted 0 WHERE deleted IS NULL做一次数据订正。这个坑在答辩现场很容易被复现千万提前自查。坑 4报销单金额小数位不准比如 99.99 变成 99.99000000000001这是用double做金额计算的典型事故。Java 的double是二进制浮点数十进制小数无法精确表示。解决SQL 里金额字段改成decimal(10,2)Java 实体类用BigDecimal代码里任何金额计算都必须用BigDecimal的add、subtract、multiply不要直接用-*。如果 POI 导出 Excel 时要把 BigDecimal 转 double只在写入单元格的那一步转计算过程全程保留 BigDecimal。坑 5前端请求后端接口报跨域但登录接口能通现象是登录成功了后面的报销单列表却报CORS policy: No Access-Control-Allow-Origin header。登录接口能通是因为它在 security 放行列表里走了不同于其他接口的过滤器路径。解决不要只依赖前端 proxy 代理后端也要统一配置跨域。Spring Boot 里写一个WebMvcConfigurer实现addCorsMappings允许来源写前端的http://localhost:8080或http://localhost:5173后端端口 8081 作为 API 服务才能和前端正常协作。如果用了 Spring Security还要在 Security 配置里允许 CORS——精确说是安全过滤器链短暂拦截了预检请求OPTIONS需要放行。6. 从“交作业”到“能面试”把报销系统改造成项目亮点的进阶技巧6.1 60 分钟读懂一份源码包的顺序数据库 → 状态机 → 权限 → 工具类拿到源码不要急着启动项目。按照“数据库模型先行、核心流程次之、基础设施最后”的顺序去读效率最高。先打开sql目录的建表语句把五张表的关系画在纸上再读ReimburseStatus枚举和ReimburseServiceImpl的提交/审批方法理解状态怎么流转然后看 JWT 过滤器和权限配置最后才看 Excel 工具类、统一返回结果封装这类杂项。读代码的过程中把注意力放在三个跨模块的设计上。统一返回结果类ResultT是所有接口的响应包装它的 code、message、data 字段在整个前端 axios 拦截器里靠它拦错误全局异常处理器RestControllerAdvice决定业务异常是返回 200 还是 500PageHelper 或 MyBatis Plus 分页对象决定了前端 table 的 total 字段来源。这三个地方搞懂任何管理系统的代码你都能快速上手。6.2 答辩前必做的三轮验证清单第一轮验证是核心流程回归。注册一个新账号走完“提交报销单 → 主管审批通过 → 财务审批通过 → 查看已打款”的完整路径期间检查每一步状态变化是否写入审批记录。第二轮验证是权限边界用员工账号直接调用主管接口确认后端返回无权限而不是越权操作用主管账号尝试财务打款接口确认同样被拦截。第三轮验证是边界条件提交金额为 0 的报销单、驳回后重新提交、删除有明细的报销单这三个场景最容易暴露数据一致性问题。我个人的习惯是在答辩前把“你在做项目过程中遇到的最大困难是什么”这个问题准备好答案。一个真实的踩坑案例比十句“我做了很多优化”更有说服力。比如你可以说在做报销金额统计时发现用 double 计算多笔报销会产生精度误差导致财务月度汇总对不上账后来把所有金额字段改成 BigDecimal 并用枚举状态机约束审批流转问题才彻底解决。讲这样一个具体的定位和修复过程能把毕业设计的深度从“会调用框架”拉升到“理解业务与技术约束”。6.3 用两个改造点让项目脱胎换骨如果时间充裕推荐做两个低成本高回报的改造。第一个是把审批操作改成消息通知——报销单提交后通过 WebSocket 或 Spring 的事件机制通知审批人前端收到推送角标提醒“你有 3 条待审批报销单”。这个改造涉及 Spring Event 的ApplicationEventPublisher代码量不大但体现了系统集成思维。第二个是在统计模块加一个报表页面按报销类型、消费趋势展示月度费用分布。前端可以用 ECharts 折线图和饼图后端只需要复用 3.3 里的分组聚合查询把结果封装成图表数据结构返回。这两个改造会让论文里的“系统实现”和“系统展示”章节有更多可写的内容答辩 PPT 里也能放下真实截图。做毕业设计最怕的不是代码写不完而是交出去的东西自己讲不清楚。这套企业报销管理系统的价值就在于此——流程经典、边界清晰、扩展点明确。把状态机、数据权限、金额精度这三个点吃透哪怕代码是拿别人的改的你也能在答辩时讲出自己的理解。希望这些落地经验能帮你在有限的时间里把一个“能跑”的资源包变成“能讲清”的项目作品。本文还有配套的精品资源点击获取
网站建设高端定制企业官网