ARTICLE DETAIL

资讯详情

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

Springboot+Vue大学生智能消费记账系统源码解析与实战指南

Springboot+Vue大学生智能消费记账系统源码解析与实战指南 简介这是一套面向毕业设计、课程设计及JavaWeb初学者的完整源码案例基于SpringbootVue实现大学生智能消费记账系统覆盖账单管理、分类统计、预算设置与数据可视化等核心功能。压缩包共含352个文件体积22.93MB其中82个java后端文件、36个vue前端组件、161个svg图标构成主体另配sql数据库脚本、yml配置、xls模板、bat启动脚本以及mp4演示视频层次清晰开箱即用。目前已有136人学习下载资源完整度与实用性得到初步验证。通过学习可掌握前后端分离架构、RESTful API设计、JWT认证、ECharts可视化、Springboot自动配置等关键技能配合安装脚本和操作录屏能快速跑通项目适合直接用于毕业论文参考、系统二次开发或面试项目展示。1. 基于SpringbootVue的大学生智能消费记账系统源码包里到底装了什么能做什么“基于SpringbootVue大学生智能消费记账系统的设计与实现源码案例设计.zip”这类标题在课程设计和毕业设计资源站里出现频率很高。它不是一个单页Demo而是一套至少包含后端、前端、SQL脚本和设计说明文档的可运行项目。核心场景是帮大学生把日常开销记清楚再按月份和分类聚合告诉你钱花在哪里、预算还剩多少。对正在做Java方向设计的人来说这套源码最大的价值在于它同时覆盖了Springboot后端、Vue前端和MySQL表结构跑通后加一两个自己的功能答辩就有实打实的东西可讲。后面几章按需求、后端、前端、排错、改造五个方向拆开每一步都能落地。2. 大学生消费记账系统的需求拆分功能清单、表设计与边界选择任何系统动手之前先画边界。大学生记账系统的用户是校园场景里的人和面向家庭或企业的记账产品比它显得“小”但这个“小”恰好是优势功能少、表结构简单、前后端容易讲清楚答辩时不会被追问到失控。2.1 核心功能清单记账、分类、预算、统计四件套需求分析的第一步是把用户故事列出来。一个大学生用户走进系统最常见的动作是今天花了18.5元记一笔到“餐饮”月底想看这学期生活费还剩多少如果这个月食堂超支了最好有个提醒。落到功能模块上就是下面四类账单管理新增、编辑、删除收入或支出记录支持金额、时间、备注、所属分类。分类管理预置餐饮、交通、购物、学习、娱乐、日用品等分类也允许用户自定义。预算管理按月份给某个分类或整体设置额度支出接近额度时给出提示。统计报表按月汇总总收入/总支出、分类占比、每日趋势曲线。其中“收入”模块要谨慎。很多学生记账项目把收入做成与支出平级的功能实际上大学生的收入来源就是生活费、兼职、奖学金频率很低。做成一张独立的收入单表会让统计逻辑变复杂更合理的做法是保留一个“类型”字段收入/支出在统计时分别过滤。预算模块只针对支出收入不参与预算这样概念清晰代码也简单。下面这组需求优先级可以帮助你判断源码里哪些功能是核心、哪些是锦上添花。带星号的是你拿到源码后至少应该能讲清楚的部分。功能模块优先级说明账单记录/编辑/删除★★★一切统计的基础必须自圆其说分类管理★★★分类表是账单表的维度设计要讲清月度汇总★★★后端SQL聚合面试和答辩高频点预算设置与预警★★常见加分项注意不要做复杂数据导出★可作为新增功能亮点多人共享账本☆建议不做超出“大学生消费记账”边界2.2 从功能到数据库表user、bill、category、budget 四张核心表根据上面的功能源码项目的数据库至少要有四张主表用户表、账单表、分类表、预算表。用户表保存账号和密码源码里通常用MD5加盐或BCrypt账单表记录每一笔收支分类表提供维度预算表存某个用户某个月对某个分类的限额。常见做法是再附加一张登录日志表或操作日志表但学生项目里有user和bill就足以支撑全部业务日志表反而是负担。下面是建表SQL的核心部分。拿到源码后先在Navicat里跑一遍再对照实体类字段检查是否有遗漏这一步决定了你后续改代码时会不会出现“字段找不到”。CREATE DATABASE IF NOT EXISTS student_account DEFAULT CHARACTER SET utf8mb4; USE student_account; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 加盐后的密文, nickname VARCHAR(50) DEFAULT COMMENT 显示昵称, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT用户表; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT DEFAULT 0 COMMENT 0为系统预置其他为用户自定义, name VARCHAR(20) NOT NULL, type TINYINT NOT NULL COMMENT 1收入 2支出, sort INT DEFAULT 0 ) COMMENT消费分类表; CREATE TABLE bill ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, category_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL COMMENT 金额, type TINYINT NOT NULL COMMENT 1收入 2支出, note VARCHAR(200) DEFAULT COMMENT 备注, bill_time DATETIME NOT NULL COMMENT 消费发生时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_time (user_id, bill_time) ) COMMENT账单表; CREATE TABLE budget ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, category_id INT DEFAULT 0 COMMENT 0为整体预算, month CHAR(7) NOT NULL COMMENT 格式 2025-04, amount DECIMAL(10,2) NOT NULL, UNIQUE KEY uk_user_cat_month (user_id, category_id, month) ) COMMENT月度预算表;表设计有两个要点值得展开。第一bill表里的type字段和category表里的type字段会重复为什么不直接通过分类去判断收支因为存在用户错选的情况而且统计时直接用bill.type过滤更快冗余一个字段在存储上代价很小换来的是单表查询的清晰度。第二bill_time和create_time是两个概念前者是用户实际消费时间后者是录入系统的时间。统计月度开销必须用bill_time否则用户在4月30日补记3月消费时会把账归错月份。budget表的unique key保证同一用户同一分类同一月份只有一条记录后续代码里做冲突更新用ON DUPLICATE KEY UPDATE会很方便。索引方面bill表里已经有一条复合索引idx_user_time它同时覆盖了“按用户过滤”和“按时间排序”两个高频操作。月份统计的SQL会按bill_time做范围过滤因此这个索引也够用。如果源码里还加了category_id的索引也不要删分类占比图需要按分类分组有索引能减少回表。2.3 功能边界哪些功能不做反而是加分项很多学生拿到需求后喜欢把微信小程序端的组队记账、图片识别发票等功能也规划进去这是最大的风险。需求扩张意味着表结构扩张到了答辩时间连核心功能都没写完。这个系统的边界我认为有三条。一是不做多人共享账单。共享需要引入账本ID、成员权限、邀请关系至少多出三张表演示难度成倍增加。二是不做周期性账单。房租、话费这类每月固定的开销完全可以用“复制上月账单”或一键重复记账代替不必上定时任务。三是不做照片识别。虽然“基于OpenCV”的标题很抓眼球但记账项目引入视觉识别会让人陷进算法调参的泥潭偏离了软件工程的考核重点。把边界说清楚反而能在答辩时体现你对最小可行产品的理解。介绍系统时主动说一句“我没有做多人协作因为主要用户是个人这样账单和预算的统计模型更纯粹”评委通常不会再追问。相比之下做了很多界面却说不清表关系反而容易被问倒。3. 后端落地Springboot 的项目结构、接口实现与分页统计后端是这套系统的中枢。拿到源码后先看它的Springboot版本、ORM选型、是否带了Swagger这些决定你后续改造的成本。这一章的讲解围绕“怎么把账算清楚”展开包含配置、接口和统计逻辑。3.1 项目结构与依赖Springboot 2.7 MyBatis-Plus 的选型理由如果标题里的源码是基于SpringbootVue后端大概率是Springboot 2.x。当前流传的学生项目里Springboot 2.7和MyBatis-Plus 3.5是主流组合原因是配置少有现成的分页插件和代码生成器对快速搭建模块很友好。JPA写起来更抽象但统计SQL不好控制不如MyBatis-Plus的注解或XML直观。如果你的源码用的是纯MyBatis也不要急着换先看它的Mapper XML写在哪后续加接口照着写就行。一个合理的后端包结构是这样src/main/java/com/example/account ├── controller # 接口层只做参数接收和返回 ├── service # 业务逻辑 ├── mapper # MyBatis-Plus的Mapper接口 ├── entity # 数据库实体 ├── config # 跨域、分页、Swagger等配置 └── common # 统一返回体、异常处理pom.xml里必须关注的依赖有spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-j、lombok、spring-boot-starter-validation。其中mysql-connector-j在Springboot 2.7里默认是8.x需要和本地MySQL版本匹配这个细节到第五章会讲它惹出的坑。登录模块建议使用JWT或简单Token如果是纯Session方案要确认前端axios是否配置了withCredentials否则登录后刷新页面就失效。3.2 核心接口记账、月度汇总、预算预警的实现代码后端接口不需要过度设计一个BillController、一个BudgetController就够用。最重要的两个接口是保存账单和按月统计后者也是面试时最容易问到的SQL聚合题。保存账单的逻辑要注意两点第一前端传过来的bill_time是一个JSON字符串后端需要用Jackson的LocalDateTime反序列化配置去接收第二保存时把user_id从登录态里拿而不是信任前端传值。很多源码直接接收前端一个userId字段这是安全隐患答辩时被问“越权问题”会很尴尬。月度汇总接口的经典写法是把某个月所有支出账单查出来再按categoryId分组。但更推荐用MyBatis-Plus的QueryWrapper加select直接在SQL层做group by返回结构体是一个只有两个字段的VO。// BillService.java public MapString, Object monthlySummary(Integer userId, String month) { // month 形如 2025-04查询范围是 2025-04-01 00:00:00 ~ 2025-04-30 23:59:59 LocalDateTime start LocalDateTime.parse(month -01T00:00:00); LocalDateTime end start.plusMonths(1); // 写法一只查当月支出总金额适合顶部卡片 ListBill bills billMapper.selectList(new LambdaQueryWrapperBill() .eq(Bill::getUserId, userId) .eq(Bill::getType, 2) .ge(Bill::getBillTime, start) .lt(Bill::getBillTime, end)); // 写法二按分类分组使用QueryWrapper的select与groupBy只返回所需字段 QueryWrapperBill qw new QueryWrapper(); qw.select(category_id, SUM(amount) as total) .eq(user_id, userId) .eq(type, 2) .ge(bill_time, start) .lt(bill_time, end) .groupBy(category_id); ListMapString, Object byCategory billMapper.selectMaps(qw); // 组装成前端图表需要的 {name: 餐饮, value: 328.5} 格式 }这里参数说明start和end采用“左闭右开”区间即ge start、lt end这样能避开23:59:59.999这种时间精度边界month参数由前端传“2025-04”后端用“-01T00:00:00”拼成LocalDateTime避免前端传完整时间戳带来的时区问题selectMaps返回的列名是数据库字段名如果要转成驼峰需要在application.yml里开启map-underscore-to-camel-case。统一返回体也是学生项目里值得补的一环。很多老源码是Controller直接返回List或Map前端拿到数据没问题但一旦后端抛出异常响应结构就变成Springboot默认的错误页前端拦截器难以统一处理。建议加一个Result 类包含code、message、data三个字段所有接口返回它。代码改动不复杂但会让项目成熟度上一个大台阶。3.3 MyBatis-Plus 分页插件的用法与配置参数账单列表一定是分页的。MyBatis-Plus里的分页不是默认生效的需要先注册一个分页插件很多新手在这里翻车以为加了Page对象就有分页结果SQL还是全量查询。写一个配置类是最标准的做法// MybatisPlusConfig.java Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); // 防止一次取太多 pagination.setOverflow(false); // 超过最大页数时是否回第1页 interceptor.addInnerInterceptor(pagination); return interceptor; } }然后在ServiceImpl里写分页查询public IPageBillVO pageBills(Integer userId, int page, int size, String begin, String end) { PageBill pageParam new Page(page, size); LambdaQueryWrapperBill qw new LambdaQueryWrapperBill() .eq(Bill::getUserId, userId) .ge(Bill::getBillTime, begin) .lt(Bill::getBillTime, end) .orderByDesc(Bill::getBillTime); IPageBill result billMapper.selectPage(pageParam, qw); return result; }分页插件的两个参数值得说明maxLimit用来防御前端把size传成99999导致数据库压力过大overflow如果设成true当用户翻到第100页但总共只有3页时会自动回第1页学生项目里建议设成false让前端拿到空数据后提示“没有更多了”。分页排序用orderByDesc(bill_time)保证最新记录在最前面。如果源码里没有这个配置类启动后查询账单会不分页这是常见问题。3.4 预算预警查询时计算比定时任务更简单预算预警这个功能学生最容易做复杂一上来就引入Quartz或Spring Task每天定时扫一遍。其实记账系统的数据量小而且用户只要在记完账之后看到提示就行完全可以在“新增账单”的接口里顺带算一次。具体逻辑是保存支出账单成功后用同一月份的账单总和与预算做对比返回一个level字段给前端“正常”或“超支”。超支时前端弹一个通知条即可。这样后端代码只要一个查询不需要额外建任务表、日志表。源码里如果真有定时任务你反而要警觉那多半是为了凑技术点而硬塞的答辩时很容易被追问“为什么不用前端提示”。预算查询的核心代码// BudgetService.java public BigDecimal getLeftAmount(Integer userId, String month, Integer categoryId) { Budget budget budgetMapper.selectOne(new LambdaQueryWrapperBudget() .eq(Budget::getUserId, userId) .eq(Budget::getMonth, month) .eq(Budget::getCategoryId, categoryId)); if (budget null) return null; // 未设置预算 QueryWrapperBill qw new QueryWrapper(); qw.select(IFNULL(SUM(amount),0) as total) .eq(user_id, userId) .eq(type, 2) .eq(category_id, categoryId) .likeRight(bill_time, month); MapString, Object map billMapper.selectMaps(qw).get(0); return budget.getAmount().subtract(new BigDecimal(map.get(total).toString())); }这里用likeRight bill_time, month是一个可读性不错的写法因为2025-04这个前缀可以直接匹配当月所有时刻。预算表里category_id为0代表整体预算在汇总时要把所有支出加总而不是某一个分类。调用时机放在BillService.saveBill方法末尾如果超出预算就抛一个带码的业务异常前端捕获到异常后弹“本月餐饮已超出预算”。别把预算逻辑写进Controller那样会导致每个需要校验的地方都要重复调用不符合单一职责。4. 前端落地Vue 脚手架、路由、axios 联调与 ECharts 图表前端拿到源码后第一件事不是看组件而是确认Vue版本、脚手架和UI库。很多旧源码是Vue 2 Element UI新源码用Vue 3 Element Plus两者语法差异很大看清版本能少走弯路。4.1 Vue 安装与环境配置用 Vite 还是 Vue CLI如果源码是Vue 3项目通常用ViteVue 2项目则是Vue CLI。下载源码后先看package.json里的vue字段和scripts再执行npm install。这里给出Vue项目的环境配置清单Node.js版本Vue 3 Vite建议用Node 16.15以上18或20都行Vue 2 CLI在Node 18下偶尔会有OpenSSL报错。npm镜像源用默认源经常卡在node-sass建议在项目根目录放一个.npmrc内容为registryhttps://registry.npmmirror.com。开发服务器端口Vite默认5173Vue CLI默认8080要与后端端口区分开。Vite配置的最小示例// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, host: 0.0.0.0 // 允许局域网访问答辩演示时方便连手机 } })如果你的后端运行在8080前端运行在5173前端请求http://localhost:8080/api/xxx会跨域。解决办法有两个后端开启CORS或者让Vite接口转发。这里先推荐后端CORS因为配置集中、前端不用改环境。Vite转发配置需要用到server.proxy字段写起来也不复杂但要注意target必须写后端完整地址否则请求会打到前端自己头上。Node版本的管理建议用nvm这是处理旧项目的“后悔药”。装完依赖后跑npm run serve看到“App running at”就说明环境没问题。如果项目里同时存在package-lock.json和yarn.lock优先用package-lock执行npm install不要混用npm和yarn否则node_modules状态会很乱。4.2 前端目录结构与路由登录守卫、路由参数传递Vue项目的src目录一般长这样src ├── api # 每个模块的接口请求文件 ├── assets # 静态资源 ├── components # 通用组件 ├── router # 路由配置 ├── store # Pinia/Vuex ├── views # Login、Register、Home、BillList、Statistics └── utils # request封装路由配置里要关注的不仅是页面地址还有路由守卫。没有登录的人不能进记账列表页这需要用vue-router的导航守卫和路由参数来实现。下面是常见写法// router/index.js import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, component: () import(../views/Login.vue) }, { path: /, component: () import(../layout/Layout.vue), redirect: /home, children: [ { path: home, component: () import(../views/Home.vue) }, { path: bill, component: () import(../views/BillList.vue) }, { path: stats/:month?, component: () import(../views/Statistics.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) // 路由守卫未登录跳转登录页 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) next(/login) else next() })统计页的路由里用了一个可选参数month这就是“vue路由参数”的典型用法从列表页点某个月份进入时用router.push(/stats/ month)传值页面里通过route.params.month拿到。如果不用URL参数而用全局状态刷新页面状态就丢了URL参数是记账系统里最可靠的月份传递方式。另外注意history模式需要在服务器端把未匹配路径回退到index.html否则刷新子路由会404。学生项目图省事可以直接用createWebHashHistory路径里多个#号但没有部署麻烦。4.3 axios 封装与跨域后端接口通了前端却拿不到数据前端请求统一封装在utils/request.js里核心是设置baseURL和拦截器。baseURL应当与后端接口前缀一致。比如后端Controller都映射在/api下前端就写成// utils/request.js import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization token return config }) request.interceptors.response.use( res res.data, err { ElMessage.error(err.response?.data?.message || 请求失败) return Promise.reject(err) } )这里把拦截器的作用说清楚请求拦截器统一加token避免每个接口手动带Header响应拦截器直接返回res.data调用方写const data await getBillList()时拿到的就是业务数据而不是AxiosResponse包了一层。超时时间15秒是常见选择太长的话启动后台导出功能会显得卡太短的话慢网络下容易失败。跨域问题的后端解法是在Springboot里加CorsConfig。常见配置是// CorsConfig.java Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true); } }这里的参数陷阱在于allowCredentials(true)和allowedOriginPatterns()同时使用时不能简单写成allowedOrigins()否则浏览器会拦截带Cookie的请求。记账项目如果用token和localStorage其实不需要credentials设成false更安全。如果你的源码里用的是Springboot 2.4以下版本可能没有allowedOriginPatterns方法把Springboot升一下级或改用前端转发是更省力的方案。4.4 统计图表ECharts 画月度趋势和分类占比图表是系统视觉上的门面。ECharts的Vue封装有很多种写法最简单的是直接引入echarts在mounted里初始化。下面是一个月度支出趋势图组件的核心代码template div reftrendRef styleheight: 320px; width: 100%/div /template script setup import { ref, onMounted, watch } from vue import * as echarts from echarts const props defineProps({ month: String, series: Array }) const trendRef ref(null) function renderChart() { const chart echarts.init(trendRef.value) chart.setOption({ xAxis: { type: category, data: props.series.map(i i.day) }, yAxis: { type: value }, series: [{ name: 支出, type: line, areaStyle: {}, data: props.series.map(i i.value) }] }) } onMounted(() renderChart()) watch(() props.month, () { trendRef.value renderChart() }) /script逻辑说明echarts.init必须在DOM挂载后调用所以放在onMounted里当父组件切换月份时子组件的props变化但DOM没有重新挂载所以要用watch监听月份变化后重新setOption。数据为空时echarts会显示一条空坐标轴想隐藏可以判断series长度为零时调用chart.clear()。图标组件还应该在onUnmounted里调用chart.dispose()释放实例否则路由切换多了页面会越来越卡。分类占比图用饼图数据格式是{name: 餐饮, value: 328.5}正好对应2.2节后端selectMaps组装出来的结构。如果前端把后端返回的下划线字段直接塞进饼图字段名不匹配会导致value全部为undefined这也是常见的图表“黑匣子”——控制台不报错页面就是空白。调试时可以先把接口的JSON打印到控制台对照图表组件里的字段名再改。5. 源码跑通的避坑清单环境、数据库、跨域、日期与图表五类这一节是踩坑记录。学生项目源码质量参差不齐环境问题占了七成以下五条是从实际跑通过程中筛出来的高频坑每条按“现象 → 原因 → 解决”的顺序讲你可以直接对着排查。5.1 Node 版本与 npm install依赖装不上是最大的黑匣子现象在项目根目录执行npm install跑到一半报错node-sass、gulp-sass这类包编译失败或出现“Cannot find module node-sass”有时node_modules装完了运行后页面白屏控制台报一堆模块缺失。原因node-sass属于原生模块对Node版本要求苛刻。如果本机Node是18或20而源码基于Node 14node-sass安装时就会编译失败。另外默认npm源下载慢过程中出现的断包也会导致依赖缺失。解决先看package.json里有没有node-sass。有的话最省事的做法是把它删掉、改用sass-loader和sassVue 2项目里的样式语法基本兼容。不想改代码就用nvm切换到源码作者指定的Node版本一般在README里有写。安装时可以强制指定镜像npm install --registryhttps://registry.npmmirror.com --loglevelinfo参数说明--registry指定镜像源--loglevelinfo是为了在终端看到到底在下载或编译哪个包。装完后跑npm run serve前先看node_modules/vue目录是否存在存在说明核心依赖装上了。Node版本管理工具建议用nvm切换后不用重装系统属于“后悔药”级别的工具。5.2 MySQL 8 连接失败密码认证和时区两个显眼坑现象后端启动后接口报Access denied for user rootlocalhost或者报Public Key Retrieval is not allowed。有时密码也对但启动时报The server time zone value ... is unrecognized。原因MySQL 8默认用caching_sha2_password认证某些驱动的非SSL连接不支持数据库密码包含特殊字符时YAML没有用引号包住导致解析错位MySQL时区不是UTC而JDBC连接串没指定serverTimezone驱动去读系统时区就会报错。解决在application.yml里把JDBC连接串写到一行并显式配置allowPublicKeyRetrieval和serverTimezone。spring: datasource: url: jdbc:mysql://localhost:3306/student_account?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver参数说明useSSLfalse是本地开发常用否则MySQL 8会尝试证书交换serverTimezoneAsia/Shanghai保证LocalDateTime转DATETIME时不差8小时allowPublicKeyRetrievaltrue解决caching_sha2_password插件在非SSL连接时报的Public Key错误。Springboot 2.7默认驱动就是com.mysql.cj.jdbc.Driver老源码里写的com.mysql.jdbc.Driver要改过来。另一个易漏点是Linux下数据库名大小写敏感源码里建表脚本如果是小写目录而配置里写的库名是大写启动时一直报“Unknown database”直接把配置改成脚本里的库名即可。5.3 端口占用与跨域配置接口404不一定是后端问题现象前端页面能打开但一登录就报Network Error或404后端明明启动了Postman里也通但从浏览器访问就是失败。后端有时也报Port 8080 was already in use。原因前端和后端分别监听不同端口浏览器请求直接打到后端但后端没有允许该来源或前端请求路径不对。端口占用则是之前启动的Java进程没停干净。解决先看浏览器开发者工具Network请求判断请求Origin和后端响应头Access-Control-Allow-Origin。如果缺了响应头优先走后端CORS配置如果请求路径是/api开头且后端Controller没有这个前缀调整前端的baseURL或Vite转发路径。端口占用的排查命令netstat -ano | findstr 8080 taskkill /PID 占用进程的PID /F排查跨域的硬办法浏览器地址栏直接输入后端接口完整路径比如http://localhost:8080/api/bill/list能看到JSON说明接口没问题问题就在前端请求地址或跨域配置。这里还要注意当你同时开着前端转发的target指向localhost:8080又把后端的CORS配置了“允许所有来源”两种机制叠在一起有时会冲突表现为OPTIONS预检请求正常、GET请求被拦截。建议只保留一种方案要么全走后端CORS要么全走前端转发不要混用。5.4 LocalDateTime 序列化与时间偏移统计少一天的血泪经验现象前端日期选择器选了2025-04-01结果后端查出来的是2025-03-31或者月度统计少了一天的数据接口返回账单时间变成了2025-04-01T00:00:00前端格式化后又出现8小时偏移。原因前后端时间类型不一致。前端传的格式是2025-04-01T00:00:00.000Z后端用String接收后直接比较字典序时区差异导致边界错位。以及Springboot对LocalDateTime默认序列化输出带T的ISO格式前端直接显示很难看。解决在Springboot里加Jackson全局配置统一LocalDateTime的序列化格式同时后端接口的参数统一用String的“yyyy-MM-dd”。// JacksonConfig.java Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; } }金额字段也有精度坑数据库的DECIMAL(10,2)到Java后应该用BigDecimal接收如果实体类里写成了Double累计到几十万时会变成科学计数法或精度丢失。拿到源码第一件事检查所有金额字段是不是BigDecimal不是就改过来。时间格式统一这件事最好在第一轮跑通前就做否则后面统计图表会一直在边界值上翻车。5.5 ECharts 白屏DOM没渲染完和data字段对不上现象统计页面打开后图表区域一片空白控制台没有报错另一种是折线图有坐标轴但没有数据饼图不居中。原因ECharts实例初始化的时机早于DOM首次渲染或数据请求是异步的图表依赖的数据还没回来就setOption了。还有源码在图表组件外层用了v-if条件从false变true时DOM重建旧的echarts实例被丢弃新DOM上没绑定实例就白屏了。解决在mounted里做初始化并在请求回调里再setOption如果数据是父组件传的用watch加nextTickwatch( () props.data, (val) { if (val val.length) { nextTick(() { if (chart) chart.setOption(buildOption(val)) }) } } )另外建议在组件卸载时调用chart.dispose()否则路由切换多了内存里一堆ECharts实例会让页面越用越卡。把“资源释放”写进项目里答辩时可以主动提一句“我做了实例销毁”是个很顺手的小亮点。6. 把源码项目改成自己的答辩亮点新增功能、验证与部署前面把项目跑通了但如果只把源码原封不动交上去容易被看成“下载即交差”。这一章讲三个最值得投入的改造点难度都不高但能明显改变项目面貌也容易在答辩时讲出亮点。6.1 选一个低成本小功能账单导出 Excel导出Excel是做课程设计时性价比最高的功能。理由有三第一它和“记账”强相关用户想把账导出来做复盘很正常第二技术上要用到EasyExcel或Apache POI这是企业常用库第三它不需要改数据库表只要在原有查询接口基础上加一个返回文件流的方法。用EasyExcel的话后端先定义一个和BillVO字段一致的POJO然后流式写出// BillController.java GetMapping(/export) public void export(RequestParam Integer userId, RequestParam String month, HttpServletResponse response) throws IOException { ListBillVO list billService.getMonthBills(userId, month); response.setContentType(application/vnd.ms-excel); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(账单_ month .xlsx, UTF-8); response.setHeader(Content-disposition, attachment;filename fileName); EasyExcel.write(response.getOutputStream(), BillVO.class) .sheet(消费明细) .doWrite(list); }前端用一个window.open(/api/bill/export?userId userId month month)就能触发下载。这套改动不到十行就能让项目从“增删改查”升级到“有生产属性”。答辩时主动提“用EasyExcel流式导出避免数据量大时内存溢出”踩点很准。6.2 验证新增接口Swagger、Postman 和单元测试三件套不要只在浏览器里跑一遍就算验证。建议按三个层次确认接口文档、手动测试、自动测试。如果你的源码里没有集成Swagger先加一个Docket Bean配置包扫描路径指向controller包。然后在Controller上加Api和ApiOperation注解。答辩时打开Swagger页面接口的请求参数和返回结构一清二楚比贴PPT截图有说服力。对新增的导出接口再补一个JUnit测试防止临时改坏了老接口。SpringBootTest public class BillServiceTest { Autowired private BillService billService; Test public void testMonthSummary() { MapString, Object summary billService.monthlySummary(1, 2025-04); assertNotNull(summary); assertTrue(summary.containsKey(totalExpense)); } }这里的核心是结果可断言而不是把测试跑绿给自己看。一个简单断言能证明你理解“月度汇总应该返回什么”。如果测试库里没有数据先插入几条固定测试数据再跑。测试方法名用中文解释更直观比如test_四月账单汇总不为空。6.3 打包部署前端 dist 交给后端jar 包一键启动最后一定要解决的问题是“怎么给老师演示”。开发模式要开两个终端答辩现场容易翻车最稳妥的方式是打包成一个jar一键启动。步骤不复杂前端执行npm run build生成dist目录把dist拷到后端src/main/resources/static下然后后端打成jar包。npm run build mvn clean package -DskipTests java -jar target/account-0.0.1-SNAPSHOT.jar把前端放进static目录后Springboot会把dist里的index.html当作默认首页后端接口仍然是/api前缀不会冲突。只注意一点如果你用history模式刷新某个子路由会404解决方法是后端加一个Controller把未匹配路径指到index.html或者改用hash模式。学生项目用hash模式最省事路径多个#号无伤大雅。部署完后验证三件事第一关掉前端dev server直接用浏览器打开8080端口能看到登录页第二完成一次“记账 → 查看统计 → 导出Excel”的完整流程第三重启服务后数据还在说明数据库持久化正常。这三件事做完你交付的状态就远超“能跑”而是“能演示、能答辩”。我自己拿到一个源码包后会先不急着看代码把README、SQL脚本、application.yml这三样先读一遍再启动项目。之前接过一个项目直接跑main方法结果是数据库没初始化折腾了三个小时才找到原因后来才发现README第一段就写了“请先导入sql目录下的脚本”。这套流程养成后十分钟就能判断一个项目是否健康希望帮到你。本文还有配套的精品资源点击获取
返回列表