1. 项目定位与总体构思
1.1 核心需求解析
在做毕业设计选题时,很多同学容易陷入两个极端:要么选一个纯管理系统类型的课题,后台管理页面堆功能,结果答辩时被评委一句“你这个系统解决了什么实际问题”问得哑口无言;要么选一个算法难度过高的课题,比如基于深度学习的什么什么识别,结果开发周期和数学基础都撑不住,最后连一个能跑的Demo都拿不出来。
“基于SpringBoot与Android的电动汽车电桩管理平台”这个题目刚好卡在了一个非常合适的难度区间。它的核心思路是以电动汽车充电桩为业务对象,构建一套由Android客户端、SpringBoot服务端组成的完整业务闭环。用户用手机App查找空闲电桩、扫码启动充电、实时查看充电状态,管理员在后台管理电桩设备、查看订单流水、处理异常告警。
这个题目最大的优势在于:技术栈完全是Java体系内闭环。Android端用Java/Kotlin开发,服务端用SpringBoot开发,数据库用MySQL,通信用RESTful API加JSON。对于计算机专业的本科毕设来说,不需要引入Python、Node.js等其他语言,学习成本和答辩压力都会小很多。同时,充电桩管理这个业务场景有明确的价值主张——新能源出行配套基础设施的数字化管理,这在当前的行业背景下本身就具备现实的选题意义,评委也更容易认可。
1.2 功能边界与模块规划
做毕业设计最忌讳的就是“贪多嚼不烂”。我在指导学生的过程中见过太多案例:一开始规划了十几个模块,做到最后Excel里功能清单打勾率不到六成,代码库里堆了一堆半成品,论文里还不知道该怎么写。所以这个项目的功能设计要遵循一个原则:把一条核心业务链路做完整,把周边辅助功能做精简。
核心业务链路是:注册登录 → 查找电桩 → 扫码/选择电桩 → 启动充电 → 实时监控 → 结束充电 → 支付结算 → 订单查询。
围绕这条链路,我建议把功能模块切分为三个端:
- 用户端(Android App):登录注册、电桩地图/列表浏览、电桩详情与状态查看、扫码启动充电、充电实时状态展示(电流、电压、电量、金额)、充电历史订单、个人中心(余额、充值、故障报修)。
- 管理端(Web管理后台,SpringBoot + Thymeleaf或Vue均可):电桩信息CRUD、电桩状态管理(启用/禁用/故障标记)、用户管理、订单管理、充电价格参数配置、统计数据概览。
- 服务端(SpringBoot核心):统一认证与鉴权、业务接口、定时任务(电桩状态巡检)、异常处理与日志记录。
管理员后台是否要做成单独的App?完全没有必要。用Web页面做管理后台,工作量可控,演示起来也比手机端方便。我在实际带项目时一般建议学生用Bootstrap + Thymeleaf模板引擎快速搞定管理端页面,把精力集中在Android端和服务端接口质量上。
1.3 技术选型背后的思考
选题定了,接下来是技术选型。这一步很多同学容易“跟风”,看到网上教程用什么就跟着用什么,完全不考虑自己项目是否匹配。我在这个项目上的选型思路如下:
服务端:SpringBoot 2.x 系列。选2.x而不是最新的3.x,主要原因是3.x基于Jakarta EE规范,部分教程和开源组件的兼容性还没有完全跟上,网上的学习资料也相对较少。毕业设计求稳,2.7.x是一个很成熟的版本,资料丰富,踩坑成本低。核心依赖包括Spring Web、Spring Data JPA或MyBatis-Plus、Spring Security(或者用JWT手动拦截,后面细说)、MySQL驱动、Lombok等。
移动端:Android原生,Java语言编写。虽然Kotlin现在是Android官方主推语言,但考虑到大多数高校的课程体系仍然以Java为主,学生用Java写Android更容易上手,网上案例也多。网络层用OkHttp + Retrofit,JSON解析用Gson,图片加载用Glide,这三个组合是Android网络开发的老牌组合拳,文档齐全,出问题搜索引擎上直接能找到解决方案。
数据库:MySQL 5.7或8.0。表结构设计我会在后面的章节详细给出。需要强调的一点是,不要把后端逻辑里需要频繁计算的数据强行塞进MySQL的存储过程或视图里,保持数据库的“朴素”——只负责存取数据,业务规则放在Service层处理。
其他关键组件:JWT做无状态认证,Java 8的日期时间API处理充电时长与计费,使用WebSocket或者Android端定时轮询来实现充电状态的实时刷新(这个选择我会重点说明),使用定时任务实现异常电桩的自动巡检。
这个组合落地时遇到的一个常见问题是Lombok在不同JDK版本下的兼容性。如果你本地的JDK版本比较高(比如JDK 17+),建议直接用SpringBoot 2.7.x自带的依赖管理,不要手动指定低版本的Lombok,否则编译阶段经常报奇怪的unknown enum constant错误。具体问题在后面的排查章节里展开。
2. 数据库设计与接口协议
2.1 表结构与关键字段设计
数据库是这个项目的“地基”。我见过不少学生直接拿现成的开源项目改改表名就当成自己的设计,结果答辩时被问到“为什么这个字段要这么设计”就完全答不上来。所以每一张表、每一个关键字段你都得知道设计意图。
对于一个电桩管理平台,我建议设计6张核心表:
用户表(user):主键、手机号(唯一索引)、密码(BCrypt加密存储)、昵称、钱包余额(DECIMAL(10,2))、状态、创建时间。钱包余额这个字段我建议冗余到用户表里,不要单独建一张钱包流水表,不然功能量又膨胀了。但需要注意,如果用余额支付,每次扣款都要做金额校验和并发控制。
电桩表(pile):主键、电桩编号(业务唯一编码,比如区域码+序号)、电桩名称、经度、纬度、地址描述、功率类型(交流/直流、快充/慢充)、当前状态(0空闲/1使用中/2故障/3离线)、单价(元/度)、创建时间。经纬度用DECIMAL(10,7)存储,这个精度足够支撑地图展示和距离计算。
订单表(order):主键、订单编号(项目内唯一,我习惯用时间戳+随机数生成)、用户ID(索引)、电桩ID(索引)、开始充电时间、结束充电时间、充电度数(DECIMAL(10,2))、充电费用、服务费、订单状态(0充电中/1已完成/2已取消/3异常终止)、支付状态(0未支付/1已支付)。
充值表(recharge):主键、用户ID、充值金额、赠送金额、充值时间、支付方式。这张表可以帮助论文里写“平台资金流管理”这种功能点,但不增加太多开发量。
故障报修表(repair):主键、用户ID、电桩ID、故障描述、图片URL、状态(0待处理/1处理中/2已处理)、提交时间、处理时间。这个表是答辩中的“加分项”——体现了你考虑到了用户与平台的互动场景。
管理员表(admin):主键、账号、密码(BCrypt加密)、角色、最近登录时间、创建时间。
这里要提供一个经验之谈:字段类型统一用一个规范。比如所有状态字段统一用TINYINT,所有金额字段统一用DECIMAL(10,2),所有时间字段统一用DATETIME。这样能避免后期写代码时因为类型不一致产生的各种隐式转换问题。我见过有学生金额用DOUBLE存储,结果在做结算时出现了小数点后第三位精度丢失,这在计费系统里是不能接受的错误。
2.2 RESTful接口设计与约定
接口协议是Android端与SpringBoot服务端之间的“对话语言”。我强烈建议在写代码之前,先花半天时间把接口文档定义好,用表格记录下来,这样两个端各自开发时不会互相牵制。
以核心业务链路为例,接口清单应该是这样的:
| 功能点 | 请求方式 | 路径 | 请求参数 | 返回说明 |
|---|---|---|---|---|
| 用户注册 | POST | /api/user/register | 手机号、密码、昵称 | 注册成功返回token |
| 用户登录 | POST | /api/user/login | 手机号、密码 | 返回token与用户信息 |
| 获取电桩列表 | GET | /api/pile/list | latitude、longitude、page、size | 返回附近电桩分页数据 |
| 获取电桩详情 | GET | /api/pile/{id} | 路径参数 | 电桩详情+当前状态 |
| 创建充电订单 | POST | /api/order/start | pileId、userId | 创建订单,返回订单号 |
| 结束充电订单 | POST | /api/order/end | orderId | 结算费用,更新状态 |
| 查询订单状态 | GET | /api/order/detail/{id} | 路径参数 | 当前充电数据与计费 |
| 用户余额充值 | POST | /api/user/recharge | userId、amount | 更新余额 |
| 提交故障报修 | POST | /api/repair/submit | pileId、description | 生成报修工单 |
有几个细节需要特别强调。
第一,所有的接口返回格式必须统一。我建议封装一个统一的Result<T>对象,包含code、message、data三个字段。成功时code为200,业务失败时code为400系列,系统异常时code为500。这样Android端接收到响应后可以直接通过code判断业务状态,不需要在服务端到处写乱七八糟的判断逻辑。
第二,接口路径要有前缀区别。/api/user/**、/api/pile/**、/api/order/**这些前缀能让你在写SpringBoot拦截器时非常方便地按需放行或拦截。比如用户未登录访问/api/pile/list是可以放行的,但创建订单就必须验证token。
第三,考虑真实的业务时序状态。启动充电这个动作,在真实的充电桩场景里,App只是下发了一个“请求指令”,电桩硬件执行插枪、认证、通电的动作后才会上报“充电中”状态。毕业设计没有真实硬件,就要用软件模拟这个状态机。我采用的方案是:用户启动充电后,服务端创建订单并生成一个模拟的任务线程,让订单状态在几分钟内自动经历“待充电→充电中→充电完成”的变化。这个设计能让你在写论文和做演示时讲清楚状态流转机制,而不是简单地用一个SQL更新把状态改掉完事。
2.3 计费规则的设计思路
计费是充电桩平台的业务核心,也是答辩时评委容易追问的点。我的建议是采用“电费+服务费”的定价模型,规则如下:
充电费用 = 充电度数 × 电费单价 + 充电时长 × 服务费单价
举个例子,某直流快充桩电费单价为1.2元/度,服务费单价为0.5元/小时。用户充了10分钟,充入2.5度电,那么费用就是 2.5 × 1.2 + (10 ÷ 60) × 0.5 = 3.0 + 0.0833 ≈ 3.08元。
这里要特别注意充电度数的计算用什么模型模拟。真实场景中,充电度数由电桩硬件采集电流电压数据后统计算得,但我们的软件系统没有硬件数据来源,就需要模拟一个合理的“充电过程”。我在项目中用一个后台线程模拟电压恒定220V(交流慢充场景),电流在30A左右波动,每三秒累加一次电能消耗:电量 += 电压 × 电流 × (3秒 ÷ 3600秒) ÷ 1000,单位折算为千瓦时。这样计算出来的度数有增长过程,Android端就能展示一个动态变化的充电进度,演示效果很好,而且这个模拟逻辑在论文里可以写成“电桩充电数据采集模块的软件模拟实现”,至少能写两页内容。
3. SpringBoot服务端的核心实现
3.1 项目初始化与目录结构
SpringBoot项目我建议直接用IDEA的Spring Initializr创建,不要去Spring官网手动下载压缩包再导入,效率太低了。创建时需要注意三点:
- Group填
com.example或你的个人域名反写,Artifact填ev-charging-server这类有业务含义的名字。 - Java版本选择8或11。对于毕业设计来说,Java 8完全够用,而且后续部署到服务器时兼容性最好。
- 依赖选择:Spring Web、Spring Data JPA、MySQL Driver、Lombok、Validation。暂时不需要引入Spring Security——我建议JWT认证用拦截器+HandlerInterceptor手动实现,因为Spring Security的配置复杂度对毕设来说是过度设计,而且答辩时如果对Security的过滤器链机制讲不清楚,反而成为扣分项。
项目包结构我习惯采用以下分层:
com.example.evcharging ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务层,核心逻辑所在 ├── mapper/repository // 数据访问层(JPA仓库) ├── entity // 数据库实体类 ├── dto // 请求和响应对象 ├── config // 配置类(拦截器、跨域、WebMvc配置) ├── interceptor // JWT认证拦截器 ├── utils // 通用工具(JWT工具、结果封装类) └── common // 全局异常处理、常量定义这种包结构的逻辑是“请求从上往下穿透、数据从下往上聚合”,每一层只做自己该做的事,出了问题也能从日志堆栈里快速定位到是哪一层出错。
3.2 用户认证与JWT拦截器实现
用户登录后,服务端签发一个JWT令牌给Android端,Android端后续每次请求都在Header里带上Authorization: Bearer <token>,服务端通过拦截器解析token来识别用户身份。这个方案是当前主流做法,代码量适中,业务上够用。
JWT工具类中需要关心的核心操作就三个:生成token、解析token、校验token是否过期。token里我建议只放用户ID和手机号,不要塞多余信息。过期时间设置为24小时,用户App端如果token过期就引导重新登录,这个逻辑在Android端可以通过拦截响应码自动处理。
拦截器里注意两个细节:
白名单机制。登录、注册、获取电桩列表这些接口要放行。我在配置类中维护一个List<String> whiteList,用AntPathMatcher做路径匹配,在白名单内直接放行,否则取Header里的token解析用户信息并放入ThreadLocal中供后续Service使用。
ThreadLocal存储用户信息。这个做法的价值在于后续的Service层方法不需要在参数里到处传userId,而是通过UserContext.getUserId()获取当前登录用户。这属于代码可维护性方面的优化,放在论文里也算是一个有亮点的设计。
提示:JWT密钥一定不要硬编码在类里,放在
application.yml中配置,例如jwt.secret=your-secret-key。虽然毕设项目没有太多安全要求,但养成好习惯对后续工作有益。
3.3 电桩管理和充电订单核心流程
电桩管理这部分,Controller层的方法逻辑比较机械,大部分是调用Service层的CRUD方法。重点在于Service层的业务判断。
以“用户扫码启动充电”为例,完整的业务序列是这样的:
- Android端扫描电桩上的二维码(二维码内容存电桩编号,实际演示可以做一个简单的“点击模拟扫码”按钮),把电桩编号传给
/api/order/start接口。 - 服务端根据电桩编号查询电桩,校验电桩是否存在、状态是否为空闲。如果状态为“使用中”或“故障”,直接返回对应业务错误码,Android端弹Toast提示。
- 检查用户余额是否足够支付本次充电的预估费用。预估方式可以简单按“最低消费金额”校验——比如余额必须大于1元才能启动充电,否则返回提示。
- 创建订单,状态为“充电中”,同时把电桩状态修改为“使用中”。
- 启动一个模拟充电任务的线程,定期更新订单中的电量、费用、时长等字段数据。
- 返回订单号和电桩信息给Android端,App跳转到充电监控页面。
“结束充电”这个接口同样有业务判断:只能结束当前用户自己的充电中订单;结算费用时要重新读取最新的电量数据计算,不能直接用创建订单时的预估数据;结算完成后把电桩状态改回空闲。
这里还涉及并发控制的隐患——多个用户同时给同一台电桩发起启动请求怎么办。我用了一个简单可靠的方案:在MySQL层面对电桩状态更新加条件限制:
@Modifying @Query("update Pile p set p.status = 1 where p.id = :pileId and p.status = 0") int lockPile(@Param("pileId") Long pileId);当lockPile返回的影响行数为0,说明电桩已被其他用户占用,Service层直接返回“电桩已被占用”的业务错误。这个方案比Java层的synchronized更可靠,因为它是数据库层面的原子操作,天然避开了多实例部署时的锁失效问题。
3.4 定时任务与异常状态巡检
充电过程中可能出现各种“意外情况”,比如用户直接杀掉了App进程,后端模拟充电线程还在继续跑。针对这个场景,我引入了两个定时任务:
订单超时巡检。每30秒扫描一次“充电中”超过预设最大时长(比如2小时模拟值)的订单,强制结束充电并完成结算,把电桩状态恢复为空闲。
电桩故障模拟任务。每5分钟随机把部分空闲电桩的状态改为“故障”,过一段时间再改回“空闲”,模拟真实环境中电桩的随机故障。这个设计有两层好处:一是让演示时的地图上能看到各种状态的电桩(不然全是绿色空闲,页面太单调);二是让故障报修功能有实际的数据来源。
SpringBoot的定时任务用@Scheduled(cron = "...")注解就能实现,在配置类上加@EnableScheduling开启即可。需要注意的坑是:@Scheduled默认只使用单线程执行器,如果有多个定时任务且它们互相无依赖关系,最好在配置中指定线程池大小,避免一个任务阻塞导致其他任务排队等待。
4. Android客户端的功能落地
4.1 项目搭建与基础框架
Android端我建议用Android Studio开发,语言用Java。新建项目时选择的Empty Activity模板就够了,不需要复杂的Fragment模板——导航结构用底部导航栏 + Fragment的方式实现,这是Android开发最主流、也最适合列表内容类App的结构。
项目的基础依赖配置(build.gradle文件的关键部分):
implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.github.bumptech.glide:glide:4.13.2' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.cardview:cardview:1.0.0'Retrofit加上Gson转换器负责接口请求和数据序列化,RecyclerView负责列表展示,CardView做电桩卡片的圆角和阴影效果,Glide负责展示占位图。这套组合我实测下来编译稳定,不用引入RXJava这类学习成本高的响应式框架。
4.2 网络层封装与统一Token管理
网络层我会封装一层ApiClient,静态方法返回Retrofit实例,统一配置baseUrl(比如http://10.0.2.2:8080/——Android模拟器访问宿主机时必须用这个IP,不能用localhost)、OkHttp的拦截器、连接超时时间等。
Token管理上,我自定义一个OkHttp的Interceptor实现自动附加token:
Interceptor tokenInterceptor = chain -> { Request originalRequest = chain.request(); String token = SharedPreferencesUtil.getString(context, "token", ""); if (!TextUtils.isEmpty(token)) { Request newRequest = originalRequest.newBuilder() .header("Authorization", "Bearer " + token) .build(); return chain.proceed(newRequest); } return chain.proceed(originalRequest); };这个统一拦截器的价值在于:所有的接口调用方法不需要手动写“从SharedPreferences取token再塞进Header”的重复代码,以后如果要增加统一的日志打印、统一的错误码处理,也可以在这个拦截器里完成。在答辩时,你可以把这个Interceptor解释为“客户端AOP思想的落地实践”。
4.3 电桩列表页与地图定位展示
电桩列表页有两种展示形式:列表模式和地图模式。对于毕业设计的功能完整性,列表模式用RecyclerView就足够了,地图模式需要引入百度地图或高德地图SDK,集成和调试成本较高,如果时间不够可以砍掉这个功能。但如果你想要亮点,也可以做一个简单的“附近电桩分布”功能——不引入地图SDK,而是用Android自带的TextView和Canvas画一个简易的分布图,画几个代表电桩位置的小圆点,根据和用户的距离排序。技术含量不高,但演示效果足够生动。
列表页的逻辑是:进入界面后获取用户位置权限,调用/api/pile/list接口传入经纬度,服务端通过Haversine公式按距离排序并分页返回电桩数据。Android端渲染时,每张电桩卡片展示电桩名称、距离、功率类型、状态标签和单价。状态标签用不同颜色区分——绿色“空闲”、黄色“使用中”、红色“故障”、灰色“离线”。这种视觉上的差异化设计在用户体验上非常重要,答辩演示时也能让评委一眼看清电桩状态分布。
一个容易忽略的小细节:定位权限在Android 6.0及以上需要运行时动态申请,在Android 11及以上对位置权限的精确性有了更细的分类。代码里用ActivityCompat.requestPermissions申请ACCESS_FINE_LOCATION即可,但要记得在onRequestPermissionsResult回调中处理用户拒绝的情况,否则后续获取定位会崩溃。
4.4 充电监控页与实时数据刷新
充电监控页是整个App最核心的页面,建议包含以下元素:
- 电桩名称和编号
- 订单号
- 充电时长(实时累加)
- 当前功率/累计电量(动态变化)
- 当前费用(实时计算)
- 电桩状态(“充电中”)
- “结束充电”按钮
数据刷新方案有两种:轮询和WebSocket。我推荐轮询的方式,理由有三:项目复杂度可控、代码量少、稳定性高。具体做法是在充电监控页启动一个Handler,每3秒调用一次/api/order/detail/{id}接口获取最新数据,更新UI上的时间、电量、费用三个字段。这个3秒的刷新频率对演示场景完全够用,而且没有真实硬件时通过轮询拿到的数据变化本身就模拟了“数据在实时更新”的效果。
如果你想让方案在论文和答辩中更有技术含量,可以补充说明“在后续工程化落地时,可以将轮询替换为WebSocket推送以保证实时性和降低服务端压力”——这样既展现了你的技术思考,又不影响当前的实际工作量。
还有一个体验细节必须写下来:在充电监控页退出时要注销Handler,否则Activity销毁后Handler还在持续执行网络请求,会造成内存泄漏。这是我见过Android开发中非常高频的Bug,在代码里一定加上handler.removeCallbacksAndMessages(null)。
4.5 扫码与订单支付的简化策略
真实的充电桩扫码流程是:用户打开App扫电桩屏幕上的二维码,解析出电桩编号,App向服务端发起充电请求。毕业设计如果做微信或支付宝的SDK支付接入,会遇到两方面困难:个人开发者没有支付接口权限,而且支付流程会大大提升App的复杂度。这里我建议采用一个合理的简化方案——余额支付。
用户可以在个人中心执行“充值”操作。为了演示效果,充值过程不要真的对接第三方支付,而是输入金额直接点击确定即完成充值,同时在后端记录一条充值流水。这实际上模拟了“用户通过第三方支付完成付款后平台收到回调,为用户账户增加余额”的完整业务逻辑。
这个简化在论文中可以这样定性表述:第三方支付涉及商务签约与平台资质审核,本项目聚焦于核心充电业务,支付环节采用虚拟余额方式进行功能验证,工程化部署时可在充值接口中无缝接入第三方支付回调。
4.6 App生命周期与会话保持
App的会话保持是决定演示体验的重要细节。因为JWT有效期设置为24小时,如果用户登录一次后把App切到后台几天,再打开时token可能已经过期。如果不处理,用户点的每一个操作都会得到“登录状态已过期”的报错,体验非常糟糕。
推荐做法是:在登录成功后将返回的token和用户信息存入SharedPreferences,App启动时检查SharedPreferences中是否存在token,如果存在就直接跳转到主界面,不需要重新登录。当接口返回401或业务码提示token失效时,清除本地token信息并跳转到登录页。
提示:Android判断登录态时,不能只判断token是否存在,还要在接口返回错误后做被动下线处理。我给拦截器加一个“全局401通知”机制:在自定义的OkHttp拦截器中判断响应码,如果是401则发送一个本地广播,所有Activity收到广播后统一执行跳转登录页的逻辑。
5. 硬件模拟与数据埋点
5.1 充电过程与电桩状态的软件模拟
前文提到,这个项目最大的设计难点在于没有真实硬件,一切设备数据都要靠软件模拟。如何处理这个模块,也直接决定了论文的技术深度和答辩质量。
我设计了一个ChargeSimulator组件,职责是模拟电桩从启动充电到结束充电的完整生命周期。具体实现如下:
- 服务端创建充电订单后,通过
@Async注解启动一个异步任务(注意主类要加@EnableAsync)。 - 异步任务内维护一个循环,每3秒执行一次:
- 更新订单中的已充时长(+3秒);
- 生成一个当前电流值(基础值+随机扰动,比如30A上下波动2A);
- 计算本次累加电量,累加到订单的总电量字段;
- 根据电量与单价实时计算费用;
- 判断是否达到预设的充电结束条件(比如达到目标电量或最大时长),如果是则自动结算并结束。
- 循环中使用
Thread.sleep(3000)控制节奏,同时用一个标志位stopFlag判断用户是否触发了“结束充电”操作。
这个模拟的巧妙之处在于它模拟的不只是“静态数据变了”,而是模拟了“电桩设备在持续采集数据并上报”这一过程。你在论文中的相关章节可以用“设备数据采集模块”“充电策略执行引擎”这样的表述来包装这个设计,内容深度和代码量都足够支撑一个完整的省级大学生创新创业项目级别的描述。
5.2 模拟数据的初始化策略
为了让演示效果更丰富,服务端启动时最好初始化一批模拟数据:
- 在城市中心区域随机生成20~30台电桩,覆盖交流慢充和直流快充两种类型;
- 随机生成5个左右的注册用户(密码统一为123456,方便演示时快速登录);
- 随机生成一批历史订单数据(过去7天内的),用于管理端统计图表的展示。
数据的初始化可以用SpringBoot的ApplicationRunner接口,在应用启动完成后执行一段初始化逻辑。需要注意的是,这个初始化要加“条件判断”,只有当数据库中电桩表为空时才执行初始化,否则每次重启服务都会重复插入数据造成脏数据。
历史订单数据很重要,它直接决定了管理端统计页面有没有内容可看。我建议生成数据时让订单数量和金额呈现出每天波动的趋势,比如工作日订单少、周末订单多,这样管理端用ECharts画出来的折线图会有起伏的曲线,答辩演示时的视觉效果会好很多。
5.3 关键业务指标的数据闭环
充电平台的运营管理人员关心什么数据?无非是几个维度:今日订单量、今日充电量、今日营收金额、电桩空闲率、用户增长趋势。管理后台的Dashboard至少要展示以下指标:
- 总电桩数量、当前空闲/使用中/故障数量
- 今日订单量、今日充电总度数、今日总营收
- 近7日订单量与营收折线图
- 近7日新增用户数柱状图
这些数据全部从数据库的订单表和用户表中聚合查询出来即可。关于数据库聚合查询,写SQL时可以尽量使用DATE_FORMAT(create_time, '%Y-%m-%d')做按天分组,返回一个List<Map<String, Object>>给前端渲染图表。这样管理后台的功能就有“数据决策”的味道了,论文里也能提炼出“充电运营数据可视化分析”这样的子模块标题。
6. 联调部署与常见问题排查
6.1 Android模拟器与后端联调
Android模拟器和本机服务端联调时有一个经典坑:在App里访问http://localhost:8080是访问不到的,因为模拟器里的localhost指向的是模拟器本身,必须使用http://10.0.2.2:8080才能访问宿主机。这个知识点非常重要,我在指导学生时反复强调,但每年还是有一半的人会踩进去。
如果使用真机调试,则需要把10.0.2.2换成你电脑在局域网中的IP地址,比如http://192.168.1.101:8080。前提是两个设备连接同一个WiFi,同时Windows防火墙需要允许Java进程入站访问。如果手机访问不到,可以临时关闭防火墙测试,确认通了之后再配置入站规则。
另外,Android 9及以上默认禁止明文HTTP请求。如果你没有配置HTTPS,请求http://开头的地址会被直接拒绝。解决方案是在AndroidManifest.xml的<application>节点加上:
android:usesCleartextTraffic="true"如果希望更规范一点,可以定义network_security_config.xml,只用明文放行指定域名的请求,这样论文里也能多一个“网络安全配置”的内容。
6.2 前后端时间一致性与订单结算准确性
充电计费和时间强相关,如果你开发的App和后端服务端的时间不一致,会出现订单时长计算混乱的问题。比如你手机时间比服务端快5分钟,启动充电时记录的服务端时间和手机本地时间对不上,会造成结算时用手机本地时间计算时长,得到一个明显不合理的费用。
解决这类问题的一个通用原则是:所有时间以服务端时间为准。Android端在展示时间时,只做格式化显示;所有关于充电时长、充电费用的计算,全部在后端逻辑中完成。App端每次拉取订单详情时,服务端直接返回已经计算好的“已充电时长(秒)”和“当前费用(元)”,App端只负责展示。这个原则在论文里可以写成一节“时间统一性设计”,说明保证了计费准确性。
6.3 高频更新场景下的并发冲突
订单详情接口会被Android端每3秒调用一次,这在并发量极低的情况下看起来没什么问题,但涉及数据库操作时必须考虑一个问题:订单结算时读取的数据是不是一致的最新数据。
举个例子:用户点击“结束充电”的瞬间,模拟充电线程正好也执行了一次电量和费用更新,这时两个操作并发修改同一条订单数据,可能造成最终结算金额和用户看到的最后一次刷新金额不一致。
解决方案是:所有写操作都通过Service层方法同步执行,并且用@Transactional事务包裹。在订单结算方法中,先通过乐观锁(版本号)或排他锁(SELECT ... FOR UPDATE)锁定订单记录,再读取最新电量计算费用并完成状态更新。对于模拟充电线程的更新操作,要求它不能同时修改“已结束”状态的订单,在SQL层加上WHERE order_status = 0条件,保证状态流转不会反向覆盖。
6.4 部署上线与论文素材准备
毕设项目只要在本地能跑通就算完成,但如果你想在论文中体现“系统部署”这一环节,可以找一个云服务器把后端和数据库部署上去。部署方案不复杂,就是典型的SpringBoot应用部署:
- 云服务器安装JDK 8、MySQL 5.7;
- 本地把项目打包成可执行JAR包(
mvn clean package); - 上传JAR包到服务器,用
nohup java -jar ev-charging-server.jar &后台启动; - 配置MySQL的远程访问权限,修改
application.yml里的datasource.url为云数据库地址。
如果你在押题“评委会不会问部署细节”,可以在Windows服务器阶段先用Tomcat或内嵌Tomcat把JAR包跑起来,然后把运行截图放在论文的“运行环境”一节中。这比在论文里写一堆“可实现容器化部署”这种空话要实在得多。
部署中常见的一个问题是MySQL的时区配置。国内服务器默认时区是东八区,但MySQL的serverTimezone参数如果配置为UTC,存储的时间会和本地时间相差8小时。建议在JDBC连接串中显式加上serverTimezone=Asia/Shanghai,避免时间错乱坑。
6.5 高频问题速查表
我把这个项目开发过程中最常遇到的问题整理成一张速查表,每一行都是我在实际操练中踩过的坑,可以帮你省掉大量搜报错的时间。
| 报错信息或现象 | 根本原因 | 解决方案 |
|---|---|---|
ConnectException: Failed to connect to /10.0.2.2 | 服务端没启动,或端口配置不对 | 先确认后端能通过浏览器访问接口,再检查App的baseUrl端口 |
CLEARTEXT communication not permitted | Android 9+禁止明文HTTP | 在Manifest中添加usesCleartextTraffic="true" |
Unknown constant enum tag | 本地JDK版本高,Lombok版本不匹配 | 用SpringBoot 2.7.x版本管理自带的Lombok版本,不手动指定 |
Duplicate entry 'xxx' for key 'phone' | 重复注册 | 数据库唯一索引冲突;在Service层先查重,再插入 |
| 订单状态不变,“充电中”永远不结束 | 异步任务没被触发 | 检查主类是否加了@EnableAsync,以及异步方法是否在另一个类中(同类内@Async调用无效) |
Table 'xxx' doesn't exist | JPA自动建表策略未配置 | spring.jpa.hibernate.ddl-auto=update |
| MySQL连接8小时超时 | 连接池中的连接空闲时间过长被回收 | spring.datasource.hikari.max-lifetime=1800000 |
| 金额计算出现小数误差 | 用Double或Float存储金额 | 统一使用BigDecimal进行金额计算,数据库用DECIMAL |
| RecyclerView滑动卡顿 | 在UI线程做了耗时的数据解析 | 网络请求和解析放在子线程(Retrofit的enqueue已做到),注意不要在getView中做复杂逻辑 |
6.6 论文写作与答辩材料组织的建议
这是给即将面对毕业论文和答辩的同学的最后一个建议。很多学生代码写完了,论文却不知道怎么组织,说明“把所有东西都塞进去”的错误思路。实际上论文章节和你的代码模块天然有对应关系,建议这样切:
- 绪论:背景写新能源产业政策支持和充电设施缺口,选题意义强调数字化管理对运营效率的提升,国内外现状可以搜几篇知网的文献各摘一段再改写。
- 需求分析:把功能模块图放出来(就是本文章1.2节的三端功能划分),加上用例图、业务流程图。这部分要写成“功能性需求+非功能性需求(安全、性能、可维护性)”。
- 系统设计:写总体架构、数据库表结构设计、接口设计。你的JWT认证方案、统一返回结果处理、时间统一性方案都放在这里突出讲。
- 系统实现:逐个模块贴核心代码和截图。关键是核心类名、核心方法名要和论文中的描述一一对应,评委如果翻代码,能按照论文里的类名找到实现,体验会非常好。
- 系统测试:用功能测试用例表(输入、预期结果、实际结果、结论)覆盖核心业务链路,再补充简单的并发测试或接口性能测试数据。
至于答辩PPT,一页一个模块,把最终演示效果截图放上去,代码只贴核心片段,不要贴大段源码。评委最关注的是你有没有真正理解项目——你在回答“为什么这样设计”时的逻辑自洽程度,要远比代码行数有说服力。
最后再分享一个小技巧:做演示时提前准备一套“演示剧本”。从注册登录开始,到找桩、启动充电、看电量增加、结束充电、查看订单,一气呵成走完整个业务闭环。如果你的演示在这个闭环中每一步都有可见的数据变化反馈,这已经是毕业设计答辩中表现最好的那一档了。在此基础上如果还能主动说出“这里的计费规则支持电费与服务费分别定价,可以通过管理后台动态调整”,那就没有哪个评委能挑出实质性问题了。