ARTICLE DETAIL

资讯详情

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

Spring Boot + Vue车辆充电桩系统毕业设计:建模、后端、避坑完整指南

Spring Boot + Vue车辆充电桩系统毕业设计:建模、后端、避坑完整指南 简介这是一套面向计算机专业毕业设计的车辆充电桩管理系统完整项目后台采用 Spring Boot 框架管理端页面使用 Vue前端展示页面为 HTML数据库选用 MySQL适合正在准备毕业设计或希望学习前后端分离开发的学生参考。系统提供首页、个人中心、维修员管理、用户管理、电桩类别管理、充电桩管理、充电桩报修管理、维修回复管理、系统管理等模块业务链路完整覆盖从充电桩建档到报修维保的常用流程。RAR 压缩包整体约 58.09MB包含源码、数据库脚本、毕业论文、答辩 PPT、环境工具包并在说明文档中附有相同框架项目的安装教程便于直接部署运行和对照学习。资源已有 49 人浏览学习对于需要快速搭建项目、完成论文写作与答辩准备的毕业生来说是一份省时且可二次开发的基础材料。1. 选这个毕业设计前先弄清楚Spring Boot Vue的车辆充电桩系统到底在做什么毕业设计选题年年扎堆但“Java车辆充电桩 Spring Boot Vue”这个题每次答辩都有人做说明它确实踩中了评审老师最关心的基本功前后端分离、数据库建模、状态接口设计和交易计费。这个系统说白了就是两件事——管住充电桩的实时状态算清每一次充电的钱。难的地方不是 CRUD而是桩的状态随时会变、订单金额和充电时间、度数挂钩这两点做扎实整个项目就立住了。它适合三类人Java 基础能上手 Spring Boot 的同学想在简历写一个完整前后端项目的求职者想低成本搭一套充电桩管理后台的从业者。这个题难度中等工作量集中在订单和计费逻辑不涉及复杂算法或硬件对接。前提是别一上来就写代码先把状态流转和费用计算想清楚。下面按“建模 → 后端 → 前端 → 避坑 → 验收”的顺序完整过一遍。2. 充电桩系统建模先行数据表、计费规则和实体类怎么互相咬合我先说结论这个项目里数据库设计占整个工作量至少三分之一。很多同学做到后端才发现订单表缺了计费快照字段回头补字段要连带改接口、改页面越改越乱。先花两小时把表定下来后面能省两天 debug 时间。充电桩的建模核心是五张表用户表、充电桩表、订单表、计费规则表再加一张可选的反馈表。其中前三张是骨架计费规则表决定了后面财务逻辑怎么写。2.1 三个核心实体充电桩表、订单表和用户表怎么设计充电桩表要能支撑管理后台的列表和地图展示字段至少覆盖编号、位置、坐标、功率、状态、单价。建表语句我一般这样写CREATE TABLE charge_pile ( id BIGINT NOT NULL AUTO_INCREMENT, pile_code VARCHAR(32) NOT NULL COMMENT 桩编号如 CP-001, location_name VARCHAR(100) DEFAULT NULL COMMENT 站点位置如滨江停车场B2, longitude DECIMAL(10,6) DEFAULT NULL COMMENT 经度地图打点, latitude DECIMAL(10,6) DEFAULT NULL COMMENT 纬度, power_type TINYINT DEFAULT 1 COMMENT 1交流慢充 2直流快充, rated_power DECIMAL(5,1) DEFAULT NULL COMMENT 额定功率kW, status TINYINT DEFAULT 1 COMMENT 0离线 1空闲 2充电中 3故障, unit_price DECIMAL(8,4) DEFAULT NULL COMMENT 单价元/kWh, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_pile_code (pile_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT充电桩表;这里有个实用细节pile_code必须唯一演示时哪怕桩是模拟数据也要让评审能凭编号区分设备。power_type和rated_power是为后面的 UI 展示服务的直流快充的桩在页面上用不同颜色标识比只显示一行文字更有真实感。unit_price单独放在桩表是为了简化计费逻辑后面如果要支持分时电价再把它拆到规则表里。订单表是整个系统最容易被改来改去的表建议一次把结算相关字段全部建齐CREATE TABLE charge_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号业务唯一, pile_id BIGINT NOT NULL COMMENT 充电桩ID, user_id BIGINT NOT NULL COMMENT 用户ID, start_time DATETIME DEFAULT NULL COMMENT 开始充电时间, end_time DATETIME DEFAULT NULL COMMENT 结束充电时间, power_amount DECIMAL(10,2) DEFAULT NULL COMMENT 充电度数kWh, total_amount DECIMAL(10,2) DEFAULT NULL COMMENT 本次总费用, prepay_amount DECIMAL(10,2) DEFAULT NULL COMMENT 下单时预扣金额, status TINYINT DEFAULT 0 COMMENT 0充电中 1已完成 2已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_pile_id (pile_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT充电订单表;prepay_amount是很多第一次做的人会漏的字段。真实充电场景下用户余额不是无限大的下单时要按预估金额冻结一部分余额订单结束以后多退少补。没有这个字段后面做结算逻辑会非常别扭。order_no不要用自增 id 直接暴露给前端建议用雪花 ID 或“yyyyMMddHHmmss随机数”拼接订单号一长串看起来也更有业务代入感。用户表相对常规手机号做登录账号密码存加密后的密文余额用DECIMAL不用FLOAT因为钱用浮点类型迟早要出脏数据。CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, phone VARCHAR(20) NOT NULL COMMENT 手机号登录账号, password VARCHAR(64) NOT NULL COMMENT 加密后密码, nickname VARCHAR(50) DEFAULT NULL, balance DECIMAL(10,2) DEFAULT 0.00 COMMENT 账户余额, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;对于毕设演示余额字段强烈建议做成手动充值模式提供一个“充值”接口就行别去对接真实支付。答辩时评审只会关心余额有没有被正确扣减和退回不会在意支付流程。2.2 计费策略怎么存固定单价、分时段还是阶梯电价充电桩计费有三种常见做法复杂度递增。最省事的是每根桩一个固定单价在charge_pile.unit_price存一个数字算费用时直接单价 × 度数。第二种是分时段计价比如峰电 1.2 元、谷电 0.6 元需要建一张规则表订单结束算费时按时间段去匹配单价。第三种是阶梯电价充电超过一定度数后单价上涨这种对毕设来说偏重我不太推荐。如果想让项目有亮点我建议做成第二种——分时段计价并配一张规则表CREATE TABLE charge_rule ( id BIGINT NOT NULL AUTO_INCREMENT, rule_name VARCHAR(50) NOT NULL COMMENT 规则名称如峰谷电价, start_time TIME NOT NULL COMMENT 生效开始如08:00, end_time TIME NOT NULL COMMENT 生效结束如12:00, unit_price DECIMAL(8,4) NOT NULL COMMENT 该时段单价元/kWh, rule_type TINYINT DEFAULT 1 COMMENT 1平时 2峰时 3谷时, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT计费规则表;分时段计费的关键坑不在建表而在订单结束那一刻怎么选价格。最简单可靠的做法是订单结束时取当前时间查询start_time 当前时间 end_time的那条规则用它的unit_price算总价。不要尝试把整段时间切割成多个片段分别计费那会让订单接口变成一个黑匣子调试到崩溃。毕设能讲清楚“当前时段单价”已经够了如果要加分可以把下单时的单价快照存进订单表这样即使规则变了历史订单也能正确对账。2.3 实体类生成建表 SQL 的两条路MyBatisX 插件与 JPA ddl-auto很多人搜“mybatisplus根据Java实体类生成创建表的sql语句”这个搜索词背后其实是一个误区MyBatis-Plus 核心框架没有内建的实体类自动建表能力MyBatisX 插件也主要是从表逆向生成实体类、Mapper方向是反的。真正想“只写实体类就自动建表”常见做法是走 JPA 的 ddl-auto这也是 Spring Data JPA 的老功能spring: jpa: hibernate: ddl-auto: update启动项目时Hibernate 会扫描带Entity注解的类自动在数据库里创建或更新表。对于纯毕设快速验证来说很爽但它有两个硬伤一是建表细节不容易控制想指定字符集、注释、索引会绕很多路二是演示环境一旦连错库update会擅自改表结构出现莫名其妙的字段错位。我给你的建议是如果想要快速起一个能跑的前后端工程可以用 JPA 的ddl-auto撑过前期但最终交出去的版本一定要用手写 SQL 文件和 Navicat 手动执行。手写 SQL 的每一条注释、每一个索引都是答辩时能说清楚的话而 JPA 自动生成的表你跟评审讲不了细节。手写建表脚本以后在 Spring Boot 的application.yml里配好数据源即可表结构是稳定的后面所有接口都基于这套稳定结构来写不会返工。3. Spring Boot 后端项目结构、状态机和结算接口的完整搭法数据库设计好以后进入后端编码。这个项目的 Spring Boot 后端不需要花哨的设计模式但要遵循清晰的三层结构Controller 层只做参数接收和返回Service 层放业务逻辑Mapper 层通过 MyBatis-Plus 操作数据库。下面按项目结构、状态机、定时任务三块展开。3.1 Maven 项目结构与配置文件pom.xml 和 application.yml 的必配项先建一个标准 Maven 项目包名建议用com.xxx.charge内部结构分controller、service、mapper、entity、config、common六个包。依赖上我一般用这套组合稳定性优先parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesSpring Boot 版本这里故意选了 2.7.18没选 3.x核心原因在后面的避坑章节会详细说很多毕业设计依赖的旧版 jar 在 jakarta 命名空间迁移后直接编译不过折腾半天并不是你的代码有问题。MyBatis-Plus 版本和 Spring Boot 版本要配套3.5.3.2配 Boot 2.7 是社区里验证过非常稳的组合。application.yml里有三个必配项数据源、MyBatis-Plus 驼峰映射、SQL 日志。我一般这样写server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/charge_pile?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case必须开这样数据库字段pile_code能直接映射到实体属性pileCode不用手写一堆TableField。log-impl打印 SQL 到控制台调试接口时看这条日志比看前端报错高效得多。serverTimezoneAsia/Shanghai是避免 MySQL 8 时区报错的关键参数一个都不能少。3.2 用状态机管理充电流程空闲 → 充电中 → 已完成充电桩的状态变化是整个系统的灵魂。桩有四种状态0 离线、1 空闲、2 充电中、3 故障订单有三种状态0 充电中、1 已完成、2 已取消。两者必须联动不能出现桩还显示空闲但订单已经在充电中的矛盾。先定义一个订单状态枚举避免代码里到处写魔数package com.xxx.charge.common.enums; import lombok.Getter; Getter public enum OrderStatusEnum { CHARGING(0, 充电中), FINISHED(1, 已完成), CANCELED(2, 已取消); private final int code; private final String desc; OrderStatusEnum(int code, String desc) { this.code code; this.desc desc; } }核心的下单方法是startCharge它做的事一定要有清晰的顺序先查桩、再查用户、冻结余额、创建订单、改桩状态。凡是涉及多个写操作的必须加上TransactionalTransactional(rollbackFor Exception.class) public Long startCharge(Long userId, Long pileId) { ChargePile pile chargePileMapper.selectById(pileId); if (pile null || !PileStatusEnum.IDLE.getCode().equals(pile.getStatus())) { throw new BizException(充电桩当前不可用请换一个桩); } User user userMapper.selectById(userId); BigDecimal prepay BigDecimal.valueOf(30); if (user.getBalance().compareTo(prepay) 0) { throw new BizException(账户余额不足请先充值); } userMapper.deductBalance(userId, prepay); ChargeOrder order new ChargeOrder(); order.setOrderNo(generateOrderNo()); order.setPileId(pileId); order.setUserId(userId); order.setStartTime(LocalDateTime.now()); order.setPrepayAmount(prepay); order.setStatus(OrderStatusEnum.CHARGING.getCode()); chargeOrderMapper.insert(order); chargePileMapper.updateStatus(pileId, PileStatusEnum.CHARGING.getCode()); return order.getId(); }这段代码有几个细节值得注意。prepay是固定 30 元为了演示可以这么做但要在注释里标明这是“按最低起充金额冻结”如果你想更严谨可以按电池预估度数去算预冻结金额。deductBalance是用UPDATE user SET balance balance - #{money} WHERE id #{id}实现的比先查出来再 set 再 update 安全避免了并发下余额被覆盖。updateStatus要带条件更新写成UPDATE charge_pile SET status 2 WHERE id #{id} AND status 1JDBC 返回影响行数为 0 说明并发下已经被别的用户抢占了相当于做了一次乐观锁保护。订单结束方法finishCharge是反向流程先查订单再算费再结算最后释放桩Transactional(rollbackFor Exception.class) public void finishCharge(Long orderId, BigDecimal powerAmount) { ChargeOrder order chargeOrderMapper.selectById(orderId); if (order null || !OrderStatusEnum.CHARGING.getCode().equals(order.getStatus())) { throw new BizException(订单状态异常无法结束充电); } BigDecimal fee chargeRuleMapper.getCurrentUnitPrice(LocalDateTime.now()) .multiply(powerAmount) .setScale(2, RoundingMode.HALF_UP); BigDecimal prepay order.getPrepayAmount(); BigDecimal diff fee.subtract(prepay); if (diff.compareTo(BigDecimal.ZERO) 0) { userMapper.deductBalance(order.getUserId(), diff); } else if (diff.compareTo(BigDecimal.ZERO) 0) { userMapper.refundBalance(order.getUserId(), diff.abs()); } order.setPowerAmount(powerAmount); order.setTotalAmount(fee); order.setEndTime(LocalDateTime.now()); order.setStatus(OrderStatusEnum.FINISHED.getCode()); chargeOrderMapper.updateById(order); chargePileMapper.updateStatus(order.getPileId(), PileStatusEnum.IDLE.getCode()); }注意RoundingMode.HALF_UP是必须写的不写默认的舍入模式在不同 JDK 版本下行为不一致。费用计算用的是BigDecimal.multiply不是double乘法这一点在避坑章节里还有详细说明。整个方法里如果任何一个更新操作失败事务回滚会把余额扣减也一起撤回这就是Transactional(rollbackFor Exception.class)存在的意义。3.3 定时任务兜底订单状态别让充电桩卡死在“充电中”真实充电桩有一个网络不可靠的问题用户手机断网、桩端故障、后台重启都会让订单停留在“充电中”状态。毕设虽然没有真实硬件但评审可能会问“如果用户充到一半走了怎么办”这时候需要一个定时任务来兜底。我的做法是加一个每 60 秒执行一次的调度方法把超过 2 小时还处于充电中的订单强制结束Component public class ChargeOrderTimeoutTask { Resource private ChargeOrderService chargeOrderService; Scheduled(fixedDelay 60000) public void handleTimeoutOrders() { LocalDateTime deadline LocalDateTime.now().minusHours(2); ListChargeOrder timeoutOrders chargeOrderMapper.selectChargingBefore(deadline); for (ChargeOrder order : timeoutOrders) { try { chargeOrderService.finishCharge(order.getId(), order.getPowerAmount()); } catch (Exception e) { log.error(强制结束充电订单失败orderId order.getId(), e); } } } }这里有个关键参数fixedDelay 60000是前一次执行完后再隔 60 秒执行下一次不是“每 60 秒固定时间点执行”这对于短任务的毕设足够了。selectChargingBefore是自定义 SQL注意加上limit 100防止数据量大时一次查到太多。需要在启动类上添加EnableScheduling注解否则这个任务不会生效。定时任务的作用不是解决真实充电桩异常而是让演示现场不会出现“强制刷新后状态对不上”的尴尬。4. Vue 前端环境配置、充电桩显示 UI 与打包部署后端接口就绪后前端是让答辩加分的关键。很多毕设界面做得像“表格套壳”而充电桩系统的 UI 天然适合做地图点位、状态卡片、实时数据刷新视觉上就比普通管理系统高一个档次。下面从前端工程初始化、页面组件到部署讲一遍。4.1 Vue 安装及环境配置路由、Axios 与跨域代理前端用一个干净的空目录初始化用 Vue CLI 还是 Vite 取决于你的环境。我建议用 Vite 创建 Vue 3 项目启动更快热更新更稳。不过你要沿用 Vue CLI 的老模板也没问题下面的配置两边通用。安装完依赖后第一步是配vue.config.js如果是 Vite 则是vite.config.js解决开发环境的跨域问题// vue.config.js const { defineConfig } require(vue/cli-service) module.exports defineConfig({ devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这里的逻辑是前端开发服务器跑在 3000后端 Spring Boot 跑在 8080。前端发起/api/pile/list请求时开发服务器把它转发到http://localhost:8080/api/pile/list从而绕开浏览器跨域限制。changeOrigin: true会把请求头里的 Host 改成目标地址很多后端框架对 Host 校验严格漏了会报 403。路由方面我强烈建议开发时先用hash模式也就是路径形如/#/piles。原因有两个hash 模式刷新页面永远能找到路由入口部署时不需要后端做任何路径穿透配置毕业设计演示出现白屏的绝大多数原因是 history 模式没配兜底。如果你为了美观想用history模式部署到 Spring Boot 时要在后端加一个转发 Controller这点放在避坑章节细讲。Axios 封装也要在这一步做好用一个统一实例管理请求前缀、超时时间和错误提示import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( (res) res.data, (err) { console.error(请求失败, err.response?.data?.message || err.message) return Promise.reject(err) } ) export default requestbaseURL: /api配合上面的 proxy后端 Controller 里统一用/api开头这样前端代码里不用在每处写完整域名。超时时间 10 秒是经验值太短会误伤慢查询太长会让演示现场一直转圈。4.2 充电桩显示 UI 开发状态面板、地图点位与订单列表页面不用做太多三张页足够首页放地图和桩位总览第二张做订单列表第三张做用户管理。核心是首页的充电桩卡片这块开发是充电桩显示 UI 开发的直接体现。桩卡片组件核心结构如下template div classpile-card :classstatus- pile.status div classpile-location{{ pile.locationName }}/div div classpile-status{{ statusText }}/div div classpile-price 单价{{ pile.unitPrice }} 元/kWh span v-ifpile.powerType 2直流快充/span span v-else交流慢充/span /div button v-ifpile.status 1 !loading classcharge-btn clickstartCharge 立即充电 /button /div /template组件里通过pile.status切换样式类status-0灰色表示离线、status-1绿色表示空闲、status-2橙色表示充电中、status-3红色表示故障。视觉上状态颜色一目了然比文字描述更能打动评审。立即充电按钮只出现在空闲状态这个条件渲染要写对否则同时打开的多个卡片会互相干扰。地图点位可以用Mapbox GL或者Leaflet毕设场景下建议用轻量的Leaflet加载快、不需要注册 token用经纬度坐标直接画 markerimport L from leaflet import leaflet/dist/leaflet.css L.map(map, { center: [30.5, 114.3], zoom: 12 }) L.marker([pile.latitude, pile.longitude]) .addTo(map) .bindPopup(${pile.locationName}br状态${statusText})如果不想引入地图库也可以直接在页面上用一个静态背景图加绝对定位的点位但那样就少了实时交互感。地图组件最关键的数据绑定是“轮询刷新”用setInterval每 10 秒请求一次桩列表接口更新当前组件的数据实现页面不用手动刷新就能看到状态变化onMounted(() { loadPiles() timer setInterval(loadPiles, 10000) }) onBeforeUnmount(() { clearInterval(timer) })4.3 把 Vue 打包放进 Spring Boot完整配置与资源路径开发完成后要部署成一个可执行 jar关键是“把 Vue 打包放进 Spring Boot”的步骤。先在前端项目根目录执行构建npm run build构建完成后dist目录下会有index.html和static或assets文件夹。打开 Spring Boot 项目的src/main/resources把dist里的所有文件复制到static目录下。Spring Boot 会自动把static作为静态资源根目录localhost:8080就能看到前端页面。如果你使用 hash 路由模式到这里已经结束了不用再做任何配置。如果你用了 history 模式则需要一个兜底 ControllerController public class PageForwardController { RequestMapping(value {/, /piles, /orders, /login}) public String forward() { return forward:/index.html; } }这个控制器把所有不匹配后端接口的路径都转发到index.html由 Vue Router 接管解析。注意forward:是 JVM 内部转发不是redirect:写错会导致无限循环刷新。如果部署后出现接口 404大部分原因是forward:/index.html把/api路径也拦截了所以要约定前端路由路径不要包含/api前缀而接口统一以/api开头两者天然隔离。最后在application.yml里确认server.servlet.context-path是空的默认根路径即可。如果有 context-path前端资源路径也会跟着变调试成本会成倍增加。打包验证用mvn clean package生成 jarjava -jar启动后访问一次根路径确认首页能出、接口能通、刷新页面不白屏这三个点都过了就说明前后端合并部署成功了。5. 避坑实录数据库、版本和部署的五个翻车现场这个章节写的每一条都是真实踩过的坑按“现象 → 原因 → 解决”梳理出来。你照着做大概率跳过这些坑但更重要是掌握排查思路。5.1 MySQL 8 启动时报时区错误现象项目启动时报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized或者连接成功后查出来的时间比数据库实际时间早 8 小时。原因MySQL 8 对时区要求严格JDBC URL 里没有显式指定时区时驱动和数据库对时区解析规则不一致。报错信息里的乱码本质上是数据库默认时区的中文字符集被错误解码。解决在数据源 URL 上追加serverTimezoneAsia/Shanghai并且放在访问参数最末尾防止半路截断url: jdbc:mysql://localhost:3306/charge_pile?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse如果你用的是 MySQL 5.x这个参数可以不写但我建议统一带着。还有一种情况是 URL 配好了但应用服务器和数据库服务器不在同一时区这种在毕设本地开发不会遇到不用管。5.2 Spring Boot 3.x 依赖升级背后的隐藏坑现象从网上找到的代码是 Spring Boot 3.0 的导入后编译报package javax.servlet does not exist或者 MyBatis-Plus 的依赖加载进来后运行报类找不到。原因Spring Boot 3.0 把javax命名空间整体迁移到jakarta很多老版本 jar 仍然依赖javax组合在一起必然冲突。MyBatis-Plus 官方适配 Spring Boot 3 的版本起步较晚网上很多旧教程不会提到这一点。解决毕设项目直接用 Spring Boot 2.7.x JDK 8 或 JDK 11这条路线最稳网上能搜到的资料和 starter 版本几乎全部兼容。如果你确实想用 Spring Boot 3.x 展示“新技术”要保证三个前提JDK 17、Spring Boot 3.2、MyBatis-Plus 3.5.5三个版本缺一不可。非要用 Boot 3 而踩坑时别去改代码里的包名那是无用功直接换版本组合才是唯一解法。5.3 Vue 打包放进 Spring Boot 后页面白屏或刷新 404现象前端开发环境一切正常npm run build后复制到static访问首页没问题但刷新 Vue 子路由时白屏或者直接提示 404。原因这是 history 路由模式的经典问题。浏览器请求/pilesSpring Boot 在后端找不到对应的 Controller 和静态文件返回 404前端根本接不到index.html。解决两个方案任选其一。方案一是把前端路由从history改成hash模式刷新永远走根路径零后端配置我最推荐。方案二是保留 history增加一个 Spring Boot Controller 做路径转发注意排除/api开头的接口请求否则接口也会被转发到index.html返回 HTML前端拿到后解析失败Controller public class PageForwardController { RequestMapping(value {/, /piles, /orders, /login}) public String forward() { return forward:/index.html; } }如果你在部署后看到接口返回 HTML 而不是 JSON就检查转发路径是不是把/api也吞进去了这是抓个正着的翻车现场。5.4 订单已经结束充电桩状态还是“充电中”现象用户在前端点结束充电接口返回成功订单状态变成了已完成但回到首页看桩的卡片还是橙色“充电中”。原因结束充电的接口在更新订单状态后没有把桩表状态从 2 改回 1或者两个更新操作没放在同一个事务里订单更新成功了、桩状态更新失败后没有回滚数据就永远不一致。解决用Transactional包住结束接口的两个写操作并在写桩状态时加上“condition update”UPDATE charge_pile SET status 1 WHERE id #{pileId} AND status 2条件更新保证只有桩确实处于充电中时才允许置回空闲如果影响行数为 0说明桩状态在别的地方被改动过抛异常让整体回滚。这类问题真实发生过演示中途被评审点破会非常尴尬。所以我在开发阶段还会故意写一条定时任务去扫描这种数据不一致定期修正具体实现就是第 3.3 节约的强制结束逻辑。5.5 用 double 算费用出现 9.9999999现象订单金额计算后显示9.99999999998或者两个 0.1 元的价格加起来是0.30000000000000004。原因Java 里double是浮点数二进制无法精确表示 0.1累积计算越错越离谱。钱一旦用浮点类型账单展示迟早露馅。解决金额相关的变量一律用BigDecimal计算时用字符串、不要用double入参并在最终结果上指定舍入模式BigDecimal fee unitPrice.multiply(powerAmount) .setScale(2, RoundingMode.HALF_UP);同时数据库字段全部使用DECIMAL(10,2)实体类型用BigDecimal别转成Double再存。这条不是能复现的奇怪问题而是常偷偷坑人的细节。答辩时如果有评审问“金额计算怎么保证精度”你能说出BigDecimal和RoundingMode.HALF_UP这两个名词已经是加分项。6. 答辩前的一天函数验证、演示数据和交付整理到了这一步代码基本能跑通了。真正拉开差距的是你怎么验证它、怎么演示它。最后一章分享一个我习惯用的验收清单以及演示现场的细节安排。6.1 用 Apifox 把全链路用例跑一遍不要靠前端页面肉眼验证“看起来没问题”和“逻辑没问题”是两回事。我习惯把 Apifox 当后端的主战场建一个项目按顺序跑通 5 条链路注册用户并充值、查看充电桩列表、空闲桩下单、结束充电并验证余额扣减、重复下单被拦截。重点是第 4 条下单时预扣 30 元结束充电实际消费 20 元数据库余额必须恢复到只少 20 元的状态。接口字段校验也要测传空的pileId、传一个不存在的订单号看后端是否返回规范错误信息。Apifox 里提前保存好断言规则每次打包前自动跑一遍比手工点页面高效得多。这个习惯在答辩前夜尤其重要改完最后一行代码就能一键回归。6.2 演示数据怎么造得让人信服演示现场最怕的是冷启动问题数据库是空的页面上一片空白评审体验极差。提前在数据库里插入 8 到 10 个充电桩分布在本地几个真实地点经纬度从地图上抄下来。订单也要预设两三条一条是今天已完成的历史订单用于展示账单页面一条“充电中”状态的订单用于现场点击“结束充电”展示状态变化。准备好一个固定测试账号余额设成 100 元。现场演示时先展示账户余额再下单充电结束后展示退款后的余额整个过程有数字变化说服力远强于只口述功能。演示脚本写 3 页纸就够了包含每个页面的停留时间、预期点击、预期弹窗。演示不在功能多在于关键链路一气呵成。6.3 交付文档和源码整理最后交付的时候源码包要注意两点一是删掉node_modules和target目录zip 包体积会小几十倍评审老师解压也不会卡死二是写一个README.md把启动顺序写清楚先建数据库执行schema.sql再启动后端最后启动前端或在 Spring Boot 里访问打包好的页面。文档和教程如果自己有原创内容章节按“需求分析、数据库设计、接口设计、系统实现、测试报告”五段来组织这正好对应评审老师翻阅文档的习惯。我每次做完这类项目都会留一个习惯把当天踩过的坑记录在 README 的“常见问题”一节哪怕只有五六条也能让后来人少走弯路。做毕设本身就是一段从“能跑”到“经得起问”的修炼愿你在答辩那天面对任何问题都能从容应答希望这篇文章能帮到你。本文还有配套的精品资源点击获取
返回列表