资讯动态

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

发布时间:2026/10/9 3:57:29 来源:尧图企业网站定制
做了几年的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和内置Tomcatspring-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虽然也很优秀但级联和懒加载处理起来更容易绕晕尤其学生党经常写出N1查询。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) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;分类表设计时要注意数据结构。我用了层级分类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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有一个非常关键的设计细节金额字段必须用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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;budget_month用字符串存储而不是日期是因为月份天然就是“年月”的概念字符串查询也直观WHERE budget_month 2025-03。加上唯一键后同一个用户同一分类同一月份只能有一条预算记录重复设置时直接按更新处理。2.3 安全认证与用户体系个人财务系统涉及敏感资金数据登录认证至少要像那么回事。我使用JWTJSON 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对象PageBillVO page new Page(pageNum, pageSize); LambdaQueryWrapperBill 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); PageBillVO 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 type2 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会返回NULLJava里转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(*); } }如果你不想让前端每次请求都带tokenallowedHeaders(*)已经够用如果是复杂请求POSTJSON浏览器会先发OPTIONS预检这个配置把预检也放行了。要注意allowedOrigins不能和allowCredentials(true)同时使用泛型*否则预检会报错。毕设阶段建议直接把IP和端口写清楚没必要追究通配符细节。ECharts的图表数据来源就是前面提到的统计接口。饼图用于支出分类占比折线图用于近半年收支趋势。我前端并不直接拼数据而是把后端返回的ListMonthStatVO映射到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: GMT8注意time-zone务必设成GMT8否则数据库连接串没加serverTimezoneAsia/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.modealways这样首次启动时自动建表。如果用的MySQL 8连接串务必要加useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai否则驱动会报SSL握手和时区错误。4.5 代码组织与答辩加分项最后说一下代码结构。很多同学把Controller里写满业务逻辑几百行看着头疼。我在这个项目里分成了controller、service、mapper、entity、dto、vo几个包严格遵循Controller只做参数接收和返回Service处理业务Mapper只负责数据库操作。答辩老师问“分层的好处”时你能用“改一个功能不影响别的模块、可测试性强”回答出来这就够了。有条件的可以加一点点缓存查询统计报表时用Spring Cache或简单的ConcurrentHashMap缓存同一个用户当月统计结果设置5分钟过期。虽然系统单机也没啥性能压力但能体现出“考虑性能”的意识。我实际演示时会先查询一次再查一次并预估接口响应时间向老师展示缓存生效后耗时明显下降。这套系统前后端加起来大约9000行代码工作量刚好在大四上学期一个月内完成。个人财务管理系统的好处是每个人都知道业务逻辑不需要向老师解释“这个系统解决什么场景”重点全都放在技术实现上。如果你正在准备毕设可以照着这个设计思路从零搭一遍源码编号06221的工程也可以作为参考。但记住毕设最重要的不是代码能不能跑而是你听完我这些细节后能指着任意一个文件解释“为什么这么做”“遇到问题是咋解决的”到那时答辩对你来说就是聊天。

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

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

免费获取报价 →
↑