1. 项目定位与需求拆解
1.1 这个项目到底在做什么
先说结论:这是一个基于 SpringBoot + Vue 的 B2C 式电车充电管理平台,系统覆盖了“找桩—预约—充电—支付—评价”的完整闭环,同时提供后台运营管理的全套能力。说白了,就是把线下充电站的业务搬上线:用户能搜到附近有空闲状态的充电桩、扫码启动充电、实时查看充电进度、完成后自动扣费;运营方能在后台维护充电桩信息、查看营收数据、管理用户和订单。
做这个选题的同学,很多人一开始会纠结一个问题:市面上充电系统那么多,我做一个会不会显得很普通?我的看法是,毕设的本质不是“造一个别人没做过的东西”,而是在一个真实业务场景里,证明你具备需求分析、系统建模、技术落地、排错优化的完整能力。充电系统刚好具备典型业务系统的所有要素——状态管理(桩的待机/充电/故障)、并发问题(多用户同时扫码)、资金安全(钱包扣费和流水)、定时任务(订单超时关闭)——这些都是面试官和答辩评委最看重的点。
毕设阶段我最推荐的定位是:做一个功能完整、业务闭环清晰、技术栈主流、代码可读性强的系统,而不是一味追求花哨功能。这套系统如果做完,你的收获其实集中在四个层面:完整的后端工程能力(SpringBoot 全家桶的使用)、数据库设计能力(核心是充电订单和桩状态的建模)、典型业务难题的解决(并发和资金一致性问题)、前后端联调部署的全栈经验。
1.2 用户角色与核心业务流程
这个系统我建议拆成两个端:用户端(微信H5或浏览器访问)和管理端(后台PC系统)。
用户端面向普通车主。核心流程很简单,但是里面藏着细节:用户注册登录后,在地图上或者列表页看到充电桩列表,列表要能展示桩的实时状态(空闲/占用/故障),空闲状态下可以发起预约或者直接下单充电;充电过程中前端页面要轮询或者通过 WebSocket 推送当前电量、电压、充电时长、已扣金额;充电结束(包括充满自动结束、用户手动结束、余额不足停止)后生成订单,展示明细并扣款。
管理端面向充电站运营人员。需要覆盖充电桩管理(增删改查、设备状态监控)、订单管理(按各种维度筛选查询)、用户管理(封禁/解封)、财务管理(充值流水、消费流水、营收统计)、数据看板(今日充电量、今日营收、活跃用户数)。
这里还要注意“全流程”三个字,它强调的是流程闭环。很多同学做系统喜欢把功能堆上去却不管业务逻辑,比如用户充电充到一半没钱了怎么办、预约了但人没到场桩却一直被占着怎么办。这些流程分支恰恰是本项目的加分项。
1.3 功能清单与验收标准
我列出当时实现的功能清单,你可以作为参考的基准线:
用户端:注册/登录(JWT鉴权)、充电桩地图与列表、充电桩详情与状态筛选、预约充电(限时段占位)、扫码/手动选择充电枪、充电中实时监控、订单列表与详情、钱包充值、在线支付(模拟/接入)、消费评价、个人信息管理。
管理端:管理员登录(RBAC权限)、充电桩/电站管理、用户管理、订单管理、评论管理、钱包流水管理、数据统计。
这套功能做完已经超过大多数同类毕设的规模。如果你的时间比较紧张,建议砍掉地图功能(用列表代替)和WebSocket(用轮询代替),优先保住充电流程这一段的主链路。
2. 技术选型与架构设计
2.1 为什么是 SpringBoot 而不是 SSM 或 SSH
现在做 Java 毕设,SpringBoot 基本是默认答案了。原因有三:第一,SpringBoot 的自动配置和约定优于配置理念,让项目的启动和维护成本大幅降低,你不用再像 SSM 时代那样写一堆 XML;第二,当前企业招聘描述里 SpringBoot 是高频词,写进简历和论文里的技术栈对口度,直接关系到答辩老师的观感;第三,SpringBoot 生态成熟,集成 MyBatis、Redis、Spring Security 等都是一把梭。
但我要提醒一点:用 SpringBoot 不等于不用理解 Spring 原理。答辩时老师大概率会问“SpringBoot 的自动配置原理是什么”“Spring IOC 和 AOP 在项目里怎么体现的”,这些还是得提前准备。我在后面的答辩问题部分会展开说。
可能有人会问,热点词里还有“SpringBoot整合Flink”、“SpringBoot整合ActiveMQ”这类组合,项目里要不要加?我的建议是:除非你论文的核心创新点就是流处理或消息队列,否则不要为了追求“冷门组合”而硬接中间件。Flink 在充电系统里其实有真实的应用场景(比如对充电行为日志做实时统计),但如果你对 Flink 不熟,接入后只会增加部署复杂度和答辨风险。
2.2 后端技术组合与版本选择
这是我的推荐组合,也是网上流传度最高的一套搭配,稳定性经过大量项目验证:
- 基础框架:SpringBoot 2.7.x
- ORM框架:MyBatis-Plus 3.5.x
- 数据库:MySQL 8.0
- 缓存:Spring Data Redis + Spring Cache
- 安全认证:JWT(jjwt 0.9.1 / hutool-jwt)
- 工具库:Hutool、Lombok、MapStruct
- 接口文档:Knife4j(swagger-bootstrap-ui的增强版)
SpringBoot 版本这里要特别说一句。现在启动项目时选择初始版本,很多人会直接拉到 3.x 甚至 4.x。SpringBoot 3.x 基于 SpringFramework 6 和 JDK 17,整体没问题,但问题出在配套生态上——早期 3.x 版本对 MyBatis-Plus、Knife4j 等还是基于 javax 命名空间,而 3.x 改成了 jakarta,如果你用了老版本的依赖会出现 “程序包 javax.servlet 不存在” 之类的报错。毕设阶段求稳,我建议直接用 2.7.18(这是 2.x 系列的最终版本),后续如果要升级再按官方迁移指南走。
2.3 前端方案:Vue 全家桶还是服务端渲染
如果前端基础一般,我建议使用 Vue2 + Element UI 的组合。原因很直白:Element UI 的中文文档完善,表格、表单、弹窗、分页这些后台系统常用组件都是现成的,改造成本低;Vue2 + Element UI 的案例数量和网上踩坑贴最多,遇到问题基本都有解。
考虑到毕设时间紧,我还见过一个更省事的方案:管理端直接用 Vue + Element UI 的后台模板改,比如若依(RuoYi)、vue-element-admin,把登录逻辑和业务页面改造一下,整体出图效率极高。但这里有一个值得注意的问题——直接拿若依系统接 SpringBoot 后端,论文里如果大篇幅写“使用了若依脚手架”,答辩时容易被追问底层框架逻辑。我的建议是:用模板可以,但核心业务代码(充电流程、订单状态机、计费逻辑)必须自己写,论文里也只体现自己写的部分。
用户端的话,可以用 Vant UI(移动端Vue组件库)做H5页面,也可以用平板适配的后台响应式页面。我的做法是用户端和管理端共用一套 SpringBoot 后端 API,前端独立部署,通过 Nginx 或直接前后端分离开发。
2.4 系统整体架构与项目分包
从架构图上看(这里用文字描述),请求先到 SpringBoot 控制层,通过 Service 层调用业务逻辑,数据访问由 MyBatis-Plus 的 Mapper 完成,跨模块的通用能力用 Redis 和工具类解决。安全层面用 JWT 拦截器做登录校验,权限用简单的 RBAC(角色-菜单/权限表)实现。
后端项目分包我会这样组织:
com.example.charging ├── controller # 接口层 ├── service # 业务接口 ├── service.impl # 业务实现 ├── mapper # MyBatis-Plus Mapper ├── entity # 数据库实体 ├── dto # 数据传输对象 ├── vo # 视图对象(返回前端) ├── config # 配置类(Redis、拦截器、CORS) ├── common # 通用类(Result、异常处理、常量) ├── utils # 工具类 ├── aspect # AOP切面 └── ChargingApplication.java不少同学喜欢把所有逻辑堆在 controller 里,三五百行的接口方法看着就头疼。我的经验是controller 层只做参数接收和结果返回,业务逻辑全部下沉到 service,事务注解加在 service 方法上。这样做的直接好处有三层:代码可读性大幅提升、事务边界清晰可控、答辩时你能很自然地讲清楚分层职责,而不是被追问时支支吾吾。
3. 数据库设计:充电业务的基础底座
3.1 核心表结构总览
数据库设计是整个系统的地基,地基不稳上层直接塌。我列一下核心表,你们感受一下规模:用户表、充电桩点位表、充电桩表(一个点位多个充电桩)、充电订单表、预约记录表、钱包表、钱包流水表、评论表、管理员表、角色表、菜单权限表。下面挑几张最容易踩坑的表细说。
3.2 充电桩与充电订单表的设计要点
充电桩表是“状态表”,一定要有冗余的状态字段和实时信息字段:
CREATE TABLE `charging_pile` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `pile_code` VARCHAR(32) NOT NULL COMMENT '充电桩编号', `station_id` BIGINT NOT NULL COMMENT '所属电站ID', `type` TINYINT NOT NULL DEFAULT 1 COMMENT '枪类型 1-慢充 2-快充', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态 0-空闲 1-充电中 2-预约占用 3-故障', `power` DECIMAL(10, 2) DEFAULT NULL COMMENT '实时功率kW', `voltage` DECIMAL(10, 2) DEFAULT NULL COMMENT '实时电压V', `electric_quantity` DECIMAL(10, 2) DEFAULT NULL COMMENT '实时电量kWh', `is_deleted` TINYINT NOT NULL DEFAULT 0, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_pile_code` (`pile_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充电桩表';这里有个经验之谈:状态字段用 tinyint 而不是字符串枚举,前端通过数据字典映射显示文案。原因不只是省空间,字符串对比和数字对比在索引命中和查询效率上差距明显,这在论文中也可以当成优化点来写。
充电订单表是核心核心,字段要比你们想象的“宽”。我强烈建议加上settlement_status(结算状态)和refund_status(退款状态)这类冗余字段,和充电主流程解耦:
CREATE TABLE `charging_order` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(64) NOT NULL COMMENT '订单号', `user_id` BIGINT NOT NULL COMMENT '用户ID', `pile_id` BIGINT NOT NULL COMMENT '充电桩ID', `station_id` BIGINT NOT NULL, `start_time` DATETIME NOT NULL COMMENT '充电开始', `end_time` DATETIME DEFAULT NULL COMMENT '充电结束', `duration_minutes` INT DEFAULT 0 COMMENT '充电时长分钟', `total_power` DECIMAL(10, 2) DEFAULT 0 COMMENT '充电电量kWh', `start_soc` INT DEFAULT NULL COMMENT '起始电量百分比', `end_soc` INT DEFAULT NULL COMMENT '结束电量百分比', `price_per_kwh` DECIMAL(10, 4) COMMENT '单价', `service_fee_per_kwh` DECIMAL(10, 4) COMMENT '服务费单价', `amount` DECIMAL(10, 2) COMMENT '订单金额', `status` TINYINT DEFAULT 0 COMMENT '0-充电中 1-已完成 2-已取消 3-退款中 4-已退款', `is_deleted` TINYINT DEFAULT 0, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_pile_id` (`pile_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充电订单表';**金额字段一定用 DECIMAL,不要用 DOUBLE。**这是一个非常经典的坑。DOUBLE 是浮点数,0.1 + 0.2 的结果会让你在日常调账单的时候怀疑人生。DECIMAL(10, 2) 在260万以内的金额都能存下,拿来存充电订单绰绰有余。
3.3 预约表与钱包表:两个容易被忽略的表
预约表要解决的核心问题是“桩被占着但人没来”。我的做法是加一个过期时间,用户在预约成功后获得一个锁定时间窗口(比如15分钟),超过时间窗口预约自动过期,桩状态从“预约占用”回到“空闲”。这里用 Redis 做定时失效管理比较方便,也可以每天跑一个定时任务扫表。预约表里还需要一个字段记录预约状态:待履约、已完成、已过期、已取消,这会直接决定用户信用记录。
钱包表的设计更要注意,因为涉及资金逻辑。不要把充值金额和消费金额只放在钱包表里就完事,流水表才是保证资金对账清晰的基础。每个充值和消费动作,既要更新钱包表里的余额,也要在流水表里insert一条记录。流水表字段建议包括:流水号、用户ID、类型(1-充值 2-消费 3-退款)、变动金额(正负)、变动前余额、变动后余额、关联订单号、创建时间。变动前/后的余额这两个字段是这个表设计的灵魂,日后如果用户说自己余额不对,翻流水的对账效率能提升十倍。
3.4 表关系与核心查询设计
用户表与订单、钱包表是一对多关系;电站表和充电桩表是一对多;订单表关联用户和充电桩;管理员和角色直接关联即可。
查询层面我提两个建议。第一,充电桩列表页的状态查询,一次 join 就能解决,不需要搞得太复杂;第二,后台管理端的订单列表,必然有“按时间区间”“按状态”“按桩号”多条件组合查询的场景,这里用 MyBatis-Plus 的 Wrapper 条件构造器拼条件即可,但要注意时间字段边界问题,比如查某一天的数据包含当天0点到23点59分59秒,不要只查 0 点导致漏数据。
4. 核心功能实现与关键代码解析
4.1 用户端:扫码充电主流程的实现
用户端最核心的一个接口就是“用户发起充电”的接口。它是典型的写多表的事务操作:检查桩的状态、校验余额、更新桩状态、生成订单、扣减预扣款(或者不预扣)、创建流水。
@Transactional(rollbackFor = Exception.class) public Result<ChargingOrderVO> startCharging(StartChargingDTO dto) { // 1. 校验充电桩状态 ChargingPile pile = pileMapper.selectById(dto.getPileId()); if (pile == null || pile.getStatus() != 0) { throw new BizException("充电桩不存在或不可用"); } // 2. 校验用户钱包余额,余额低于阈值拒绝启动 UserWallet wallet = walletMapper.selectByUserId(dto.getUserId()); if (wallet == null || wallet.getBalance().compareTo(BigDecimal.valueOf(10)) < 0) { throw new BizException("余额不足,请先充值"); } // 3. 更新桩状态为充电中 pile.setStatus(1); pileMapper.updateById(pile); // 4. 创建订单 ChargingOrder order = new ChargingOrder(); order.setOrderNo(OrderNoGenerator.generate()); order.setUserId(dto.getUserId()); order.setPileId(pile.getId()); order.setStatus(0); order.setStartTime(new Date()); orderMapper.insert(order); // 5. 生成充电启动记录,记录起始电量和金额 return Result.success(chargingOrderVO); }这段代码的逻辑并不复杂,复杂度全部藏在@Transactional里。事务保证了更新桩状态和生成订单要么全部成功要么全部失败,避免出现“桩显示充电中但订单没建”的业务脏数据。事务注解是必备的,但要注意自调用问题——同一个类内部的startCharging调另一个方法,事务会失效。我当时就踩过这个坑,排查半天才意识到是事务自调用导致回滚没生效。
4.2 充电中监控:轮询还是 WebSocket
充电过程中端上需要实时显示“当前电量、电压、功率、已用时长、实时金额”。实现方式有两种:
方式一:前端定时轮询GET /api/pile/currentStatus?pileId=xxx接口,比如每3秒请求一次。简单可靠,无需维护连接,但会带来无效请求。
方式二:后端用 WebSocket 推送充电状态。SpringBoot 里用 Spring WebSocket 模块就能实现,每个用户建立连接后,后端通过会话推送充电数据,但要做心跳检测和断线重连。
对于毕设来说,轮询完全够用,且实现成本最低。你可以在前端写一个 setInterval 定时器,每2~3秒调一次实时状态接口,用 setTimeout 递归代替 setInterval 可以避免重叠请求的问题(如果上一次请求还没返回就发起下一次调用)。我在项目里就是轮询方案,加了一个“接口返回异常时自动停止轮询”的保护逻辑,最后在论文的“系统优化与改进”里把“后续可接入WebSocket”写成预期扩展,反而多了一个亮点。
4.3 管理端:充电桩管理模块的实现思路
管理端的核心是运营效率,不是简单增删改查。我的充电桩管理页面包含:搜索筛选区(电站、状态、类型、桩编号关键字)、表格区(桩码、类型、状态、实时数据、创建时间、操作按钮)、新增/编辑弹窗(绑定电站、填写功率类型)。
这里有个细节:桩的“状态”修改,不能直接改数据库字段。例如一个正在充电中的桩,管理员如果强行改成“空闲”,会造成订单和桩状态不一致。我的实现是在管理端只允许维护“故障/维护”状态,充电中和空闲的切换只能由用户充电流程触发,这就是职责分离的体现,答辩的时候可以主动提这个设计考量。
4.4 JWT认证与权限控制的落地
登录注册环节,我建议用 JWT 而不是传统的 Session。原因很简单:前后端分离架构下,Session 需要处理跨域携带 Cookie、集群共享 Session 等一堆问题;JWT 自包含用户信息,无状态,分布式友好,并且和现在企业项目的使用习惯一致。
JWT 的核心流程:用户登录 -> 后端验证账号密码 -> 生成 token 返回前端 -> 前端后续请求在 Header 里带Authorization: Bearer <token>-> 后端拦截器验签解析用户信息 -> 放行或拒绝。
拦截器配置有一个细节很容易写错:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns( "/api/user/login", "/api/user/register", "/api/pile/list", "/api/pile/detail", "/doc.html", "/webjars/**", "/v3/api-docs/**" ); }注意:放行的路径要包含接口文档相关的路径,否则你调试接口的时候登录都登录不了。
权限控制我用的是 RBAC 的最简实现:管理员表里存 role(超级管理员/运营人员),拦截器里校验角色,如果是运营人员,对用户管理等敏感接口做拒绝处理。完整版的 RBAC(用户-角色-权限点)适合放在加分项里。
4.5 计费逻辑:核心业务参数的拆分
计费逻辑是答辩老师最容易深挖的地方,值得认真设计。我把费用拆成两部分:基础电费单价 + 服务费单价。日间和夜间单价可以不同(峰谷电价),这个在系统中我使用电价策略表来管理。
核心公式如下:
订单总金额 = SUM(每个时间段充电电量 × 对应单价) + 服务费最常见也最好实现的是阶梯价/分时计费模式。我在系统里定义了 PriceStrategy 表:
CREATE TABLE `price_strategy` ( `id` BIGINT PRIMARY KEY, `type` TINYINT COMMENT '1-基础电费 2-服务费', `name` VARCHAR(32) COMMENT '时段名称,如尖峰、平段、谷段', `start_time` VARCHAR(8) COMMENT '开始时间 08:00', `end_time` VARCHAR(8) COMMENT '结束时间 12:00', `price` DECIMAL(10,4) COMMENT '单价(元/度)', `is_active` TINYINT DEFAULT 1 ) COMMENT='电价时段策略表';结算的时候,根据充电开始时间和结束时间,逐段计算每个电价时段的电量。如果只用总电量乘以单一电价省事,但在论文里体现不出业务深度。建议一定要做分时计费,哪怕只做“尖峰平谷四个时段”,答辩差距一眼就能看出来。
4.6 定时任务与订单超时处理
用户发起充电后,如果一直没有上传结束信息,我需要一个兜底机制自动把状态改为“充电完成”或“异常结束”。我用 Spring 的@Scheduled注解来定时扫描“充电中”的订单,判断其持续时间是否超过阈值,若超过(比如6小时)则自动标记为结束并执行结算退费。
@Scheduled(cron = "0 */5 * * * ?") public void autoFinishTimeoutCharging() { List<ChargingOrder> timeoutOrders = orderMapper.selectTimeoutChargingOrders(360); for (ChargingOrder order : timeoutOrders) { // 执行结束充电逻辑:计算电量、金额、更新订单状态、恢复桩状态 finishCharging(order, true); } }这种定时任务在论文里是一个加分亮点,因为体现了你考虑了“异常边界情况”,而不是只写了理想主流程。但注意了,由于单机定时任务在部署多个副本时会重复执行,可以在调度逻辑里通过 Redis 分布式锁做幂等保护。毕设阶段跑单实例不用处理,但要在论文中提一下“生产环境可以引入XXL-Job或Redis锁”。
5. 实操部署与本地跑通流程
5.1 环境准备与初始化
开发这个项目需要的软件清单:JDK 8 或 11、Maven 3.6+、MySQL 8.0、Redis 6.x、Node.js 14+、IDEA 2023+。
初始化项目我建议直接用 IDEA 的 Spring Initializr 创建,勾选 Web、MySQL、Redis 相关依赖,再用 MyBatis-Plus 官网的 starter 手动引入 MyBatis-Plus。这样做的好处是初始依赖干净、版本可控,不会出现一些课程项目里视版本混乱。
项目创建后第一时间做三件事:配置application.yml里的数据源和 Redis 连接;写一个/api/ping接口测试启动;在数据库执行建表 SQL。先把地基打稳再开发功能,很多同学上来就写业务,结果数据库没连上,排查半天浪费时间。
5.2 本地联调的关键配置
前后端联调阶段,最烦的就是跨域问题。SpringBoot 里统一配置跨域即可:
@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); } }注意allowedOriginPatterns在 SpringBoot 2.4 之后替代了allowedOrigins("*"),目的是兼容 allowCredentials(true) 的情况。如果你跟着老教程写,可能直接报 “When allowCredentials is true, allowedOrigins cannot be specified with '*'”。
接口文档我用 Knife4j 生成,访问/doc.html就能看到所有接口,前端同学和我对接的时候就靠它,省了大量的沟通成本。这也是一个非常推荐的实践:所有有前后端分离的项目都应该引入接口文档,这不是加分项,是必要项。
5.3 打包部署与演示环境
演示的时候最怕环境不一致导致白屏、请求失败。我的经验是提前一周就准备好两个环境:一个本地开发环境、一个演示环境。演示环境可以是虚拟机里装一套 Docker,也可以是一台云服务器。
SpringBoot 打包:
mvn clean package -DskipTests java -jar charging-manager-1.0.0.jar --spring.profiles.active=prod前端打包后 可以用 Nginx 部署,注意一个最常见的坑:Vue 打包后vue-router如果不是用的 Hash 模式,刷新页面会 404。要么改用 Hash 模式路由,要么在 Nginx 里配置 try_files:
location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这个坑几乎每周都能在网上看到有人问,提前配置好能减少演示事故。
6. 常见问题与排查技巧实录
6.1 启动类问题速查表
| 问题现象 | 原因 | 解决办法 |
|---|---|---|
Failed to configure a DataSource | 数据源配置缺失或连接串错误 | 检查application.yml的 url/username/password,确认 MySQL 已启动 |
ClassNotFound: javax.xml.bind.JAXBException | JDK 版本过高,javax.xml 模块被移除 | 使用 JDK 8/11,或引入javax.xml.bind依赖 |
| 端口被占用 | 本地 8080 被其他进程占用 | 更换server.port,或lsof -i:8080查杀进程 |
MyBatis-Plus实体映射不上 | 表名或字段名驼峰不一致 | 确认map-underscore-to-camel-case: true,或@TableName注解 |
这里最常遇到的是第一个。很多同学把application.yml里的数据库密码写错,或者数据库名写错,启动时控制台直接报错。建议把url里加useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8,不然会出现时区问题或者 SSL 握手告警。
6.2 SpringBoot 版本不一致引发的依赖冲突
热点词里有一条“springboot版本太高”,我深有体会。有次帮学弟调项目,他的 SpringBoot 是 3.2.x,MyBatis-Plus 用的还是 3.5.3 版本,编译报错报得让人怀疑人生。原因是 MyBatis-Plus 3.5.3 依赖了mybatis-spring2.x,它对 Boot 3.x 的自动配置机制不兼容。
解决有三个思路:一是把 SpringBoot 降到 2.7.x;二是把 MyBatis-Plus 升到 3.5.5+(适配 jakarta 命名空间);三是用mybatis-plus-spring-boot3-starter这个专用 starter。毕设建议直接走方案一,省事。
6.3 前端联调的经典错误
- 接口 404:检查 controller 的 RequestMapping 路径是否和前端请求一致,尤其注意项目有没有配置
server.servlet.context-path。 - 接口 500:打开后端控制台看详细堆栈,大部分是空指针、SQL 错误、参数绑定失败。
- 登录后请求返回 401:检查 token 是否放到了 Header,拦截器是否把该接口放行。
- 前端请求跨域报错:确认后端 CORS 配置是否覆盖了该路径,确认是否使用了正确的前端代理。
我印象最深的一次排错:前端一直报 401,排查发现是 token 里保存的用户 ID 是字符串,后端拦截器解析出来变成数字,而用户 ID 在数据库中正好有前缀零(比如“000123”),解析后变成了123,导致查询用户失败。这就是典型的“类型不一致导致看似权限问题实际是业务问题”的场景。所以拦截器里解析 token 之后,一定要做一次用户真实验证,而不是盲目信任 token 里的数据。
6.4 答辩高频问题与应答思路
根据过往经验,答辩老师对这类系统最常问的问题如下:
SpringBoot 自动配置原理是什么?答:
@SpringBootApplication组合了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,其中@EnableAutoConfiguration通过AutoConfigurationImportSelector加载META-INF/spring/...ImportSelector里注册的自动配置类,再根据条件注解@ConditionalOnClass、@ConditionalOnMissingBean等判断是否生效。表的设计为什么这样分?答:按业务聚合拆分,避免单表字段过多和与主流程无关的冗余;订单和流水分离,便于对账。
并发场景如何解决?答:核心是悲观锁和数据库唯一约束。充电订单号唯一索引防止重复;修改桩状态可以用
UPDATE charging_pile SET status=1 WHERE id=? AND status=0的乐观/条件更新方式,避免两个用户同时抢到同一个空闲桩。JWT 和 Session 的区别?答:Session 存在服务端,靠 Cookie 关联;JWT 存在客户端,服务端无状态,靠签名防篡改。JWT 更适合分布式系统,但无法主动失效,过期时间要控制合理。
项目里的难点和亮点?答:难点是充电桩状态一致性、分时计费和订单异常闭环;亮点是引入了 JWT 统一鉴权、定时任务自动兜底、资金流水可追溯。
6.5 毕设避坑心得
最后分享几个坑过无数人的心得,都是网上教程不会写但你大概率会踩的:
第一,不要上来就敲代码。先花两天把需求文档、ER 图、接口文档画好,后面开发速度快十倍。我做这个充电系统,前期设计花了整整一周,真正编码也就三周。
第二,预留足够的测试时间。很多人前六周拼命写代码,最后两天才开始跑通流程,结果演示现场漏电、充不上电、订单金额不对,全翻车。至少留一周做全流程回归测试:注册、登录、充值、扫码充电、结算、退款,每个环节都要人工跑两遍。
第三,要留一手“心机设计”。比如给充电订单加一个“充电历史趋势图”这样的统计分析功能,成本很低,但答辩的时候非常有画面感。论文里也可以借此多写一节,既撑了字数又亮了技术点。
我自己的体会是:做毕设真正的收获不在系统本身,而在“把一个模糊的业务需求变成一套清晰可运行的系统”这个过程。充电系统这个选题非常适合拿来练手,它的复杂度刚好在一个本科生的能力边界——有挑战但踮脚能够着,做完了你会对 SpringBoot 整个生态有非常扎实的掌握。后面还可以在这个基础上扩展多电站管理、充电预约分时定价、App 扫码,整个项目甚至能变成一个真正可运营的产品雏形。