毕业设计选题这件事,我一直有个很实在的建议:与其纠结“高大上”的新兴框架和冷门技术,不如找一个业务逻辑完整、前后端分离、能跑通真实数据链路的经典组合,把它做深做透。个人理财系统管理系统就是这类题目里的“标准答案”之一——SpringBoot 做后端接口,Vue 做前端页面,MySQL 存账目流水,MyBatis 负责数据库操作,整套下来既能展示你对 Java 全栈的理解,又不会让工作量失控。
这篇文章会结合我手头完成的“基于 SpringBoot+Vue 的个人理财系统管理系统”源码项目,从技术选型的理由、数据库设计的花活、核心功能的实现思路,到部署演示时容易踩的坑,一条线讲清楚。不管你是准备拿它当毕设,还是想练手全栈项目,都可以直接照这份思路去落地。
1. 为什么这种“双端理财系统”是稳妥且容易出彩的选题
先说说选题层面的判断,因为这决定了你后面三个月的投入方向。理财系统这个业务域最讨巧的地方在于:它是典型的数据驱动型业务,而且用户能感知到的“功能丰富度”很高。收入支出记账、分类统计、预算警报、图表报表、账户管理,每一块拆开看都是独立的小功能模块,合在一起又构成完整闭环。用一套系统讲完“用户能注册登录、能记账、能看统计、能设预算、能管理个人信息”,评审老师一听就知道你的项目是完整的,而不是只有一个空壳页面。
技术上选 SpringBoot + Vue 这个组合,原因也很直白。SpringBoot 让后端开发省去了大量 XML 配置,内嵌 Tomcat 之后打包就是一个 JAR,部署非常方便;Vue 配合 Element UI 能快速搭建出表格、表单、弹窗这些管理后台常见组件;MyBatis 半自动化的 SQL 控制力刚好匹配“银行台账”这种需要写复杂统计查询的场景——比如按月份分组汇总支出、按分类计算占比之类的 SQL,用 MyBatis 写动态 SQL 或自定义 resultMap 都比全自动 ORM 更可控。
我见过不少同学在选题时陷入一个误区:为了“显得有难度”,硬塞 Redis、RabbitMQ、ElasticSearch 这些中间件。不是说不能用,但如果你连项目本身都还没跑利索,堆技术栈只会让答辩变成大型翻车现场。理财系统的数据量、并发量根本没到需要消息队列的程度,硬加反而会被追问到空洞。把 SpringBoot + Vue + MySQL + MyBatis 这条链路上的每个点都讲扎实,比堆十个中间件却答不上细节值钱得多。
还有一点值得说:理财系统的“个人”属性决定了它的权限模型非常简单。不需要处理角色继承、部门树、数据权限隔离这些复杂内容,一个用户一张表,登录后通过 Token 或 Session 识别身份,查询数据时强制带上用户ID条件即可。这种简洁性让项目逻辑容易闭环,你能把更多精力放在代码规范和接口设计上,而不是陷进业务泥潭。
2. 技术选型背后的实质理由:为什么不选其他组合
很多教程会直接甩给你一套技术栈然后开始敲代码,但我更想聊聊选型背后的取舍,这往往是答辩时最能体现你“真的懂”的地方。
2.1 后端框架:SpringBoot 的优势在“生态整合”而非“技术新”
SpringBoot 严格来说不是一个新框架,而是一个“整合方案”。它的核心价值是把 Spring MVC 的配置、数据源配置、事务管理、日志配置等一堆繁琐内容用自动配置的方式消化掉。对个人项目来说,最直观的收益是:你只需要关注 Controller、Service、Mapper 三层代码怎么写,不用花两周去调 xml 配置文件。
有人会问,为什么不用更轻量的 Java 框架,比如 JFinal?如果你做过企业实际项目接触过 Spring 生态,会发现大部分公司的 Web 服务都是基于 Spring 家族构建的。用 SpringBoot 做出的项目,简历上写起来有分量,面试时也能自然地往 Spring IoC、AOP、自动配置这些话题上延伸。JFinal 这类框架虽然上手快,但在国内企业普及度还是差一些,对找工作的帮助有限。
2.2 前端框架:Vue 的核心优势是“渐进式”和“组件化”
Vue 最打动我的地方是它不逼你一步到位。你可以只用它的数据绑定和指令,把后台管理页面做出来,也可以逐步引入 Vue Router、Vuex/Pinia、Axios,形成完整的前后端分离架构。对于学生项目来说,这套体系的学习曲线比 React(JSX、Hooks 全家桶)和 Angular(RxJS、依赖注入)平滑不少。
配合 Element UI 这类组件库,表格、分页、表单校验、弹窗提示这些后台系统的“高频零件”都能直接复用。比如理财记录的增删改查页面,核心就是用 el-table 展示数据、el-form 做录入、el-pagination 处理分页,三个组件搞定大部分工作。这样你就能把省下来的时间投入到后端查询优化和前端图表展示这些更能出彩的地方。
2.3 数据库操作层:MyBatis 的“半自动”才是精髓
选 MyBatis 而不是 Spring Data JPA,是因为理财系统的统计查询太适合手写 SQL 了。比如“查询本月支出按分类汇总”:
SELECT category_id, SUM(amount) AS total FROM tb_record WHERE user_id = #{userId} AND type = 'expense' AND DATE_FORMAT(record_date, '%Y-%m') = #{month} GROUP BY category_id这种带分组和聚合的查询,用 MyBatis 写不仅直观,还能精确控制索引的使用。而 JPA 虽然能通过方法名推导查询,但遇到复杂统计时要么写 JPQL,要么备选原生 SQL,反而绕路。MyBatis 另一个优势是 SQL 和 Java 代码分离,调整查询语句不用重新编译 Java 类,对于后期改 bug、调报表逻辑特别友好。
我不建议在这个项目里混用 JPA 和 MyBatis,两种 ORM 的事务管理、缓存机制不完全一致,混用容易出一些很难排查的诡异问题。保持一套 MyBatis 走到底,加上通用 Mapper 减少重复的增删改查代码,效率和可控性都能兼顾。
3. 核心业务模块与数据库设计:先建模再写代码
一个理财系统能否真正“立住”,数据库设计占了很大权重。很多同学习惯拿到需求就写接口,结果做到图表统计的时候发现表结构缺字段,被迫回炉重建,浪费大量时间。我的习惯是先在纸上把业务模块和实体关系理清楚。
3.1 模块划分与业务流程梳理
我把这个系统拆成六个核心模块:
- 用户管理:注册、登录、个人信息维护、密码修改。
- 记账管理:收入/支出记录的增删改查,支持按时间范围、分类、金额区间筛选。
- 分类管理:预设常见收入支出分类,也允许用户自定义分类。
- 预算管理:按月设置总预算或分类预算,超支时给出提醒。
- 统计报表:月度收支对比、分类占比,通过图表组件可视化。
- 数据维护:记录导出、回收站或软删除等细节功能(根据答辩要求可选)。
其中记账模块是整个系统的核心。业务上要注意一个关键点:记录必须绑定“用户ID + 账户ID”,并且要区分收入与支出两种类型。很多初学设计者会把收入和支出去建两张表,这会给统计时带来巨大麻烦——你想算“本月结余”,就得关联两张表做减法。正确做法是一张记录表里用 type 字段区分,1代表收入、0代表支出,统计时通过聚合函数一次完成。
3.2 核心表结构设计详解
我实际使用的表结构大致如下,这里只讲几个关键表的字段设计思路。
用户表tb_user:
CREATE TABLE `tb_user` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(32) UNIQUE NOT NULL, `password` VARCHAR(128) NOT NULL, `nickname` VARCHAR(32), `email` VARCHAR(64), `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;密码字段长度定成 128,是为了容纳 BCrypt 加密后的哈希串,而不仅仅是明文密码。这里多说一句:千万不要用明文存密码,哪怕只是个人项目也要有这个意识。用 Spring Security 自带的 BCryptPasswordEncoder 或者 JBCrypt 库做哈希处理,是成本最低的安全加分项,答辩时提到这一点会非常加分。
记录表tb_record:
CREATE TABLE `tb_record` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `user_id` INT NOT NULL, `category_id` INT NOT NULL, `type` TINYINT NOT NULL COMMENT '1收入 0支出', `amount` DECIMAL(10,2) NOT NULL, `record_date` DATE NOT NULL, `remark` VARCHAR(255), `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_user_date` (`user_id`, `record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;金额一定要用DECIMAL(10,2)而不是FLOAT或DOUBLE。这个坑我见有人踩过:浮点数在 Java 和 MySQL 中都有精度损失问题,算总账时会出现 0.1 + 0.2 = 0.30000000000000004 这样的情况,财务管理场景是不允许的。
user_id和record_date上建联合索引,是为了让“查某人某段时间的记录”这个高频操作走索引,避免全表扫描。个人项目数据量不大,索引收益未必明显,但这个设计意识本身就是加分项。
分类表tb_category和预算表tb_budget我就不列完整建表语句了,说两个关键点:分类表要有type字段区分收入分类和支出分类;预算表建议设计成budget_month和category_id联合唯一约束,保证“某用户某月某分类只能有一条预算记录”,这样更新预算时可以直接用INSERT ... ON DUPLICATE KEY UPDATE,省掉先查后改的逻辑。
3.3 数据库设计的核心原则:高范式遇统计需求可以适度反范式
很多教材强调三范式,实际做这种统计型项目时,我会适当冗余字段来换取查询效率。比如记录表里同时冗余一个category_name或者关联查询时通过 JOIN 获取,我更推荐直接存category_id,展示时再 JOIN 分类表取名称。原因很简单:分类名称允许用户修改,如果冗余了名称,用户改分类名时还得级联更新所有历史记录,非常麻烦。
真正需要做冗余的是统计场景。比如用户首页要展示“本月收入、本月支出、本月结余、总资产”,如果每次都实时 SUM 全表,数据量上来后性能会明显下降。我的方案是加一个tb_user_overview表,定期(比如每次记账后)刷新这几个汇总数字,或者干脆用 MySQL 的定时事件每晚统计一次。个人项目用同步更新就好,记账成功后同时更新汇总表,这个写法也体现你对“读写分离思想”的初步理解。
4. 关键后端接口与前端交互实现逻辑
结构理清楚之后,进入具体实现。这里我不贴全部源码,只挑几个体现项目含金量的关键点展开,这些代码片段可以直接用到你的项目里。
4.1 统一返回结果与全局异常处理
前后端分离架构里,接口返回格式的统一是基本功。我习惯定义一个Result类:
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.code = 200; result.message = "success"; result.data = data; return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.code = code; result.message = message; return result; } }配合一个@RestControllerAdvice全局异常处理器,把业务异常、参数校验异常、系统异常分别拦截,返回对应的错误码和提示信息。这样前端 axios 拦截器里只需要判断code是否为 200,就能统一处理成功与失败分支,不用每个接口都写 try-catch。这套模式的另一个好处是接口文档非常干净,前端同学(或者你自己写前端时)一眼能看懂数据结构。
4.2 登录鉴权的设计方案:JWT vs Session
个人理财系统对登录态的要求不高,但我还是推荐用 JWT 而不是传统的 HttpSession。原因有两个:
第一,前后端分离之后,前端可能部署在 8080 端口,后端在 8081 端口,跨域情况下 Session 的 Cookie 处理会比较麻烦。JWT 把用户信息(至少是用户ID)签名后放在请求头Authorization字段里,不依赖 Cookie,天然适配跨域场景。
第二,答辩时聊到“无状态鉴权”会比说“我用 Session 存了一下用户”听起来专业不少。实现上也不需要引入 Spring Security 全家桶,自己写一个拦截器处理即可:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录和注册接口 if ("/api/user/login".equals(request.getRequestURI()) || "/api/user/register".equals(request.getRequestURI())) { return true; } String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { response.setStatus(401); return false; } try { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token.replace("Bearer ", "")) .getBody(); request.setAttribute("userId", claims.get("userId")); return true; } catch (Exception e) { response.setStatus(401); return false; } } }这里有一点要注意:JWT 的 SECRET_KEY 不能写在代码里硬编码。虽然个人项目无所谓,但养成习惯,放到application.yml配置文件中,用@Value注入。这个细节被问到的概率很高,答得好就是亮点。
然后写一个UserContext工具类,在拦截器里把当前登录用户ID塞入 ThreadLocal,Service 层需要知道操作者是哪个用户时,直接UserContext.getUserId()就行。这比你层层从 Controller 往 Service 传 userId 参数清爽得多。
4.3 记账与统计查询的核心 SQL
记账的增删改查不难,关键是统计。这里我展示两个高频使用的统计查询,也是答辩时用来证明“你真的写过后端”的实例。
按月统计收支汇总:
@Select(""" SELECT DATE_FORMAT(record_date, '%Y-%m') AS month, SUM(CASE WHEN type = 1 THEN amount ELSE 0 END) AS income, SUM(CASE WHEN type = 0 THEN amount ELSE 0 END) AS expense FROM tb_record WHERE user_id = #{userId} AND record_date BETWEEN #{startDate} AND #{endDate} GROUP BY month ORDER BY month """) List<MonthlySummaryVO> selectMonthlySummary(@Param("userId") Integer userId, @Param("startDate") String startDate, @Param("endDate") String endDate);按分类统计支出占比:
@Select(""" SELECT c.name AS categoryName, SUM(r.amount) AS total FROM tb_record r JOIN tb_category c ON r.category_id = c.id WHERE r.user_id = #{userId} AND r.type = 0 AND DATE_FORMAT(r.record_date, '%Y-%m') = #{month} GROUP BY r.category_id ORDER BY total DESC """) List<CategoryStatVO> selectCategoryStat(@Param("userId") Integer userId, @Param("month") String month);如果你用的是 MyBatis 的注解方式,上面的写法就够用;如果用 XML 映射文件,注意${}和#{}的区别——能用#{}的地方绝不用${},前者走预编译,能防 SQL 注入。这是个必考的面试点,也是代码审查里最容易挑刺的地方。
4.4 前端 Vue 部分的实现要点
前端我习惯先搭好 Axios 封装与路由守卫,再写业务页面。Axios 封装的核心代码就两段:
请求拦截器,自动带上 Token:
axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => Promise.reject(error));响应拦截器,统一处理 Session 过期:
axios.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { Element.Message.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); } return res.data; }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(error); } );路由守卫用来控制页面访问权限:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path !== '/login' && !token) { next('/login'); } else { next(); } });页面层面,记账页面的表单我用el-form加自定义校验规则,金额字段限制两位小数,日期字段限制不能晚于今日(除非你允许未来记账,这个看需求)。最关键的一点是:任何时候读写数据都要带上userId,而前端不应该从 localStorage 里拿明文 userId 传给后端,而是由后端从 Token 中解出用户身份。这样能避免用户手动改浏览器数据越权访问他账目的问题。
图表展示我用 ECharts 的按需引入,两个图表就够:一个折线图展示近 6 个月收支趋势,一个饼图展示本月支出分类占比。前端把后端返回的统计数组稍微 map 成 ECharts 需要的{ value, name }结构即可,这个工作量不大,但视觉效果好,答辩演示时非常出效果。
5. 项目从开发到部署的完整链路与常见坑点
很多同学代码写完了,卡在“跑不起来”上。这里我总结几条高频问题,都是实际开发中真实踩过的。
5.1 环境与版本匹配是最容易忽略的隐形杀手
我强烈建议一开始就固定版本组合。我这次用的是 JDK 1.8 + Spring Boot 2.7.x + MySQL 8.0 + Vue 2.6 + Element UI 2.15 + MyBatis Spring Boot Starter 2.2.x。这套组合在网上有大量配套教程,出问题时搜解决方案也容易。
最大的坑集中在两点:
一是MySQL 8.0 的驱动类和时区问题。com.mysql.jdbc.Driver是 MySQL 5.x 用的,8.0 需要写成com.mysql.cj.jdbc.Driver,连接串里还要加serverTimezone=Asia/Shanghai,否则报时区错误。另外 8.0 默认使用 caching_sha2_password 认证,某些旧版本连接工具会报认证失败,需要在建用户时指定 mysql_native_password,或者在连接串上加useSSL=false&allowPublicKeyRetrieval=true。
二是Spring Boot 与 MyBatis 的包扫描路径。@MapperScan扫描的是 Mapper 接口所在的包,如果写错路径,启动时不会报错,但调用时会出现Invalid bound statement (not found)。排查时先看 Mapper 接口的包名和 XML 的namespace是否一致,再看mapper-locations配置是否指向了 resources 下的 mapper 目录。
5.2 前后端跨域问题的标准解法
前后端分离开发时,跨域是逃不掉的问题。最省事的处理是在后端加一个全局跨域配置:
@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); } }注意两个细节:如果用了allowCredentials(true),allowedOrigins不能写*,而要用allowedOriginPatterns("*"),这是 Spring Boot 2.4 之后的行为差异;如果自定义了 JWT 拦截器,必须对OPTIONS预检请求直接放行,否则浏览器预检失败,所有跨域 POST 请求都会挂在 CORS error 上,前端看到的报错还是误导性的“网络错误”,特别容易绕晕人。
5.3 部署到服务器时的坑
本地跑通并不代表服务器上能跑通。我遇到过至少三个问题:
第一,application.yml 里的数据库密码写死在配置里。虽然个人项目影响不大,但我会用jasypt做一次简单的加密,至少让答辩时不至于被挑刺“你把密码硬编码了”。也可以直接把 MySQL 密码改为强密码,再通过环境变量注入。
第二,前端打包后和后端合并部署。开发阶段前后端分离跑两个端口,部署时更省事的方案是:前端npm run build后生成 dist 目录,把 dist 下的静态文件放到 SpringBoot 的src/main/resources/static里,再启动后端。这样整个项目一个端口,演示时不用开两个服务。但要注意,Vue Router 如果是 history 模式,后端需要做一个路由 fallback,把非 API 路径全部转发到 index.html,否则刷新页面会 404。如果嫌麻烦,直接改成 hash 模式就行,地址里多一个#而已。
第三,服务器防火墙/安全组不开端口。这套项目后端默认 8080 端口,演示时如果连不上,先检查安全组入方向有没有放行 8080。这个问题的经典表现是 Localhost 能访问、服务器公网 IP 访问不了、云服务商控制台显示端口未监听,其实只是安全组没放行。
5.4 答辩演示时的数据准备技巧
答辩之前一定要把演示数据准备充分,这一点经常被忽略。我这里建议提前往数据库里插入至少 3 个月的演示数据:每月有 15 到 20 条支出记录、5 到 8 条收入记录,覆盖餐饮、交通、购物、工资、理财等多个分类,让图表有看头。
另一个实用的技巧是准备几个“对比演示”场景:比如手动插入一条大额支出,刷新图表看对应分类占比是否变大;比如临时修改某个月的预算为很低的值,故意触发超支提醒;比如故意把密码输错,展示错误提示。这些“能展示异常分支”的演示往往比平稳的“走流程”更让老师提起兴趣,因为这证明你考虑到了边界情况。
6. 源码工程结构优化与“可答辩”的代码习惯
代码写完了,还有一个容易被忽视的关键:工程结构的组织方式会直接影响答辩印象分。老师翻开你的项目,第一眼看的不是业务逻辑,而是包结构清不清晰、命名规不规范。
6.1 包结构推荐
我推荐这种按“技术层次 + 业务模块”结合的包结构:
com.example.finance ├── FinanceApplication.java ├── common │ ├── Result.java │ ├── GlobalExceptionHandler.java │ ├── JwtInterceptor.java │ └── UserContext.java ├── config │ └── WebConfig.java // 注册拦截器、跨域配置 ├── controller │ ├── UserController.java │ ├── RecordController.java │ ├── CategoryController.java │ ├── BudgetController.java │ └── StatsController.java ├── service │ ├── RecordService.java │ └── impl │ └── RecordServiceImpl.java ├── mapper │ ├── UserMapper.java │ ├── RecordMapper.java │ └── ... ├── entity │ ├── User.java │ ├── Record.java │ └── ... ├── dto │ ├── LoginDTO.java │ ├── RecordQueryDTO.java │ └── ... └── vo ├── MonthlySummaryVO.java ├── CategoryStatVO.java └── ...分层的意思是实体对象(Entity)不乱传。Controller 层接收 DTO,Service 内部做业务处理,Mapper 返回 Entity,最后封装成 VO 返回前端。比如登录接口,前端传LoginDTO,后端校验后返回的不是整个 User 实体,而是一个LoginVO,里面只包含用户ID、昵称和 Token。每层职责清晰,老师问“为什么这么设计”,你可以直接答出防止实体字段泄露和前端数据结构耦合。
6.2 代码细节里藏着的加分项
几个很小的细节,加起来就是明显的好印象:
参数校验不要只在 Controller 写,核心校验下沉到 Service。比如插入记录时,先校验
amount是否为负数、recordDate是否为空、分类是否属于当前用户。Controller 层用@Validated做基础格式校验,但归属权校验必须在 Service 层做,否则绕过 Controller 直接调用 Service 也能插入非法数据。金额计算统一使用 BigDecimal。不光是数据库字段,Java 层的金额加减也都要用
BigDecimal,避免使用double。如果前端传金额用字符串,后端转换成 BigDecimal 时要注意new BigDecimal("19.9")而不是new BigDecimal(19.9),前者才是精确的。SQL 语句里凡是涉及用户数据的查询,无条件加上
user_id = #{userId}条件。这是防止水平越权的底线设计。哪怕分类列表这种看起来“每个用户都一样”的数据,也必须加。因为个人理财系统是多用户系统,你不能让 A 用户看到 B 用户预算或记录了。
6.3 事务与并发场景的处理
记账这个动作天然涉及多步操作:插入记录、更新汇总表、可能还要检查预算。这三步必须放在同一个事务里,否则第二步失败后第一步的数据会残留在库里。我用@Transactional注解在 Service 方法上,同时传播行为使用默认的REQUIRED,即“有事务则加入,无事务则新建”。
关于并发,个人项目真的没多少并发压力,但我在预算检查时顺便考虑了超支提醒的幂等性问题:如果用户同一天记了 10 笔账,预算状态不应该每次都重复提醒,而是只有当“当月累计支出跨过预算线”这个阈值变化时才提醒。实现上我用一个RemindFlag字段(0-保底提醒一次,1-已提醒),查询当月累计支出后和预算比较,如果超支且未提醒则置位并推送通知。这种小设计听起来不复杂,但体现了“业务闭环意识”。
7. 从项目到作品集:怎么把这次开发变成可持续的积累
项目完成以后别急着删掉,建议做三件收尾的事:写一份结构化的 README、整理一份接口文档、准备五分钟的项目演示脚本。
README 至少要包含:项目简介、技术栈说明、功能清单、启动步骤(包括建库建表的 SQL、前端启动和打包命令)、项目结构树。这份文档既是给老师看的,也是给未来的自己看的——三个月后你想复用这个项目改造成其他管理系统时,靠的就是这份文档而不是模糊的记忆。
接口文档不一定要用 Swagger,虽然 Spring Boot 集成 Springdoc 很方便。但如果你时间紧,手写一个 Markdown 表格也够用,把每个接口的 URL、请求方式、请求参数、返回示例列出来即可。这会让答辩时的演示更有条理:你说到“查询月度汇总”时,直接调出接口文档对应位置,展示请求和响应,比在代码里翻半天有说服力得多。
五分钟演示脚本是最容易被忽略但回报最大的准备:第一分钟展示登录注册和用户信息;第二分钟演示新增收入/支出记录、编辑、删除;第三分钟展示列表分页、筛选搜索;第四分钟切到统计图表页面,拿着真实数据讲趋势;最后一分钟打开数据库,展示表结构和联合索引,顺手演示一个慢查询优化(哪怕只是混合索引后 explain 结果变好)。这样一套下来,该展示的都展示了,节奏也控制得住。
最后说一句掏心窝的话:这种经典全栈项目最大的意义,不是让你在答辩时“炫技”,而是让你完整走一遍从需求分析、数据库设计、接口开发、前端联调到部署上线的全流程。中间踩的每一个坑,将来在工作中大概率还会遇到,但那时你已经有排查思路了。这套源码项目的价值就是帮你把“课本知识”变成“手感经验”,从这个角度看,它比一个简单的“高分毕设”值钱得多。