ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SpringBoot+SSM的财务预算管理系统设计与实现

基于SpringBoot+SSM的财务预算管理系统设计与实现 在企业级应用开发这个圈子里财务预算管理系统算是一个非常经典的业务场景。不管是你去面试后端岗位还是在学校做毕业设计只要简历上写着“做过预算管理”面试官大概率会追问预算编制流程、审批流设计、数据权限隔离这些细节。今天我就结合手头这套基于Java、SpringBoot和SSM框架的财务预算管理系统把整个项目的设计思路、核心代码、部署调试过程以及那些容易让人栽跟头的坑一次性讲清楚。这篇文章适合正在做毕业设计的学生、想转行做Java后端开发的初学者以及公司里需要快速搭建内部预算管理工具的技术人员。项目本身不大但麻雀虽小五脏俱全涉及用户登录鉴权、预算编制审批、部门数据隔离、报表统计等常见企业级功能足够你从中提炼出一套能迁移到其他管理系统的通用方法论。1. 项目定位与核心需求拆解1.1 财务预算管理系统到底在解决什么问题先聊点实际的。很多刚接触这个项目的同学会以为财务预算管理系统就是一个“记流水账”的软件这其实是个误区。预算管理本质上是一个“事前规划、事中控制、事后分析”的闭环体系。事前要把下一年度或者下个季度的收入目标、成本限额、费用指标拆解到每个部门甚至每个项目事中要确保每一笔支出都被预算约束不能超支事后还要能对比预算数和实际执行数找出差异分析原因。这套系统我拆开看了源码整体是基于SSM框架改造的SpringBoot工程。SSM在早几年是Java后端的主流组合SpringBoot出现之后约定优于配置的思想让开发效率提升了一大截。这个项目没有直接用最简单的单用户版本而是保留了多个角色登录的机制这一点很关键——因为预算一定是有审批流程的部门经理填报财务审核老板拍板缺了角色和流程预算系统就没有灵魂。1.2 技术选型的逻辑与取舍技术选型这部分很多人只是机械地复制pom.xml依赖从不思考为什么。我直接说结论SpringBoot相对传统SSM的优势核心体现在三个方面。第一是自动装配。传统SSM项目你需要手动配置DispatcherServlet、Spring容器、MyBatis的SqlSessionFactory还要处理一大堆XML文件。SpringBoot把这些默认配置全部封装好了你只需要在application.yml里写数据库连接、端口号、日志级别几个关键参数项目就能跑起来。对毕业设计和中小型企业内部系统来说这个效率提升非常明显。第二是依赖管理。SSM时代引入一个框架往往要连带引入五六个兼容版本的依赖版本冲突是家常便饭。SpringBoot的Starter机制直接把web、aop、jdbc这些常用场景的依赖打包成统一的坐标你引入一个spring-boot-starter-web相关依赖全部到位。项目里用到的pagehelper分页插件、druid连接池、lombok都是通过这种简洁的方式集成的。第三是内嵌容器。传统SSM项目要打war包丢到独立的Tomcat里SpringBoot通过内嵌Tomcat直接java -jar启动部署成本低了一个量级。这一点在你们公司CI/CD流水线自动化部署的时候尤其好用。不过我也要说句公道话作为开发人员不能只会SpringBootSSM底层原理还是得懂。这套系统的数据访问层用的就是MyBatisSQL都写在Mapper.xml里你能直观地看到SQL和业务代码的边界。搞清楚这一层你以后用MyBatis-Plus或者其他ORM框架都是手到擒来。1.3 部署方式与项目结构的一手观察打开源码工程你会发现它的命名和分包很规范不是那种随便写的Demo。项目结构基本是这样的src/main/java ├── com/company/finbudget │ ├── controller // 控制层接收请求返回视图或JSON │ ├── service // 业务层预算编制、审批流转、统计分析 │ ├── mapper // 数据访问层接口 │ ├── entity // 实体类和数据库表结构对应 │ ├── common // 统一返回结果、常量、工具类 │ └── config // 拦截器、跨域配置、静态资源配置 src/main/resources ├── mapper // MyBatis的SQL映射文件 ├── static // 静态资源目录 ├── templates // 模板文件目录 └── application.yml // 核心配置文件实体层对应预算主表、预算明细表、部门表、用户表、审批记录表五张核心表。控制层和服务层是标准的Controller-Service-Mapper三层结构。我看了一下代码风格方法体干净异常处理也比较统一说明作者写这套代码的时候是有意识往企业级规范上靠的对学习者来说这套代码的参考价值比那些乱糟糟的课程Demo高很多。2. 数据库设计与核心业务流程2.1 数据表关系从用户到预算主表的设计链路做管理系统的第一步永远是设计数据库。这个项目的表结构不复杂但关系还是挺清晰的。用户表存放登录账号、姓名、部门ID还有一个角色字段管理员、财务人员、部门经理。部门表就是几个基础属性。最关键的是预算主表和预算明细表。主表记录本次预算的名称、预算年度或期间、编制部门、预算总金额、状态草稿、待审批、已通过、已驳回、审批人等明细表记录每一笔预算的科目名称、预算金额、备注说明。这里有一个很经典的细节主表和明细表一定要分开因为预算填报页面往往要做动态增行部门经理一次可能填报几十个科目的预算明细数据肯定是多条的。如果你偷懒把科目金额都塞进一个字段用逗号分隔后面做统计报表的时候会非常痛苦。2.2 预算编制到审批的状态机流转业务流程上这套系统最核心的一条线就是预算审批流。我把它拆成四个状态草稿、审批中、已通过、已驳回。部门经理登录之后选择预算年度和部门系统自动带出上一年的实际执行数作为参考然后录入每个科目的预算金额。填报界面通常支持循环动态添加科目这个交互很多同学做不好其实后端的思路很简单前端把treeTable数据组装好提交的时候直接用JSON数组传过来Controller用List接收Service层循环判断非空的入库。保存草稿之后可以再次修改确认无误后点击提交审批状态从“草稿”变成“审批中”。此时数据不可再编辑直到审批人做出通过或驳回的操作。审批人查看预算明细如果觉得某科目预算太高可以填写审批意见并驳回系统记录当前时间和审批人ID。整个流程通过一张审批记录表维护每条预算的流转历史保证可以追溯。这个状态机的实现强烈建议你在Service层写一个专门的方法去处理状态流转而不是在Controller层到处setStatus。这样做的原因是状态变更往往伴随着额外的业务动作比如提交审批后要生成一条审批流水驳回后要恢复可编辑权限。把这些逻辑收拢到一个事务方法里不容易出现半途失败导致的数据不一致。3. 核心功能模块解析与关键代码实现3.1 用户登录与权限控制登录模块本身不复杂但它是所有系统的门面有两个点值得拿出来细说。第一个是密码加密。我看过太多同学的毕业设计密码直接用明文存在数据库里这样做非常危险。稍微有点安全意识的人都会用MD5加盐甚至BCrypt去处理。这套项目里的做法是可取的至少做了MD5加盐。我建议后续可以升级成Spring Security的BCryptPasswordEncoder虽然会增加一些配置量但安全性完全不是一个量级。第二个是登录拦截器。项目用拦截器校验用户是否登录比如在WebMvcConfig里注册一个HandlerInterceptor重写preHandle方法判断session里有没有登录用户没有就重定向到登录页放行静态资源和登录接口。比起在每个Controller方法里手动判断这种方式把鉴权逻辑集中管理可维护性高很多。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user (User) request.getSession().getAttribute(loginUser); if (user null) { // 未登录根据请求类型跳转或返回json提示 response.sendRedirect(/login); return false; } return true; } }3.2 预算编制模块前端动态表单与后端数据接收预算编制是这个系统使用频率最高的功能。我建议你们前端用Layui的表格组件来做动态行因为它自带单元格监听和删除行事件对后端开发出身、不擅长写复杂Vue组件的同学来说学习成本最低。关键步骤是页面通过layui的table.render初始化一个空表格定义科目名称、预算金额、备注三个字段。点击“添加科目”按钮调用table.addRow方法插入空行。编辑完点击保存做一个单元格遍历校验空值和数字格式通过后用一个hidden表单或者Ajax数组提交给后端。PostMapping(/budget/save) ResponseBody public Result saveBudget(RequestBody BudgetSaveParam param) { // param.getItems() 即前端传过来的预算明细集合 budgetService.saveBudgetWithItems(param.getMainInfo(), param.getItems()); return Result.success(); }这里埋个坑前端页面在提交动态表格数据之前一定要重新计算总金额。如果数据较多建议用reloadData强制刷新表格再遍历采集数据。我调试的时候遇到过total一直为0的情况找半天发现是提交按钮的click事件比表格渲染完成更早触发数据还没灌进去解决方式是提交前先做一个table.reload操作等待回调完成后再读取。3.3 预算审批模块角色隔离与状态流转审批模块的业务逻辑集中在两处一是不同角色能看到哪些单据二是操作审批时如何安全地切换状态。从系统设计角度我建议不用复杂的权限框架直接在SQL层面通过观察者角色判断这样既直白又好维护。审批人登录之后、只查询状态为“审批中”且属于自己审批范围的数据部门经理登录之后只能看到自己部门创建的预算单。实现时要特别注意SQL拼接的安全性不能使用字符串拼接参数必须用MyBatis的Param注解传递变量防止SQL注入。select idselectPendingBudget resultTypeBudgetMain select * from budget_main where status PENDING if testrole FINANCE and budget_type COST /if order by create_time desc /select再看状态流转的代码。审批通过和驳回在事务里做三件事更新主表状态、插入一条审批记录、判断是否要触发后续操作比如通知、生成预算快照。三个动作缺一不可所以Service方法必须加上Transactional注解。如果漏了这个注解中途抛出异常会导致状态更新了但流水没记录查账的时候会陷入死局。我排查过很多次这类问题最后基本都是事务边界不一致导致的。3.4 预算执行与统计报表系统里如果光有预算填报审批没有执行跟踪充其量是个申请表。我仔细看了源码这套系统是有预算执行功能板块的用来做预算数和实际执行数的对比分析。实际执行数通常来源于报销单、付款单等业务表在预算系统里可以做一个汇总也可以手工录入。统计报表这部分实现思路我会拆成三步。第一步按月度汇总实际执行数据SQL对业务表按部门编码和月份进行group by第二步将预算数据按同样的维度组织和实际执行数据做关联第三步前端用ECharts的柱状图或者双轴图展示预算金额vs实际金额对比财务人员能直观地看到哪些科目超支了。SELECT dept_id, MONTH(execute_date) AS month_num, SUM(actual_amount) AS actual_total FROM budget_execute WHERE execute_date BETWEEN #{startDate} AND #{endDate} GROUP BY dept_id, MONTH(execute_date)4. 部署调试全流程还原4.1 本地开发环境搭建与数据库初始化首先是开发工具环境。JDK建议用1.8别一上来就装JDK 17很多SSM老代码在JDK 11以上会出现javassist之类的字节码兼容问题报错信息还不直观。数据库我用的是MySQL 5.7Navicat导入项目里自带的sql文件把库名和application.yml里的url地址对齐。如果你们本机装的是MySQL 8.x那连接驱动记得换8.0的com.mysql.cj.jdbc.Driver并且URL后面要加serverTimezoneAsia/Shanghai防止时区报错。4.2 启动过程中的常见异常处理我按照常规流程启动遇到三个典型问题这里列出来你们可以少走弯路。第一个是数据库连接失败Caused by Communications link failure。这种基本是用户名密码不对或者驱动没生效。检查一下application.yml里的driver、url、username、password四个字段第一次跑的时候强烈建议把密码加上引号避免yml解析特殊字符出错。第二个是MyBatis的Mapper扫描不到报Invalid bound statement (not found)。原因通常是Mapper接口的路径和XML的namespace写得不一致。检查两个地方接口上面的MapperScan扫描的基础包是否正确resources/mapper目录下的XML文件是否和接口在同一个包路径下。这个错真的可以让你白看半小时我实际排查的建议是直接全局搜索namespace和接口全限定名对一下。第三个是页面静态资源404样式和JS加载不出来。在SpringBoot里静态资源放在classpath:/static/目录下默认映射路径是/。你写/css/style.css就能直接访问到static/css/style.css。如果还是404多半是项目里配置了自定义的静态资源映射路径可以换成这个Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/static/**) .addResourceHandler(classpath:/static/); }4.3 账号角色与演示数据准备系统初期导入的数据一定要包含两种角色的演示账号。部门经理账号的部门ID要和预算主表里的编制部门对应上财务审批人账号要在角色字段里标记清楚。为了让演示效果更完整建议往预算明细表里插入上一年度预算以及几条“已通过”、“审批中”、“已驳回”的样例数据。这样不同权限的人登录进去主页面都能看到对应的功能入口不至于登录之后空空如也。5. 调试文档与讲解资料的整理方法论很多同学忽略调试文档和讲解视频的作用我在这里用过来人的身份劝一句这项工作的重要性不亚于写代码本身。尤其是答辩或者汇报的时候能清晰地讲出系统设计思路、模块关系、关键代码逻辑比代码跑起来更能加分。我把整理要点归纳为三条第一画出系统模块图和数据流图。不用特别专业但一定要把用户角色、功能模块、数据流转路径标注清楚。预算从编制到审批到执行的三个节点如何穿梭每个节点对应哪个表哪个状态这条链路理清了整个系统讲起来就很顺。第二梳理核心功能的操作说明。不用写成操作手册那么全面但每个核心页面入口在哪里、操作时有哪些前置条件、操作结果是什么一定要在文档里写明白。建议用表格整理页面URL、功能描述、操作角色、预期结果四列。第三准备高频问题清单。比如“如果预算审批被驳回数据会回到哪个状态”“如何避免多部门提交同一科目预算造成重复统计”“系统怎么保证财务数据的安全性”。调试文档里如果能把这些问题逐个给出答案和对应的代码位置答辩的时候完全可以做到从容应对。6. 项目测试与排错实录6.1 功能自测清单项目交给用户或者导师之前一定要按照下面的清单过一遍缺一不可。首先用部门经理账号登录走一遍完整的预算填报流程保存草稿、再次编辑、提交审批。然后切换审批人账号处理这张预算单先驳回再重新提交最后通过。这几个状态之间的流转要反复测几遍确保每一步在界面上都有明确的状态提示。再用部门经理账号查询自己部门的预算单确认列表只显示本部门数据看不到其他部门的内容。这一点涉及数据权限隔离。在纯后端实现中如果SQL漏了部门过滤会出现越权查询的严重问题。这里建议你在Service层打印日志配合调试器观察SQL确认dept_id过滤条件是否生效。最后验证统计报表造几笔实际执行数据用不同科目编码命名字段检查总数和明细的对比结果是否和手算一致。统计口径最容易漏的就是月份条件很多同学月份条件过滤错位导致全年数据被当成了当月数据这个我在自测阶段见过太多例了。6.2 复盘总结我踩过的坑第一个是事务失效。如果Service方法用this调用了同类中的另一个方法Spring的AOP代理就不会拦截这次调用即使目标方法上有Transactional也不会生效。如果你把预算保存和日志记录分成两个方法又在同一个类里互相调用就会遇到这种情况。经验是控制好事务边界把需要保证原子性的操作尽量放在同一个方法里外部只调用Service入口方法。第二个是通用返回结果Result对象。项目里定义了Result把code、msg、data封装起来。Controller里必须统一返回Result而不是裸返回Map或者Object。这样前端在Ajax回调里统一判断code200即可省得每个接口都写一堆if分支。当时我在改接口的时候漏改一个导致前端一直报错排查后才发现某个接口直接返回了boolean没有包Result前端解析不到code字段。第三个是日期类型序列化问题。预算填报页面传年份如果前端传的是字符串“2025”后端实体类Date字段会出现转换异常或者变成1970年。强烈建议在application.yml配置统一日期格式或者增加Jackson配置类Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd))); builder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd))); }; }7. 系统可扩展方向与二次开发建议7.1 从单体到模块化的演进路径如果这套系统要放进企业真实环境有两条线是必须扩展的。第一条是权限控制现在没有用Spring Security后续可以引入把登录和鉴权彻底标准化。这样去做权限管理远比在Service层写角色判断更可靠。第二条是审批流程现在是一条直线审批后续业务方可能会提多级审批、会签、退回到指定节点等需求。想要彻底解决可以接一个工作流引擎不过对于中小项目直接改数据库的审批人字段加一个序号也能实现简单的多级审批性价比更高。7.2 数据权限与统计分析优化除了权限统计分析也可以做深。现在只做了简单的月度执行率后续需求大概率会演进出预算执行率预警。意思是当某科目支出达到预算90%的时候系统自动给部门经理发提醒。实现上不复杂加上一个定时任务控制查询频率即可。这样既让系统具备了一定的决策辅助能力又不至于把架构搞得过分复杂。还有一个值得考虑的方向是多租户。如果你们公司是集团制有多个子公司数据库层面要么在每家子公司数据库加租户ID字段要么通过独立库隔离。前者适合轻量隔离后者适合对数据安全要求更严格的场景具体情况具体分析。8. 实用小技巧与拓展思考最后把这些零散的实用技巧整理一下都是实际运行中得来的经验。日志打印是有规范的。Controller层记录请求入参和响应结果Service层记录核心业务节点不要满天打info日志导致排查问题的时候啥也看不清。建议在配置里区分级别开发环境用debug生产环境用info日志文件按照日期切割留最近30天即可。SQL性能优化方面预算主表和明细表一定要建索引查询频繁的status、dept_id、create_time三个字段都可以加普通索引。数据量过万之后一次全表扫描带来的延迟就可能让用户怀疑人生索引是成本最低的优化手段。关于团队协作如果你们是多人一起开发建议把MyBatis的Mapper接口和XML文件放同一个包路径下这样就算不见面通过包结构就能知道代码归属。配置的时候要在pom.xml的build节点加上mapper资源扫描否则打包之后XML文件不会进到classes目录resources resource directorysrc/main/resources/directory includes include**/*.xml/include /includes /resource /resources说到底这套财务预算管理系统最大的价值不是代码本身多高大上而是它把一个真实企业里天天在发生的业务场景用一套符合主流开发规范的Java技术栈落下来了。你把它跑通、理解透之后再去接触更高阶的微服务架构、分布式事务、工作流引擎会发现核心思路都是相通的。我自己的经验是刚开始做项目不要贪大求全先把一个系统的业务闭环走通把数据流转理清楚比什么花架子都管用。
返回列表