1. 项目概述
这个车辆管理系统是我去年为一个物流公司开发的实际项目,当时他们正面临车辆调度混乱、维修记录丢失等问题。系统采用前后端分离架构,后端使用SpringBoot+MyBatis,前端用Vue3,数据库是MySQL。经过3个月的开发和2个月的试运行,目前已经稳定运行了半年多,日均处理200+车辆调度请求。
提示:在开始开发类似系统前,建议先梳理清楚业务流程。我们最初就因为没理清违章处理流程而返工了两次。
2. 技术选型解析
2.1 后端技术栈
选择SpringBoot是因为它开箱即用的特性。实际开发中我们用到了这些关键依赖:
- spring-boot-starter-web:处理HTTP请求
- mybatis-spring-boot-starter:数据库操作
- spring-boot-starter-security:权限控制
- spring-boot-starter-validation:参数校验
java复制// 典型控制器示例
@RestController
@RequestMapping("/api/vehicles")
public class VehicleController {
@Autowired
private VehicleService vehicleService;
@GetMapping("/{id}")
public ResponseEntity<Vehicle> getVehicle(@PathVariable String id) {
return ResponseEntity.ok(vehicleService.getById(id));
}
}
2.2 前端技术栈
Vue3的组合式API让代码组织更灵活。项目中使用的主要技术点:
- Vue Router:页面路由
- Pinia:状态管理
- Element Plus:UI组件库
- Axios:HTTP请求
javascript复制// 典型API调用示例
import { ref } from 'vue'
import { useVehicleStore } from '@/stores/vehicle'
const vehicleStore = useVehicleStore()
const vehicles = ref([])
async function loadVehicles() {
try {
await vehicleStore.fetchVehicles()
vehicles.value = vehicleStore.vehicles
} catch (error) {
console.error('加载车辆数据失败:', error)
}
}
3. 数据库设计详解
3.1 核心表结构优化
原始设计中的车辆表在实际使用中发现了几个问题:
- 缺少车辆照片字段
- 保险状态用tinyint不方便查询
- 没有记录最后维护时间
优化后的车辆表结构:
sql复制CREATE TABLE `vehicle` (
`id` varchar(20) NOT NULL COMMENT '车辆ID',
`plate_number` varchar(15) NOT NULL COMMENT '车牌号',
`brand` varchar(50) NOT NULL COMMENT '品牌',
`model` varchar(50) NOT NULL COMMENT '型号',
`purchase_date` date NOT NULL COMMENT '购买日期',
`insurance_status` enum('VALID','EXPIRED','NONE') NOT NULL DEFAULT 'NONE' COMMENT '保险状态',
`last_maintenance` datetime DEFAULT NULL COMMENT '最后维护时间',
`image_url` varchar(255) DEFAULT NULL COMMENT '车辆照片',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_plate` (`plate_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引设计经验
根据实际查询需求,我们添加了这些索引:
- 车牌号唯一索引:用于快速查找特定车辆
- 购买日期索引:用于统计车辆年限
- 保险状态+最后维护时间联合索引:用于筛选需要续保的车辆
注意:MySQL的enum类型虽然可读性好,但后期修改枚举值需要alter table。如果状态可能频繁变更,建议改用tinyint+字典表的方式。
4. 核心功能实现
4.1 车辆调度算法
物流公司最关心的是如何高效调度车辆。我们实现了基于优先级的调度算法:
- 优先选择空闲车辆
- 其次选择里程数较低的车辆
- 最后考虑维修记录较少的车辆
java复制public List<Vehicle> findAvailableVehicles(LocalDate date, int requiredSeats) {
return vehicleMapper.selectList(new QueryWrapper<Vehicle>()
.eq("status", "AVAILABLE")
.ge("seat_capacity", requiredSeats)
.notExists("SELECT 1 FROM schedule WHERE vehicle_id = vehicle.id AND schedule_date = {0}", date)
.orderByAsc("last_maintenance_mileage")
.last("LIMIT 5"));
}
4.2 维修预警系统
通过分析维修记录,我们实现了这些预警规则:
- 每5000公里提醒保养
- 异常维修频率预警(同部位3个月内维修2次以上)
- 保险到期前30天提醒
sql复制-- 查找需要保养的车辆
SELECT v.id, v.plate_number, v.last_maintenance_mileage, v.current_mileage
FROM vehicle v
WHERE v.current_mileage - v.last_maintenance_mileage >= 5000
AND v.status = 'AVAILABLE';
5. 权限控制方案
系统采用RBAC模型,定义了这些角色:
- 管理员:全权限
- 调度员:车辆调度相关权限
- 维修员:维修记录相关权限
- 驾驶员:查看自己相关信息权限
Spring Security配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/schedule/**").hasAnyRole("DISPATCHER", "ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
6. 性能优化实践
6.1 数据库优化
- 大表分页查询使用延迟关联:
sql复制SELECT * FROM vehicle
WHERE id IN (
SELECT id FROM vehicle
WHERE status = 'AVAILABLE'
ORDER BY last_maintenance_date
LIMIT 20 OFFSET 40
)
- 频繁更新的统计字段使用定时任务计算,避免实时count(*)
6.2 前端性能优化
- 车辆列表使用虚拟滚动,处理1000+数据
- 表单提交添加防抖处理
- 路由懒加载减少首屏体积
javascript复制// 虚拟滚动示例
<el-table
:data="visibleData"
:row-height="rowHeight"
:total="vehicles.length"
@scroll="handleScroll"
>
<!-- 表格列定义 -->
</el-table>
7. 部署方案
我们最终采用的部署架构:
- 前端:Nginx静态部署
- 后端:Docker容器化部署
- 数据库:阿里云RDS MySQL 5.7
- 缓存:Redis集群
dockerfile复制# 后端Dockerfile示例
FROM openjdk:11
COPY target/vehicle-system.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
8. 踩坑记录
-
日期处理问题:
- 前端传的日期字符串时区问题导致差一天
- 解决:统一使用UTC时间传输
-
MyBatis批量插入性能:
- 最初用foreach拼接SQL导致性能差
- 改用BatchExecutor提升10倍性能
-
Vue3响应式丢失:
- 直接赋值导致响应式失效
- 解决:使用reactive或ref包装对象
java复制// MyBatis批量插入优化方案
@Transactional
public void batchInsert(List<Vehicle> vehicles) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
VehicleMapper mapper = session.getMapper(VehicleMapper.class);
for (Vehicle vehicle : vehicles) {
mapper.insert(vehicle);
}
session.commit();
} finally {
session.close();
}
}
这个项目让我深刻体会到,一个好的管理系统不仅要功能完善,更要考虑实际业务场景。比如我们最初设计的维修记录没有关联具体故障部位,后来发现这对分析车辆常见故障很有用,又追加了这部分功能。