🚗 从业务到代码:实现车辆保养里程与时间提醒系统的一次深度拆解
作者:天天摸鱼的java工程师
标签:Java 后端、系统设计、MySQL、并发、定时任务、架构思维
🧭 一、业务背景
在车联网的时代,车辆保养提醒系统已成为智能出行服务中的标配之一。每辆车都需要定期保养,常见的触发条件有两种:
- 按里程:比如每 5000 公里保养一次
- 按时间:比如每 6 个月保养一次
车主经常忘记这些时间节点,而平台的价值就是在合适的时间,提醒用户进行保养。
🎯 二、核心目标
- 实时更新车辆当前里程
- 动态计算下次保养时间与里程
- 及时推送保养提醒
- 系统高并发支持,保证性能可扩展
📐 三、系统设计概览
模块划分清晰,职责单一,易于扩展
+-------------------+
| 数据接入层 | <-- 车辆上报当前里程
+-------------------+
|
v
+-------------------+
| 保养规则引擎 | <-- 计算下次保养时间/里程
+-------------------+
|
v
+-------------------+
| 提醒调度任务 | <-- 定时扫描即将保养车辆
+-------------------+
|
v
+-------------------+
| 消息推送服务 | <-- 推送提醒(短信/APP)
+-------------------+
🧩 四、数据表设计
以下是简化后的核心表结构设计:
1. vehicle_info — 车辆基础信息表
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 车辆ID |
| user_id | BIGINT | 用户ID |
| plate_number | VARCHAR | 车牌号 |
| current_mileage | INT | 当前里程(km) |
| last_update_time | DATETIME | 最近上报时间 |
2. vehicle_maintenance_plan — 保养计划表
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| vehicle_id | BIGINT | 车辆ID |
| last_maintenance_time | DATETIME | 上次保养时间 |
| last_maintenance_mileage | INT | 上次保养里程 |
| mileage_interval | INT | 每隔多少公里保养 |
| time_interval_month | INT | 每隔几个月保养 |
| next_maintenance_time | DATETIME | 下次保养时间 |
| next_maintenance_mileage | INT | 下次保养里程 |
3. maintenance_remind_log — 保养提醒记录表
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| vehicle_id | BIGINT | 车辆ID |
| remind_time | DATETIME | 提醒时间 |
| remind_type | VARCHAR | 里程/时间 |
| is_sent | TINYINT | 是否已发送 |
🧠 五、核心逻辑实现
1. 🚘 车辆里程上报接口
@PostMapping("/api/mileage/report")
public void reportMileage(@RequestBody MileageReportDTO dto) {
vehicleService.updateMileage(dto.getVehicleId(), dto.getMileage());
}
@Transactional
public void updateMileage(Long vehicleId, int mileage) {
Vehicle vehicle = vehicleRepository.findById(vehicleId)
.orElseThrow(() -> new NotFoundException("车辆不存在"));
if (mileage > vehicle.getCurrentMileage()) {
vehicle.setCurrentMileage(mileage);
vehicle.setLastUpdateTime(LocalDateTime.now());
vehicleRepository.save(vehicle);
// 检查是否需要更新保养计划
maintenanceService.checkAndUpdatePlan(vehicle);
}
}
2. 🔁 保养计划自动更新逻辑
public void checkAndUpdatePlan(Vehicle vehicle) {
MaintenancePlan plan = maintenancePlanRepository.findByVehicleId(vehicle.getId());
int nextMileage = plan.getLastMaintenanceMileage() + plan.getMileageInterval();
LocalDateTime nextTime = plan.getLastMaintenanceTime().plusMonths(plan.getTimeIntervalMonth());
plan.setNextMaintenanceMileage(nextMileage);
plan.setNextMaintenanceTime(nextTime);
maintenancePlanRepository.save(plan);
}
3. ⏰ 定时任务:扫描即将保养车辆
@Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
public void scanVehiclesForRemind() {
List<MaintenancePlan> plans = maintenancePlanRepository.findUpcomingMaintenance(
LocalDateTime.now().plusDays(3), // 提前3天提醒
200 // 公里差范围
);
for (MaintenancePlan plan : plans) {
if (!remindAlreadySent(plan)) {
pushReminder(plan);
saveRemindLog(plan);
}
}
}
4. 🚀 推送逻辑(伪代码)
private void pushReminder(MaintenancePlan plan) {
String message = String.format("您的车辆即将需要保养,建议在 %s 或行驶至 %d 公里时进行保养。",
plan.getNextMaintenanceTime().toLocalDate(),
plan.getNextMaintenanceMileage());
pushService.send(plan.getVehicleId(), message);
}
⚙️ 六、性能与高并发优化建议
✅ 1. 表设计优化
- 所有主表使用 BIGINT 自增主键
- 高频表如
vehicle_info使用 单表分区或垂直拆分 - 建立复合索引
(vehicle_id, last_update_time)提高查询效率
✅ 2. 缓存策略
- 使用 Redis 缓存车辆当前里程,减少数据库写压力
- 缓存即将保养计划列表,调度任务只处理缓存数据
✅ 3. 异步解耦
- 推送逻辑使用 Kafka、RocketMQ 等 异步消息队列解耦
- 定时任务可使用 ElasticJob / XXL-Job 实现分布式调度
✅ 4. 批量处理
- 推送提醒、日志入库等操作采用 批处理 提升吞吐能力
🧪 七、测试策略
- 单元测试:覆盖 mileage 上报、保养计算逻辑
- 集成测试:模拟高并发上报 + 推送提醒
- 压力测试:使用 JMeter 模拟百万级车辆数据上报
🧵 八、总结与思考
从一个简单的“保养提醒”功能出发,背后其实是一个高并发 + 实时计算 + 定时调度 + 消息推送 + 数据一致性的完整系统。它融合了我们作为 Java 工程师需要掌握的多种知识点:
- 领域建模
- 服务解耦
- 高性能设计
- 系统扩展能力
“代码只是冰山一角,业务理解与系统设计才是立足之本”。