news 2026/10/9 3:57:28

基于Spring Boot的个人财务管理系统设计与实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot的个人财务管理系统设计与实现详解

做了几年的Java后端,也帮人看过不少计算机毕业设计,说实话,个人财务管理系统这类题目几乎是每年都会出现的选题。最近正好整理一套基于Spring Boot的个人财务管理系统,项目编号06221,源码和数据库脚本都齐全,很适合作为计算机毕设。这篇文章就把这套系统的设计与实现思路完整拆开聊一聊,包括核心功能怎么拆、数据库表怎么设计、预算和统计报表怎么做,以及我实际开发中踩过的一些坑。如果你正在选型Spring Boot毕设题目,或者已经选了财务管理方向但不知道怎么下手,这篇应该能给你一个能直接抄作业的参考。

1. 项目背景与设计思路

1.1 为什么选Spring Boot做个人财务系统

个人财务管理系统的核心是“记账+分析”,说白了就是让用户记录每一笔收入和支出,然后按分类、按时间统计出结余和消费趋势。这类系统业务逻辑不算复杂,但胜在功能链路完整,非常适合作为计算机专业的毕业设计——它既有增删改查的基础操作,又有权限校验、统计聚合这些进阶点,且工作量可控,一个人完全做得了。

选Spring Boot而不是SSH或者纯Servlet,理由其实很现实。Spring Boot把Spring的配置项做成了自动化装配,起步阶段不需要手写一堆XML,写几个Controller就能跑起来,这对毕设来说能省出大量时间给前端和文档。另外Spring Boot内置Tomcat,打包成Jar后一条命令就能启动,答辩演示时非常省事。

再说说为什么不是Spring Cloud这类微服务框架。毕设的关键是“小而完整”,不是“大而空”。个人财务系统只有单一用户核心业务,没有分布式事务、服务治理这种需求,硬套微服务反而会把源代码撑得很虚。Spring Boot单体能满足所有场景,数据量也不会有压力,把有限精力放在界面交互和统计逻辑上才是正解。

1.2 系统整体功能拆解

项目标题里的“个人财务管理系统”听起来宽泛,但我把需求收敛成下面这些功能点:

  • 用户注册与登录:使用JWT做无状态认证,密码加密存储。
  • 收支记录管理:新增、编辑、删除、分页查询每一笔收入/支出。
  • 分类管理:预设日常分类,如餐饮、交通、购物、工资、奖金等,也允许用户自定义。
  • 预算管理:按月份设置某个分类的预算金额,并计算剩余额度。
  • 统计报表:按月汇总收支,按分类统计占比,用图表展示趋势。
  • 数据导出:将查询结果导出为Excel文件,方便留存。

这些功能覆盖了从单表CRUD到多表关联、从普通查询到聚合统计的完整梯度,老师问起来也讲得出层次。尤其预算管理和统计报表这两块,是区分“会写增删改查”和“有设计能力”的重要分水岭。

技术栈上我采用前后端分离方案:后端Spring Boot提供RESTful API,前端用Vue 3 + Element Plus + ECharts做界面和图表。如果你们学校要求必须用模板引擎,也可以把前端改成Thymeleaf,但核心后端逻辑是通用的。下文涉及代码的地方,我以后端为主。

2. 核心技术选型与模块设计

2.1 Spring Boot版本与依赖管理

这里必须多说一句,因为很多同学栽在版本上。我最初用Spring Boot 3.2,但答辩老师机器上可能还是JDK 8,结果跑不起来。后来我调整成Spring Boot 2.7.18配JDK 8,整个项目就稳了。如果你不是准备研究新特性,毕设选题就老老实实用Spring Boot 2.7.x——它既支持JDK 8,也兼容JDK 11,生态最成熟。

依赖方面我用了这些核心组件:

依赖作用
spring-boot-starter-web提供REST API和内置Tomcat
spring-boot-starter-validation参数校验
mybatis-plus-boot-starter简化数据访问,自带分页插件
mysql-connector-javaMySQL驱动
jjwt-api 0.11.5JWT生成与解析
hutool-all加密、日期、Excel导出工具
lombok简化实体类代码

