ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+Vue个人理财系统实战:从架构设计到部署上线

2026/10/7 2:59:53 拓冰建站 浏览量
SpringBoot+Vue个人理财系统实战:从架构设计到部署上线 1. 项目核心设计与模块拆解1.1 技术栈选型的理由个人理财系统这个项目我前前后后做过几个版本从最早的纯JSP到最后这套SpringBoot Vue前后端分离方案算是踩遍了这条路的所有坑。先说结论SpringBoot Vue MyBatis MySQL这套组合切这个场景是够用的而且是当前中小型管理系统里最主流、找参考资料最容易、踩坑成本最低的搭配。为什么不用更重的Spring Cloud微服务个人记账的场景没有高并发数据量也就是个人几年流水微服务那套分布式事务、服务注册发现纯属给自己找麻烦。为什么不用JPA而选MyBatis理财系统里有大量自定义统计SQL比如按月份分组汇总收支、按分类聚合占比MyBatis写SQL可控性更强一个标签搞定复杂查询而JPA在这种报表场景下得写JPQL还要考虑映射反而绕。前端选Vue而不是React核心原因是Vue对中小型项目更友好模板语法贴近HTML思维单文件组件结构清晰配合Element UI组件库做个管理后台基本是开箱即用。这四件套的另一个实际好处是招聘市场认可度高、教程资源密如牛毛遇到任何一个报错搜索引擎一搜基本都有答案。做技术选型不要追求最前沿要追求可维护、可复现、可交付。1.2 前后端分离架构与功能模块划分这套系统的整体架构分两层后端跑在8080端口提供纯JSON接口前端跑在5173端口Vite默认做页面渲染两者通过HTTP通信。重点说一下RESTful接口风格比如记账操作就是POST /api/bill查询就是GET /api/bill/page删除就是DELETE /api/bill/{id}URL只表示资源操作交给HTTP方法区分。前端页面规划上我建议至少拆六个视图登录页、首页仪表盘、账目管理、分类管理、统计图表、个人中心。每个视图对应一个Vue Router路由配合侧边栏菜单做切换。仪表盘放本月收入、本月支出、结余、预算进度这类汇总卡片下面接一个最近十笔流水的表格顶部再放一个折线图展示近6个月收支趋势一屏之内让用户看清自己的财务概貌。后端按业务域分包controller接收请求并做参数校验service处理业务逻辑mapper通过MyBatis操作数据库。分类管理别看简单做不好后面统计全乱。我用的方案是一级分类硬编码到枚举里餐饮、交通、居住、购物、娱乐、医疗、教育、其他二级分类存数据库表用户可以在二级分类下自由扩展。账户体系也建议做两张表一个存用户基本信息一个存账目流水通过user_id关联这样以后想扩展多账户、预算管理都有基础。1.3 数据库表设计思路数据库设计是整个项目的地基我吃过大亏第一版表结构没设计好做统计功能时被迫反工。传销式的设计教训不细说直接分享这版可用的表结构。用户表t_user核心字段就五个id自增主键、username唯一索引、password注意要存BCrypt加密后的密文、create_time、avatar。这套系统只有登录不做注册审批流所以不需要status字段。账目表t_bill是核心字段有id、user_id、type0支出1收入、category_id、amount、occur_date、remark、create_time。amount记得用DECIMAL(10,2)别用FLOAT——浮点类型算金额会产生0.10.2不等于0.3这种经典问题银行家算法都救不了你。分类表t_category有id、name、user_id、is_default四个字段is_default为1表示系统预置分类用户不能删。我建议额外加一张t_budget预算表字段为id、user_id、category_id、month、amount。这张表让系统能做一个本月餐饮预算3000已花2200的进度提醒功能不大但演示时特别出效果。索引方面t_bill表必须给(user_id, occur_date)建联合索引因为所有统计查询都绕不开这两个条件的组合过滤没有索引数据量过万后查询会肉眼可见地变慢。在创建表结构时有个细节所有时间字段直接存date或datetime类型别图省事存字符串。存字符串当时看着方便到了做趋势图按月分组时DATE_FORMAT函数没法直接处理字符串就得先转换每多一次转换就是多一个出bug的机会。2. 环境准备与项目部署全流程2.1 后端环境搭建与SpringBoot配置要点先说环境版本这是新手最容易翻车的地方。JDK用1.8SpringBoot用2.7.x这两个搭配最稳。SpringBoot 3.0以上要求JDK 17如果你本机只装了JDK 8直接引入3.x的依赖会在启动就报UnsupportedClassVersionError这个错误就是版本不匹配的铁证。MySQL用5.7或8.0都行只需要注意驱动配置略有差异——5.7用com.mysql.jdbc.Driver8.0用com.mysql.cj.jdbc.Driver前者在新版驱动里已经被标记淘汰了。项目结构用Maven标准布局通过Spring Initializr生成或直接手建都一样核心文件有四个pom.xml管理依赖、application.yml做配置、启动类加上SpringBootApplication注解、mapper接口配XML文件。pom.xml里除了spring-boot-starter-web和spring-boot-starter-test这几个全家桶起步依赖还需要加mybatis-spring-boot-starter、mysql-connector-java、lombok如果你不想写一堆getter/setter、jjwt做登录令牌这四个。application.yml配置文件里数据源部分我是这样写的spring: datasource: url: jdbc:mysql://localhost:3306/personal_finance?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.finance.entity configuration: map-underscore-to-camel-case: trueURL里的serverTimezoneAsia/Shanghai一定要加。MySQL 8.0默认时区跟中国本地有偏差不加这个参数数据库连接会报Server returns invalid timezone的错。map-underscore-to-camel-case这个配置很关键开启后数据库里的occur_date字段能自动映射到Java类的occurDate属性不用每个字段都写TableField注解。2.2 Vue前端环境配置与代理设置前端这半边首先装Node.js版本建议16.x或18.x LTS。Node版本太新可能导致node-sass这类编译型依赖装不上因为它们的编译版本还没跟上。安装完Node后npm也跟着就绪在项目目录执行npm install -g vue/cli安装脚手架或者直接用npm create vitelatest finance-web -- --template vue创建Vite项目。创建完项目后第一件事就是配代理不然前端调接口会被浏览器同源策略拦下来。在vite.config.js里这样配置server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端发的/api/bill/list请求会自动转发到8080端口的后端服务。配上这层代理后前端代码里统一用axios的baseURL/api后续部署到生产环境只需要把target改成实际服务器地址前后端代码都不用动。axios请求拦截器这个环节我建议一开始就做不然后面每个页面写重复代码。拦截器做成两件事请求发出前从localStorage取token并塞到请求头Authorization字段响应返回时统一判断HTTP状态码如果是401说明登录过期直接清掉本地token并跳回登录页。这两段代码在各项目里基本是复制粘贴的不让用户手动在每个请求里传token省一大堆事。2.3 数据库初始化与MyBatis配置要点数据库初始化用Navicat执行SQL脚本就行我习惯把建表语句和初始化数据分开两个SQL文件。初始化数据部分要预置好一级分类和常用二级分类。建表完成后别急着连后端先在Navicat里手动跑几条INSERT测试下。MyBatis这块要理解一个核心逻辑Mapper接口定义方法XML文件写SQL通过命名空间绑定。一个典型的新增语句insert idinsert parameterTypecom.example.finance.entity.Bill useGeneratedKeystrue keyPropertyid INSERT INTO t_bill (user_id, type, category_id, amount, occur_date, remark, create_time) VALUES (#{userId}, #{type}, #{categoryId}, #{amount}, #{occurDate}, #{remark}, NOW()) /insertuseGeneratedKeystrue配合keyPropertyid能让插入后自动把数据库自增的ID回填到Java对象的id字段方便后续操作。如果不设置这个万一后面要拿着新账单ID去更新或删除你还得再查一遍。关于mybatis二级缓存这个热词我多说一句默认二级缓存是关闭的全局开启就要在mapper XML里加 标签加上实体类实现Serializable接口。但个人理财这类对实时性要求极高的数据缓存的意义不大反而容易读出脏数据。我建议保持默认关闭别为了引入技术而引入技术。3. 核心功能实现与关键代码解析3.1 用户登录与JWT权限控制登录是每个管理系统的门面我做的是JWTJSON Web Token方案。原理不复杂用户登录成功后后端用密钥签发一个包含用户ID和过期时间的令牌返回给前端前端存到localStorage后续每次请求带上后端验签通过就放行。跟Session方案最本质的区别是Session存在服务端内存里JWT无状态分布式部署不用考虑session共享问题。后端签发token的代码我用jjwt库核心逻辑这样写String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();有效期我设置7天实际项目里可以兼顾用户体验和安全。为了让JWT校验不侵入每个Controller我写一个拦截器或者直接用一个简单的Filter对所有排除登录接口之外的请求做token校验。排除名单包含/api/login、/api/register这些开放接口。校验逻辑三步走取请求头的Authorization字段、剥掉Bearer 前缀、调用Jwts.parser()解析抛异常就返回401。密码安全这一节现在很多人还存明文这是很危险的。Spring Security自带BCryptPasswordEncoder你不想引整个Security框架也可以单引spring-security-crypto这个模块加密、校验都封装好了。注册时password字段直接passwordEncoder.encode(明文)登录时passwordEncoder.matches(明文, 密文)比对。BCrypt自动加盐同一个密码每次加密出来的密文都不一样即使数据库被拖库也没法直接反查原始密码。3.2 账目收支CRUD的实现细节账目管理是整个系统最核心的CRUD增删改查四类操作里删除我建议做逻辑删除而非物理删除。别急着删以后对账时总需要回溯历史数据。我在t_bill表加了个deleted字段默认0删除操作执行一次UPDATE把deleted置为1查询条件统一加where deleted 0。这样误删的数据还能恢复审计也能查到痕迹。分页查询这节要重点说。接口入参设计为pageNum页码、pageSize每页大小、type收入/支出筛选、categoryId、startDate、endDate、keyword七项。用PageHelper插件的话代码就很简洁PageHelper.startPage(pageNum, pageSize); ListBillVO list billMapper.selectCondition(query); PageInfoBillVO pageInfo new PageInfo(list);PageHelper的原理是在执行SQL前用拦截器自动拼上LIMIT语句所以它必须紧贴mapper查询方法那行代码中间不能隔着其它数据库操作否则分页参数会被覆盖。如果依赖装不上手写limit也行就是多一个count查询再手动算总页数工作量稍微多一点但可控性更强。BirBillVO这个视图对象是建议做的因为前端表格需要展示分类名称而不是分类ID。用JOIN查出来SELECT b.*, c.name AS category_name FROM t_bill b LEFT JOIN t_category c ON b.category_id c.id。VO类里加一个categoryName字段接收这个值前端就能直接显示。查出结果后金额字段该格式化的格式化该加颜色标识的在vue里用条件class控制即可。表单校验我放在前后端各做一次前端做是为了用户体验输入非法数字立刻提示不浪费请求后端是为了数据安全防止有人绕过前端直接打接口。后端的校验用Spring的Valid注解配合NotBlank、DecimalMin这些约束注解在Service入口参数上加Validated非法数据会被框架拦截统一返回400错误信息。3.3 统计报表与可视化大屏的实现思路这一块是项目的演示亮点也是区分会做CRUD和理解了业务的分水岭。核心统计需求有三类本月支出按分类占比、近六个月收支趋势、年度收支汇总。“本月支出按分类占比”的SQL用到了MySQL的DATE_FORMAT函数精确到月份SELECT c.name AS category_name, SUM(b.amount) AS total FROM t_bill b LEFT JOIN t_category c ON b.category_id c.id WHERE b.user_id #{userId} AND b.type 0 AND b.deleted 0 AND DATE_FORMAT(b.occur_date, %Y-%m) DATE_FORMAT(NOW(), %Y-%m) GROUP BY b.category_id, c.name这条查询算清楚了一个核心思路DATE_FORMAT在WHERE里做月份过滤、GROUP BY按分类聚合、SUM做金额加总。前端拿到数据后用ECharts的饼图一接入就能展示占比。ECharts的引入很简单执行npm install echarts在需要的组件里import * as echarts from echarts然后初始化实例、setOption传配置项就完事。近六个月趋势图的SQL核心是分组统计但有个小坑某个月可能没有任何支出记录用简单的GROUP BY该月就不会出现在结果集里折线图就会少一个点。我的处理方案是Java代码里补齐12个月的映射——先在内存里生成12个月的map初值0再把SQL查出来的结果覆盖进去这样图永远是平滑完整的这个是纯SQL层面不好处理的业务逻辑放到Service层反而简单。这个经验值得记下来很多统计报表的缺口问题都用代码补零方案解决。ECharts的配置项里我在tooltip里加了格式化函数展示金额加元后缀在legend里控制图例显示位置从顶部改成右侧因为饼图放顶部占地方还挡住数据。折线图的平滑曲线用的是smooth: true比折线看起来更柔和业用户观感明显更好。做可视化一定要站在使用者视角调细节不是配置完不管就行的。4. 常见问题排查与避坑指南4.1 后端启动与数据库连接问题启动SpringBoot项目最常见的三大故障我几乎每个项目都会遇到。第一个就是端口占用。8080被某个进程占了后端启动就会报Port already in use而直接失败。解决办法要么在application.yml改端口要么在命令行执行netstat -ano | findstr 8080找到占用进程PID任务管理器结束进程。我习惯在yml里用server.port: 8080统一管理端口绝不写到代码里。第二个是数据库连接失败。报错一般是Access denied for user rootlocalhost居多这种基本就是用户名或密码不对。但更隐蔽的是Public Key Retrieval is not allowed——这是MySQL 8.0的SHA256密码认证机制导致的解决方案是在数据源URL后面加上allowPublicKeyRetrievaltrue。这个参数几乎每个MySQL 8.0新手项目都会遇到我提前加好省得日后排查。第三个是驱动类找不到。明明mysql-connector-java依赖写进pom了启动就报ClassNotFoundException。排查关键在确认依赖是否真的引入了Maven本地仓库是否下载完整。遇到这种情况先mvn clean再mvn dependency:tree查看依赖树看mysql驱动到底在不在。4.2 前端接口联调与跨域问题前端调用后端接口时如果页面报CORS policy: No Access-Control-Allow-Origin 说明浏览器拦截了跨域请求。配置了Vite代理后这个问题基本不会出现。但有些项目把前后端分别部署在不同域名比如前端在console.example.com、后端在api.example.com这种就必须在后端加CORS配置。后端加CORS比较简单推荐用WebMvcConfigurer统一配置不要在每个Controller加CrossOrigin注解那样维护成本高Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:5173) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true); } }联调期还有一个高频问题前端明明改了代码页面就是不变。这多半是浏览器缓存了旧的JS文件Vite基于ESM热更新正常情况改了自动刷新如果没生效就按CtrlShiftR强制刷新。如果是打包部署后的静态文件不更新检查Nginx是否配置了缓存策略开发阶段建议直接加禁用缓存的响应头。还有那个vue项目源码怎么发给别人的热词我顺便说一嘴——交付源码给别人时一定要删掉node_modules目录别人拿到后执行npm install重新安装。node_modules动辄几百兆压缩包传过去又慢又容易中断。同时确保package-lock.json一同交付这个文件锁定了每个依赖的精确版本安装结果能一致。4.3 MyBatis与SQL执行的性能边界个人理财系统的数据量单纯CRUD性能不会成为瓶颈。但统计报表的SQL容易写出性能陷阱。第一个陷阱是SELECT *我只在快速开发时用它线上或正式接口一律明确列出字段原因有两个一是减少IO二是全字段查出来如果表结构变更而代码没同步容易出错。第二个陷阱是统计查询忽略索引前面建议过给(user_id, occur_date)建联合索引这个前提前置条件满足。MySQL锁这块也简单说说。实际项目中遇到Waiting for table metadata lock这种状态通常是有人开了一个事务没提交还改了表结构把表锁住了。排查方法是用SHOW PROCESSLIST看会话状态找到空闲但占用锁的连接KILL掉。日常开发中要及时提交事务、用完连接就关闭避免资源泄漏。mybatis二级缓存、一级缓存这两个热词也提醒大家一级缓存是SqlSession级别的默认开启同一个SqlSession内两次相同查询会命中缓存。但注意Spring整合MyBatis后每次mapper方法调用都会开启新SqlSession一级缓存基本等于失效。如果你的代码里有循环里反复查同一数据的场景这种重复查询建议直接查一次放Map里复用比依赖缓存机制可靠。5. 项目上线部署与后续扩展建议5.1 前端打包与Nginx部署细节开发完成后进入部署环节前端构建很简单在vue项目根目录执行npm run build即可产物输出到dist目录。关键坑在于Vue Router如果用的是history模式直接部署到Nginx后刷新页面会报404。原因很简单history模式是前端路由模拟的路径Nginx服务器上根本没有对应的物理文件。处理方案有两个要么把路由改成hash模式URL带#符号丑但省事要么在Nginx配置里加一条location try_files规则location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }try_files这段的作用是访问路径对应的文件不存在时统一回退到index.html由前端路由接管。这个配置基本上是每个Vue部署项目必备务必记牢。后端部署我推荐打成jar包。在pom.xml里配置好spring-boot-maven-plugin执行mvn clean package打出来启动用java -jar finance-server.jar。如果服务器只有1G内存记得启动参数加-Xmx256m限制堆内存避免JVM默认占太多把机器搞卡。生产环境日志通过logback配置rolling策略按天滚动、保留7天份避免日志文件无限膨胀占满磁盘。5.2 系统功能向多用户与自动化演进这个项目交付后想持续升级第一个方向是多用户体系完善。目前单靠user_id区分用户账目统计天然隔离。但这个粒度下用户换设备后本地token失效就得重新登录体验一般。可以做刷新令牌机制登录发一个短期的access_token和一个长期的refresh_tokenaccess过期后用refresh去换新的用户7天内无需重新登录。这套机制在Spring Security或Sa-Token框架里都有现成支持工作量不大但体验提升明显。第二个方向是导入导出功能。Excel导入记账流水用EasyExcel写个监听器几百行代码就能搞定。用户从银行导出的流水Excel格式乱七八糟要做的是把日期转成标准格式、把金额正负转成收支类型这些清洗逻辑写清楚就能沉淀为系统的核心能力。导出方面就是查全量数据然后利用模板写注意大数据量做分批导出防止内存溢出。第三个方向是接入智能分析。每月的账单明细其实是可以做消费习惯分析的比如本月餐饮支出较上月增长15%娱乐支出占总支出比例连续三月上升。这类分析用规则引擎也能实现大半不需要上机器学习。把规则配置化根据聚合结果触发事件推送让系统从被动记录工具变成主动提醒助手。我个人认为这才是个人理财系统真正产生价值的升级路径——工具本身不会帮你省钱但工具给出洞察后花钱习惯才有可能改变。我自己的记账习惯就是因为这个功能坚持了三年每个月看到餐饮分类的柱状图比上月高出一截下个月自然就有了克制。这个系统的完整度已经达到可以拿去写作课设或者作为求职简历项目的水平代码结构清晰表设计规范功能覆盖了从登录鉴权到可视化统计的完整链路。后续重点放在细节打磨比如移动端适配、暗色模式、多币种支持、定时提醒。技术在推进但理财系统真正考验的是对钱的流动这件事的记录与洞察做到什么程度。把基础打好后面加什么能力都顺畅。