资讯动态

Spring Boot个人财务系统实战:从搭建到报表分析的全流程解析

发布时间:2026/10/17 2:10:45 来源:尧图企业网站定制
1. 项目核心需求拆解——先想明白这个系统到底要做什么很多同学拿到“基于Spring Boot的个人财务管理系统”这个毕设题目第一反应是不就是账单增删改查嘛简单。真做起来才发现光是“个人财务”这四个字就能扯出一堆隐藏需求。我见过太多人从CRUD起步最后被统计报表、金额精度、数据权限这些问题反复折磨所以第一步先把需求拆透。这个系统面向的核心用户就是个人用户解决的核心问题是用户无法直观了解自己的钱花在哪、收入来自哪、每月结余多少。你说记账App一大堆为什么还要用Spring Boot自己做因为毕设的重点不在于“取代某某App”而在于完整走一遍Web系统的分析、设计、编码、测试流程把教科书里的三层架构、数据库设计、异常处理、权限拦截这些东西落到真实项目里。功能上我个人认为必须覆盖几条线一是用户体系要能注册、登录不能裸奔二是账单管理收入、支出、转账至少收入和支出要拆清楚三是分类管理每一笔账要归类伙食、交通、娱乐否则后面统计就是一笔糊涂账四是预算管理可以按月份和分类设置消费上限五是统计报表按月汇总收支、按分类看占比这是整个系统的价值高地也是最容易出彩的模块。说句实在话如果只做增删改查答辩时大概率会被追问到哑口无言。但把统计报表和预算提醒做出来整个系统的完整体验就立住了。还有一些容易被忽略的细节需求账单需要支持按日期范围和关键词筛选分页列表要能看总条数删除操作要不要软删除金额字段用什么类型存储日期区间怎么处理闭开区间。这些点跟后面数据库设计和代码实现强相关提前想清楚能省很多返工时间。权限方面个人系统小不需要复杂的多角色权限模型但“登录后才能访问”这个拦截是要做的。更细一点如果用户A登录后直接越权查用户B的账单数据这就是严重的数据泄露漏洞答辩老师一定会问。个人系统同样要守住数据归属这一层。适合参考这个项目的人我建议是这三类一是正在做同类毕业设计、需要系统化思路的同学二是想用Spring Boot做完整全栈练手、但没想清楚功能怎么设计的初学者三是工作了想快速搭一个个人记账工具、顺便梳理后端工程化的开发者。搞清楚需求和目标后再开始动手写代码你会发现顺手很多。1.1 明确核心功能边界与角色划分个人财务系统虽然叫“个人”但至少要有两个角色视角游客和登录用户。游客只能看登录页和注册页登录用户能管理自己的所有数据。有些同学喜欢引入“管理员”角色我认为对个人系统来说意义不大还会把权限复杂度抬上去。毕设的核心是展示完整业务闭环不是把权限做成RBAC模型表演。功能边界一定要在文档里写清楚特别是“不做什么”。比如这个系统不做多币种兑换不做第三方支付对接不做家庭共享账本。明确边界的好处是答辩时老师说“你怎么不做某某功能”你可以有理有据地说这是定位取舍而不是暴露自己没想过。我当时在需求说明里专门写了一节“非功能需求与边界申明”反而成了加分项。角色用例方面登录用户的核心用例包括管理账单增删改查、按条件查询、管理分类预设加自定义、设置月度预算、查看统计报表。每个用例配几条关键的业务规则比如“支出账单发生时对应分类的月度预算剩余额度要联动更新”。这些规则最好在数据库设计之前定下来否则后面改表结构非常痛苦。1.2 数据流转与状态设计——把账算明白财务系统的核心是数据流转要闭环。用户记一笔支出数据从页面表单进来经过后端校验写入账单表同时要影响到分类表的统计、预算表的剩余金额、月度汇总报表的数据源。这里有个很容易踩的坑如果预算剩余金额是冗余存储那么每一笔账单的增删改都要同步更新冗余字段事务控制稍有不慎就会数据不一致。我自己的做法是预算剩余金额不直接冗余存储而是通过SQL实时聚合计算。月度汇总报表也一样用GROUP BY按月份聚合账单表得到结果不入库存储。个人系统的数据量级摆在那里最多几万条账单实时聚合一秒内都能出结果根本不需要引入复杂的汇总表设计。这样既保证了数据一致性又把实现复杂度降下来了是非常适合毕设体量的取舍。状态设计上账单本身不需要复杂状态机。但为了体现设计能力我加了一个“软删除”状态账单表加deleted字段逻辑删除而非物理删除。这样做的好处是统计报表只需要过滤deleted0的数据误删的账单还能从数据库里找回而且能在文档里写上一段“为什么选择逻辑删除而非物理删除”展示你的工程意识。1.3 技术选型背后的逻辑技术栈的选择说到底要看三个约束官方的任务书要求、你掌握的熟练度、以及这个技术栈是否能支撑需求实现。常见的组合是Spring Boot MyBatis Plus MySQL Thymeleaf或者Spring Boot MyBatis Plus MySQL Vue。前后端不分离的方案更容易落地工程结构简单答辩演示时不需要启动两个服务Vue方案视觉效果更现代但联调成本、部署复杂度都会上升。我个人推荐的做法是如果不是老师明确要求前后端分离优先考虑单体服务端渲染方案。原因是毕设的评审核心在后端逻辑也就是Controller、Service、Mapper这三层质量。把精力花在JPA级联关系、SQL聚合分析、事务注解、参数校验、异常处理上远比纠结Vue组件通信更有价值。持久层框架我强烈建议MyBatis Plus而不是原生MyBatis。不是MyBatis不行而是MyBatis Plus的分页插件、自动填充、逻辑删除、BaseMapper泛型CRUD能帮你省下好几天的机械工作量把时间留给真正有含金量的统计SQL和业务逻辑上。Spring Boot版本建议用2.7.x系列稳定且资料多规避Spring Boot 3的潜在兼容坑。2. 数据库与项目结构设计——地基不牢后面全抖如果说业务逻辑是财务系统的血肉那数据库表结构就是骨架。我见过有人把账单的类型设计成varchar存“吃饭”“打车”“工资”结果统计分类占比时只能写一堆case when。毕设项目最好老老实实用自增主键加外键逻辑关联把表拆干净宁可多一张关联表也不要把一堆字段塞进一张表里。这个系统我设计的核心表结构有六张用户表、账单表、分类表、预算表、账户表、操作日志表。用户表存账号密码和基本信息账单表存每笔收支的金额、类型、分类id、时间、备注分类表分预设和用户自定义两类预算表记录用户某月某分类的预算额度跟账单是弱关联账户表记录支付宝、微信、现金等资金渠道操作日志表记录关键写操作用于排障和回溯。其中最有讲究的是账单表和分类表的关联关系。分类表不能纯静态预设因为用户有自定义需求。我的做法是分类表加一个user_id字段user_id为0表示系统预设分类查询时用“where user_id 0 or user_id 当前用户”取并集逻辑这样既有默认值兜底又允许个性化扩展。这里的SQL需要做一点去重处理预设和用户自定义优先取用户自己的分类。金额字段的设计更是重灾区。普遍建议用decimal(10,2)如果你知道double在精度上的坑就不会想用。在需要精确计算财务金额的系统中浮点数误差是不可接受的。转账、结余计算、预算比较这些操作全部是基于精确十进制运算。如果项目中已经有double存金额的历史代码建议尽快改造这一步越早做越心安。时间字段也有讲究。账单的发生时间用datetime记录创建时间用timestamp由数据库自动填充更新时间用timestamp on update。在Java实体里用LocalDateTime而不是Date这样配合Jackson能避免时区偏移问题。日期区间查询时一定要把结束日期自动加一天再比较否则当月最后一天的账单就会查不出来这个细节我后面会单独讲。主键策略我选的是数据库自增而不是UUID。虽然分布式场景UUID更安全但个人系统的数据量级根本不需要分布式主键。自增主键的好处是索引体积小、查询快、分页有序。逻辑外键不要建物理外键约束但在代码层面必须控制。物理外键在删除分类时会触发一系列级联限制尤其是分类被账单引用时物理外键会让删除超级麻烦而逻辑外键配合业务校验反而更灵活。2.1 数据库表结构详解与字段设计思路先看最核心的账单表我拆开来讲。主键id用bigint自增user_id指向用户表这是数据归属的核心字段所有查询语句都必须带上这个条件防止越权访问。bill_type列用tinyint0表示支出1表示收入这个设计比存字符串强得多统计时可以直接用SUM配合IF表达式不用写字符串比较。category_id关联分类表amount列是decimal(10,2)这个是全系统的命脉字段精度必须死守。bill_time存业务发生时间而不是直接用create_time当天这个设计决定了月度报表的准确性。想象一个场景用户在1月31日23点59分记了一笔账如果按创建时间统计就归到1月但实际消费发生在2月这时候报表就对不上。备注字段remark用varchar长度200就够不需要text节省空间也足够用。deleted列是逻辑删除标记默认0删除时改成1查询统一过滤。分类表的字段设计中分类名用varchar(50)user_id区分预设和用户自己创建type字段区分收入分类和支出分类。预算表是整个系统最复杂的关联表字段包括user_id、month存yyyy-MM格式、category_id、budget_amount、create_time。这里month用varchar(7)存储而不是datetime目的是简化月份匹配逻辑SQL里直接按月字符串GROUP BY即可。2.2 项目目录结构与分层设计后端工程结构我推荐经典的四层分包方式清晰且符合大多数答辩老师的口味。controller层作为请求入口只做参数接收和响应包装service层承载核心业务逻辑包括账单增删改查、预算联动、统计聚合mapper层通过MyBatis Plus的BaseMapper和自定义SQL完成数据访问model层放实体类、DTO和VO。dto是数据传输对象用于接收前端入参vo用于返回前端展示数据两者跟数据库实体剥离开。如果你做完这四层之后还有余力建议单独加一个common包存放统一返回结果类、全局异常处理器、分页结果对象、常量类。有了统一返回结构前后端对接的约定就固定下来了业务异常也由全局处理器统一接管Controller里的try-catch脏代码会大幅减少。一个好的通用类设计带给代码的整洁度提升是肉眼可见的。前端方面静态页面放在templates目录和static目录下。Thymeleaf页面里通过th:if、th:each做条件渲染和列表循环表单数据通过modelAttribute绑定。资源文件的组织顺序也要符合MVC约定css、js、图片归类存放避免后面写联调脚本时被路径问题缠住。2.3 通用返回类与统一异常处理统一返回体R 很不起眼但对整个开发流程帮助巨大。这个类通常包含code、message、data三个字段成功返回code为200业务失败返回自定义错误码。有了这个约定前端用axios或者jQuery判断code是否等于200就能统一决定是渲染数据还是弹出错误提示比返回裸对象再猜状态合理得多。注意在R类上标注序列化注解确保LocalDateTime等Java 8时间类型输出格式正常。统一异常处理用RestControllerAdvice注解实现在类上声明两个方法一个拦截BizException另一个兜底拦截NullPointerException等未预期异常。好处是业务层遇到问题只需要抛出带错误码的BizException全局处理器统一转成R格式返回。以前在Service里层叠catch打印堆栈、再包一层的做法实际上是一种内耗型的错误处理既没提升用户体验又让代码难以阅读。日志切面也值得做用Aspect在Controller层记录每个接口的耗时和入参。这套设计让系统运行状态可观测排障时能快速定位是哪一条SQL慢。日志级别别全打INFO写操作打INFO查询接口建议用DEBUG否则高并发测试时日志量会很感人。日志里绝不能打密码等敏感字段。3. 核心功能模块的详细设计与落地实现功能模块的详细设计是展示工程能力的主战场。这里我挑几个含金量最高的模块展开因为普通CRUD不值得花篇幅写但CRUD之外的思考才是区分优秀代码和作业代码的分水岭。3.1 用户登录注册与拦截器权限控制用户模块的第一件事是密码加密存储。密码一定不能用明文存储哪怕这是毕设。加密方案我建议加盐哈希而不是简单MD5使用BCrypt这个算法Spring Security框架里集成得很成熟。BCrypt最大的特点在于每次生成的哈希值都不相同这在一定程度上解决了两条相同记录比对的问题对暴力破解的抵御能力也比MD5强很多。自己写MD5加盐也行但BCrypt方案要省心得多。注册接口只需要处理四个参数账号、密码、确认密码、昵称。校验逻辑包括账号不能重复、两次密码一致、密码长度至少6位。密码加密后写入用户表。登录成功后生成token存入内存用拦截器统一校验。这里我需要提醒一点不要只在登录接口返回token字符串就完事了必须考虑“后续每次请求都携带token”这个交互闭环前端要写axios拦截器统一带请求头后端在拦截器里解析出用户id存入ThreadLocal供Service层随时取用。数据归属控制的终极形态是Service层所有查询方法的第一行必定通过getCurrentUserId()拿到当前登录用户id然后所有的where条件都带上这个参数。例如查看账单列表时SQL里会同时存在user_id ? and deleted 0这两个条件。如果你忘了加user_id这个条件那这个系统随时可能翻到另一个人的账单这在答辩演示时丢脸非常彻底说明你的数据隔离设计有漏洞。拦截器配置上要注意白名单设置。登录页面、注册接口、静态资源路径不需要拦截除此之外的接口必须登录。配置方式是在注册拦截器时调用excludePathPatterns方法传一个白名单数组。这里有个文档注释都值得写的点拦截器只是前端访问控制真正保护数据安全的是Service层的每个查询都带上用户id这两层要配合使用。3.2 账单管理模块条件检索、分页、参数校验账单模块是CRUD的核心。列表接口接收一组查询参数包括账单类型、分类id、起止日期、关键词、页码、页数。这里比较考验工程能力的是把这组条件正确拼装成MP的QueryWrapper。我的做法是构造一个LambdaQueryWrapper根据参数是否为空决定是否追加条件让Controller层保持轻薄。注意type等于null时就不要加到where里这种判断逻辑虽然不是高深技术却直接影响接口正确性。日期区间查询是最容易出bug的地方。前端传的开始日期是2024-01-01结束日期是2024-01-31你以为SQL应该BETWEEN 2024-01-01 AND 2024-01-31但这样会把1月31日00:00:00之后的记录全部漏掉。正确的做法是把结束日期加一天变成2024-02-01再用ge和lt两个条件组合。也就是billTime startDate AND billTime endDatePlusOneDay。这个“左闭右开”的区间设计是整个查询模块正确性的基石。分页查询我直接用MyBatis Plus的Page对象传入页码和页数返回结果里直接带total和records。MyBatis Plus还自动封装了分页对象的总数计算但这背后会执行一条count语句。字段不一致时得留意数据库表字段和Java实体的驼峰映射要在配置里开启map-underscore-to-camel-case否则查询出的账单对象里categoryId永远为null排查半个小时后你会感叹这个字段映射开关有多重要。参数校验方面实体类字段上加上NotBlank、NotNull、DecimalMin这些注解然后在Controller参数上标注Validated。例如金额字段加DecimalMin(value 0.01, message 金额必须大于0)这样非法数据在入口处就被拦截了。而分类id则加一条NotNull校验。Spring框架的validation是一件非常实用的事几乎没有额外成本效果却立竿见影。3.3 统计报表模块SQL聚合分析的艺术统计报表是整个系统里最体现技术含量的模块也是最容易拿分的部分。这里我拆成两块来讲一是收支总览报表二是分类占比报表。收支总览需要计算当前月份总收入、总支出、结余我用三条SQL分别SUM。更优雅的做法是一条SQL搞定SELECT SUM(IF(bill_type1, amount, 0)) as income, SUM(IF(bill_type0, amount, 0)) as expense FROM bill WHERE user_id? AND DATE_FORMAT(bill_time, %Y-%m) ? AND deleted0。这里用MySQL的IF函数在聚合时根据bill_type分流一次查询就把两种类型金额都算出来了。如果想更有展示效果可以按天生成折线图数据。这里分享一个月度消费趋势的SQL写法SELECT DATE(bill_time) as day, SUM(amount) FROM bill WHERE DATE_FORMAT(bill_time,%Y-%m)2024-01 AND bill_type0 GROUP BY DATE(bill_time)。GROUP BY之后的结果列表直接传给前端作为ECharts折线图的data完全不需要后端再循环加工。这条GROUP BY的SQL虽然简单但在毕设项目里是加分项。分类占比报表最考验SQL功底。核心逻辑是按category_id分组求和然后JOIN分类表把分类名带出来。例如SELECT c.category_name, IFNULL(SUM(b.amount),0) FROM bill b LEFT JOIN category c ON b.category_idc.id AND c.deleted0 WHERE ... GROUP BY b.category_id。注意我用了LEFT JOIN加ON条件的写法避免分类被删后账单也变为null这也是一种数据完整性的兜底。3.4 预算管理模块预警逻辑的巧妙实现预算管理的核心是预算额度的设置和超额预警。用户可以选择某个月份和分类设置预算上限。校验逻辑是同一分类same月份的预算记录不能存在两条所以新增时要做唯一性校验。当然SQL层面也可以用唯一索引去兜底字段是user_id、month、category_id三者的联合唯一索引有这道保险后代码层的并发判断才万无一失。超额预警的实现思路是查询某分类在指定月份的消费总额用SUM聚合再和预算表里对应金额比较。关键在于这就是两条简单的数据查询不用写复杂的存储过程或定时任务。前端展示时可以做成进度条消费额除以预算额度超过100%变红色。此外还可以在账单新增成功后顺带查询消费汇总及预算金额把剩余额度返回给前端提示用户“本月餐饮预算剩余多少元”这个交互设计非常贴近实际软件产品。3.5 数据可视化图表展示的逻辑统计报表页面的前端我推荐用ECharts而不是手写Canvas或Highcharts。ECharts的饼图、环形图、折线图配置简洁中文文档全社区例子多复制一个折线图基础模板再替换数据数组十分钟之内就能出一个像样的图表。而且ECharts不需要授权CDN加载很快适合在答辩现场演示。数据格式化上要注意图表的数据源应该来自后端聚合接口返回的ListMapString, Object不要前端自己聚合。原因是前端拿到的原始明细数据在数量和分类上都需要汇总逻辑在前端做会显得可信度低。后端写SQL聚合不但效率高而且能演示你在SQL层面的功底。把图表数据和列表数据分成两个接口返回是清晰接口设计的体现。4. 实操过程从零构建整个系统有了设计落地是关键。这一部分我希望把实操过程中的步骤和坑都写实你跟着走一遭就有完整的感知。但我也要提醒一下具体操作和工具版本会因为时间推移有所变化一定要以官方最新文档为准我这里分享的是思路和易错点。4.1 工程初始化与依赖引入创建工程推荐用Spring Initializr填好Group和Artifact选Java 8或11依赖选Spring Web、MySQL Driver、Validation。然后手动加MyBatis Plus和Lombok依赖如果你决定前端是Thymeleaf同样这里加上。Lombok的Data注解能省掉整个实体类的getter/setter噪音几页代码压成几十行对看代码的人非常友好。一个工程初始化的常见坑点是Spring Boot与MyBatis Plus版本不匹配。解决建议是直接用官方文档标注的对应版本一般MyBatis Plus 3.5.x对应Spring Boot 2.x都能跑通。版本不匹配时最常见的报错是BeanDefinitionStoreException或者NoSuchBeanDefinitionException如果你看到这两个错误先不要怀疑自己的代码把依赖版本的兼容性查一遍再说。配置application.yml时数据源地址用jdbc:mysql://localhost:3306/finance?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。这个serverTimezone参数不加字符集和时区会闹脾气。开启MyBatis Plus的驼峰映射和逻辑删除配置具体是mybatis-plus.global-config.db-config.logic-delete-field: deleted然后再配置logic-delete-value与logic-not-delete-value分别对应删除和非删除的字段值。这套配置搞定后代码里就可以通过TableLogic注解实现自动逻辑删除省去每个Mapper层手动拼接deleted条件。4.2 通用代码封装与登录模块实现第一步写R 统一返回类构造放一个静态成功方法、一个失败方法。第二步写全局异常处理器拦截参数校验异常和业务异常。第三步写ThreadLocal工具类保存当前用户信息。第四步写JWT工具类生成和解析token。这四个类一写完就像给系统铺好了轨道后面的代码只需要在轨道上正常行驶即可。登录模块我做个细节展开。Controller接收LoginDTOService层先校验账号是否存在再校验密码是否匹配两个都不通过时抛出对应异常信息避免告诉用户“账号或密码错误”。密码校验用BCryptPasswordEncoder的matches方法把明文密码与数据库密文比对。登录成功后用工具类生成token返回同时清除该用户可能存在的旧token防止并发登录导致会话混乱。拦截器实现时实现HandlerInterceptor接口在preHandle方法里从请求头获取token调用工具类解析。解析成功则把用户信息存入ThreadLocal解析失败抛出未登录异常。配置类上标注Configuration把拦截器注册进InterceptorRegistry登录页面和注册接口排除在白名单外。每次请求结束时记得在afterCompletion里remove掉ThreadLocal中保存的用户信息防止线程复用污染数据。4.3 账单分类的导入与维护功能实现分类管理模块虽然看起来简单却是最容易体现细节的地方。系统预设分类需要在数据初始化时插入这一步可以在项目启动时通过CommandLineRunner执行也可以在数据库初始化脚本里写死。我更推荐写在init.sql脚本里简洁易懂而且方案可复现。预设分类建议包括餐饮、交通、购物、居住、娱乐、医疗、工资、理财、人情等这些是日常记账的基础分类。用户自定义分类的添加接口需要校验分类名不能为空、不能超过长度、同一用户的分类名不能重复。删除分类时面临一个关键决策这个分类下可能已经存在账单记录直接物理删除会导致账单失去分类关联报表统计出错。我的方案是给分类表同样加逻辑删除标记被软删除的分类不影响历史账单的历史聚合。而新建的自定义分类没有一个was_deleted重复名校验这里可以写一个校验帮助用户同一名字已经用过后自动加后缀。此外也可以直接拒绝重名看你对产品细节的把控。4.4 前后端联调要点即使是服务端渲染前后端联调也是一个关键环节。Thymeleaf页面获取列表数据的方式是Controller返回视图名称并填充ModelAndView页面通过th:each遍历。但分页这个场景下如果你想做异步刷新表格那还是得单独返回到R对象配合AJAX请求弄个局部刷新。这种情况我建议的折衷方案是页面加载时第一次渲染用Thymeleaf之后的分页、查询、删除全部走AJAX接口这样兼顾首屏速度和交互体验。前端页面核心包括登录页、注册页、主页数据总览、账单列表页、分类管理页、预算管理页、统计分析页。实际开发中我建议抽取一个公共的navbar片段所有页面通过th:replace引入减少重复代码。表格的操作列加上编辑和删除按钮删除操作要二次确认弹窗防止误删。查询表单用startDate、endDate两个日期输入框点击查询按钮触发表格数据刷新并重置页码到1。统计页用两个div分别放折线图和饼图饼图数据通过fetch调用聚合接口动态加载。5. 常见问题排查与调试实录——我踩过的坑都在这里开发和调试过程中会遇到不少问题这里选最典型、最让人郁闷的几个展开讲每一类我基本都亲自撞过墙应该能帮你少走不少弯路。5.1 日期查询丢失边界数据这是账单列表模块最常见的bug。客户反馈说查2024年1月的账单1月31日的记录就是不显示。我第一次排查时先看SQLBETWEEN 2024-01-01 AND 2024-01-31看着完全正确结果却漏了最后一天这就是上面提到的datetime与date精度问题。修正思路是代码中判断最大日期那天单独加23:59:59或者更普适地把结束日期加一天用lt比较。这个细节如果存在组合条件里还需要补把加了一天之后的日期传进SQL。用bug驱动法来讲很多同学都是在这一步才真正理解为什么底层会把date转成00:00:00。5.2 localStorage或SessionStorage数据过期与一致性前端存储token时如果放在sessionStorage里浏览器一关闭就丢了但某些浏览器持久化用户登录状态又不想要。我用的折衷方案是token存localStorage同时存一个expireTime字段请求前统一判断是否过期。Token的过期时间设置为24小时超过时间就清理本地缓存并跳转登录页。这样即使用户每天访问一次不会频繁被要求登录也不会长期处于过期的登录态。这个方案在个人系统里很合适不必引第三方依赖。5.3 LocalDateTime前后端序列化格式不统一后端返回的时间字段是2024-01-01T10:30:00前端却显示一小堆T字母加一串数字特别像时间戳拼接。这是因为Jackson默认序列化LocalDateTime时带上T分隔符与JSON常用的“yyyy-MM-dd HH:mm:ss”格式不符。解决办法是在application.yml中配置spring.jackson.date-format和time-zone或者在实体类字段上标注JsonFormat(pattern yyyy-MM-dd HH:mm:ss)。前者是全局生效不用每个字段都标注推荐优先改全局配置。5.4 分页查询时total异常或永远为0这种情况多半是因为MyBatis Plus的分页插件没有注册。只引入了mybatis-plus-boot-starter还不够必须显式声明分页拦截器配置类把PaginationInnerInterceptor加上并指定DbType.MYSQL。另一类原因是连接数据库的URL中带了allowMultiQueriesfalse部分分页SQL会同时执行count和page查询这时候要确认为true。如果你连上这个配置了查询仍然total为0多半是查询条件拼接错误导致数据源实际上查不到东西可以先在控制台输出SQL确认。5.5 金额精度失真——decimal还是double的教训我有个测试账户塞了十几笔金额为0.1、0.2、0.3的数据进去一合计得到的结果是3.0000000000000004。当时看到这个数据我想了半天最后才意识到double浮点数导致的精度误差。后来账单表全部改成decimal(10,2)Java实体用BigDecimal接收SQL聚合时用SUM(amount)也因为字段类型是decimal而自动精确。改完以后报表、结余、分类占比全部正常再也没看到一长串诡异的小数尾巴。存储金额这类字段别存侥幸心理用double改为decimal一次到位。如果是学习阶段时间紧至少要确保在SQL层用ROUND函数或者CAST成decimal。5.6 跨域或静态资源被拦截导致页面空白有同学写了完整的登录页和首页运行后发现首页空白的看不到静态CSS。别急着怀疑代码写错先检查静态资源路径是否被拦截器排除。thymeleaf模板渲染时资源路径要写th:href{/css/style.css}而不是字符串硬拼。如果安全框架或拦截器把静态资源也拦了会把css/js请求重定向到登录页这样CDN资源全部404页面自然就裸奔了。拦截器excludePathPatterns里要保留静态资源目录如/css/, /js/, /images/**, 也可能包含favicon.ico否则控制台里那一堆404会让你心烦气躁。5.7 数据库连接乱码与表格数据中文显示问题中文乱码很难排查因为它可能出现在三个位置数据库连接URL少了characterEncodingutf8、表的默认字符集不是utf8mb4、前端页面没声明 。我推荐建表时统一DEFAULT CHARSETutf8mb4连接URL加上characterEncodingutf8。若是前端Ajax提交的中文乱码则检查form表单是否没设置accept-charset或后端Controller入参编码。实操中还有一个隐蔽点mybatis经配置文件写的SQL如果字符串拼接中文如果IDE编码是GBK几乎必乱IDE全局编码统一UTF-8是最稳妥的。5.8 事务不生效或自调用导致回滚失败在Service里同一个类的另一个方法调用当前类方法时如果方法上带有Transactional那么Spring事务代理会失效。Spring通过AOP代理实现事务而自调用不走代理所以注解形同虚设。解决办法是业务方法分成两个不同的Service类或者注入自身代理。如果想观察一个事务到底有没有生效我建议在方法内故意抛一个RuntimeException之后到库里查数据是否回滚。注意rollbackFor属性必须指定为Exception.class或Throwable.class否则捕获了检查异常事务就不会自动回滚。5.9 MyBatis Plus中LambdaQueryWrapper与字段命名踩坑当实体字段使用驼峰命名而数据库列名使用下划线时开启下划线转换开关后LambdaQueryWrapper在多数场景能正常对应。但如果使用了注解TableField(category_id)显式指定了数据库列名那么QueryWrapper的字段引用最好用数据库列名否则生成的SQL会混淆。经典案例是某同学LambdaQueryWrapper.eq(Bill::getCategoryId, cid) 结果生成categoryId列名数据库报错unknown column。排查重点看控制台打印SQL按提示去沿着mapper配置往下找。5.10 部署到云服务器上的环境适配问题本地跑得风生水起部署到服务器上总出幺蛾子。最常见就是JDK版本不兼容本地用JDK 11编译的jar包放到JDK 8的服务器上直接报UnsupportedClassVersionError。记得服务器上的JDK版本要大于等于本地编译版本。数据库连接地址要改成服务器的公网IP或内网地址不能用localhost。另外部署后端口和防火墙要放行不然服务起来了外部访问不上。如果项目里静态资源带绝对路径访问服务器就需要改用相对路径能省很多事。6. 结语与个人实操心得这个系统全做完后我对Spring Boot的工程化能力有了更直接的认知。设计反复打磨了三天编码加自测用了一周调试时间其实比编码时间还长。但恰恰是调试过程让我学会了如何系统性定位问题先看控制台SQL再看后端响应最后才怀疑数据结构。很多初级同学拿到报错一头扎进代码里翻效率极低。我建议你在开发前把日志打印和统一异常处理做得扎扎实实这会成为你调试时期最有用的辅助工具。做好了基础功能统计报表、数据可视化、逻辑删除这些点可以再往深度挖。比如还可以加一个“月度消费趋势对比”图表把今年和去年12个月的消费曲线画在一个图上或者加一个“收支结构分析”按消费类型做环形占比图。这些功能的本质都是在聚合SQL上做文章你SQL练得越熟能玩的就越多。这种能力放在真实企业后端开发里非常吃香。如果有机会做扩展我建议把Excel导入导出加上用EasyExcel导出月度账单明细这也是实际财务系统中非常常见的能力。再加一个按Excel批量导入的历史账单迁移工具整个系统的实用性会上一个台阶。从个人项目做到这里单从技术考察角度来说绝对已经超过一般毕设的期望值了。最后说一个所有项目通用的建议坚持写开发笔记。无论是表结构的变动、SQL性能调优的心得、还是某个debug排错的详细过程全部记下来。这不止是为了写论文撑篇幅更是帮你系统化知识的最好方式。养成这个习惯毕业答辩时你能讲的细节会多到让老师觉得你真的在这门技术上下过功夫。这个项目就是一面镜子你投入得越多从里面学到的东西就越扎实。

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

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

免费获取报价 →
↑