ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue个人财务系统实战:从本地启动到MySQL部署

SpringBoot+Vue个人财务系统实战:从本地启动到MySQL部署 简介这是一套基于Spring Boot与Vue.js开发的个人财务管理系统完整源码包面向Java全栈初学者及Web项目实践者旨在帮助开发者快速掌握前后端分离架构下的金融类应用开发全流程。资源包含377个文件主体为98个Java后端业务逻辑与配置类、40个Vue组件实现界面交互、161个SVG图标资源支撑UI展示辅以SQL建表脚本、YML配置、BAT启动脚本等工程必需文件整体压缩包仅8.18MB轻量易部署。目前已有198人学习下载适合用于课程设计、毕设参考或技术栈整合练习。用户可直接运行获取含用户管理、多维度账单录入与分类统计、可视化理财目标跟踪及收支趋势图表的完整功能系统并通过清晰的模块划分如IndexHeader.vue.bak等预览文件体现的布局结构理解典型金融管理系统的前端组织逻辑与后端分层设计规范。1. 为什么一个“个人财务管理系统”值得你花两小时跑通它SpringBoot Vue 落地不是为了炫技而是把记账这件事从 Excel 拖进可控、可扩展、可调试的工程闭环里你有没有试过用 Excel 记账前三天激情满满分类列得工整公式套得漂亮第七天发现“餐饮”和“外卖”重复统计“交通”里混进了打车发票和地铁充值第十五天想查“上月奶茶支出占比”却卡在筛选嵌套和透视表刷新失败上。这不是你懒是工具和问题不匹配——记账本质是结构化数据采集 时序聚合 条件可视化而 Excel 是二维表格引擎不是状态机。这个 SpringBoot Vue 的个人财务管理系统源码包恰恰踩在了真实痛点上它用 SpringBoot 做稳态后端REST API MyBatis-Plus H2/MySQL 双支持Vue 做响应式前端Element Plus 组件库 Vue Router 动态路由 Pinia 状态管理数据库设计直击财务场景核心账户、账单、分类、预算四张主表外键约束不是玩具 demo而是能当天部署、次日录入、一周内跑出「本月餐饮超支 37%」预警的真实系统。它适合三类人刚学完 SpringBoot 和 Vue 基础想练手的新人代码结构清晰、注释到位需要快速搭建内部财务看板的小微团队改个 logo、换套配色就能上线以及厌倦了 SaaS 工具隐私顾虑、想把数据完全握在自己服务器上的技术型用户。别被“个人”二字劝退——它的分层架构、事务控制、前后端分离模式就是企业级系统的最小可行切片。2. 本地跑通从解压到首页渲染的 5 分钟实操路径含 SpringBoot 版本兼容性避坑2.1 解压与目录结构速览看清源码包里的“真实战场”拿到基于SpringBootVue的个人财务管理系统源码数据库项目说明.zip后先别急着 IDEA 打开或npm install。解压后你会看到三个一级目录backend/SpringBoot 后端工程Maven 结构含pom.xml、src/main/java、src/main/resourcesfrontend/Vue 前端工程标准 Vue CLI 项目含package.json、src/、vue.config.jsdocs/项目说明文档PDF 或 Markdown含数据库 ER 图、接口列表、部署步骤提示务必先打开docs/里的README.md或项目说明.pdf确认两点一是后端要求的 JDK 版本常见为 JDK 8 或 JDK 17二是前端要求的 Node.js 版本常见为 v16.x 或 v18.x。版本错配是 80% 的首次启动失败根源比代码 bug 更致命。2.2 后端启动SpringBoot 服务跑起来的关键三步SpringBoot 后端默认使用 H2 内存数据库开发友好无需额外安装 MySQL 即可验证功能。按以下顺序操作# 进入 backend 目录 cd backend # 1. 检查 JDK 版本必须匹配项目要求 java -version # 2. 使用 Maven 打包并运行跳过测试避免环境干扰 mvn clean package -Dmaven.test.skiptrue # 3. 启动 jar 包注意不是 mvn spring-boot:run这是打包后最稳方式 java -jar target/finance-system-1.0.0.jar成功启动后控制台会输出类似Started FinanceSystemApplication in 4.2 seconds (JVM running for 4.8)的日志并监听http://localhost:8080。此时访问http://localhost:8080/swagger-ui.html若集成 Swagger或http://localhost:8080/actuator/health若启用 Actuator返回{status:UP}即表示后端服务已就绪。参数说明-Dmaven.test.skiptrue是关键开关跳过单元测试避免因本地环境缺失测试数据库或 Mock 数据导致构建失败target/finance-system-1.0.0.jar中的1.0.0是pom.xml中version定义的版本号实际文件名以你解压包中pom.xml为准。2.3 前端启动Vue 项目本地联调的最小命令链前端需与后端 API 对齐端口和代理配置。frontend/vue.config.js中通常已预设开发代理// frontend/vue.config.js module.exports { devServer: { port: 8081, // 前端本地端口 proxy: { /api: { target: http://localhost:8080, // 指向后端 SpringBoot changeOrigin: true, pathRewrite: { ^/api: } // 剔除 /api 前缀后端接口如 /user/login 实际请求 /api/user/login } } } }启动步骤如下# 进入 frontend 目录 cd frontend # 1. 安装依赖确保 Node.js 版本匹配v16.x 最稳v20.x 可能报 crypto 模块错误 npm install # 2. 启动开发服务器 npm run serve若npm install报错node-sass编译失败执行npm uninstall node-sass npm install sass --save-dev因为node-sass已废弃sassDart Sass是官方推荐替代。启动成功后浏览器访问http://localhost:8081应看到登录页。输入默认账号如admin/admin123见docs/文档或后端application.yml中spring.sql.init初始化脚本即可进入主界面。逻辑说明Vue 开发服务器8081通过proxy将所有/api/**请求转发至 SpringBoot8080规避跨域生产环境则需将 Vue 打包后的静态文件放入 SpringBoot 的src/main/resources/static目录实现同域部署。3. 数据库落地从 H2 内存库切换到 MySQL 的完整迁移清单含建表语句与初始化数据3.1 理解原始数据库设计四张核心表如何支撑财务逻辑该系统数据库设计紧扣个人财务管理本质非通用 CRUD 模板。docs/中的 ER 图或 SQL 脚本揭示了关键结构表名主要字段业务意义外键关联accountid, name, balance, type(现金/银行卡/支付宝), remark账户总览余额实时计算—categoryid, name, type(income/expenses), parent_id收入/支出分类树支持二级如“餐饮→外卖”self-referencebillid, amount, type, account_id, category_id, date, remark一笔账单金额、类型、归属账户与分类account.id,category.idbudgetid, category_id, month, amount, used_amount每月分类预算used_amount由账单自动累加category.id注意bill.date为DATE类型非DATETIME聚焦日粒度统计budget.used_amount不是视图而是由后端 Service 在新增/修改账单时主动更新保证强一致性。3.2 MySQL 替换 H2五步完成生产级数据库切换H2 仅用于快速验证MySQL 才是长期存储选择。切换需同步修改后端配置与初始化逻辑创建 MySQL 数据库与用户CREATE DATABASE finance_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER finance_userlocalhost IDENTIFIED BY StrongPass123!; GRANT ALL PRIVILEGES ON finance_db.* TO finance_userlocalhost; FLUSH PRIVILEGES;修改后端application.yml# backend/src/main/resources/application.yml spring: datasource: url: jdbc:mysql://localhost:3306/finance_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: finance_user password: StrongPass123! driver-class-name: com.mysql.cj.jdbc.Driver sql: init: mode: always # 强制每次启动执行 schema.sql 和 data.sql schema: classpath:schema-mysql.sql data: classpath:data-mysql.sql提供schema-mysql.sql建表-- backend/src/main/resources/schema-mysql.sql CREATE TABLE account ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, balance decimal(12,2) DEFAULT 0.00, type varchar(20) NOT NULL COMMENT cash/bank/alipay, remark varchar(200) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 其他表略完整脚本见 docs/ 或源码 resources 目录提供>-- 插入默认账户、分类、预算模板 INSERT INTO account VALUES (1,现金,0.00,cash,日常零钱); INSERT INTO category VALUES (1,工资,income,NULL),(2,餐饮,expenses,NULL),(3,交通,expenses,NULL); INSERT INTO budget VALUES (1,2,2024-06,3000.00,0.00); -- 餐饮月预算3000元验证 MySQL 连接启动后端观察日志是否出现HikariPool-1 - Starting...和Initialized JPA EntityManagerFactory而非 H2 的H2 database started。访问http://localhost:8080/api/account/list应返回 MySQL 中的账户数据。参数说明useSSLfalse是本地开发必要项生产环境应启用 SSLserverTimezoneAsia/Shanghai防止时间戳错乱allowPublicKeyRetrievaltrue解决 MySQL 8.0 认证插件兼容问题。4. 前后端联调避坑那些让你卡在登录页、图表不渲染、数据不更新的 5 个血泪现场4.1 现象登录成功后页面空白控制台报TypeError: Cannot read property username of undefined原因前端 Vuex/Pinia store 中用户信息未正确持久化或后端/login接口返回的 token 格式与前端axios拦截器期望不符如后端返回{token: xxx}前端却解析response.data.data.token。解决检查frontend/src/store/modules/user.js中loginaction 的 payload 解构对比后端LoginController.login()返回的 JSON 结构确保axios.defaults.headers.common[Authorization]设置正确通常是Bearer ${token}。4.2 现象图表ECharts显示 “No data” 或空白Network 中/api/chart/monthly返回 404原因Vue Router 的mode: history导致刷新页面时后端无法识别前端路由如/dashboard而/api/chart/monthly是后端接口404 说明请求根本没到达 Controller。解决在 SpringBoot 中添加 WebMvcConfigurer 配置将所有非 API 路径 fallback 到前端 index.html// backend/src/main/java/com/example/config/WebConfig.java Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/**) .addResourceLocations(classpath:/static/); registry.addResourceHandler(/favicon.ico) .addResourceLocations(classpath:/static/favicon.ico); } Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{spring:\\w}) .setViewName(forward:/index.html); registry.addViewController(/**/{spring:\\w}) .setViewName(forward:/index.html); } }; } }4.3 现象新增账单后账户余额未更新但数据库bill表已插入原因SpringBoot Service 层未开启事务或Transactional注解位置错误如加在 private 方法上无效导致bill插入成功但后续account.updateBalance()失败时未回滚。解决检查BillService.saveBill()方法是否在 public 方法上标注Transactional并确认其所在类已被 Spring 托管有Service注解在方法内手动抛出异常测试回滚Transactional public void saveBill(Bill bill) { billMapper.insert(bill); if (bill.getAmount() 10000) { throw new RuntimeException(模拟大额账单风控拦截); // 触发回滚 } accountService.updateBalance(bill.getAccountId(), bill.getAmount(), bill.getType()); }4.4 现象Vue 打包后部署到 Nginx登录页 CSS 错乱按钮文字重叠原因vue.config.js中publicPath配置错误。若部署在 Nginx 的子路径如http://your-domain.com/finance/publicPath必须设为/finance/否则资源路径如/js/app.js会请求根目录而非子路径。解决修改frontend/vue.config.jsmodule.exports { publicPath: process.env.NODE_ENV production ? /finance/ : /, // 其他配置... }并在 Nginx 配置中添加 locationlocation /finance/ { alias /var/www/finance/; try_files $uri $uri/ /finance/index.html; }4.5 现象MySQL 切换后中文分类名如“餐饮”存入数据库变成????原因MySQL 服务端、数据库、表、连接四层字符集未统一为utf8mb4或 JDBC URL 缺少characterEncodingutf8mb4参数。解决检查 MySQL 全局设置SHOW VARIABLES LIKE character_set%;确保character_set_serverutf8mb4创建数据库时指定CREATE DATABASE finance_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;修改 JDBC URLjdbc:mysql://localhost:3306/finance_db?characterEncodingutf8mb4...检查表结构SHOW CREATE TABLE category;确认DEFAULT CHARSETutf8mb4。5. 进阶实战给系统加上「微信消息提醒」和「Excel 导出」两个高价值功能附可抄代码5.1 微信模板消息推送让超支提醒直达手机基于微信公众号模板消息个人财务系统最大价值不是记录而是预警。当某类支出超过月预算 90%自动发送微信模板消息到用户手机比邮件更及时。这无需复杂 SDK只需调用微信公众号模板消息 HTTP API。后端实现SpringBoot在application.yml中配置公众号 AppID 和 AppSecretwechat: app-id: wx1234567890abcdef app-secret: your_app_secret_here template-id: ZYXWVUTSRQPONMLKJIHGFEDCBA987654新增WeChatService调用模板消息接口// backend/src/main/java/com/example/service/WeChatService.java Service public class WeChatService { Value(${wechat.app-id}) private String appId; Value(${wechat.app-secret}) private String appSecret; Value(${wechat.template-id}) private String templateId; Autowired private RestTemplate restTemplate; public void sendBudgetAlert(String openId, String category, BigDecimal used, BigDecimal budget) { // 1. 获取 access_token缓存 2 小时此处简化为每次调用 String tokenUrl https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid appId secret appSecret; MapString, Object tokenResp restTemplate.getForObject(tokenUrl, Map.class); String accessToken (String) tokenResp.get(access_token); // 2. 发送模板消息 String msgUrl https://api.weixin.qq.com/cgi-bin/message/template/send?access_token accessToken; MapString, Object payload new HashMap(); payload.put(touser, openId); payload.put(template_id, templateId); payload.put(data, buildTemplateData(category, used, budget)); restTemplate.postForObject(msgUrl, payload, Map.class); } private MapString, Object buildTemplateData(String category, BigDecimal used, BigDecimal budget) { MapString, Object data new HashMap(); data.put(first, Map.of(value, 【财务预警】您的预算即将用尽)); data.put(keyword1, Map.of(value, category)); data.put(keyword2, Map.of(value, used.toString() 元)); data.put(keyword3, Map.of(value, budget.toString() 元)); data.put(remark, Map.of(value, 请及时调整消费计划)); return data; } }在BudgetService.checkAndAlert()中触发Scheduled(fixedRate 3600000) // 每小时检查一次 public void checkAndAlert() { ListBudget overBudget budgetMapper.selectOverBudget(); // 自定义 Mapper 查询超支预算 for (Budget b : overBudget) { User user userService.getByAccountId(b.getAccountId()); // 关联用户 if (user.getWechatOpenId() ! null) { weChatService.sendBudgetAlert(user.getWechatOpenId(), categoryService.getNameById(b.getCategoryId()), b.getUsedAmount(), b.getAmount()); } } }关键点openId需用户在系统中绑定微信前端调用微信 JS-SDK 获取后端存入user.wechat_open_id字段模板 ID 在微信公众平台申请字段名keyword1需与模板一致。5.2 Excel 导出用 Apache POI 实现带样式的收支明细导出非简单 CSV用户常需导出数据给会计或做进一步分析。纯 CSV 缺乏格式而专业报表需合并单元格、货币格式、标题居中。Apache POI 是 Java 生态最稳方案。后端 Controller 添加导出接口// backend/src/main/java/com/example/controller/BillController.java GetMapping(/export/excel) public void exportExcel(HttpServletResponse response, RequestParam String startDate, RequestParam String endDate) throws IOException { ListBill bills billService.listByDateRange(startDate, endDate); // 创建工作簿 XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(收支明细); // 样式标题样式 XSSFCellStyle titleStyle workbook.createCellStyle(); XSSFFont titleFont workbook.createFont(); titleFont.setBold(true); titleFont.setFontHeightInPoints((short) 14); titleStyle.setFont(titleFont); titleStyle.setAlignment(HorizontalAlignment.CENTER); // 第一行标题 XSSFRow titleRow sheet.createRow(0); String[] headers {日期, 类型, 账户, 分类, 金额, 备注}; for (int i 0; i headers.length; i) { XSSFCell cell titleRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(titleStyle); } // 数据行 XSSFCellStyle dataStyle workbook.createCellStyle(); dataStyle.setDataFormat(workbook.createDataFormat().getFormat(¥#,##0.00)); // 货币格式 for (int i 0; i bills.size(); i) { XSSFRow row sheet.createRow(i 1); Bill b bills.get(i); row.createCell(0).setCellValue(b.getDate().toString()); row.createCell(1).setCellValue(b.getType().equals(income) ? 收入 : 支出); row.createCell(2).setCellValue(accountService.getNameById(b.getAccountId())); row.createCell(3).setCellValue(categoryService.getNameById(b.getCategoryId())); XSSFCell amountCell row.createCell(4); amountCell.setCellValue(b.getAmount().doubleValue()); amountCell.setCellStyle(dataStyle); row.createCell(5).setCellValue(b.getRemark()); } // 自动列宽 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 响应输出 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamefinance_export_ startDate _ endDate .xlsx); workbook.write(response.getOutputStream()); workbook.close(); }前端调用避免window.open被拦截// frontend/src/api/bill.js export function exportExcel(startDate, endDate) { return axios({ method: get, url: /api/bill/export/excel, params: { startDate, endDate }, responseType: blob // 关键告诉 axios 返回二进制流 }) } // 组件中调用 async handleExport() { try { const res await exportExcel(this.startDate, this.endDate) const blob new Blob([res.data], { type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet }) const url window.URL.createObjectURL(blob) const link document.createElement(a) link.href url link.download finance_export_${this.startDate}_${this.endDate}.xlsx link.click() window.URL.revokeObjectURL(url) } catch (error) { this.$message.error(导出失败 error.message) } }避坑提示responseType: blob必不可少否则 axios 会尝试 JSON.parse 二进制流导致报错window.URL.createObjectURL()是安全创建临时 URL 的标准方式比FileSaver.js更轻量。6. 我的三年财务系统迭代习惯从“能跑”到“敢用”的四个硬核动作跑通一个开源财务系统只是起点真正让它成为你每天愿意打开的工具需要持续注入“人味”。我维护自己这套系统三年从最初照着 README 部署到现在每月自动同步银行流水、每季度生成家庭资产负债表靠的不是技术多炫而是四个刻进肌肉记忆的动作第一永远用 Git 分支隔离改动。main分支只保留原始源码feature/wechat-alert、fix/mysql-charset、hotfix/login-bug这些分支名不是形式主义——它让我在升级 SpringBoot 版本时能一键git checkout main git pull回滚到干净状态再把wechat-alert的 commit cherry-pick 过去。没有分支就没有可追溯的演进。第二数据库变更必走 Flyway。早期我直接改schema-mysql.sql结果某次git pull覆盖了同事的建表语句导致线上数据丢失。现在所有 DDL/DML 都写成V1__init.sql、V2__add_budget_used_amount.sqlFlyway 自动执行且记录flyway_schema_history表。哪怕服务器崩了重启后它会精准补上缺失的迁移而不是凭记忆猜“上次改了哪张表”。第三前端静态资源加指纹。vue.config.js中开启filenameHashing: true让app.a1b2c3.js这种带哈希的文件名成为标配。这样每次npm run build后Nginx 无需清缓存用户访问的就是最新 JS——再也不用教父母“按 CtrlF5 强刷页面”。第四日志里埋业务上下文。log.info(用户{}在{}修改账单{}原金额{}新金额{}, userId, LocalDateTime.now(), billId, oldAmount, newAmount)这样的日志比log.info(update bill success)有用一百倍。上周排查一笔重复扣款就是靠这行日志定位到定时任务和手动编辑的并发冲突而不是翻三天前的全量日志大海捞针。这些动作不难但坚持下来系统就从“玩具”变成了“工具”——它不再需要你时刻盯着而是默默在后台运转像水电一样可靠。当你某天发现自己已经三个月没手动记过一笔账所有数据都来自银行 API 自动同步而报表依然准时生成那一刻你就懂了所谓技术落地不过是让工具足够诚实好让你腾出手来真正关注生活本身。希望帮到你。本文还有配套的精品资源点击获取
返回列表