ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis+MySQL企业级个人理财系统全栈实战解析

SpringBoot+Vue+MyBatis+MySQL企业级个人理财系统全栈实战解析 从拿到这套SpringBootVueMyBatisMySQL架构的“企业级个人理财系统管理系统”源码到完全跑通前后花了我一个晚上加一个上午。先给结论这是目前市面上特别适合拿来学架构、做毕设、写进简历的完整项目之一整套代码的分层结构清晰前后端分离MySQL表设计也踩过很多坑整体改造成本很低。这篇文章我就把项目从里到外拆开讲一遍包括需求拆解、技术选型、数据库设计、核心接口逻辑、从零部署过程以及我实际跑代码时踩过的坑和排查思路给准备用这套源码的读者一份可以直接照做的全流程参考。1. 项目拆解这到底是个什么系统1.1 系统定位与核心目标很多人看到“企业级”三个字会以为这是一套给大公司财务用的系统其实这里的“企业级”更多指代码规范性、分层逻辑、扩展能力达到了工程化标准而不是说部署环境有多高端。本质是一套个人理财管理系统解决的是个人用户记账难、账目乱、消费没计划的问题。我花了半小时把核心功能摸了一遍发现它的核心诉求非常聚焦用户可以注册登录添加自己的资产账户银行卡、现金、信用卡、电子钱包维护收入和支出的分类然后每天记录流水月底看统计报表超额了有预算预警。就这么几个核心闭环做透了就是一套可落地的理财工具。1.2 功能模块全景拆解这套系统的功能按业务域划分大致可以分成六个模块每个模块都是完整的前后端CRUD模块核心功能说明用户认证注册、登录、退出、个人信息维护前端用拦截器跳转、后端用JWT拦截认证账户管理银行卡、现金、信用卡、电子钱包的增删改查账户余额在记账时自动联动更新分类管理支出/收入分类的增删改查内置若干默认分类也可自定义交易流水记一笔支出/收入/转账支持备注、时间、交易对象全系统最核心的模块所有统计都基于它预算管理按分类设置月度预算实时计算已用比例超过阈值会在首页给出预警提示统计报表月度收支趋势、分类占比、账户余额总览后端聚合SQL 前端图表渲染这六个模块就是一套标准的管理系统闭环先登录再维护基础数据然后产生业务流水最后做统计分析。理解了这套结构以后做任何管理系统都能快速套用。1.3 这套源码到底适合谁我在跑代码的时候就明显感觉到这个项目定位是“被学习、被复用”的而不是那种业务逻辑极其复杂的商业软件。如果你属于下面这几类人拿这套源码去研究是非常对口的计算机专业准备毕设的学生题目自带“企业级”“管理系统”关键词技术栈统一且主流视频答辩都拿得出手。1-3年经验的初级后端开发想系统看一套规范的CRUD项目如何分层、如何做权限、如何做聚合查询。准备跳槽刷项目经验的人这套代码面试时非常好讲从表结构设计到统计SQL都有话可说。想快速搭一套内部工具做二次开发的人把理财换个领域比如仓库管理、员工报销表结构一改就能复用。2. 技术选型为什么偏偏是这套组合2.1 SpringBoot专治各种配置繁琐现在做Java后端不选SpringBoot基本属于给自己找麻烦。早期Spring项目搞一套环境要写大量的XML配置数据源、事务、扫描包、监听器全堆在里面新人光看配置就能劝退。SpringBoot的核心逻辑是约定大于配置自动装配机制把大部分样板配置全部接管了我只需要在application.yml里写上数据源、端口、日志级别启动类一跑内嵌的Tomcat直接起来省掉了部署WAR包的环节。个人理财系统这种规模的项目用SpringBoot尤其舒服——维护成本低、启动速度快、依赖管理清晰mvn直接拉包不会出现那种“配置十分钟运行两秒崩”的尴尬。2.2 Vue Element UI管理系统的效率答案前端选Vue我是完全赞同的。没错React也行但Vue在国内管理系统领域的生态太成熟了。配合Element UI的表格、表单、弹窗、菜单组件记账系统需要的那些页面——左菜单右内容区、表格流水、表单弹窗、统计图表——几乎不用自己造轮子。Vue最核心的收益是响应式数据绑定和组件化开发。比如记账页面里选了“支出”类型分类下拉自动切换成支出分类选了账户余额自动带出。这种联动逻辑如果用原生JS写要操作大量DOM用Vue只用维护一个type变量和对应的computed属性就行。组件化则让页面结构变成积木AccountList.vue、TransactionForm.vue、CategoryChart.vue每个组件自己管自己互不干扰。2.3 MyBatis把复杂SQL握在自己手里这里有个很多新手会问的问题JPA用起来不是更省事吗为什么用MyBatis我个人的判断是理财系统里最核心的价值是统计报表而统计报表天然需要写聚合SQL。JPA的自动CRUD确实方便但一旦涉及多表关联、分组聚合、按月统计、动态查询条件JPA的QueryDSL或者Specification写起来反而绕。MyBatis的Mapper.xml给了你完全的控制权SQL是什么样就是什么样性能好不好一眼能看出来。所以这套源码里增删改查用了MyBatis-Plus的BaseMapper自动生成统计类接口全部手写XML里的自定义SQL这种组合非常聪明——简单的事不重复写代码复杂的事留足掌控空间。2.4 MySQL最稳的存储底座MySQL在这个体量的系统中完全没有短板尤其是在Windows/Linux下部署都极其成熟。这套源码用的是MySQL 5.7数据库名是finance_db字符集采用utf8mb4——这一点非常关键因为utf8mb4才能存emoji和生僻字否则用户昵称里打一个特殊字符就报错Incorrect string value。存储引擎必须用InnoDB事务安全、行级锁、崩溃恢复能力都有保障。这里提醒一下如果你在自己电脑装的是MySQL 8.x跑这套源码完全没问题只需注意驱动版本和时区参数这个后面实操环节详细说。2.5 JWT无状态认证的必要性前后端分离项目里Session方案不太好使。原因是前端和后端可能部署在不同的域名或端口SessionId的传递和跨域Cookie问题会搞得人头皮发麻。这套源码采用JWT方案用户登录成功后后端签发一个Token前端每次请求在请求头里带上Authorization: Bearer xxx后端写一个拦截器统一校验。本质上就是一次请求带一张“通行证”服务端不用保存登录状态天然适合分布式部署。3. 系统架构与数据库设计核心中的核心3.1 整体调用链路这套系统是标准的前后端分离架构调用链路按层依次是浏览器 → Vue前端页面 → Axios发送HTTP请求 → SpringBoot的Controller → Service业务层 → Mapper数据访问层 → MySQL每一层只做自己该做的事。Controller只负责收参数、调Service、返回结果Service层负责业务规则比如记账时校验分类是否存在、同步更新账户余额Mapper层只做SQL交互。这种分层最大的好处是可测试性和可维护性。我拿到源码之后想加一个“导出账单”功能只需要在Controller加接口、在Service加方法、在Mapper加SQL三个文件各管一段不会牵一发动全身。后端统一返回结构也很规范所有接口都封装成ResultT对象包含三块信息public class ResultT { private Integer code; // 状态码200成功500业务异常401未登录 private String message; // 提示信息 private T data; // 返回数据泛型 }前端Axios响应拦截器里统一判断code200就返回数据401就跳转登录页不用每个接口单独写重复的错误处理。3.2 数据库六张核心表这套源码数据量不大但表结构设计覆盖面很全我直接把核心表的结构和处理逻辑列出来方便要改造成其他系统的人参考。第一张用户表sys_user是基础字段包含主键、用户名、密码BCrypt加密、昵称、邮箱、头像、状态、创建时间、更新时间。用户名加了唯一索引密码绝对不能明文存储登录时用BCrypt.matches()校验。第二张账户表base_account记录用户的资产账户字段有账户名称、账户类型、余额、卡号、备注。这里注意一个设计细节账户余额是冗余存储的也就是说流水表里新增一条收入记录时不但插入流水还会同步UPDATE base_account SET balance balance #{amount}。好处是首页查询账户余额不用每次都做SUM聚合响应速度快坏处是删除流水时要记得反向扣减余额否则账就平不了。第三张分类表base_category字段包含分类名称、类型1支出/2收入、排序、图标。这张表在初始化时会插入一批默认数据比如餐饮、交通、购物、工资、奖金等。第四张交易流水表biz_transaction是整个系统数据量最大、查询最频繁的表核心字段包括账户ID、分类ID、交易类型支出/收入/转账、金额、交易时间、交易对象、备注。这张表的索引设计很关键强烈建议保留(user_id, trans_time)联合索引因为统计报表几乎都是按时间范围去查。没有这个索引数据量一上5000条按月统计就开始卡了。3.3 金额精度与软删除两个细节值得展开说。第一是金额字段所有涉及钱的字段全用DECIMAL(12,2)绝对不能用FLOAT或DOUBLE。浮点数在计算机中本身是近似值0.10.2算出来可能是0.30000000000000004做钱财计算早晚出事。DECIMAL是精确小数类型虽然存储空间略大但做金额计算必须保证确定性。第二是逻辑删除用户删除一条分类时系统没有真正执行DELETE而是给deleted字段置为1。因为已经发生的交易流水还关联着这个分类如果把分类物理删掉历史记录的关联关系就断了统计报表会出现“分类名查不到”的尴尬。这点也是企业级逻辑的最好体现——不追求代码上的干净而是保证业务数据的完整可追溯。4. 核心功能实现从接口设计到关键逻辑4.1 登录认证与拦截器流程登录接口是整个项目最核心的入口。前端把用户名密码用POST请求发给/api/auth/login后端Service层先查用户是否存在再用BCrypt校验密码全部通过后生成JWT返回给前端。生成的Token里包含了用户ID和用户名设置了过期时间。源码头部的JWT拦截器是关键部分它实现Spring的HandlerInterceptor接口在preHandle方法里做三件事从请求头里取Authorization字段如果为空直接返回401。用JWT工具类解析Token解析失败过期或签名不对返回401。把解析出来的用户ID塞进ThreadLocal后续的Service层直接从UserContext.get()拿当前登录用户不用每个方法都传参。这中间有一个典型的坑ThreadLocal用完必须调用remove()清理否则Tomcat的线程池复用会导致数据串到下一个用户请求。源码里写没写这个清理我得打一个问号建议你拿到手后自己检查拦截器的afterCompletion方法没有UserContext.remove()就自己加上。4.2 记账接口的业务联动记一笔账的接口是POST /api/transaction这是整个系统业务规则最复杂的一个点。正常逻辑要依次完成1. 校验用户传入的账户ID、分类ID是否属于当前用户 2. 判断交易类型支出→账户余额减收入→账户余额加转账→源账户减目标账户加 3. 插入交易流水记录 4. 更新对应账户余额其中“校验分类归属”这一步特别容易被忽略。如果不校验用户传入一个别人的分类ID流水虽然插进去了但统计的时候数据就乱了。我在代码里看到有做这部分校验这点值得肯定。转账逻辑要格外注意它是同一张biz_transaction表里同时插入两条记录一条是“转出”账户减钱一条是“转入”账户加钱两条记录的金额相同、备注中带彼此关联信息。有些简化版项目会用一步更新两个账户余额的方式实现虽然也行但流水明细会少一条以后看账本的时候会迷糊。4.3 统计报表的聚合SQL统计报表是这套系统在面试时最出彩的部分。月度收支趋势图需要按天分组聚合SQL大概是SELECT DATE_FORMAT(trans_time, %Y-%m-%d) AS day, SUM(CASE WHEN trans_type 1 THEN amount ELSE 0 END) AS expense, SUM(CASE WHEN trans_type 2 THEN amount ELSE 0 END) AS income FROM biz_transaction WHERE user_id #{userId} AND trans_type IN (1, 2) AND DATE_FORMAT(trans_time, %Y-%m) #{month} GROUP BY day ORDER BY day;分类占比图则要把所有支出按分类聚合SELECT c.name AS categoryName, SUM(t.amount) AS total FROM biz_transaction t LEFT JOIN base_category c ON t.category_id c.id WHERE t.user_id #{userId} AND t.trans_type 1 AND DATE_FORMAT(t.trans_time, %Y-%m) #{month} GROUP BY t.category_id ORDER BY total DESC;这两个SQL是统计模块的核心也是报表数据和首页图表对接的基础。前端拿到[{day: 2025-03-01, expense: 100, income: 200}]这种数据ECharts直接就能画折线图。4.4 预算模块的预警逻辑预算功能的原理不复杂但做得完整也需要好几步用户选择分类、输入当月预算金额保存后系统每天汇总当月该分类的支出。首页加载时查询所有预算把“已用金额/预算金额”算成比例超过80%给出黄色提示超过100%标红预警。这套逻辑里比较简单的是比例计算比较复杂的是“已用金额”的查询。5. 从零部署实操完整跑通这套源码5.1 环境准备清单在直接启动之前请先确认你本机的环境缺一个都会卡住环境推荐版本说明JDK1.8或11SpringBoot 2.x在这两个版本下最稳Maven3.6及以上用于后端依赖管理和打包Node.js14.x-18.x用于前端Vue项目构建MySQL5.7或8.0建库脚本在sql/finance_db.sql前端包管理器npm或yarn建议npm install时加--registryhttps://registry.npmmirror.com我强烈建议先启动后端再启动前端因为前端启动成功后需要向后端发请求后端没着前端就是一片报错。5.2 数据库导入与配置第一步是创建数据库并导入表结构和初始数据。命令行执行mysql -u root -p finance_db.sqlfinance_db.sql脚本建好之后会自动插入默认管理员账号通常是admin/admin123具体要看脚本里的INSERT语句以及初始化分类数据。如果不想命令行操作也可以用Navicat或DataGrip直接运行SQL脚本注意执行前先创建同名数据库。然后配置后端application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/finance_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里的serverTimezoneAsia/Shanghai是关键。如果你用的是MySQL 8.0而注释缺失启动大概率会报Server returns invalid timezone错误。map-underscore-to-camel-case: true会自动把数据库里的create_time映射成Java实体的createTime少写一堆TableField注解。log-impl这里配置成StdOutImpl可以在控制台打印SQL调试时非常方便生产环境记得关掉。5.3 后端启动步骤进入后端项目根目录依次执行mvn clean install -DskipTests mvn spring-boot:run第一次执行Maven会下载大量依赖可能需要几分钟请耐心等待。看到控制台输出Started FinanceApplication in xxx seconds就代表启动成功了。这时浏览器直接访问http://localhost:8080/api/user/info会是401这是正常的代表拦截器生效。5.4 前端启动与代理配置进入前端目录执行npm install npm run serve这里有一个非常关键的配置前端项目的vue.config.js里必须配置开发代理把/api开头的请求转发到后端http://localhost:8080module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };配置代理的本质是解决开发环境跨域问题——前端跑在3000端口后端跑在8080端口跨端口请求会被浏览器拦截。搞了代理之后前端页面里的请求都写成相对路径/api/xxx请求先到Vite的开发服务器再被转发到后端。这样做的好处是开发环境和生产环境的前端代码完全一样不用区分环境变量切换域名。启动成功后访问http://localhost:3000用脚本里初始化的管理员账号登录能进主页看到仪表盘数据就说明全链路通了。5.5 生产环境打包发布开发跑通后如果想部署到服务器官方给了两步标准操作# 第一步前端构建并拷贝到后端的静态资源目录 npm run build # 将dist目录下的所有文件复制到后端 resources/static/ 下 # 第二步后端打包成可执行Jar mvn clean package -DskipTests java -jar target/finance-0.0.1-SNAPSHOT.jar这样打包的好处是前端页面直接作为SpringBoot的静态资源被提供整个系统只有一个Jar包部署在服务器上的运维成本最低。但这里有一个值得重视的坑前端路由如果是History模式打包部署后刷新页面就会出现404。因为刷新时浏览器请求的是/xxx路径后端没有对应的Controller。解决办法是在后端写一个WebMvcConfigurer把非/api开头的路径都转发到index.html页面。如果你的源码没有这段配置部署后刷新就等着白屏吧。6. 常见问题与排查技巧实录6.1 后端启动失败的三大高频原因第一个高频问题是Failed to configure a DataSource通常就是application.yml里的数据源配置没生效。检查点有两个一是是不是改了配置文件但没重新启动二是application.yml所在目录是否正确。很多人会把配置放在src/main/resources外面的文件夹里导致SpringBoot根本没扫描到。第二个高频问题是数据库连接超时或时区报错。MySQL 8.x的驱动com.mysql.cj.jdbc.Driver必须带上serverTimezoneAsia/ShanghaiMySQL 5.7的驱动则是com.mysql.jdbc.Driver两者不要混用。第三个高频问题是端口被占用。执行netstat -ano | findstr 8080查看占用进程如果是Java进程说明有旧实例没关干净任务管理器强制结束或者直接改配置里的server.port换个端口。6.2 前端联调时的跨域与代理失效页面能打开但所有请求都报503或者ERR_CONNECTION_REFUSED首先确认后端是不是真的在8080端口跑着再看vue.config.js的代理配没配对。这里有个细节代理配置改了之后必须重启npm run serveVite开发服务器不会自动加载配置文件的修改。如果代理代码写得没问题但还是跨域报错改成用CORS方案兜底——后端加一个CorsFilter允许前端域名访问。两者有一个生效就行同时配了也不冲突CORS方案主要用于生产环境前后端分开放的情况。6.3 前端部署后刷新404这个问题在5.5节已经提过前端路由History模式下刷新页面找不到路径是部署阶段最高频的问题。我个人的建议是小型项目直接前端用hash模式路由URL里会带一个#号刷新天然不会404代价是URL不太好看大型项目才建议用History模式配转发。这个源码默认用的哪一种你要看router/index.js里createWebHistory还是createWebHashHistory一眼就能分辨。6.4 MyBatis常见报错与定位思路最典型的报错是Invalid bound statement (not found)意思是Mapper接口找到了但对应的XML找不到。原因是application.yml里的mapper-locations路径配的不对或者XML文件没有编译到target/classes目录下。如果XML文件放在src/main/java目录里需要额外在pom.xml配置resources标签把*.xml包含进去否则打包时会直接跳过这些XML。还有一类是ResultMap配置错误比如java.math.BigDecimal cannot be cast to java.lang.Double这通常是因为实体类字段类型和数据库映射字段类型不匹配。金额相关的字段实体类保持BigDecimal别为了省事换成Double。6.5 筛选排查技巧小结我个人的排查习惯是后端接口报错先看控制台日志里MyBatis打印的SQLSQL能正常打印说明数据库连接和Mapper映射都没问题SQL打印出来但结果不对再去检查聚合条件前端页面报错按F12打开开发者工具Network面板里看请求状态码500去查后端日志401去看Token带没带404去看路由路径对不对。定位到具体请求再逐层往下排查效率比盲猜高得多。7. 最后说几句实在话我实际体验下来这套源码最值得研究的不在业务本身而是它作为一个管理系统的完整性——从用户认证到分类管理再到流水的增删改查、统计报表、预算预警每个环节该有的东西都在而且表结构设计相对规范。对准备毕设答辩的人来说随便挑一个模块往深处讲都能讲出设计思路对工作党来说把这套结构吃透以后接手任何“XX管理系统”的项目都能快速上手。如果你自己也要搭类似项目我的建议是不要改大架构优先在业务规则上做强化。拿到源码后先改数据库连接和初始密码再跑通整个流程然后从流水记账这个核心模块入手去读代码一条链路读下来比从头看十遍视频教程都管用。
返回列表