多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot+Vue个人理财系统毕业设计:从数据库设计到接口实现全解析

SpringBoot+Vue个人理财系统毕业设计:从数据库设计到接口实现全解析 又到了一年两度的毕业设计选题季后台收到最多的咨询就是Java Web做什么题目好而SpringBootVue个人理财系统几乎是被问及次数最多的一个。这个题目的热度不是没有道理技术上它覆盖了前后端分离、数据库设计、接口开发、图表展示这条完整链路业务上它的记账、分类、统计功能边界清晰不用依赖外部系统更重要的是它的完整源码、SQL脚本和接口文档都是现成可交付的很适合作为Java Web方向的毕业设计项目。这篇博文就围绕这个项目把设计思路、数据库表结构逻辑、后端接口实现、前端页面组织、以及从源码到可运行的全流程一次性拆开讲清楚。不管你手里是完整的源码包还是只有半成品这篇文章都能帮你把项目的里里外外吃透至少做到答辩时老师问任何一个模块你都能答出为什么这么设计。1. 个人理财系统到底要做什么需求与功能边界很多同学拿到项目源码的第一步是急急忙忙启动看效果。我建议反过来先花半小时把需求边界理清楚。个人理财系统名字看上去很大但核心业务其实就一句话记录每一笔收入和支出按分类和时间做汇总统计。它不是支付宝、不是股票交易软件它的价值在于个人账本管理。1.1 核心功能模块划分一套完整的个人理财系统我建议只做四个模块用户认证模块注册、登录、退出。用户体系是所有功能的前提没有它数据归属说不清楚。账目管理模块新增一笔收入/支出、编辑、删除、分页查询、按日期范围和分类筛选。这是系统的核心操作对象。分类管理模块收入和支出的分类维护比如餐饮、交通、工资、理财收益。分类是统计的维度没有分类后面的图表就塌了。统计报表模块按月汇总收支金额按分类统计占比用折线图和饼图展示。这四块构成了一个完整闭环用户登录后记几笔账然后到统计页看到这个月的钱花到哪去了。整个请求链路是Vue页面 - axios调用接口 - SpringBoot Controller - Service - Mapper - MySQL - 数据返回 - ECharts渲染这正是Java Web毕设最想考察的东西。1.2 有哪些功能是不该做的和几届做这个题目的同学聊过之后我发现最容易翻车的不是功能太少而是功能太多。有人给理财系统加了个股票模拟交易有人加了多人拼单AA制还有人试图对接支付宝账单导入。功能一旦超出个人记账的边界就会带来一堆复杂问题数据从哪来权限怎么算安全性怎么保障写代码的时间翻倍不说还容易因为实现不完整被答辩老师追问到卡壳。我的建议是毕设项目的核心价值在于完整不在于丰富。一个用户、一张账单表、一套统计逻辑只要做到干净规范就足以拿一个不错的分数。预算提醒、账单导出这类功能如果时间充裕可以作为锦上添花但优先级永远排在核心四模块之后。2. 为什么选SpringBootVue出这个项目技术选型的底层逻辑选型这件事得先想清楚为什么是它。Spring Boot和Vue的组合在近几年的Java Web毕设里几乎是统治级的方案理由很实在。2.1 Spring Boot为什么是后端最优解先说Spring Boot。它最核心的价值是自动配置 起步依赖。你不需要像早年SSH框架那样配置一大堆XML文件只需要引入spring-boot-starter-web依赖它就能自动把Spring MVC、内嵌Tomcat、JSON序列化这些基础设施准备好。对于毕设这种需要快速交付的项目来说这套机制能省掉至少一周的搭环境时间。再配合MyBatis-Plus单表CRUD几乎不用写SQLBaseMapper把增删改查和分页全部封装好了。这意味着你可以在很短时间内把账单、分类这类基础操作做完把精力放到更有含金量的统计查询、权限校验和接口设计上。2.2 版本选择Spring Boot 2.7还是3.x这里我必须多说几句因为这是今年毕设翻车的高发区。很多同学下载源码的时候用的是Spring Boot 2.7但自己创建项目时IDEA默认或者阿里云上拉的是Spring Boot 3.x结果编译报一堆javax找不到的错误。原因在于Spring Boot 3.x要求JDK 17并且把包名从javax.servlet改成了jakarta.servlet。如果你学校机房的JDK还是8或者你的老教程写的是import javax.servlet.*那就老老实实用Spring Boot 2.7.x。这个版本是2.x系列的最终维护版本稳定资料多JDK 8直接运行。如果你用的是JDK 17那可以直接上3.x但对应的依赖版本必须升上去比如MyBatis-Plus要用3.5.3javax相关的代码全部换成jakarta。2.3 Vue 2还是Vue 3新项目建议直接Vue 3Vue版本的选择也是差不多的逻辑。Vue 3已经是绝对主流Element Plus组件库也早就成熟了。但如果你手里拿到的教程是Vue 2的照着一写语法完全对不上——Vue.use、new Vue()、this.$router这些在Vue 3里全变了。我的建议很直接新项目直接Vue 3 Vite Element Plus。原因有三点Vue 3的组合式APIsetup语法糖写业务代码更清晰逻辑复用也方便。Vite启动速度比Webpack快一个量级开发体验好很多。Element Plus就是Element UI的Vue 3版本组件用法几乎一样学习成本不高。配套的Node.js版本Vite 5建议用18老版本的Node会导致启动直接报错。这一条加入后面的环境检查清单里。3. 数据库设计从记账场景推导出的表结构数据库是SQL脚本的核心也是答辩时老师一定会细看的东西。一套个人理财系统的库不需要很复杂但每一张表的存在逻辑都要能讲清楚。3.1 三张核心表和一张辅助表个人理财系统的表结构我推荐设计四张表表名作用关键字段说明sys_user用户表id,username,password,nickname密码存BCrypt加密结果biz_category分类表id,user_id,name,type,icontype区分收/支biz_bill账单表id,user_id,category_id,amount,type,remark,record_date核心业务表biz_budget预算表id,user_id,category_id,amount,month可选模块账单表是最核心的字段设计上有几个细节要特别注意。第一金额用DECIMAL(10,2)绝对不用FLOAT或DOUBLE。浮点数在计算机底层是二进制表示的0.1 0.2会出现精度丢失而钱这种东西不允许误差。DECIMAL是定点数专门解决货币精度问题。这是个高频答辩问题答上来就是加分项。第二账单表里冗余了type字段。为什么已经有了category_id还要在账单表里存一个收支类型因为查询的时候按收入/支出做汇总统计是最高频的操作。如果不冗余这个字段每次统计都得去关联分类表才能知道这笔账是收入还是支出。冗余字段换查询性能这是很典型的数据库设计思维可以在答辩时主动讲。第三外键不建物理外键。分类表被账目表引用但表结构上不要写FOREIGN KEY约束。原因是毕设阶段数据量不大物理外键会影响插入删除的效率而且管理起来麻烦。归属关系通过应用层代码控制删除分类之前先检查这个分类下有没有账单数据有就提示用户先处理账单。3.2 索引设计让统计查询不犯愁账目查询和统计是最频繁的操作所以索引要围绕这两个场景建。建议建两个组合索引idx_user_record_date (user_id, record_date)支撑查某人某段时间的流水。idx_user_category (user_id, category_id)支撑按分类查账单。组合索引的顺序有讲究user_id放前面是因为所有查询都限定在登录用户自己的数据范围内先按用户过滤再做时间和分类的筛选。这也是一个可以写在文档里的设计细节。3.3 SQL脚本的导入坑编码和库的选择SQL脚本本身不难但导入的时候十个有八个出问题。最常见的是中文乱码。解决方案是导入前确认SQL文件的字符集是utf8mb4而不是utf8。utf8mb4是utf8的超集能存Emoji这类四字节字符。MySQL 8.0默认就是utf8mb4MySQL 5.7需要在建库时指定CREATE DATABASE IF NOT EXISTS finance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;另外如果你的SQL脚本里没有CREATE DATABASE这一段就得先手动建库、USE切换到对应库再执行脚本。不然会直接报没有选择数据库的错误。用命令行导入的完整方式是mysql -u root -p finance_db finance_db.sql如果嫌命令行麻烦用Navicat的运行SQL文件功能也可以但一定要确认当前连接选中的是目标数据库。4. 后端核心模块拆解认证、账单、统计接口怎么实现后端是Spring Boot的重头戏代码结构直接影响答辩观感。这里我按一个比较规范的工程分层来讲controller、service、mapper、entity、common。4.1 统一返回结果和异常处理我见过很多课设代码接口返回值五花八门有的成功返回true有的返回data失败就抛异常给前端直接看500页面。这在答辩时非常减分。规范做法是定义统一的返回体public class ResultT { private Integer code; private String message; private T data; }配合全局异常处理RestControllerAdvice业务异常和系统异常全部被捕获返回结构一致。前端axios只需要判断code就能统一处理成功、参数错误、未登录、服务器异常这几种情况。这套机制实现了前后端约定先行是工程化意识的体现。4.2 用户模块注册、登录与Token校验注册接口的逻辑很直白但有两个点要做好。一是参数校验用Valid注解用户名不能为空、密码最短长度、两次密码一致这些都是后端必须校验的不能只靠前端拦截。二是密码加密存储用BCryptPasswordEncoder绝对不能把明文密码写进数据库。登录成功之后怎么保持会话毕设项目用JWT是最合适的。它的思路是登录验证通过后后端生成一个签名字符串返回给前端前端存在本地存储里每次请求都带上。后端通过拦截器验证签名的合法性判断用户身份。核心代码大概是这样的public class JwtUtil { private static final String SECRET your-secret-key; public static String generateToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7L * 24 * 3600 * 1000)) // 7天有效 .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } }拦截器要做两件事放行登录、注册接口其余请求从Header里取Authorization解析失败就返回401。这里有个容易踩的坑一定要放行登录接口否则会让登录请求陷入验证token - 没有token - 拦截 - 无法登录的死循环。4.3 账单模块分页条件查询与归属校验账单列表页要用到的是分页 条件查询。MyBatis-Plus的分页插件配置好之后Service层用LambdaQueryWrapper动态拼接条件即可LambdaQueryWrapperBill wrapper new LambdaQueryWrapper(); wrapper.eq(Bill::getUserId, currentUserId) // 只能查自己的 .eq(billQuery.getType() ! null, Bill::getType, billQuery.getType()) .eq(billQuery.getCategoryId() ! null, Bill::getCategoryId, billQuery.getCategoryId()) .ge(billQuery.getStartDate() ! null, Bill::getRecordDate, billQuery.getStartDate()) .le(billQuery.getEndDate() ! null, Bill::getRecordDate, billQuery.getEndDate()) .orderByDesc(Bill::getRecordDate);这里有一个安全性细节必须提所有涉及数据归属的查询都要强制带上当前登录用户的ID。前端传什么参数只是条件后端从Token里解析出的用户ID才是边界。这能防止用户通过篡改请求参数去查别人的账。越权问题是答辩老师重点考察的内容能做到这一条项目档次立刻不一样。同样的逻辑也适用于编辑和删除先查这条账单是不是当前用户的不是就直接抛业务异常无权操作。删除分类之前也要先查这个分类下有没有账单数据有就不能删。4.4 统计模块SQL聚合与前端图表的数据契约统计模块是整个项目里最能体现从数据到可视化价值的部分。统一在数据库层做聚合前端只是把结果填充到图表里。按月份汇总收支核心SQL如下SELECT DATE_FORMAT(record_date, %Y-%m) AS month, type, SUM(amount) AS total_amount FROM biz_bill WHERE user_id #{userId} AND record_date BETWEEN #{startDate} AND #{endDate} GROUP BY month, type ORDER BY month;按分类统计占比需要关联分类表SELECT c.name AS category_name, SUM(b.amount) AS total_amount FROM biz_bill b LEFT JOIN biz_category c ON b.category_id c.id WHERE b.user_id #{userId} AND b.type #{type} GROUP BY c.id, c.name;前端用于ECharts的数据格式我建议后端直接组装成图表组件需要的结构折线图的categories数组月份和series数组收入/支出两组数据饼图的{name: 餐饮, value: 300}列表。这样前端拿到的就是图表友好的数据不用在Vue里做二次聚合。当你答辩的时候能主动讲出后端做聚合前端做渲染这一分层逻辑老师通常会满意地点点头。5. 前端页面与交互Vue项目从初始化到图表展示前端部分的工作量我通常跟学生说是页面多但都不重。真正花时间的不是写页面而是把接口联调、状态管理、权限控制这套骨架搭顺。5.1 项目初始化和目录结构用Vite创建项目的标准命令是npm create vitelatest finance-web -- --template vue cd finance-web npm install随后安装配套依赖npm install vue-router4 pinia axios element-plus echarts这里有个国内环境很常见的问题npm官方源在国内下载慢装到一半卡住最后报一堆网络错误。解决办法是先把镜像切到国内源再装npm config set registry https://registry.npmmirror.com我觉得这一句命令能帮人省下至少半小时。目录结构建议设计为src/ api/ // 封装axios和各个接口模块 router/ // 路由配置含全局守卫 store/ // Pinia存用户信息和token views/ // 页面级组件 components/ // 可复用组件5.2 axios封装Token自动携带与错误处理axios请求拦截器做一件事从Pinia或localStorage里取token注入到请求头。这样业务代码里调用接口时完全不用手动处理鉴权service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config })响应拦截器做另外两件事后端返回的code不等于200时统一弹出提示消息遇到401未登录或token过期清理本地状态并跳回登录页。这里我还要特别说一个联调细节路由和接口的路径设计。建议后端所有Controller都加一层/api前缀前端axios的baseURL也设为/api然后在Vite配置里做代理转发server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样开发环境不存在跨域问题前端请求/api/bill/list会被代理到后端。千万不要在前端代码里写死http://localhost:8080不然换一台机器跑项目光改这个地址就能让人崩溃。5.3 核心页面与图表展示前端页面里统计页是最出效果的部分。ECharts的用法本身不难难的是时机。图表必须在DOM渲染完成后再初始化所以要在onMounted里调用接口拿数据拿到后再setOptiononMounted(async () { const res await getDashboardData(selectedMonth.value) lineChartRef.value.setOption({ xAxis: { data: res.data.categories }, series: [ { name: 收入, type: line, data: res.data.incomeData }, { name: 支出, type: line, data: res.data.expenseData } ] }) })一个常见的显示bug如果某个月没有收入或支出接口返回的数组长度对不上图表坐标会错位。解决方案是后端返回数据时把没有记录的几个月份也补上0值保证数组长度一致。这种补齐数据的前后端考量也是实操中才会踩到的问题。6. 从源码到可运行SQL导入、后端启动、前端联调全流程这一节是给拿到项目源码但是跑不起来的人写的。我按标准的启动顺序把每一步的关键点和常见报错整理出来。6.1 环境准备JDK、Maven、Node版本对照先把环境版本检查一遍版本不匹配是很多启动失败的根源。建议按这个组合来组件推荐版本说明JDK8 或 17根据Spring Boot主版本决定Maven3.6用于后端依赖下载和打包MySQL5.7 或 8.0注意字符集设置Node.js16推荐18Vite 5要求较高版本npm随Node附带国内建议换镜像源查看版本的方式java -version、mvn -version、node -v。不匹配的先升级或换版本不要抱着侥幸心理往下走。6.2 导入SQL脚本并启动后端导入SQL后打开后端项目的application.yml确认三件事数据库地址、用户名、密码。常见问题就三种Access denied for user数据库密码写错了。Unknown database库名拼错了或者SQL脚本根本没建库成功。Communications link failureMySQL服务没启动或者端口不是默认的3306。后端启动成功后控制台会输出类似Started FinanceApplication in x seconds的日志。同时监听8080端口。如果提示Port 8080 is already in use说明端口被其他程序占用了可以关掉占用进程也可以改server.port换个端口。6.3 安装前端依赖并启动开发服务器前端启动的完整流程npm install npm run devnpm install阶段常见的坑是node-sass安装失败。新项目用Vite都会直接走sassdart-sass不再依赖node-sass这种需要编译原生模块的包。如果你下载的老项目还是node-sass建议把依赖里的node-sass换成sass再安装。启动成功后Vite会打印一个本地访问地址一般是http://localhost:5173。浏览器打开用SQL脚本里预设的测试账号登录通常是个admin/admin123之类的初始账号能看到数据就说明前后端联调完成。6.4 项目源码怎么发给别人打包与交接规范很多同学拿到别人的源码或者要把自己做好的项目发给老师最关心的就是发一堆文件夹过去能不能跑。规范的做法是交付一份压缩包里面包含sql/目录数据库脚本文件。backend/目录后端完整代码以及一份README说明数据库配置。frontend/目录前端完整代码以及一份README说明npm安装和启动步骤。docs/目录接口文档、答辩PPT、演示录屏如果需要。后端生成可执行jar包mvn clean package -DskipTests前端打包静态文件npm run build如果想演示的时候不用同时开两个服务可以把前端dist/目录里的文件拷到后端src/main/resources/static/下重启后端浏览器直接访问8080端口就能看到页面。但这种做法只适合最终演示开发阶段还是建议前后端分离跑方便改代码。7. 接口文档不只交付更是理清系统边界的工具接口文档是这个项目标题里的关键词之一。很多初学者觉得文档是写给别人看的自己开发用不上。但实际上接口文档在开发阶段的作用是契约先行。先把接口的地址、参数、返回结构定好前后端各自开发最后联调的时候按契约核对效率远高于一边写一边商量。7.1 接口文档应该包含什么一份合格的接口文档至少要覆盖以下内容全局说明Base URL、鉴权方式Header里的token如何传递、统一的返回结构示例。接口明细每个接口的请求地址、请求方式、请求参数参数名、类型、是否必填、说明、响应示例、错误码说明。使用示例建议附一个完整的登录-加账单-查统计的调用流程示例。举个例子账单分页查询接口的文档描述至少要有这样一张表参数类型必填说明currentInteger是当前页码默认1sizeInteger否每页条数默认10typeInteger否收支类型1收入 2支出categoryIdLong否分类IDstartDateString否起始日期yyyy-MM-ddendDateString否结束日期yyyy-MM-dd7.2 手写Markdown文档还是用Swagger两个方案都可行看你的实际情况。手写Markdown文档的优点是可控性强字段说明可以写得很详细导出PDF后可以直接打印附在毕设材料里。缺点是接口一多维护工作量大改了代码忘了改文档是常有的事。Swagger或者它的增强版knife4j会自动扫描Controller上的注解生成在线文档几乎零维护成本访问/doc.html就能看到所有接口。但自动生成的文档在答辩展示上不如精心整理的手写文档直观。我的建议是两者结合项目里集成knife4j方便自己调试和演示同时在docs/目录里放一份精简的手写接口文档作为正式交付材料。答辩的时候先展示手写文档说明设计规范再切到knife4j现场调接口效果很加分。8. 实测踩坑与加分建议代码之外更重要的几件事这个项目我前后实践过多次有些坑是拿到源码就能直接看到的有些是要跑起来才发现的。这里集中整理一下相当于帮你把弯路提前走完。8.1 高频踩坑清单Spring Boot版本和JDK不匹配。老生常谈2.7配JDK83.x配JDK17。拿到源码先看pom里的java.version再决定用哪个JDK。时间字段的格式错乱。前端传给后端的日期是字符串后端实体里如果直接用LocalDate接收格式对不上会报400。统一方案是加JsonFormat(pattern yyyy-MM-dd)注解或者用DateTimeFormat。金额取值用错了类型。double累计多次后出现0.30000000000000004这种鬼数字一度以为是框架bug其实是浮点精度问题。所有金额计算全部换成BigDecimal尤其是统计接口里做SUM的地方。未登录拦截把自己拦死了。登录接口和注册接口必须放行否则系统根本进不去。拦截器里配一个白名单这个问题就解决了。新增账单时分组统计的日期边界。统计某月的收支SQL里用record_date BETWEEN 2025-01-01 AND 2025-01-31涉及跨月数据时容易出现边界遗漏。建议统一用DATE_FORMAT(record_date, %Y-%m) 2025-01来做月份筛选语义更清晰。8.2 真正拉开差距的加分点能让你的项目在班里脱颖而出的往往不是功能数量而是细节质量。以下几个点建议优先做参数校验DTO加NotBlank、NotNull后端自己挡住脏数据。统一异常体系业务异常抛出后由全局处理器统一包装前端拿到提示语是该分类下存在账单记录无法删除而不是一段堆栈。数据权限校验查询、修改、删除都强制带当前用户ID杜绝越权操作。日志规范关键操作打印logger.info比如用户id为5的用户删除了id为10的账单。出了事能查操作记录答辩时还能讲一句系统预留了审计能力。事务控制涉及多表操作比如删除分类时同时处理相关账单加上Transactional保证原子性。8.3 答辩前一定要会讲的三个问题拿到这个项目答辩被问到的概率最高的三个问题我提前帮你备好答案为什么金额用DECIMAL不用FLOAT回答浮点数二进制精度丢失钱不允许误差DECIMAL是定点数。你的JWT放在哪里如果被偷了怎么办回答存前端本地存储过期时间设7天服务端拦截器校验签名未来可引入Redis做黑名单。统计是在前端算的还是后端算的回答统一在后端SQL聚合前端只负责渲染。理由数据库是数据分析的正确位置避免把大量原始数据拉到前端造成性能浪费。做毕设这几年下来我最大的体会是一个系统能不能拿高分看的不是它有多少花哨功能而是每一层代码是否都有人认真想过为什么。SpringBootVue个人理财系统的精妙之处正在于它足够基础所以每一个设计决策——从表结构到接口规范从异常处理到图表数据契约——都能被清晰地表达和论证。如果你手头正拿着这个项目的源码别急着跑起来看效果按这篇文章的顺序把需求、表结构、后端分层、前端骨架、启动流程、接口文档这几块逐个过一遍把为什么弄明白了这个项目才真正成为你自己的东西。
返回列表