恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
SpringBoot+Vue个人财务管理系统全栈开发实战:从数据库设计到部署
首页
资讯中心
/
SpringBoot+Vue个人财务管理系统全栈开发实战:从数据库设计到部署
SpringBoot+Vue个人财务管理系统全栈开发实战:从数据库设计到部署
发布时间:2026/9/4 11:12:54
简介本资源是一套完整的个人财务管理系统实战项目源码面向Java与Vue全栈初学者及课程设计开发者解决个人收支记录、理财目标跟踪与财务数据可视化等实际管理需求。压缩包共377个文件8.18MB涵盖98个后端Java核心业务与配置类、40个前端Vue组件含账单、统计、计划等模块、161个SVG图标资源、16个JS工具脚本、2个SQL建表与初始化脚本、2个YML配置文件以及bat/cmd启动脚本和静态资源文件结构清晰、模块边界明确便于分层学习与二次开发。目前已有196人学习下载资源附带详细项目说明文档完整呈现Spring Boot后端REST接口设计、Vue.js组件化开发流程、MySQL数据库建模逻辑及前后端联调要点开箱即用适合快速掌握企业级财务类应用的工程实践路径。1. 项目概述从零构建一个现代化的个人财务管理系统最近在整理自己的技术项目库翻到了一个几年前做的个人财务管理系统当时是为了解决自己“钱花哪儿了”的痛点用 SpringBoot 和 Vue 前后端分离搭了一套。没想到这套东西后来成了我面试、带新人、甚至接一些小型定制化开发需求的“样板工程”。今天我就把这个项目的里里外外彻底拆解一遍从技术选型、核心设计到每一行关键代码的考量毫无保留地分享出来。无论你是想学习前后端分离项目实战的在校生还是需要快速搭建一个类似管理系统的开发者这篇文章都能给你一份可以直接“抄作业”的完整指南。这个系统的核心目标很明确让个人或家庭能够清晰、便捷地记录和分析收支流水形成可视化的财务报表。它不是一个复杂的 ERP 或会计软件而是聚焦于“个人”这个场景所以在设计上追求轻量、直观和快速上手。整个项目采用经典的 SpringBoot 后端 Vue 前端架构数据库选用最普遍的 MySQL技术栈成熟、社区活跃意味着你遇到任何问题都能快速找到解决方案。接下来我会带你深入这个项目的每一个模块看看一个看似简单的增删改查应用背后有哪些值得琢磨的设计思考和实战技巧。2. 技术栈选型与项目架构解析2.1 为什么是 SpringBoot Vue选择这个组合绝非盲目跟风。几年前微服务和前后端分离的概念正火热但对于个人项目或小团队而言全套微服务显得过于笨重。SpringBoot 的“约定大于配置”理念让我能快速搭建一个结构清晰、功能完备的后端服务而无需在 XML 配置上耗费精力。它内嵌了 Tomcat一键启动对于开发、测试、部署都极其友好。更重要的是SpringBoot 生态完善整合 MyBatis、Spring Security、Redis 等组件几乎都是“开箱即用”极大地提升了开发效率。前端选择 Vue 而非 React 或 Angular主要基于两点考虑。一是渐进式框架的特性我可以从一个简单的页面开始逐渐引入组件、路由、状态管理学习曲线平缓。二是其文档和中文社区在当时乃至现在都非常活跃对于独立开发者或小型团队来说遇到问题能更快地找到答案。Vue 的单文件组件.vue将模板、逻辑和样式封装在一起使得代码的组织结构非常清晰维护起来也方便。这个组合构成了一个松耦合、高内聚的现代化 Web 应用基础后端专注 API 和数据前端专注交互和展示职责分明。2.2 后端架构设计与核心包结构后端项目采用典型的分层架构这是保证代码可维护性和可扩展性的基石。我的项目包结构大致如下com.personalfinance ├── controller // 控制层接收请求调用服务返回结果 ├── service // 业务逻辑层核心计算和处理在这里 │ └── impl // 服务接口的实现类 ├── mapper // 数据访问层即 MyBatis 的 Mapper 接口 ├── entity // 实体类与数据库表一一对应 ├── dto // 数据传输对象用于前后端交互常与实体不同 ├── vo // 视图对象用于封装返回给前端的数据 ├── config // 配置类如 WebMvcConfig、SecurityConfig ├── interceptor // 拦截器用于权限验证、日志记录等 ├── filter // 过滤器 ├── util // 工具类库 └── exception // 全局异常处理分层的好处Controller层只负责协议转换HTTP到Java对象和路由非常薄。所有业务逻辑都放在Service层这意味着即使未来要替换 Web 框架比如换成 Spring WebFlux业务代码也几乎不用动。Mapper层通过 MyBatis 的注解或 XML 与数据库交互做到了数据访问细节的隔离。Entity是纯粹的数据库映射而DTO和VO则根据前后端交互的实际需求来设计避免了将数据库结构直接暴露给前端带来的安全风险和耦合。注意很多新手容易犯一个错误就是直接用Entity作为接口的入参和出参。这会导致很多问题比如实体类中有数据库关联字段如密码password被意外序列化返回给前端或者前端传入的字段修改了你不希望被修改的实体属性。严格区分Entity、DTO、VO是构建健壮后端服务的第一步。2.3 前端 Vue 项目结构规划前端 Vue 项目使用 Vue CLI 搭建结构清晰符合官方推荐的最佳实践。src ├── api // 所有与后端交互的 axios 请求封装 ├── assets // 静态资源如图片、样式 ├── components // 可复用的 Vue 组件 ├── router // Vue Router 路由配置 ├── store // Vuex 状态管理根据复杂度决定是否引入 ├── views // 页面级组件 ├── utils // 前端工具函数 └── main.js // 应用入口文件在财务管理系统这种数据驱动型应用中我强烈建议使用Vuex进行状态管理即使初期你觉得状态不多。因为像“当前用户信息”、“全局的账户列表”、“筛选条件”等数据在多个组件如侧边栏、顶部导航、主内容区中都需要共享。如果通过组件间层层传递props或频繁使用事件总线代码很快就会变得难以维护。Vuex 提供了一个集中式的存储使得状态变化可预测、可追踪。路由设计采用嵌套路由来组织页面。例如有一个“财务”父路由/finance其下嵌套“流水”/finance/flow、“报表”/finance/report、“分类管理”/finance/category等子路由。这样可以利用router-view实现布局的复用比如保持顶部导航和侧边菜单不变只切换主要内容区域。3. 数据库设计与核心业务表详解数据库设计是系统的基石设计得好后续业务扩展和性能优化会轻松很多。个人财务系统的核心是“流水”所有设计都围绕它展开。3.1 核心表结构设计我设计了五张核心表1. 用户表 (user)CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 加密后的密码, email varchar(100) DEFAULT NULL COMMENT 邮箱, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;设计思考password字段长度要给足因为使用 BCrypt 等算法加密后字符串会很长。utf8mb4字符集支持存储 Emoji 等特殊字符。create_time和update_time是审计字段对于数据追溯非常有用。2. 账户表 (account)CREATE TABLE account ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 所属用户ID, account_name varchar(100) NOT NULL COMMENT 账户名称如支付宝、招商银行卡, account_type tinyint(4) NOT NULL COMMENT 账户类型1-现金2-储蓄卡3-信用卡4-网络支付, balance decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 当前余额, initial_balance decimal(15,2) NOT NULL DEFAULT 0.00 COMMENT 初始余额, is_active tinyint(1) DEFAULT 1 COMMENT 是否激活, remarks varchar(500) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账户表;设计思考余额balance使用DECIMAL(15,2)类型确保金融计算的精确性避免浮点数误差。account_type用字典值表示便于扩展。initial_balance用于记录开户时的金额与通过流水计算出的余额进行核对是保证数据一致性的重要手段。3. 收支分类表 (category)CREATE TABLE category ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, parent_id bigint(20) DEFAULT 0 COMMENT 父级分类ID0表示根分类, category_name varchar(100) NOT NULL COMMENT 分类名称, type tinyint(4) NOT NULL COMMENT 类型1-收入2-支出, icon varchar(100) DEFAULT NULL COMMENT 图标标识, sort_order int(11) DEFAULT 0 COMMENT 排序, is_system tinyint(1) DEFAULT 0 COMMENT 是否为系统预置分类, PRIMARY KEY (id), KEY idx_user_type (user_id,type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收支分类表;设计思考采用parent_id实现无限级树形分类比如“支出”下有“餐饮”“餐饮”下再有“早餐”、“午餐”。is_system字段用于区分用户自定义分类和系统预置分类如“餐饮”、“交通”等通用分类系统分类不允许删除保证了基础数据的完整性。sort_order用于前端展示时排序。4. 核心流水表 (transaction_record)CREATE TABLE transaction_record ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, account_id bigint(20) NOT NULL COMMENT 账户ID, target_account_id bigint(20) DEFAULT NULL COMMENT 目标账户ID用于转账, category_id bigint(20) NOT NULL COMMENT 分类ID, amount decimal(15,2) NOT NULL COMMENT 金额正数为收入负数为支出, transaction_type tinyint(4) NOT NULL COMMENT 交易类型1-普通收支2-转账, transaction_date date NOT NULL COMMENT 交易日期, remarks varchar(500) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id,transaction_date), KEY idx_account (account_id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT交易流水表;设计思考这是最核心的表。amount字段设计为带符号的十进制数用一个字段同时表示收支简化了业务逻辑。transaction_type是关键它将“普通收支”和“转账”区分开。对于转账amount记录的是从account_id转出的正数或负数取决于习惯同时需要记录target_account_id。transaction_date单独用一个DATE类型字段并建立联合索引idx_user_date这对于按日、月、年查询统计的性能至关重要。5. 预算表 (budget)CREATE TABLE budget ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, category_id bigint(20) DEFAULT NULL COMMENT 分类ID为空则为总预算, budget_amount decimal(15,2) NOT NULL COMMENT 预算金额, period varchar(20) NOT NULL COMMENT 周期MONTHLY-月度YEARLY-年度, year_month varchar(7) DEFAULT NULL COMMENT 年月格式2023-10用于月度预算, year int(4) DEFAULT NULL COMMENT 年份用于年度预算, remarks varchar(500) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_category_period (user_id,category_id,period,year_month,year) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预算表;设计思考预算支持两个维度总预算和分类预算。通过category_id是否为空来区分。period字段定义周期year_month和year字段用于精确关联到具体的时间范围。唯一索引uk_user_category_period防止用户为同一分类在同一周期创建重复预算。3.2 表关系与索引优化策略关系transaction_record表通过user_id,account_id,category_id分别与user,account,category表关联。所有核心业务表都带有user_id实现了数据隔离为未来多用户扩展打下基础。索引策略主键所有表使用自增BIGINT主键InnoDB 引擎下主键即聚簇索引能有效组织数据存储。外键实际项目中我没有使用数据库外键约束而是在应用层Service保证数据一致性。原因有二一是外键影响批量插入和更新的性能二是在分库分表等复杂场景下外键会成为障碍。取而代之的是通过逻辑关联和事务来保证。业务查询索引如idx_user_date(user_id,transaction_date)这是流水查询最常用的条件组合覆盖索引能极大提升“查询某用户某时间段流水”的速度。idx_account,idx_category则用于按账户或分类筛选。实操心得关于是否使用外键的争论很多。在像个人财务系统这样业务逻辑相对简单、由单一应用控制的项目中我倾向于不用数据库外键把一致性控制权交给业务代码。这样代码的逻辑更清晰性能也更可控。但务必在 Service 层的方法上使用Transactional注解确保相关操作在一个事务内完成。4. 后端核心业务逻辑实现4.1 用户认证与权限控制系统使用基于 Token 的无状态认证JWT这是前后端分离项目的标准做法。1. Spring Security 配置我自定义了一个JwtAuthenticationFilter放在 Spring Security 的过滤器链中。这个过滤器会检查请求头中的Authorization字段解析 JWT Token如果有效则将用户信息设置到 SecurityContext 中。Component public class JwtAuthenticationFilter extends OncePerRequestFilter { Autowired private JwtTokenUtil jwtTokenUtil; Autowired private UserDetailsServiceImpl userDetailsService; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { String token authHeader.substring(7); String username jwtTokenUtil.getUsernameFromToken(token); if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { UserDetails userDetails userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }2. 密码存储绝对不要明文存储密码我使用 Spring Security 提供的BCryptPasswordEncoder。它在加密时会自动加盐Salt并且每次加密的结果都不同能有效抵御彩虹表攻击。Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // ... 其他配置 }用户注册时调用passwordEncoder.encode(rawPassword)进行加密后存储。登录校验时调用passwordEncoder.matches(rawPassword, encodedPassword)进行比对。4.2 流水记录的核心服务逻辑流水的新增、修改、删除不仅仅是简单的数据库操作它必须同步更新关联账户的余额这是一个典型的需要事务管理的业务。1. 新增流水以支出为例Service Transactional(rollbackFor Exception.class) public class TransactionServiceImpl implements TransactionService { Autowired private TransactionRecordMapper transactionMapper; Autowired private AccountMapper accountMapper; Override public void addExpense(TransactionRecordDTO dto) { // 1. 参数校验 (略) // 2. DTO 转 Entity TransactionRecord record convertToEntity(dto); record.setAmount(dto.getAmount().negate()); // 支出金额记为负数 // 3. 插入流水记录 transactionMapper.insert(record); // 4. 更新源账户余额 (减少) Account account accountMapper.selectById(dto.getAccountId()); BigDecimal newBalance account.getBalance().subtract(dto.getAmount()); account.setBalance(newBalance); accountMapper.updateById(account); // 如果涉及转账还需要更新目标账户余额增加 } }关键点整个方法被Transactional注解包裹。这意味着如果在更新账户余额时发生异常之前插入的流水记录也会被回滚保证了“流水”和“余额”这两个紧密关联的数据始终一致。2. 删除流水删除操作更需谨慎因为需要根据被删除的流水反向计算并恢复账户余额。Override Transactional(rollbackFor Exception.class) public void deleteRecord(Long recordId) { // 1. 查询出要删除的流水记录 TransactionRecord record transactionMapper.selectById(recordId); if (record null) { throw new BusinessException(流水记录不存在); } // 2. 恢复关联账户的余额 Account account accountMapper.selectById(record.getAccountId()); // 注意原流水金额是负数支出或正数收入恢复时要取反 BigDecimal revertAmount record.getAmount().negate(); BigDecimal newBalance account.getBalance().add(revertAmount); account.setBalance(newBalance); accountMapper.updateById(account); // 3. 删除流水记录 transactionMapper.deleteById(recordId); }4.3 复杂查询与统计报表实现财务报表是系统的价值所在。核心是编写灵活的查询并利用数据库的聚合函数。1. MyBatis 动态 SQL 查询月度收支统计前端可能传入多种筛选条件时间范围、账户、分类等。MyBatis 的动态 SQLif标签非常适合这种场景。首先在TransactionRecordMapper.xml中定义select idselectMonthlySummary resultTypecom.personalfinance.vo.MonthlyStatVO SELECT DATE_FORMAT(transaction_date, %Y-%m) as month, SUM(CASE WHEN amount 0 THEN amount ELSE 0 END) as total_income, SUM(CASE WHEN amount 0 THEN amount ELSE 0 END) as total_expense, SUM(amount) as net_flow FROM transaction_record WHERE user_id #{userId} if teststartDate ! null AND transaction_date #{startDate} /if if testendDate ! null AND transaction_date #{endDate} /if if testaccountId ! null AND account_id #{accountId} /if GROUP BY DATE_FORMAT(transaction_date, %Y-%m) ORDER BY month DESC /select然后在 Service 层调用这个 Mapper 方法返回的数据结构MonthlyStatVO包含了月份、总收入、总支出、净流水直接传给前端用于绘制折线图或柱状图。2. 分类支出占比统计这个查询用于生成饼图展示钱都花在哪些类别上了。select idselectCategoryExpenseStat resultTypecom.personalfinance.vo.CategoryStatVO SELECT c.category_name as name, SUM(ABS(t.amount)) as value -- 支出取绝对值 FROM transaction_record t JOIN category c ON t.category_id c.id WHERE t.user_id #{userId} AND t.amount 0 -- 只统计支出 AND t.transaction_date BETWEEN #{startDate} AND #{endDate} AND c.type 2 -- 支出分类 GROUP BY t.category_id, c.category_name ORDER BY value DESC /select性能提示这类统计查询如果数据量很大几十万上百万条在transaction_date和user_id上建立联合索引是必须的。同时可以考虑定期将统计结果计算好存入缓存如 Redis或单独的统计表避免每次请求都进行全表扫描式的聚合计算。5. 前端 Vue 关键模块开发实录5.1 基于 Element UI 的页面布局与组件封装UI 框架选用 Element UI因为它组件丰富、设计规范能快速搭建出美观的后台管理系统界面。我采用经典的“上-左-右”布局顶部导航栏、左侧菜单栏、右侧主内容区。1. 布局组件 (Layout.vue)使用 Element 的Container布局容器结合 Vue Router 的router-view实现动态内容切换。template el-container el-header个人财务管理系统/el-header el-container el-aside width200px !-- 导航菜单根据路由动态生成 -- el-menu :routertrue :default-active$route.path el-menu-item index/dashboard仪表盘/el-menu-item el-submenu indexfinance template slottitle财务管理/template el-menu-item index/finance/flow流水管理/el-menu-item el-menu-item index/finance/report统计报表/el-menu-item /el-submenu !-- 更多菜单项 -- /el-menu /el-aside el-main !-- 主内容区页面在此渲染 -- router-view/router-view /el-main /el-container /el-container /template2. 封装可复用的数据表格组件 (DataTable.vue)流水管理、账户管理等页面都需要表格展示。我封装了一个通用的表格组件通过props接收列配置columns和数据tableData通过events暴露行操作事件如编辑、删除。template el-table :datatableData stylewidth: 100% el-table-column v-forcol in columns :keycol.prop :propcol.prop :labelcol.label :widthcol.width :formattercol.formatter /el-table-column el-table-column label操作 width180 v-ifhasOperation template slot-scopescope el-button click$emit(edit, scope.row) sizesmall编辑/el-button el-button click$emit(delete, scope.row) typedanger sizesmall删除/el-button /template /el-table-column /el-table /template script export default { props: { tableData: Array, columns: Array, hasOperation: { type: Boolean, default: true } } } /script在流水页面中可以这样使用data-table :table-dataflowList :columnsflowColumns edithandleEdit deletehandleDelete /这样表格的样式、分页、排序等逻辑只需要维护一份大大提高了代码复用率。5.2 状态管理Vuex 在财务数据流中的应用虽然是一个单用户系统但状态管理依然重要。我在store目录下建立了以下模块// store/modules/user.js - 用户模块 const state { token: localStorage.getItem(token) || , userInfo: null }; const mutations { SET_TOKEN(state, token) { state.token token; localStorage.setItem(token, token); }, SET_USER_INFO(state, info) { state.userInfo info; } }; const actions { login({ commit }, userInfo) { return new Promise((resolve, reject) { loginApi(userInfo).then(res { commit(SET_TOKEN, res.data.token); resolve(); }).catch(error reject(error)); }); }, // ... 其他action }; // store/modules/finance.js - 财务数据模块 const state { accountList: [], categoryList: [] }; const mutations { SET_ACCOUNT_LIST(state, list) { state.accountList list; }, SET_CATEGORY_LIST(state, list) { state.categoryList list; } }; const actions { async loadCommonData({ commit, dispatch }) { // 并行请求账户和分类列表 const [accountRes, categoryRes] await Promise.all([ getAccountListApi(), getCategoryListApi() ]); commit(SET_ACCOUNT_LIST, accountRes.data); commit(SET_CATEGORY_LIST, categoryRes.data); } };设计好处共享状态用户登录后token和userInfo全局可用。账户和分类列表在多个页面流水录入、报表筛选都需要从 Vuex 获取避免重复请求。数据缓存loadCommonDataAction 可以在应用初始化时调用一次然后将数据存在 Vuex 中除非手动刷新否则页面切换时不再请求提升了用户体验。逻辑集中所有与后端交互的数据获取和状态变更逻辑都集中在 Store 的 Actions 中组件变得非常“薄”只负责触发 Action 和渲染数据符合关注点分离的原则。5.3 图表集成使用 ECharts 实现数据可视化报表页面的核心是图表。我选择 ECharts功能强大且免费。在 Vue 中集成我使用了官方推荐的vue-echarts组件库。1. 安装与引入npm install echarts vue-echarts在main.js或单独的图表组件中按需引入import Vue from vue; import ECharts from vue-echarts; import echarts/lib/chart/line; // 按需引入折线图组件 import echarts/lib/chart/pie; // 按需引入饼图组件 import echarts/lib/component/title; import echarts/lib/component/tooltip; import echarts/lib/component/legend; Vue.component(v-chart, ECharts);2. 月度收支趋势图组件示例template div classchart-container v-chart :optionschartOptions :autoresizetrue stylewidth:100%; height:400px;/v-chart /div /template script export default { data() { return { chartOptions: { title: { text: 月度收支趋势 }, tooltip: { trigger: axis }, legend: { data: [收入, 支出, 结余] }, xAxis: { type: category, data: [] }, // 月份数据从后端获取后填充 yAxis: { type: value }, series: [ { name: 收入, type: line, data: [] }, { name: 支出, type: line, data: [] }, { name: 结余, type: line, data: [] } ] } }; }, async mounted() { const res await this.$api.finance.getMonthlySummary(); const { months, incomeData, expenseData, netData } res.data; // 动态更新图表配置 this.chartOptions.xAxis.data months; this.chartOptions.series[0].data incomeData; this.chartOptions.series[1].data expenseData.map(Math.abs); // 支出显示为正数 this.chartOptions.series[2].data netData; } }; /script关键技巧ECharts 的配置项options是响应式的。当从后端获取到数据后直接更新this.chartOptions中对应的data数组图表就会自动重新渲染。对于更复杂的交互如点击图例筛选、数据区域缩放可以在options中配置相应的event处理函数。6. 前后端联调与部署实战6.1 解决跨域与 API 对接规范开发环境下前端运行在localhost:8080后端运行在localhost:9090浏览器同源策略会阻止请求。解决方法有两种1. 前端代理开发环境首选在 Vue 项目的vue.config.js中配置module.exports { devServer: { proxy: { /api: { target: http://localhost:9090, // 后端地址 changeOrigin: true, pathRewrite: { ^/api: // 重写路径去掉 /api 前缀 } } } } };这样前端代码中请求/api/transaction/list开发服务器会自动代理到http://localhost:9090/transaction/list完美解决跨域。2. 后端配置 CORS生产环境必要在 SpringBoot 后端增加一个全局 CORS 配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) // 生产环境应替换为具体前端域名 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }API 规范我遵循 RESTful 风格设计接口但不过度教条。主要原则是使用 HTTP 方法表达操作意图GET-查询POST-新增PUT-修改DELETE-删除返回统一格式的 JSON 响应体。{ code: 200, message: 成功, data: { ... }, // 成功时的数据 timestamp: 1678886400000 }前端 axios 拦截器会统一检查code非 200 则弹出message中的错误信息。6.2 项目打包与部署后端打包使用 SpringBoot 的 Maven 插件直接打成可执行的 JAR 包。mvn clean package -DskipTests生成的target/*.jar文件包含了所有依赖只需 Java 运行环境即可启动java -jar your-finance-system.jar --spring.profiles.activeprod通过--spring.profiles.active指定使用application-prod.properties中的生产环境配置如数据库连接、日志级别。前端打包npm run build这会生成一个dist目录里面是优化、压缩过的静态文件HTML, JS, CSS。部署方案简易部署将前端dist目录下的所有文件复制到 SpringBoot 项目的src/main/resources/static/目录下重新打包。这样前后端就在同一个 JAR 包里通过一个端口提供服务。适合个人使用或演示。分离部署推荐将前端静态文件部署到 Nginx 或 Apache 服务器上后端 JAR 包单独运行。Nginx 配置反向代理将/api/路径的请求转发到后端服务同时直接提供前端文件。这种方式更利于前后端独立升级和扩展。# Nginx 配置示例 server { listen 80; server_name your-domain.com; location / { root /path/to/vue/dist; index index.html; try_files $uri $uri/ /index.html; # 支持 Vue Router 的 history 模式 } location /api/ { proxy_pass http://localhost:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }6.3 常见问题与排查技巧实录在开发和部署这个系统的过程中我踩过不少坑这里总结几个典型的1. 前端路由在刷新后报 404问题使用 Vue Router 的history模式在非根路径如/finance/report下刷新页面Nginx 会返回 404。原因这个路径在前端是虚拟路由Nginx 在dist目录下找不到对应的finance/report.html文件。解决在 Nginx 配置中为前端服务添加try_files $uri $uri/ /index.html;指令。它的作用是当请求的文件找不到时会尝试返回index.html由前端路由接管。2. 后端服务内存持续增长问题服务运行一段时间后内存占用越来越高。排查使用jps和jstack命令查看 Java 进程和线程状态。使用jmap -heap pid查看堆内存概况。最常见的原因是不当的静态集合类缓存如HashMap持续增长或者数据库连接未正确关闭。解决检查代码中的缓存逻辑确保有合理的淘汰策略如使用 LRU 算法的LinkedHashMap或引入 Redis。确保所有Service或Component都是单例且不持有大量实例数据。检查 MyBatis 的 Mapper 调用确保没有在循环中执行大量 SQL。3. 日期时间处理混乱问题前端传的日期字符串到后端变成前一天。原因JSON 序列化/反序列化时时区处理不一致。前端new Date()生成的是本地时间后端new Date()可能被当作 UTC 时间处理。解决前后端约定所有日期时间均以UTC 字符串格式ISO 8601传输例如2023-10-27T08:00:00.000Z。后端处理在application.properties中设置spring.jackson.time-zoneGMT8和spring.jackson.date-format。在实体类字段上使用JsonFormat(patternyyyy-MM-dd HH:mm:ss, timezoneGMT8)。数据库存储使用datetime类型并确保数据库服务器时区正确。4. 流水记录删除后账户余额对不上问题这是最严重的业务逻辑错误。排查首先检查删除流水的 SQL 是否在一个事务内。检查更新账户余额的逻辑计算恢复金额的公式是否正确。切记删除一条支出流水金额为负恢复余额时应该是 (-amount)即加上一个正数。检查是否有并发操作。两个用户同时操作同一账户可能导致余额计算错误。解决事务确保删除和更新余额在同一个Transactional方法中。并发控制在更新账户余额的 SQL 中使用乐观锁。在account表增加一个version字段版本号。更新时带上版本号条件UPDATE account SET balance #{newBalance}, version version 1 WHERE id #{id} AND version #{oldVersion};如果更新影响的行数为 0说明数据已被其他操作修改应抛出异常并回滚事务提示用户重试。对账功能定期如每天运行一个后台任务根据所有流水重新计算每个账户的理论余额并与账户表的当前余额对比。如果发现不一致记录错误日志并发出告警。这是保证数据最终一致性的最后一道防线。这个个人财务管理系统的构建过程是一次完整的全栈开发实践。它涉及了需求分析、数据库设计、后端业务开发、前端交互实现、部署运维和问题排查等多个环节。麻雀虽小五脏俱全。通过这个项目你不仅能掌握 SpringBoot 和 Vue 的技术栈更能理解一个完整应用从设计到上线的全流程思考。最重要的是它解决了一个真实的需求让你对自己的财务状况有了更清晰的掌控。本文还有配套的精品资源点击获取