这里解释一下为什么用MyBatis-Plus而不是Spring Data JPA。对于毕设来说,MyBatis-Plus的语法直觉性强,selectPage一步到位,而且生成的Mapper方法名极易看懂——老师看代码时不会卡在“这是什么鬼”的困惑上。JPA虽然也很优秀,但级联和懒加载处理起来更容易绕晕,尤其学生党经常写出N+1查询。MyBatis-Plus还有代码生成器,建好表之后可以直接生成实体和Mapper,节省大量时间。

2.2 数据访问层设计与数据库表结构

个人财务系统的表结构不能设计得太复杂,但也不能只有一张表硬扛。我设计了四张核心表:用户表、分类表、收支记录表、预算表。表之间用逻辑外键关联,不用数据库物理外键,避免删除时产生约束麻烦。

先看用户表:

CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录名', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `nickname` varchar(50) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

分类表设计时要注意数据结构。我用了层级分类,parent_id为0表示顶级分类,用户可以自己加二级分类,但不要超过两级,否则前端菜单和统计都会很复杂。每个分类归属于某个用户。之所以加user_id,是为了做到“一个用户一套分类”,互不干扰。收支记录表则是每天记账的核心数据载体:

CREATE TABLE `t_bill` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `category_id` bigint(20) NOT NULL, `type` tinyint(1) NOT NULL COMMENT '1收入 2支出', `amount` decimal(10,2) NOT NULL COMMENT '金额', `bill_date` date NOT NULL COMMENT '记账日期', `remark` varchar(255) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`,`bill_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有一个非常关键的设计细节:金额字段必须用decimal(10,2),绝对不能用double或float。浮点数在Java和数据库里都有精度损失问题,今天记一笔0.1元,累计多了就会出现0.999999这种脏数据。我在代码里用BigDecimal接收和计算,能保证金额精度。另一个细节是bill_date用date类型而不是datetime,因为记账本质上只关心“哪天发生”,不关心“几点几分”。查询月账单时直接BETWEEN startDate AND endDate,走联合索引idx_user_date,速度很快。

预算表稍微特殊一点,它需要按分类和月份做唯一约束:

