SpringBoot家庭理财系统:从CRUD到事务、报表与安全部署
发布时间:2026/9/12 17:07:02 作者:尧图编辑部 阅读量:1,286

简介一份基于SpringBoot的家庭理财管理系统完整项目面向Java后端学习者与毕业设计开发者适合用来理解SpringBoot整合Web、持久层及前端资源的实际工程结构。压缩包共233个文件大小4.47MB其中包含96个Java源码、14个XML配置与2个SQL脚本覆盖后端业务、持久层配置及数据库初始化前端部分有40个JS、22个HTML和12个CSS另有图片与字体资源可支撑用户登录、收支记账、预算管理和图表分析等功能演示。当前已有58人浏览学习。资源附带了Maven构建脚本、README说明等目录清晰能帮助读者快速导入项目并梳理SpringBoot的分层开发思路。适合作为课程设计参考或SpringBoot入门项目的实战案例尤其便于从完整代码中学习RESTful API、持久层框架和前端交互的配合方式。1. SpringBoot 家庭理财系统先分清记账 CRUD 和真正麻烦的部分拿到“基于SpringBoot的家庭理财管理系统.zip”这类压缩包时第一反应通常是解压、导入 IDEA、启动页面。但真正决定这个项目好不好改、能不能交付的不是首页长什么样而是数据模型和事务边界。家庭理财的核心动作只有一个“记账”可一旦同时存在账户余额、收支分类、月度预算和统计报表事情就从 CRUD 变成了数据一致性、统计口径和环境部署。适合读这套方案的人有三类拿 SpringBoot 做毕设的学生接手现成 zip 做二次开发的工程师以及想用最小成本搭内部记账原型的后端。下面不假设 zip 里具体包名按一个可跑的 SpringBoot 项目通常该有的样子把模型、接口、统计、部署和排错讲清楚。2. SpringBoot 家庭理财系统的数据模型与 MyBatis-Plus 接口搭建对“基于SpringBoot”的项目第一步永远是看依赖而不是看代码。解压 zip 后先去根目录找 pom.xml确认 parent 版本、JDK 版本和持久层框架。家庭理财这种业务量级最常见的组合是 SpringBoot MyBatis-Plus MySQL少部分会保留 JPA。下面按这套组合讲。2.1 解压后第一步判断单体结构还是前后端分离打开src/main/resources看一眼。如果 templates 或 static 下有成套页面文件且 Controller 直接返回视图名这是单体模板项目如果前端只有 index.html 并调用/api开头地址或者根本没有页面资源那 zip 里的 SpringBoot 只承担后端接口。观察点单体模板项目前后端分离后端src/main/resources/templates存在 .html 页面通常没有src/main/resources/static有 css/js 或打包产物可能有静态资源返回类型ModelAndView / String统一 JSON鉴权方式SessionToken 或 Session判断结果直接决定启动后的访问路径。模板项目访问http://localhost:8080/能看到页面纯接口项目要配合前端 devServer 或把前端资源放入 static 后重新打包。我一般会先跑起来再按接口文档逐个验证。2.2 家庭理财最小表结构五张表撑起主要功能家庭理财不需要账单分期、理财产品持仓那些复杂字段最小可用模型是用户、账户、分类、流水、预算五张表。账户里存余额流水里存每一次收入支出分类给流水打标预算按月做限额。这五张表足够覆盖 SpringBoot 面试里常问的关联查询、分组统计和事务更新。表名职责关键字段sys_user登录用户id, username, password, nicknamefm_account资金账户id, user_id, account_name, balancefm_category收支分类id, user_id, category_name, typefm_transaction收支流水id, account_id, category_id, amount, type, trade_timefm_budget月度预算id, user_id, category_id, budget_month, limit_amountfm_transaction里的type建议用0/1表示支出/收入而不是让金额带正负号。原因很简单统计时SUM(CASE WHEN type1 THEN amount ELSE 0 END)一眼就能看懂避免“负数收入”这种口径混乱。这是记账类项目最常见的隐藏坑。2.3 用 SpringBoot 常用注解写账户与流水实体实体类直接用 MyBatis-Plus 注解不需要写 XML 映射。TableName指定表名TableId(type IdType.AUTO)表示数据库自增主键TableField处理驼峰与下划线字段映射。Data TableName(fm_account) public class Account { TableId(type IdType.AUTO) private Long id; private Long userId; private String accountName; private BigDecimal balance; } Data TableName(fm_transaction) public class Transaction { TableId(type IdType.AUTO) private Long id; private Long accountId; private Long categoryId; private BigDecimal amount; private Integer type; private LocalDateTime tradeTime; }Data来自 Lombok省去 getter/settertradeTime会自动映射到trade_time这是 MyBatis-Plus 默认开启驼峰映射的结果。如果 zip 里用的是 JPA注解换成Entity和Column(name trade_time)即可业务代码差异不大。2.4 账户间转账必须走事务不能两个独立 update家庭理财的“还信用卡”“转零钱”会同时改两个账户余额。很多初学者写成先扣 A 账户再加 B 账户中间一旦报错账就平不上。这个场景是 SpringBoot 事务最典型的使用点。在 Service 方法上标注Transactional保证两个更新操作同成功或同失败Service public class AccountService { private final AccountMapper accountMapper; public AccountService(AccountMapper accountMapper) { this.accountMapper accountMapper; } Transactional(rollbackFor Exception.class) public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) { Account from accountMapper.selectById(fromAccountId); Account to accountMapper.selectById(toAccountId); from.setBalance(from.getBalance().subtract(amount)); to.setBalance(to.getBalance().add(amount)); accountMapper.updateById(from); accountMapper.updateById(to); } }需要注意rollbackFor Exception.class这个参数。Spring 默认只在遇到运行时异常时回滚Exception子类里的 IOException 等检查型异常不会触发回滚。家庭理财里虽然很少抛检查型异常但这个写法能让事务边界更明确。另一个细节是 balance 用BigDecimal而不是double0.1 0.2的浮点误差在金额计算里不允许出现。Controller 层只做参数校验。金额由前端传后端用DecimalMin(0.01)兜底避免负数或 0 元流水进入数据库。3. SpringBoot 定时任务与统计报表预算超支的两种实现方式记账类系统做完增删改查之后价值体现在两个地方月度统计和预算提醒。SpringBoot 里这两块的标准解法分别是 SQL 聚合和Scheduled定时任务。3.1 统计报表先敲定“收入-支出-结余”的口径先想清楚一个口径问题结余是不是等于“本期收入减去本期支出”。对家庭理财来说结余应该等于“期初账户余额 本期收入 - 本期支出”否则信用卡还款会让支出虚高。如果 zip 里的报表让你看不明白八成是口径写差了。分析到这里就不难理解统计必须区分“流水发生时间”和“入账时间”。建议报表全部按trade_time即交易时间分组而不是按创建时间create_time。补录账单在记账类应用里非常常见create_time不具备业务意义。3.2 月度支出汇总优先用 SQL而不是把全表拉到内存业务量小的家庭理财项目用 Java 8 Stream 在内存里分组也能跑但内存方案的问题在于流水表和分类表 join 之后字段膨胀一旦日期过滤条件漏掉全表数据都进堆。我一般让 Mapper 直接返回聚合结果数据库计算 GROUP BY 永远更稳。在 Mapper 接口里定义一个统计方法public interface TransactionMapper extends BaseMapperTransaction { ListMonthlySummary selectMonthlySummary(Param(userId) Long userId, Param(type) Integer type, Param(year) Integer year); }对应 XML 里写聚合 SQLselect idselectMonthlySummary resultTypecom.example.family.dto.MonthlySummary SELECT DATE_FORMAT(trade_time, %Y-%m) AS month, COUNT(*) AS transactionCount, SUM(amount) AS totalAmount FROM fm_transaction t JOIN fm_account a ON t.account_id a.id WHERE a.user_id #{userId} AND t.type #{type} AND YEAR(trade_time) #{year} GROUP BY DATE_FORMAT(trade_time, %Y-%m) ORDER BY month /select这里把fm_accountjoin 进来是为了让统计范围正确落在“当前用户”名下而不是所有家庭成员混在一起。DATE_FORMAT(trade_time, %Y-%m)按年月分组SUM(amount)只统计指定类型。若想同时看支出和收入可以拆成两个 SQL 或用SUM(CASE WHEN type 1 THEN amount ELSE 0 END)的写法。3.3 预算超支提醒Scheduled 定时任务的正确写法预算提醒不适合放在“每次打开报表时现算”常见做法是每天凌晨跑一次扫描把超支记录写入提醒表或发站内信。SpringBoot 里用Scheduled就能实现无需引入 Quartz。Component public class BudgetCheckTask { private final BudgetService budgetService; public BudgetCheckTask(BudgetService budgetService) { this.budgetService budgetService; } Scheduled(cron 0 0 8 * * ?) public void checkDailyBudget() { budgetService.checkOverBudget(LocalDate.now().getYear(), LocalDate.now().getMonthValue()); } }cron 表达式0 0 8 * * ?表示每天早上 8 点触发六个字段分别是秒、分、时、日、月、周。对比几个常见写法cron 表达式执行时机适用场景0 0 8 * * ?每天早上 8 点预算超支提醒0 0 1 * * ?每天凌晨 1 点流水日终汇总0 0 0 1 * ?每月 1 号零点月度报表生成0 0/30 * * * ?每 30 分钟一次轻量轮询别忘了在主类或配置类上加EnableScheduling这是 SpringBoot 定时任务最常见的漏配项。另外Scheduled默认单线程执行多个任务相互阻塞。家庭理财里任务数量少影响不大如果后续加了短信发送、账单导出的任务可以配置Async或引入线程池。3.4 什么时候才需要引入 Redis 缓存家庭理财系统的日活通常不高统计 SQL 直接查数据库并没有压力。但如果是“打开首页就要展示近 6 个月收支图表”的场景每次查询都在线聚合九次响应会变慢。常见做法是把统计结果按“年用户”缓存到 Redis定时任务里每天重算一次用Cacheable或手动stringRedisTemplate.opsForValue().set()都行。缓存失效时间设置为“当天凌晨 4 点后”避开通宵记账的少数场景。不过不要因为 SpringBoot 项目自带 spring-boot-starter-data-redis 就强行缓存。家庭理财的量级一个ConcurrentHashMap也能撑住Redis 引入的前提是统计查询已经能从日志里看到明显的耗时。4. 把 zip 里的 SpringBoot 项目真正跑起来配置、启动和报错排查拿到 zip 后最常见的挫败不是代码不会写而是项目起不来。配置、版本、端口、数据源全是变量。这一章按实际启动顺序把参数和报错讲清楚。4.1 IDEA 导入Maven 依赖下载和 JDK 版本确认解压后用 IDEA 的File - Open直接选文件夹IDEA 会识别 pom.xml 并开始下载依赖。等右下角 Maven 导入进度条结束先看两处Project Structure - Project SDK是否和pom.xml里java.version一致Settings - Maven - Runner - JRE是否为对应 JDK。大多数“启动直接报错”都和这两个地方错位有关。如果 zip 里带了mvnw.cmd也可以在命令行执行./mvnw spring-boot:run效果等同 IDEA 内启动。第一次下载依赖耗时取决于网络环境出现红色波浪线时先mvn -U clean compile强制更新快照而不是急着改代码。4.2 application.yml 里几个关键参数SpringBoot 项目的主配置通常在src/main/resources下的 application.yml 或 application.properties 中。家庭理财类项目最少要配数据源、端口和 MyBatis-Plus 日志server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/family_finance?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: ${DB_PASSWORD:123456} driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted几个参数值得留意配置项作用常见误用server.port应用端口改配置不重启以为端口会热加载spring.datasource.url数据库地址忘记加 serverTimezone导致时间差 8 小时${DB_PASSWORD:123456}环境变量注入密码直接把明文密码写进打包文件logic-delete-fieldMyBatis-Plus 逻辑删除不加注解删除变成物理删除${DB_PASSWORD:123456}表示优先读环境变量DB_PASSWORD没有才用默认值 123456。部署到服务器时用环境变量注入密码比把密码写死在 jar 里安全得多。4.3 SpringBoot 版本太高导致的启动失败“springboot版本太高”不是段子而是真实场景。zip 里如果用 SpringBoot 3.xJDK 必须 17 以上如果 pom 里写的是 2.7.x但本机用 JDK 21有时也会出现依赖不兼容。这是 SpringBoot 自动装配在初始化阶段被依赖版本卡住的典型表现。启动失败的典型报错是Unsupported class file major version 65major version 65是 JDK 21 的特征而当前 SpringBoot 版本依赖的字节码解析工具不认识它。解决办法不是换 IDE而是对齐版本要么把本机 JDK 切回 17要么把 SpringBoot 版本降到 2.7.x 分支并使用 JDK 8 或 11。我一般遇到这类项目先看spring-boot-starter-parent版本再决策parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.13/version relativePath/ /parent2.7.13 是 2.7.x 分支里踩坑较少的版本。如果 zip 里是 3.x请确认本机至少 JDK 17而且javax.servlet相关依赖要换成jakarta.servlet这是老项目升级后找不到HttpServletRequest的直接原因。4.4 常见启动报错速查报错现象排查方向处理办法Failed to configure a DataSource数据源没配或连接失败检查 url、账号密码、数据库是否创建Invalid bound statementMapper 接口和 XML 不对应确认 XML namespace 和接口全路径一致Port 8080 was already in use端口占用改 server.port 或lsof -i:8080找进程Consider defining a bean of type xxxMapper扫描不到 Mapper主类加MapperScan(com.xxx.**.mapper)java.lang.NoClassDefFoundError依赖冲突或不完整mvn dependency:tree查冲突再 clean install启动阶段的日志要分两层看第一层是 Spring Boot 自动装配报告第二层才是堆栈。看到APPLICATION FAILED TO START时先找日志里Description:后面的段落那一段才是 SpringBoot 给出的原因堆栈信息经常是根因被包装后的表象。5. 进阶技巧验证 SpringBoot 项目的 heapdump 和配置端点是否对外暴露最后说一个容易被忽略但值得马上检查的点SpringBoot 项目如果引用了spring-boot-starter-actuator默认或错误配置下会把 heapdump、env、beans 这些敏感端点暴露在公网。家庭理财系统里带着真实账单和账户余额这类泄露比功能缺陷更危险。5.1 为什么 heapdump 是敏感信息重灾区/actuator/heapdump会把 JVM 堆内存完整下载下来Redis 密码、数据库账号、用户会话、第三方密钥都在堆里。很多开发者以为内网部署就不需要管实际上端口映射到公网后扫描器探测/actuator路径非常常见。5.2 用 curl 验证当前项目是否中招先启动项目然后用下面命令试探curl -s -o /tmp/heapdump.hprof -w HTTP %{http_code}, size %{size_download} bytes\n \ http://localhost:8080/actuator/heapdump如果返回HTTP 200且下载文件开头能看到JAVA PROFILE字节流说明 heapdump 暴露。size_download会给出堆文件大小大的堆文件即使下载下来也要小心处理不要随手转发。再检查配置泄露curl -s http://localhost:8080/actuator/env | head -n 20返回 JSON 里如果出现datasource或password字段说明敏感配置可以被未授权读取。5.3 快速修复并确认端点收敛在 application.yml 里只暴露健康检查端点management: endpoints: web: exposure: include: health,infoinclude改为health,info后heapdump、env、beans 等端点不再通过 Web 访问。重新启动再用 curl 验证curl -s -o /dev/null -w %{http_code}\n http://localhost:8080/actuator/heapdump期望输出404。如果项目连 actuator 都没引入说明没有这个暴露面但更彻底的做法是确认生产环境不开启management.endpoints.web.exposure的全部端点。management.endpoint.health.show-details也建议保持默认的never避免健康检查接口把数据库和磁盘状态细节暴露给外部。本文还有配套的精品资源点击获取