ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Java SpringBoot3+Vue3+MySQL个人财务全栈开发实践

Java SpringBoot3+Vue3+MySQL个人财务全栈开发实践 个人财务管理系统如果是自己从零开发最容易出的问题不是功能不够多而是技术栈之间衔接不上。最近我用 JAVA SpringBoot3 Vue.js3 MySQL 这套组合完整做了一遍发现这个项目非常适合用来打通前后端全流程后端有鉴权、CRUD、统计接口前端有登录、路由、表单、图表展示数据库则要处理预算、分类、流水这些真实业务表。整篇文章按实际开发顺序拆开包括环境准备、数据库设计、后端接口、前端页面、联调排错和部署思路尽量让看完的人能把整套流程复制下来。先说结论这套组合能做好是因为 SpringBoot3 负责把后端接口和业务逻辑组织清楚Vue.js3 负责把页面交互做灵活MySQL 负责存住账目流水。困难点主要集中在版本匹配、跨域处理、金额精度和登录状态维护这几个环节。如果你当前的需求是做毕业设计、个人记账工具或者想验证一套主流全栈开发流程这篇文章值得花点时间看完。1. 先把这套技术栈读懂才知道要准备什么1.1 这个系统到底解决什么问题个人财务管理系统的核心价值并不是“把页面做得好看”而是解决一个人每天记账、归类、统计、控制预算这个完整闭环问题。实际要能跑起来至少需要处理注册和登录确保每个人的账目只属于自己。日常收入的添加、支出记录、修改和删除。收支分类管理比如餐饮、交通、工资、理财。按月统计收入和支出。查看每个分类花了多少钱。给某个月份设置预算并反馈超支情况。这些需求合在一起就是对个人账本的全流程管理。如果你只是练习某项技术数据库表可以很简单。如果你想做成一个能长期使用的系统这个流程图很重要登录鉴权、账目流水、分类汇总、预算预警、统计报表每一步都要串联起来。1.2 为什么推荐 JAVA SpringBoot3 Vue.js3 MySQL以前做类似系统常见方案是 JSP Servlet MySQL开发慢前后端放在一起维护起来很费力。换成 SpringBoot3 Vue.js3 之后前后端职责分得很清楚后端只返回 JSON前端负责渲染和交互。MySQL 在这里扮演持久化角色把账本数据稳定保存住。这套组合适合以下场景学习 Java 全栈开发想理解接口如何设计。做课程设计、毕业设计需要完成度高、能演示的 Web 系统。想在本地部署一个自用记账工具方便自己扩展功能。它也有学习成本SpringBoot3 和 SpringBoot2 在很多默认配置上有差异Vue3 和 Vue2 在语法与响应式原理上也不同使用前需要先确认版本体系。1.3 先从文档和项目结构入手不要直接写业务新手写个人财务系统时最常犯的错误是还没理清表结构就开始在 Controller 里堆代码。要做到后面顺利前期先把分层结构定下来。比较常见的后端结构是controller接收请求校验基础参数调用服务。service写核心业务逻辑。mapper负责数据库操作。entity对应数据库表的实体类。dto接收前端参数的对象。vo返回给前端的数据结构。config配置 WebMvc、跨域、拦截器。utilsJWT、日期处理等工具。前端结构可以是views展示页面。router路由配置。store保存登录态和用户信息。api封装 axios 请求。components通用组件。这样分好之后问题定位会更快接口 500 就先查 Service页面没数据就先查 api 层和网络请求字段对不上就先查 VO 的返回值。不要指望把几十个接口写在同一个 Controller 里也能做好维护短期看方便后期加一个统计功能都可能互相影响。2. 环境准备和工程骨架先把前后端都跑起来2.1 安装 JDK 和 MySQL 时的版本意识环境准备排在第一位的不是“能把软件装出来”而是版本匹配。SpringBoot3 是基于 Jakarta EE 的一套新版本默认使用 jakarta.servlet不再使用 javax.servlet。这意味着后端选择 SpringBoot3 时JDK 版本至少要 17。如果机器上还在用 JDK8 或 JDK11直接创建 SpringBoot3 工程要么启动报错要么需要换版本时间成本会明显增加。推荐先确认以下三个版本JDK17 或更高。Maven3.6。SpringBoot3.x实际以你创建工程时选择的版本为准。安装 MySQL 时最常出现的问题是服务启动了但连不上或密码策略不明确。如果是在 Windows 上使用安装版安装过程中会让设置 root 密码如果使用压缩包版需要先初始化再启动服务。这个过程不要跳过日志检查确认 3306 端口能正常监听再进入下一步。2.2 创建 SpringBoot3 后端工程工程创建方式比较灵活可以用 IDEA 直接新建也可以使用 Spring Initializr 生成后导入。创建一个新工程时基础依赖建议选Spring Web提供 Controller、接口调用、内嵌 Tomcat。Spring Validation做参数校验。MySQL Driver连接 MySQL。创建完成后pom.xml 里至少会包含类似下面的内容dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency如果使用 MyBatis-Plus 这类增强框架再额外引入对应依赖。这里要注意一点MyBatis-Plus 对 SpringBoot3 的适配版本与 SpringBoot2 不同不要拿老教程里的依赖直接塞进新工程容易报版本冲突。2.3 创建 Vue.js3 前端工程前端工程推荐使用 Vite 创建速度快配置直观。npm create vitelatest finance-web -- --template vue进入项目后安装依赖cd finance-web npm install npm install vue-router4 npm install pinia npm install axios npm install element-plus npm install echarts上面命令里的依赖不是全都要装完才动手。最稳妥的顺序是先把 vue-router 和 axios 装好保证页面能跳转、请求能发出再逐步加入组件库和图表库。很多项目是启动时依赖冲突结果发现是组件库版本和 Vite 版本不匹配。前端项目启动后默认端口通常是 5173后端的默认端口通常是 8080。两边端口不同联调时需要配置代理或开启跨域这个问题后面单独说。2.4 先让前后端各跑一个默认页面不要一上来就写登录注册。后端可以先写一个最简单的接口RestController RequestMapping(/api/health) public class HealthController { GetMapping public String health() { return ok; } }启动后端后在浏览器访问 http://localhost:8080/api/health 能看到返回 ok说明 SpringBoot3 的服务基本正常。前端可以先清空 App.vue 默认代码只保留一个简单模板。运行 npm run dev看到页面能访问说明前端编译正常。这一步看起来简单价值很大它把“环境问题”和“代码问题”尽早分开。很多人的项目做到一半才启动报错信息非常长一会儿怀疑 Maven一会儿怀疑 Node实际上最开始就没有确认骨架能跑。3. 数据库表和实体层先做预算、分类还是先做流水3.1 先设计合理的最小表结构个人财务管理系统如果照着真实记账软件做表会非常多。个人实测时更建议从一组最小表开始先跑通业务流程后面再考虑要不要加标签、成员、多账户。下面是一套很经典的四表结构用户表保存登录用户名、密码、昵称。分类表保存收入分类或支出分类。流水表保存每一笔账单。预算表保存月份预算按月份和分类做区分。创建数据库时可以先执行CREATE DATABASE finance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;示例表结构CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE biz_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, name VARCHAR(50) NOT NULL, type TINYINT NOT NULL COMMENT 1收入2支出 ); CREATE TABLE biz_transaction ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, category_id BIGINT, amount DECIMAL(14,2) NOT NULL, type TINYINT NOT NULL COMMENT 1收入2支出, transaction_date DATE NOT NULL, remark VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE biz_budget ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, category_id BIGINT, budget_month VARCHAR(7) NOT NULL COMMENT 格式: 2025-06, amount DECIMAL(14,2) NOT NULL );需要注意几个设计点金额字段用 DECIMAL不用 FLOAT 和 DOUBLE这是为了精确保存金额。每张业务表都带 user_id这是为了控制数据属于哪个用户。category_id 在预算和流水之间建议用长整型关联。流水表里记录 type是为了和分类表保持一致性。3.2 实体和数据库字段要做到一一对应数据库创建完成后后端实体类要能覆盖表中所有需要读写字段的列避免漏字段。public class Transaction { private Long id; private Long userId; private Long categoryId; private BigDecimal amount; private Integer type; private LocalDate transactionDate; private String remark; private LocalDateTime createTime; }不要为了省事把所有字段统一成 String比如金额在实体里用 BigDecimal日期如果只是按天统计可以用 LocalDate。Java 的时间类型在前端联合使用时需要考虑格式化这一步可以先统一处理后面联调章节会展开。3.3 索引和查询频率的关系个人财务系统虽然数据量不会太大但统计接口大概率要按月份过滤 transaction_date所以建议在 transaction_date 和 user_id 上建立索引ALTER TABLE biz_transaction ADD INDEX idx_user_date (user_id, transaction_date);这里解释一下为什么查询“某人某月的所有账单”时先用 user_id 过滤再用 transaction_date 排序这个索引能减少扫描行数。查询“按分类汇总”时需要读大量流水记录合理的索引能减轻 MySQL 压力。预算表和分类表不需要过分加索引数据量小、查询模式单一加了反而增加维护成本。4. SpringBoot3 后端接口认证、记账、统计4.1 统一返回结果和业务异常个人财务系统后端接口不建议每个方法返回裸对象。裸对象在数据正常时没感觉但出现业务异常时前端无法用统一结构判断是否成功排错会变得很麻烦。推荐定义一个统一的返回结构格式大致如下public class ApiResultT { private Integer code; private String message; private T data; }定义code 200 表示成功。code 401 表示未登录。code 500 表示服务端异常。同时定义一个全局异常处理器。当删除一条不存在的交易、修改不属于当前用户的记录时抛出业务异常由异常处理器统一包装返回。这样做的好处是前端 axios 拦截器只需要根据 code 做集中处理不需要每个月统计接口里手工判断返回值。4.2 登录鉴权使用 JWT 而不是每次查数据库个人项目做登录方案推荐用 JWT服务端不需要保存 session。登录成功后后端生成一个 token前端请求时把 token 放在请求头里后端每次从 token 中解析 userId。示例逻辑不需要真正贴出完整代码库核心是这样一张流程图用户提交用户名和密码。后端校验密码是否正确。正确则生成 token返回给前端。前端存储 token并在 axios 请求拦截器中统一添加到 Authorization 请求头。SpringBoot3 提供一个拦截器校验所有除了登录、注册外的接口必须带 token。SpringBoot3 中写拦截器时注意包路径是 jakarta.servlet。对于密码存储不要用明文保存。个人项目中常用 BCrypt 做哈希加密生成结果是一段带盐值的密文。即使数据库泄露也无法直接看到用户明文密码。4.3 核心记账接口如何设计接口清单可以这样规划POST /api/auth/login登录。POST /api/auth/register注册。GET /api/transaction/list?typeyearMonth2025-06流水列表。POST /api/transaction新增流水。PUT /api/transaction/{id}修改流水。DELETE /api/transaction/{id}删除流水。GET /api/stats/month-summary?yearMonth2025-06月度汇总。GET /api/stats/category-summary?type2yearMonth2025-06分类统计。POST /api/budget保存预算。GET /api/budget/list?yearMonth2025-06查看预算。接口数量不用贪多关键是把参数和返回逻辑做稳。新增流水时Service 中要注意的一点当前登录用户 ID不能从前端传来的 userInfo 字段获取而是从 JWT 里解析出来。这样做是为了防止用户传 userId1直接改别人的账。正确方向是拦截器解析完 token 后把 userId 放到请求上下文里业务层统一读取。举例Long currentUserId JwtUtils.getUserId(request); transaction.setUserId(currentUserId);这样能把安全问题规避大半。4.4 月度统计和分类统计的实现统计接口查询场景很典型举一个简单例子支出分类统计。SELECT category_id, SUM(amount) AS total FROM biz_transaction WHERE user_id ? AND type 2 AND DATE_FORMAT(transaction_date, %Y-%m) ? GROUP BY category_id ORDER BY total DESC;如果你不喜欢在 SQL 里使用 DATE_FORMAT也可以传入 startDate 和 endDate再用 BETWEEN 过滤AND transaction_date BETWEEN #{startDate} AND #{endDate}显式的开始和结束日期范围比字符串格式化更高效也更方便索引利用。返回统计结果时不要直接返回 List即使代码简单也会让前端不好处理。更推荐定义一个 SummaryVO字段包括分类 ID、分类名称、金额、占比再把分类名称关联出来。4.5 预算超支提醒到底怎么做预算功能看起来复杂做成接口逻辑后并不难。预算的数据结构是按月和分类管理。如果用户当月没有针对某个分类设置预算那这条分类不做超支判断。如果设置了规则可以是当月支出预算 3000分类餐饮预算 1200。统计时先计算当月总支出。如果总支出大于预算金额返回超支提示。分类预算如果超支前端可以标红对应分类。在后端可以把预算表和统计数据关联起来写一个 BudgetStatusVO。字段包括 budgetAmount、usedAmount、ratio、overFlag。前端拿到 ratio 后用进度条展示预算使用百分比会非常直观。5. Vue3 前端怎么把这些页面串起来5.1 创建登录页和数据请求封装登录页是第一个入口它解决的问题是拿到 token 并保存。登录成功后建议这样处理const loginRes await loginApi(form); localStorage.setItem(token, loginRes.data.token); router.push(/);为什么用 localStorage 而不是 sessionStorage页面刷新后 sessionStorage 可能丢localStorage 能维持会话。如果你希望关闭浏览器后必须重新登录可以用 sessionStorage这里根据自己的产品逻辑决定。axios 封装也很关键。创建 request.js 时至少做三件事baseURL 统一配置。请求拦截器从 localStorage 取 token。响应拦截器判断 code401 则跳回登录页。import axios from axios import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) request.interceptors.response.use( response { const res response.data if (res.code 401) { router.push(/login) return Promise.reject(new Error(未登录)) } return res }, error { return Promise.reject(error) } ) export default request5.2 路由和页面结构前端路由建议配置为/login登录和注册页面。/重定向到 /dashboard。/dashboard仪表盘显示本月收支和预算进度。/transactions流水列表。/categories分类管理。/budget预算设置。/reports统计报表。在路由配置中需要设置 meta.requiresAuth 字段。全局前置守卫里判断 token 是否存在没有 token 就强制跳到登录页。这种结构很简单但能形成基本权限边界。5.3 记账页面怎么写才顺手记账页面建议设计成左右两栏或卡片式不要全部堆在一个长页面。核心字段收支类型切换收入或支出。分类选择。金额输入。日期选择。备注。对应的表单项如下el-select v-modelform.categoryId placeholder请选择分类 el-option v-foritem in categoryList :keyitem.id :labelitem.name :valueitem.id / /el-select el-input-number v-modelform.amount :precision2 :step1 /分类列表要根据“支出/收入”动态加载。用户在切换收入、支出时分类列表要自动刷新。后端接口一开始可以设计为 GET /api/category/list?type2前端在类型变化时重新调用。5.4 用 ECharts 展示统计图表统计页面是整个系统中最有“完成度”的部分。月度趋势可以用折线图展示统计每月的收入支出曲线import * as echarts from echarts const chart echarts.init(document.getElementById(chart)) chart.setOption({ xAxis: { data: monthList }, yAxis: {}, series: [ { name: 支出, type: line, data: expenseList }, { name: 收入, type: line, data: incomeList } ] })分类占比使用饼图更直观series: [{ type: pie, data: categoryData }]要注意的是 ECharts 容器必须有固定高度否则页面里会出现图表不显示、高度为 0 的问题。可以在图表父容器上设置 height: 400px 或 500px再初始化图表。组件卸载时建议调用 chart.dispose() 释放资源避免切换页面时内存占用上升。6. 前后端联调最常踩的坑跨域、日期、金额精度、权限6.1 开发时用 Vite 代理解决跨域后端接口通过 localhost:8080 访问前端页面通过 localhost:5173 访问浏览器就会认为这是跨域请求。如果没有处理控制台会频繁出现 CORS 错误。处理方式有两种方式一是后端开启跨域配置。方式二是开发阶段更推荐的做法在前端配置 Vite 代理请求 /api 时转发到 8080。在 vite.config.js 中加入server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端仍然请求 /api/login实际由 Vite 转发到后端。代码里看不出地址变化但跨域问题被解决了。后端如果也需要开启跨域可以用一个 WebMvcConfigurer 配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE); } }开发阶段两个都配置也能工作但建议项目里确定一个是主要访问链路不要两边都依赖否则排查请求走向时容易被绕晕。6.2 日期格式不一致带来的返工后端 LocalDate/LocalDateTime 序列化到前端时大概率会变成数组或标准 ISO 字符串而不是你想要的 2025-06-18。在 SpringBoot 的 application.yml 中增加统一格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果要单独处理 LocalDate可以额外添加配置类。也可以简单一点在 VO 的日期字段上使用注解JsonFormat(pattern yyyy-MM-dd, timezone GMT8) private LocalDate transactionDate;建议前期直接统一配置否则遇到列表页和表单编辑页时日期显示不一致又要一处一处排查。6.3 金额回显出现 19.999999 或 0.30000000000004如果你在后端把金额定义成 double前端会看到莫名其妙的浮点误差。个人财务系统涉及金额应全部使用 BigDecimal。MySQL 字段用 DECIMAL(14,2)Java 实体用 BigDecimal前端输入金额时默认保留两位小数。这样设计能最大程度保证金额一致性。如果前端拿到字符串后想转数字也可以用 Number 转换但每次计算时要谨慎涉及加减乘除统一交给后端处理。6.4 登录过期、拦截器放行和 401 循环如果 token 过期后端返回 code401前端响应拦截器里跳转 /login这是一个正常链路。但会出现频繁 401 的情况常见原因如下。一是登录页、注册页也走了需要鉴权的拦截器。在 SpringBoot3 后端登录和注册接口要放到放行列表中否则用户还没登录就访问登录页接口直接返回未登录导致页面白屏。建议放行路径写清楚registry.addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/auth/register);前端路由守卫也要放行 /login否则页面一旦刷新就会被打回登录页。二是 axios 里请求失败后没有把错误抛出去而是在拦截器里反复跳转登录形成死循环。最简单的做法是每个请求只处理一次错误跳转前记录当前路径跳转一次即可后端接口再返回错误时检查当前是否已在登录页。6.5 列表字段名和后端返回不一致Vue 页面写item.createTime后端返回的是create_time这会导致页面显示 undefined。解决办法是统一后端返回结构。建议后端 VO 字段使用驼峰命名 createTime由 Jackson 序列化输出 JSON。MySQL 字段下划线不影响 Java 实体只要在配置文件里开启 map-underscore-to-camel-caseMyBatis 映射时自动转换。如果使用 MyBatis-Plus一般默认开启下划线转驼峰。如果使用原生 MyBatis需要确保 resultMap 字段正确或者开启对应配置。7. 从“能跑”到“好用”批量导入、日志和部署思路7.1 批量导入流水数据记账系统一个人手动录数据很麻烦。若想真正用起来可以加批量导入功能。批量导入需要设计一个模板文件模板字段建议包含日期、类型、分类、金额、备注。导入时读取 Excel 文件在 Service 中逐行处理。此时最容易踩的坑不是解析代码而是脏数据处理日期格式是不是标准。金额是不是负数。分类名称是否和当前用户已有分类匹配。类型值是否只写了“收入/支出”。更稳的方式是先让用户选择导入分类映射如果发现某几行格式错误不要整体失败可以返回错误明细{ successCount: 99, failCount: 1, failRows: [ { row: 15, message: 金额格式不正确 } ] }这样用户才知道哪一行需要修改而不是点击导入后只看到“失败”两个字。7.2 观察日志和慢 SQL本地开发时日志主要用来排查报错。SpringBoot3 默认日志输出足够日常使用出现问题先看控制台异常的第一行 Caused by 部分不要只看顶部的异常描述。如果是统计接口变慢优先确认数据库里是否执行了全表扫描。测试方法很简单在 MySQL 里用 EXPLAIN 查看执行计划观察 rows 行数是否过大。EXPLAIN SELECT * FROM biz_transaction WHERE user_id 1 AND transaction_date BETWEEN 2025-06-01 AND 2025-06-30;如果 type 是 ALL说明没用索引应该检查是否建立了以 user_id 和 transaction_date 为前缀的索引。7.3 生产环境部署方案个人项目部署并不复杂核心目的是让人能稳定访问后端和前端。后端打 jar 包方式mvn clean package -DskipTests java -jar target/finance-server.jar前端打包npm run build打包后默认生成 dist 目录里面是静态文件。Nginx 配置中把 dist 目录作为网站根目录并把 /api 请求代理到后端服务server { listen 80; server_name finance.example.com; location / { root /var/www/finance-web/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files 的作用是让 Vue Router 的前端 history 模式路由刷新时不返回 404。比如直接访问 /transactionsNginx 会重新指向 index.html由前端接管路由。后端如果部署在服务器上不要用 root 权限跑业务应用建议单独建一个运行用户。MySQL 数据库需要及时备份个人系统至少可以每天备份一次mysqldump -u root -p finance_db finance_db_$(date %F).sql要加定时任务时注意运行环境的变量是否完整比如 mysqldump 的路径可能不在普通用户 PATH 中。8. 回顾一下整套流程我认为最值得先做稳的是这三件事第一件先把最小的端到端链路跑通。也就是登录后能新增一条支出记录并能按月查出来。这个链路一旦通了后面加图表、加预算、加分类都只是在这个稳定结构上追加。第二件把接口权限和数据权限想清楚。个人系统虽然使用者少但接口必须是“当前登录用户只能看到当前登录用户的数据”。这个设计如果放在后期再补会牵一发而动全身。比如统计接口一开始没有按 user_id 过滤等数据量多了再补就会发现旧数据混乱还得清理。第三件把日志和错误信息留好。开发期间多打印必要参数比如新增流水时打印 userId、amount、type排查问题时你才能判断是前端传参不对还是后端逻辑有误。日志不是浪费是在问题发生时能省最多时间的工具。就实际使用感受来说这套个人财务管理系统用 JAVA SpringBoot3 Vue.js3 MySQL 实现难度并不在于某个单独的功能有多复杂而在于把你学的后端接口、前端交互、数据库设计粘合成一个完整产品。建议先按最小表结构和最少页面做完第一版跑通之后再逐步加入统计图表、批量导入、预算预警这些增强功能。只要数据库设计不出现金额精度和归属字段缺失这种基础问题后端接口保持统一返回结构前端把 token 和跨域处理好这个系统就能稳定运行后续继续扩展也很自然。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表