CREATE TABLE `t_budget` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `category_id` bigint(20) NOT NULL, `budget_month` varchar(7) NOT NULL COMMENT '格式:2025-03', `amount` decimal(10,2) NOT NULL COMMENT '预算金额', PRIMARY KEY (`id`), UNIQUE KEY `uk_user_cat_month` (`user_id`,`category_id`,`budget_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

budget_month用字符串存储而不是日期,是因为月份天然就是“年+月”的概念,字符串查询也直观:WHERE budget_month = '2025-03'。加上唯一键后,同一个用户同一分类同一月份只能有一条预算记录,重复设置时直接按更新处理。

2.3 安全认证与用户体系

个人财务系统涉及敏感资金数据,登录认证至少要像那么回事。我使用JWT(JSON Web Token)方案。用户登录成功后,后端把用户ID和用户名加密进token,前端后续请求都带上Authorization: Bearer <token>。后端写一个拦截器统一校验。

这里很多人会问:“用Session不就行了吗?为什么非要JWT?”其实毕设答辩时讲JWT是有加分项的,因为它能体现出你对“无状态认证”的理解。前后端分离架构里,前端可能部署在8080端口,后端在8081端口,Session跨域存在天然劣势,JWT则不需要服务端保存状态,天然适合分布式环境。

JWT的集成也简单。我在项目里加入jjwt依赖后,封装了一个JwtUtil工具类:

public class JwtUtil { private static final String SECRET = "finance-system-secret-key-2025"; public static String generateToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

注意签名密钥不要直接写死,我这里是演示方便。实际项目中应该放在配置文件或环境变量里。token有效期我设置成7天,对个人工具型系统来说刚好,不需要频繁重新登录。密码存储用的是BCrypt,这一点必须强调:一定不能存明文密码,也不要用MD5或SHA1。BCrypt加盐机制天然抵抗彩虹表,Spring Security里自带BCryptPasswordEncoder,但我的项目没引入完整Security,单独引入spring-security-crypto即可,避免Security的权限配置干扰开发。

3. 核心功能实现与实操细节

3.1 收支记录模块的实现

记账是整个系统的地基,功能本身不复杂,但几个细节要注意。Controller接收前端传来的参数时,金额必须用BigDecimal,日期用LocalDate,且通过@RequestBody配合@Validated校验:

@PostMapping("/api/bill") public Result saveBill(@RequestBody @Valid BillDTO dto) { Bill bill = new Bill(); BeanUtils.copyProperties(dto, bill); bill.setUserId(currentUserId()); bill.setCreateTime(LocalDateTime.now()); billService.save(bill); return Result.success(); }

这里有一个常见的坑:前端传的category_id可能是用户自己创建的分类,也可能已经被删除。如果不校验,就会出现“账单关联到不存在的分类”,统计报表时会查不到对应分类名。我在saveBill里加了一步校验,先从分类表查一条id = dto.getCategoryId() AND user_id = currentUserId()的记录,查不到就直接返回“分类不存在”。看似多了一次查询,但保证数据不会被污染。

分页查询账单时,我用MyBatis-Plus的Page对象:

Page<BillVO> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Bill> query = new LambdaQueryWrapper<>(); query.eq(Bill::getUserId, currentUserId()) .eq(StringUtils.hasText(type), Bill::getType, type) .between(beginTime != null && endTime != null, Bill::getBillDate, beginTime, endTime) .orderByDesc(Bill::getBillDate); Page<BillVO> result = billMapper.selectPage(page, query);

selectPage底层会自动拼接LIMIT,不需要手写分页SQL。这里要注意,between条件需要判空,否则用户不选时间范围时会把全表数据都卡进查询条件。我习惯把所有可能为空的条件先用StringUtils或ObjectUtils包一层,这样接口对前端友好很多。

3.2 预算管理与统计报表

预算模块的核心是“预算告警”。用户设置一个分类的月预算后,我需要实时算出这个月这个分类已经花了多少,然后对比预算给出剩余比例。这部分的SQL用聚合函数实现:

@Select("SELECT COALESCE(SUM(amount),0) FROM t_bill " + "WHERE user_id=#{userId} AND category_id=#{categoryId} " + "AND type=2 AND DATE_FORMAT(bill_date,'%Y-%m')=#{month}") BigDecimal getMonthExpenseByCategory(@Param("userId") Long userId, @Param("categoryId") Long categoryId, @Param("month") String month);

注意COALESCE(SUM(amount),0)很关键。如果当月没有支出,SUM会返回NULL,Java里转BigDecimal时容易NPE。COALESCE把NULL变成0之后,剩余额度计算就安全了。DATE_FORMAT在bill_date字段上执行会导致索引失效,但考虑到个人系统的量级很小,这种查询完全没问题。如果你希望严谨,可以把月份条件改成bill_date >= '2025-03-01' AND bill_date < '2025-04-01',那样就能走索引。

统计报表我建议分成三类接口:月度收支总览、分类占比、趋势图。

月度总览的逻辑:传入某个月,分别SUM收入和支出,同时计算上一月的支出做环比。分类占比用于ECharts饼图:按该月支出分类分组,再SUM金额。趋势图用于近6个月或近12个月的折线图:按月份GROUP BY,输出[{month: '2025-01', income: 123, expense: 456}]这种结构。

这里再分享一个聚合查询的细节——用MyBatis写好统计后,返回的VO里不要用Map散装,而是定义一个MonthStatVO,字段固定。Map虽然写起来快,但类型不明确,前端解析时谁都得猜。老师在代码审查时看到清晰的VO类,印象分会高不少。

3.3 图表展示与前端对接

后端接口设计完成后,前端就像拼图一样简单了。我选择Vue 3 + ECharts的方案,ECharts做图表实在方便。截止到这块,后端的主体能力已经齐了,但很多同学做完整套下来,却发现前端调用接口后拿不到数据。我遇到过最普遍的问题就是跨域。

Spring Boot后端默认只允许同源访问,前端运行在http://localhost:5173,后端在http://localhost:8081,两者端口不同,浏览器就会拦截请求。解决方式最简单的是在后端启动类或配置类写一个Cors过滤器:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(Registry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*"); } }

如果你不想让前端每次请求都带token,allowedHeaders("*")已经够用;如果是复杂请求(POST+JSON),浏览器会先发OPTIONS预检,这个配置把预检也放行了。要注意allowedOrigins不能和allowCredentials(true)同时使用泛型*,否则预检会报错。毕设阶段建议直接把IP和端口写清楚,没必要追究通配符细节。

ECharts的图表数据来源就是前面提到的统计接口。饼图用于支出分类占比,折线图用于近半年收支趋势。我前端并不直接拼数据,而是把后端返回的List<MonthStatVO>映射到ECharts的series里,这样后续修改图表更直观。

4. 实战开发中的常见问题与排查技巧

4.1 Spring Boot版本与JDK版本不匹配

这算是我在答复同学的时候遇到最多的问题。很多新手从网上直接拉最新版Spring Boot 3.x,然后又用学校机房默认的JDK 8运行,结果一启动就报错,提示UnsupportedClassVersionError。原因很简单:Spring Boot 3.0之后强制要求JDK 17及以上,连带javax包也改成了jakarta。如果你不想跟版本纠缠,就选Spring Boot 2.7.x + JDK 8,这个组合最稳。

还有一个小坑是Lombok版本。JDK 8环境下,Lombok用1.18.24版本没问题;如果你把编译级别调到17,旧版Lombok会抛java.lang.NoSuchMethodError。解决办法是升级lombok.version到1.18.30同时把JDK切回8。在pom.xml里显式声明版本号,别用spring-boot-starter-parent的默认传递版本。

4.2 MyBatis-Plus分页不生效

正常引入MyBatis-Plus后,直接调用selectPage会出现分页不生效但也不报错的现象——日志里看到SQL没有LIMIT。原因是漏配了分页插件。需要在配置类添加:

@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }

没有这个拦截器,MyBatis-Plus的物理分页就用不了,它只是查询全部数据再内存分页,毕设数据量小看不出问题,但老师问“分页实现原理”时你答不上来。配置好之后,SQL里会自动出现LIMIT ?。

4.3 金额计算精度与前端展示问题

说好的用BigDecimal,但很多同学在JSON序列化时把BigDecimal变成了数字,前端JavaScript处理大整数时会损失精度。虽然个人系统的金额一般到不了亿,但万一用户输入了1234567890.12,前端拿到的就是浮点数,可能变成1234567890.119999。稳妥方法是后端序列化为字符串。在属性上加@JsonFormat(shape = JsonFormat.Shape.STRING),前端展示就永远是精确的字符串,计算乘除时再转回数字。

还有一个小细节是时间格式化。LocalDate默认序列化格式是2025-03-01,这没问题,但LocalDateTime会变成2025-03-01T12:30:00,前端要手动处理。我习惯在application.yml里统一配置一下:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

注意time-zone务必设成GMT+8,否则数据库连接串没加serverTimezone=Asia/Shanghai时,时间会差8小时。两个地方一起配,基本不会出乱子。

4.4 打包部署与答辩演示

毕设演示时最尴尬的场面是什么?是现场启动项目,结果数据库连不上。我的建议是答辩前一晚用一键打包命令跑通完整流程。前端项目先在vue.config.js里设置outputDir到后端src/main/resources/static,或者用nginx托管前端,然后后端执行mvn clean package -DskipTests。如果前端编译产物能打进Jar包,演示时只需要java -jar finance.jar,省去启动前端Node服务的步骤,现场稳定性大幅提升。

打包过程中也有个坑:Spring Boot的repackage目标和普通Jar包重名。如果你在target目录下看到两个同名jar,一个是原始包,一个是可执行包,别弄混。运行要选那个能执行的,也就是带spring-boot-maven-plugin处理过的。如果你在前端编译后构建后端,不小心把原始Jar拷去运行,就会报“没有主清单属性”。

数据库初始化上,我在src/main/resources里放了db.sql,并在application.yml设置了spring.sql.init.mode=always,这样首次启动时自动建表。如果用的MySQL 8,连接串务必要加useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai,否则驱动会报SSL握手和时区错误。

4.5 代码组织与答辩加分项

最后说一下代码结构。很多同学把Controller里写满业务逻辑,几百行看着头疼。我在这个项目里分成了controller、service、mapper、entity、dto、vo几个包,严格遵循Controller只做参数接收和返回,Service处理业务,Mapper只负责数据库操作。答辩老师问“分层的好处”时,你能用“改一个功能不影响别的模块、可测试性强”回答出来,这就够了。

有条件的可以加一点点缓存:查询统计报表时用Spring Cache或简单的ConcurrentHashMap缓存同一个用户当月统计结果,设置5分钟过期。虽然系统单机也没啥性能压力,但能体现出“考虑性能”的意识。我实际演示时会先查询一次,再查一次并预估接口响应时间,向老师展示缓存生效后耗时明显下降。

这套系统前后端加起来大约9000行代码,工作量刚好在大四上学期一个月内完成。个人财务管理系统的好处是每个人都知道业务逻辑,不需要向老师解释“这个系统解决什么场景”,重点全都放在技术实现上。如果你正在准备毕设,可以照着这个设计思路从零搭一遍,源码编号06221的工程也可以作为参考。但记住,毕设最重要的不是代码能不能跑,而是你听完我这些细节后,能指着任意一个文件解释“为什么这么做”“遇到问题是咋解决的”,到那时答辩对你来说就是聊天。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 3:56:32

逆向工程入门指南:从基础知识到实操训练的学习路径

1. 入门逆向工程前&#xff0c;先搞清楚这三件事提到逆向工程&#xff0c;很多人脑海里浮现的画面是炫酷的调试器界面、疯狂跳动的汇编代码、一个回车之后软件授权就被绕过——说实话&#xff0c;我当年也是一样&#xff0c;以为学了逆向就能“所见即所得”地拆掉一切软件。真正…

作者头像 李华
网站建设 2026/10/9 3:56:20

pstack-claude:用Linux栈跟踪诊断Claude/Codex本地代理故障

1. 项目概述&#xff1a;pstack-claude 是什么&#xff0c;它解决的是哪类开发者的真实痛点&#xff1f;pstack-claude 这个名字乍看像一个工具组合词&#xff0c;但拆开来看——“pstack”是 Linux 系统中用于打印进程栈跟踪&#xff08;process stack trace&#xff09;的经典…

作者头像 李华
网站建设 2026/10/9 3:55:56

ASP校友录网站设计全流程:数据库、安全与部署实战

1. 校友录这个"老需求"&#xff0c;为什么我还要用ASP来做先说个背景。我接这个"asp校友录网站设计"的项目时&#xff0c;很多人第一反应是&#xff1a;都什么年代了&#xff0c;还用ASP&#xff1f;确实&#xff0c;如果今天从零做一个全新系统&#xff0…

作者头像 李华
网站建设 2026/10/9 3:55:27

无框架数据驱动UI自动化:用CSV和动作表实现最简方案

聊到 WebDriver 做数据驱动 UI 自动化&#xff0c;很多人第一反应是&#xff1a;上 TestNG、上 JUnit、搞 DataProvider&#xff0c;或者干脆自研一套平台。我在多个项目里试过各种路子&#xff0c;最后的结论恰恰相反——最稳定的方案&#xff0c;往往是不用任何测试框架&…

作者头像 李华
网站建设 2026/10/9 3:55:12

低频量化周报实战:风险溢价比与可转债策略全解析

1. 周报的整体设计与模块搭建逻辑做低频量化这些年&#xff0c;我一直有个执念&#xff1a;策略报告不是写给外人看的漂亮文档&#xff0c;而是写给未来自己看的操作备忘录。每周一份周报&#xff0c;核心目的只有一个——用数据和规则&#xff0c;对抗盘中的情绪波动。很多人以…

作者头像 李华
网站建设 2026/10/9 3:54:53

Java Swing实战:从零实现一个简易画图工具

做Java一段时间后&#xff0c;难免会有这样的时刻&#xff1a;看了不少Spring Boot的服务端代码&#xff0c;却总觉得对“界面”二字的理解停留在HTML和浏览器层面。这时候&#xff0c;如果一个学生或者刚入行的新人跑来问我&#xff0c;想快速理解事件驱动编程、GUI架构、图形…

作者头像 李华