尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot城市固废清运车辆管理系统:从源码到部署

Spring Boot城市固废清运车辆管理系统:从源码到部署 做城市固废清运车辆管理系统这个项目我最大的感受是真正花时间的不是写代码而是把业务逻辑理清楚。这套系统基于Spring Boot开发是典型的后台管理车辆调度应用面向环卫公司或市政管理单位用来管理清运车辆的档案、任务派发、作业轨迹和数据统计。如果你正在做毕业设计或者团队内部刚好需要搭建一套类似的固废清运管理工具这份从源码到部署的完整拆解可以直接作为参考样板。我会从需求拆解、架构选型、核心模块、数据库设计、部署实操到排坑技巧把整个项目的来龙去脉一条条讲清楚帮你少走弯路。1. 项目概述与业务场景拆解1.1 城市固废清运的业务痛点是什么城市固废清运不是简单的“派车拉垃圾”。在实际作业中管理方需要面对的是车辆分散在不同区域司机跑哪条路线、是否按时完成清运、车辆油耗和里程是否异常、突发任务怎么调配这些环节如果全靠打电话和记录本来管理数据很难对齐也很容易扯皮。传统做法往往是每天早会口头派单司机跑完回来填纸质单子管理员晚上再手动录入Excel。这种模式的问题很明显一是不实时车辆到没到指定点位只能在事后知道二是数据零散一个月的清运量、里程、油耗统计起来耗时耗力三是责任不清晰车辆出了故障或者点位没清干净很难追溯到具体任务和责任人。所以这个系统的核心价值就是把车辆、任务、轨迹、记录整合到一个平台上让管理员能随时看到每辆车的状态司机能通过简单的流程接收任务、上报完成数据自动沉淀成报表减少人工干预。1.2 系统核心用户与功能清单这个系统主要服务三类角色系统管理员、调度员、司机。管理员负责基础数据的维护比如添加车辆信息、录入司机账号、配置清运点位等调度员的核心工作是创建清运任务、分配给对应车辆并跟踪任务状态司机端则是接收任务、上报开始和完成、上传简单的作业记录。从功能上看梳理下来有这么几个核心模块车辆管理车牌号、车型、载重、所属车队、年检状态、维修记录。司机管理姓名、电话、驾驶证号、绑定车辆。清运任务管理任务编号、点位地址、计划开始/结束时间、负责人、任务状态。任务派发调度员手动派单或者按优先级自动排队。轨迹记录车辆定位坐标、上报时间、里程统计可以对接GPS设备或者手动打卡模拟。清运记录每次作业的点位、时间、清运重量或车次形成历史归档。统计报表按日/周/月生成清运次数、里程汇总、车辆出勤率等图表。系统管理登录账号、角色权限、操作日志。我说的这些功能在后面的源码实现里都会有对应代码支撑不是只画饼的页面。接下来我会逐个模块拆把关键设计逻辑和代码细节拿出来聊。2. 系统架构设计与技术选型理由2.1 为什么选择Spring Boot作为基础框架市面上做管理系统的后端框架很多Spring Boot的生态成熟度、社区文档、岗位需求匹配度目前仍然是最稳的选择。尤其做这个课题时选择Spring Boot有三个实际考量。第一零配置起步非常香。不需要像传统SSM那样自己配一堆XML文件spring-boot-starter-web一拉内嵌容器直接跑起来写个Controller就能看效果对快速搭建原型很友好。第二生态覆盖了常规业务所需的一切。访问数据库有spring-boot-starter-data-jpa或者spring-boot-starter-jdbc权限认证可以继承Spring Security也可以简单用AOP自定义注解做甚至用拦截器来实现选择非常多。第三部署上手成本低。项目打包成jar目标机器上有Java环境就能启动不像以前还要装Tomcat、配置web.xml。对这个项目的部署讲解环节来说能省下很多体力活。当然如果你的团队已经统一使用别的框架比如Quarkus或Micronaut那没必要迁移但作为单机部署的管理系统Spring Boot确实是性价比最高的路线。2.2 前后端分离还是服务端渲染我最终的选择做管理系统最纠结的一件事就是前端方案。我当时在两个方案里摇摆了很久一种是前后端分离前端用Vue或者React后端纯API接口另一种是Spring Boot自带Thymeleaf模板引擎做服务端渲染。前后端分离的好处是后期扩展App或者小程序更方便接口可以复用。但代价是项目体积变大需要额外维护Node环境和构建流程对于以论文LW为目的的毕设项目来说反而增加复杂度评审老师只看功能的时候也没太大差别。所以我最后选了服务端渲染为主局部用原生Ajax。页面直接用Thymeleaf模板表单提交、列表刷新走Controller跳转地图或者轨迹展示这种需要动态交互的部分用script标签写Ajax调后端接口拿JSON数据组装。这样做的好处非常明显部署落地就是一个jar包不需要再起一个前端静态服务器也不会出现跨域问题。代码里一套Session搞定登录状态安全性也好控制。如果你不想用Thymeleaf换成FreeMarker也可以逻辑基本一样。2.3 核心依赖与版本选型版本选择上我踩过坑特意提醒一下Spring Boot版本不是越高越好。当时我直接用最新的Spring Boot 3.x结果发现它强制要求JDK 17而且很多老教程里的写法都变了比如javax包变成jakarta对刚接触的人来说很容易迷糊。如果你是照着类似毕业设计来做我更推荐使用Spring Boot 2.7.x系列配合JDK 8或者JDK 11资料多、坑少、版本稳定。我这次项目用的依赖清单大致如下依赖组件版本选择用途说明JDK1.8JDK 8稳定、兼容性最好Spring Boot2.7.14统御所有Starter版本mybatis-plus3.5.3简化SQL操作内置分页插件mysql-connector-java8.0.33连接MySQL数据库lombok1.18.30简化实体类getter/setterthymeleaf由Spring Boot管理服务端模板渲染bootstrap5.2.3前端库快速搭建后台管理UI版本这个东西很多人不重视跑到后面才发现不兼容所以我强烈建议先按我这份列表去建项目避免掉进版本黑洞。3. 核心模块设计与源码实现细节3.1 车辆档案管理与状态流转实现细节车辆是整个系统的基础资源所有任务和轨迹都围绕车辆展开。车辆管理的核心不只是增删改查更重要的是状态流转。我定义车辆状态为可用、出勤中、维修中、停用四种。可用表示可以分配任务出勤中表示当前正在执行任务不可再被分配维修中表示需要维修不参与调度停用则是退出运营。状态流转的规则在Service层里严格控制而不是任由前端随便改。以派单流程为例Controller调用TaskService的assignTask方法方法内先根据车辆ID查询车辆状态如果不是“可用”状态就直接抛出业务异常并用一个统一的响应体包装错误信息Service public class VehicleServiceImpl implements VehicleService { Override public boolean assignTask(Long vehicleId, Long taskId) { Vehicle vehicle this.getById(vehicleId); if (vehicle null) { throw new BusinessException(车辆不存在); } if (!可用.equals(vehicle.getStatus())) { throw new BusinessException(车辆当前状态为 vehicle.getStatus() 不能分配任务); } vehicle.setStatus(出勤中); this.updateById(vehicle); // 同步更新任务表绑定车牌号 return true; } }这里的核心思路是把业务规则放在Service层避免脏数据入库。前端提供按钮真正能不能操作由后端判断。如果你用JPA或者MyBatis-Plus其实换来换去都一个逻辑关键是状态字段要用String还是int包装我建议用String直接存中文值方便页面显示也方便排错。车辆列表我额外做了关键词搜索和分页用了MyBatis-Plus的Page对象配合LambdaQueryWrapper可以实现动态条件拼接public PageVehicle searchVehicles(int page, int size, String keyword) { LambdaQueryWrapperVehicle wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Vehicle::getPlateNumber, keyword) .or().like(Vehicle::getVehicleType, keyword); } wrapper.orderByDesc(Vehicle::getCreateTime); return vehicleMapper.selectPage(new Page(page, size), wrapper); }这样一个车辆管理基本模块就成型了增删改查的Controller没什么技术含量这里就不赘述。3.2 清运任务派发与路线规划逻辑解析清运任务派发是这个系统里业务最复杂的环节。系统里一个任务包含几个关键字段任务编号、点位地址、要求完成时间、任务类型比如生活固废、建筑固废、实际完成时间、照片或者备注。派发逻辑分两种场景手工派单和自动推荐。手工派单是调度员勾选待处理的点位列表选择当前可用的车辆一键分配自动推荐则是系统根据车辆当前位置和点位距离算出预估时间匹配最合适的车。我最初把路线规划实现得过于复杂引入了图计算和GPS坐标距离算法但发现真实使用中录入精度不够点位坐标不准确导致计算结果偏差很大。后来我调整了思路路线规划不做全局最优只做基于状态和距离的启发式选择保证简单可用即可。计算两个坐标距离用球面距离公式即可我封装成一个静态方法放在DistanceUtil里public class DistanceUtil { // 地球半径单位米 private static final double EARTH_RADIUS 6371000; public static double getDistance(double lat1, double lng1, double lat2, double lng2) { double radLat1 Math.toRadians(lat1); double radLat2 Math.toRadians(lat2); double a radLat1 - radLat2; double b Math.toRadians(lng1) - Math.toRadians(lng2); double s 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) )); return s * EARTH_RADIUS; } }自动推荐的大致逻辑是遍历所有“可用”状态的车辆每辆车计算到目标点位的距离加上预估任务耗时选择预计完成时间最短的车辆。实现上就是一个Collections.sort加一个简单的距离排序并不需要复杂算法。针对一个城市区域的几十辆车来说这个性能完全够用秒级出结果。派发完成后任务状态变成“待执行”车辆状态同步变成“出勤中”。司机端登录后只能看到属于自己的待执行任务点“出发”后任务进入“执行中”点“完成”后记录清运结果并释放车辆状态。整个状态机的流转我用一张枚举表来管理任务状态车辆状态触发动作待派发可用创建任务待执行出勤中已派单执行中出勤中司机点击出发已完成可用司机点击完成已取消可用调度员取消3.3 轨迹追踪与数据上报模块实现轨迹追踪是清运车辆管理系统里比较有“科技感”的功能也是最容易忽悠人的功能。实际上毕设或者轻量级内部系统不需要真的对接硬件GPS终端可以用两种方式模拟一是每辆车设置一个默认经纬度任务执行时通过页面手动更新当前位置二是做一个“模拟上报”接口每秒随机生成一个坐标模拟车辆移动轨迹。我采用的方式是手动打卡上报定时任务自动生成模拟坐标两种模式可切换用application.yml里的一个track.simulatetrue来开关。数据落库的实体类设计成一张vehicle_track表主要字段包括车辆ID、纬度、经度、上报时间。车辆轨迹展示时把某辆车当天的所有轨迹点取出来通过Ajax返回JSON数组前端用百度地图JS API或者高德地图JS API绘制连线。后端提供一个简单接口RestController RequestMapping(/api/track) public class TrackController { Autowired private VehicleTrackMapper trackMapper; GetMapping(/list) public Result list(Long vehicleId, String dateStr) { ListVehicleTrack list trackMapper.selectList( new LambdaQueryWrapperVehicleTrack() .eq(VehicleTrack::getVehicleId, vehicleId) .ge(VehicleTrack::getTrackTime, dateStr 00:00:00) .le(VehicleTrack::getTrackTime, dateStr 23:59:59) .orderByAsc(VehicleTrack::getTrackTime)); return Result.success(list); } }前端拿到数据后直接循环创建Marker再用Polyline连线。由于是模拟轨迹所以地图上不会太好看但功能链路是完整的。如果你真的要在生产环境用替换成MQTT接入硬件上报即可表结构无需变更只需要多写一个TCP或者MQ订阅服务。3.4 司机考勤与绩效统计要点司机出勤数据算是这套系统里额外加分的模块很多公司不仅仅需要清运记录还需要考核司机每天到底跑了几趟车、有没有迟到、工作量是否均衡。我实现了一个简单的考勤逻辑司机登录系统后每天第一次访问“开始工作”按钮时在work_attendance表插入一条记录记录上班时间和日期结束工作时更新下班时间。这样便可以得到每天在岗时长。绩效统计则完全由任务记录表驱动。每次司机完成一个清运任务就自动累加工时、清运点数。月度统计时直接按月份分组查询任务表统计每个司机的完成次数和总里程ListMapString, Object monthStats taskMapper.selectCountGroupByEmployee(month);SQL大概是这样SELECT employee_id, COUNT(task_id) AS task_count, SUM(actual_mileage) AS total_mileage FROM clear_task WHERE DATE_FORMAT(finish_time, %Y-%m) #{month} GROUP BY employee_id;这里有一个易踩的坑业务报表统计一定要用数据库聚合函数不要在Java内存里循环累加因为量大之后会特别慢而且数据容易遗漏。特别是毕设答辩演示的时候小程序或管理端页面加载报表卡顿几秒全场气氛会非常尴尬。4. 数据库设计与关键表结构剖析4.1 业务数据如何拆分成表这个系统大概拆了6张核心表和一辅助表。拆表的原则是一个业务对象一张表一个业务关系一张表。车辆、司机、任务、轨迹这些是独立实体各建表任务和司机之间是多对一关系字段直接挂在任务表上轨迹是任务运行过程中的流水数据单独成表避免查询列表时扫描大量无关行。从规范角度讲表结构要至少满足第三范式但同时考虑查询性能某些冗余字段我故意保留了比如任务表里直接冗余了司机姓名和车牌号虽然司机表里有但任务列表页每天被反复查询冗余字段可以少两次关联查询换来更快的响应这笔账是划算的。4.2 核心表结构定义示例以下是我在项目里实际使用的部分表结构你可以直接参考建表。车辆信息表vehicle字段名类型说明idbigint主键自增plate_numbervarchar(20)车牌号vehicle_typevarchar(20)车辆类型load_capacitydecimal(10,2)额定载重吨fleet_namevarchar(50)所属车队statusvarchar(10)可用/出勤中/维修中/停用next_maintenancedate下次维护提醒日期清运任务表clear_task字段名类型说明idbigint主键task_novarchar(30)任务编号唯一索引site_addressvarchar(200)点位地址site_latdouble点位纬度site_lngdouble点位经度plan_timedatetime计划完成时间actual_timedatetime实际完成时间employee_idbigint绑定司机IDvehicle_idbigint绑定车辆IDtask_statusvarchar(10)待派发/待执行/执行中/已完成/已取消remarkvarchar(500)备注信息车辆轨迹表vehicle_track字段名类型说明idbigint主键vehicle_idbigint车辆IDlatdouble纬度lngdouble经度track_timedatetime上报时间建表语句就不贴完整了核心是索引的创建clear_task表的employee_id、vehicle_id、task_status这三个字段在查询条件里经常出现必须建索引。vehicle_track表的vehicle_id和track_time建议建联合索引否则轨迹查询时间长了会很慢。4.3 数据库事务与数据一致性处理写业务逻辑时最容易出问题的地方是跨表更新。比如上面提到的派单操作需要同时更新任务表状态、车辆表状态两个操作必须放在一个事务里否则会出现任务已经绑定了车但车辆状态还是“可用”的情况重复派单就会产生。我在Service方法上直接加Transactional注解这是Spring Boot里最常规也是最有效的手段。要注意的是自调用场景下事务会失效比如同类里一个方法调用另一个带事务的方法此时可以使用TransactionTemplate来手动控制。为了避免毕设代码里出现这种隐蔽bug我建议干脆统一用TransactionTemplate写事务代码块transactionTemplate.execute(status - { taskMapper.updateStatus(...); vehicleMapper.updateStatus(...); return null; });这样无论代码怎么重构都不会踩自调用事务失效的坑排错也更直观。5. 部署讲解从源码到本地运行的完整流程5.1 环境准备与JDK配置要点部署讲解是这套源码里比较有价值的部分也是很多新手挂掉的地方。首先明确本地运行这个Spring Boot项目需要准备三个环境JDK、Maven、MySQL。JDK版本我强烈建议用JDK 8因为如果服务器是老机器或者你本身电脑上还有别的项目依赖老版本避免来回切换环境变量是最省心的。装完之后验证环境是否正常java -version mvn -v如果mvn -v提示找不到命令说明Maven没有配置环境变量。Windows下需要把Maven的bin目录加到PATH里Linux/macOS下则需要修改~/.bashrc或~/.zshrc。这些操作看着基础但实际很多人卡在第一步因为环境变量配置有误导致命令不可用。接着说Maven镜像设置。国内拉取依赖经常卡死是因为默认走了中央仓库网络不稳定。在settings.xml里配置阿里云镜像才是正解mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/central/url /mirror配置好后依赖下载速度肉眼可见起飞。5.2 初始化数据库与导入表结构代码里的资源配置默认连接你的本地MySQL。你需要先创建数据库比如叫waste_vehicle_management字符集选择utf8mb4避免中文乱码。然后执行项目附带的schema.sql或者init.sql脚本来建表。导入数据库的命令方式mysql -uroot -p -h127.0.0.1 waste_vehicle_management schema.sql执行完毕后用show tables;验证有没有成功生成。我遇到过很多同学卡在这一步原因是schema.sql文件编码不对或者存在BOM头导致执行报错。解决办法是用文本编辑器保存为UTF-8无BOM格式再重新执行一次。如果你的MySQL账号密码不是默认的root/123456还得同步修改application.yml里的数据源配置这点千万不要忘了。5.3 application.yml核心参数配置配置文件是部署讲解里的重点。我项目里的application.yml大概是这个结构server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/waste_vehicle_management?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl有几个参数值得专门提示一下。第一数据库URL上必须带useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai否则中文乱码、时间偏差分分钟找你麻烦。第二thymeleaf.cache: false是开发模式的必备配置改完页面模板不用重启项目刷新浏览器就能看到效果大大提升调试效率。发布生产环境后再改回true。第三mybatis-plus.configuration.log-impl配置SQL控制台打印调试阶段非常有用能直接看到执行的SQL语句锁定SQL语法问题。5.4 编译、打包与启动运行项目配置无误后在项目根目录执行Maven编译打包命令mvn clean package -DskipTests打包成功后在target目录下会生成一个类似waste-vehicle-0.0.1-SNAPSHOT.jar的jar包。启动有两种方式。第一种直接用java命令启动java -jar target/waste-vehicle-0.0.1-SNAPSHOT.jar第二种如果你想在服务器上长期运行推荐使用后台运行方式nohup java -jar target/waste-vehicle-0.0.1-SNAPSHOT.jar server.log 21 启动后在控制台或者server.log里看到Started ... in x.xx seconds就说明启动成功。浏览器访问http://localhost:8080默认跳转到登录页。首次访问如果页面样式布局错乱大概率是静态资源404了。检查一下项目里static目录下是否放置了css/js文件以及Controller有没有错误地拦截了静态资源路径。这个属于部署中的高频问题我在下面一节专门汇总。6. 常见问题与排查技巧实录6.1 端口占用导致启动失败启动时最经典的报错就是Port 8080 was already in use.原因是本机已经有进程占用8080端口。排查方法Windows下执行netstat -ano | findstr 8080找到对应的PID然后在任务管理器里结束进程或者用命令行taskkill /PID xxxx /F强制关闭。Linux/macOS下执行lsof -i:8080查看占用端口的进程ID再用kill -9 PID关闭。更简单的做法是直接改配置文件里的server.port换成8081或者8088启动就不会冲突了。在本地开发时我习惯将端口改成8081避免和其他常用服务撞车。6.2 中文乱码问题中文乱码一般出现在两个位置数据库字段乱码和页面显示乱码。数据库字段乱码根本原因在于建库建表时的字符集不是utf8mb4。解决方式已经建库的可以使用以下命令修改ALTER DATABASE waste_vehicle_management CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE clear_task CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;页面显示乱码检查application.yml中是否有characterEncodingutf8同时检查Thymeleaf模板文件本身存储编码是否为UTF-8。用IDEA打开文件看右下角编码显示如果显示GBK改为UTF-8并重新保存。另外还有一个隐蔽坑是HTTP响应头不生效。如果响应头里没有Content-Type: text/html;charsetUTF-8浏览器会猜测编码。Thymeleaf通常会自动处理但如果你用原生HttpServletResponse返回内容建议手动设置编码。6.3 数据库连接失败或驱动类异常部署到新环境最常见的错误是java.sql.SQLException: Access denied for user rootlocalhost或ClassNotFoundException: com.mysql.cj.jdbc.Driver。前者说明账号密码错误核对application.yml里的username和password即可。后者说明项目没有引入MySQL驱动依赖或者依赖版本跟MySQL客户端通信协议不兼容。这里特别提醒如果数据库是MySQL 8.x驱动类必须写com.mysql.cj.jdbc.Driver如果是MySQL 5.x可以写com.mysql.jdbc.Driver。依赖版本上我推荐使用mysql-connector-java:8.0.33这个版本对MySQL 5.7和8.x都能兼容比较省心。6.4 前端页面加载不出样式或Ajax请求403部署完整Jar包后发现页面有内容但样式是乱糟糟的首先看浏览器F12控制台的资源请求路径。通常是因为项目没有配置context-path导致静态资源路径缺失或者是Spring Security拦截了资源请求没有放行/static/**。我这里用的是自定义拦截器判断登录Session需要额外放行/css/**、/js/**、/img/**。在WebMvcConfigurer里注册Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns( /login, /api/login, /css/**, /js/**, /img/** ); }如果你用了Spring Security对应放行逻辑也差不多核心是权限过滤器的permitAll()配置。这类问题排查优先级很高几乎新手必踩。6.5 时间字段比真实时间早8小时或晚8小时在使用LocalDateTime后发现数据库存的时间和真实时间不一致这是数据库时区配置导致的。解决方式是在连接URL上添加serverTimezoneAsia/Shanghai。还有ackson序列化时如果直接返回给前端展示可能带T或者偏移量建议在application.yml里统一配置时间格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai这样所有接口返回的时间都统一为2025-01-15 10:30:00这种格式前端展示不用再二次处理。7. 一些实际开发与答辩中的心得最后分享几个我在做这个项目过程中的经验这些可能比上面的代码细节更有参考价值。第一不要在业务逻辑里堆硬编码状态值。初始写代码时图方便直接用字符串常量到处比较后面想做状态枚举统一管理就要全局替换改起来很痛苦。后来我引入了VehicleStatusEnum和TaskStatusEnum来管理所有状态流转代码可读性好很多调试时也直观。第二系统里加一点数据模拟工具。为了让答辩演示不冷场我写了一个DataGenerateRunner项目启动时会自动生成一批车辆、司机和任务数据。这个设计非常实用因为评审老师不会关心你数据库里有没有数据但没数据演示的时候页面空荡荡的效果极差。第三部署时建议保留一份演示环境的初始化数据库脚本包含几辆模拟车辆和几条任务记录这样就算现场环境变了也能快速恢复到能演示的状态。我自己的习惯是schema-data.sql一条命令做完建表和造数据省掉了不少临时操作。这个项目后续可以扩展的方向也很多比如接入真实GPS硬件上报、增加小程序司机端、引入消息队列做任务异步派发、对接电子围栏做站点打卡。底子打得干净后续怎么长都顺。如果你正在做类似的Spring Boot课题这套系统的思路可以照搬但代码细节最好还是自己过一次改改表字段和页面文案就成了你自己的东西。真正动手写一遍比对着视频敲十遍都管用。
返回列表