- 在application-prod.yml中将MQTT服务enabled设置为true - 引入StringRedisTemplate用于Redis分布式锁控制-重构GPS轨迹日志保存方法,增加Redis原子锁控制频率 -仅在GPS速度不为0时保存轨迹日志,避免无效数据- 使用Redis setIfAbsent实现2分钟内同一设备只保存一次日志 - 完善日志记录,增加设备、速度、经纬度等关键信息 -优化异常处理,记录具体设备号便于问题追踪
283 lines
9.2 KiB
Java
283 lines
9.2 KiB
Java
package com.gxwebsoft.hjm.service;
|
||
|
||
import cn.hutool.core.date.DateUtil;
|
||
import cn.hutool.core.util.ObjectUtil;
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.alibaba.fastjson.support.geo.Point;
|
||
import com.gxwebsoft.common.core.utils.JSONUtil;
|
||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||
import com.gxwebsoft.hjm.entity.Gps;
|
||
import com.gxwebsoft.hjm.entity.HjmCar;
|
||
import com.gxwebsoft.hjm.entity.HjmFence;
|
||
import com.gxwebsoft.hjm.entity.HjmGpsLog;
|
||
import com.gxwebsoft.hjm.service.impl.HjmCarServiceImpl;
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import javax.annotation.Resource;
|
||
import java.time.Instant;
|
||
import java.time.LocalDateTime;
|
||
import java.time.ZoneId;
|
||
import java.util.concurrent.TimeUnit;
|
||
|
||
/**
|
||
* GPS消息处理器
|
||
*
|
||
* @author 科技小王子
|
||
* @since 2025-07-02
|
||
*/
|
||
@Service
|
||
public class GpsMessageProcessor {
|
||
|
||
private static final Logger logger = LoggerFactory.getLogger(GpsMessageProcessor.class);
|
||
|
||
@Resource
|
||
private HjmCarService hjmCarService;
|
||
|
||
@Resource
|
||
private HjmGpsLogService hjmGpsLogService;
|
||
|
||
@Resource
|
||
private HjmFenceService hjmFenceService;
|
||
|
||
@Resource
|
||
private RedisUtil redisUtil;
|
||
|
||
@Resource
|
||
private StringRedisTemplate stringRedisTemplate;
|
||
|
||
@Resource
|
||
private HjmCarServiceImpl hjmCarServiceImpl;
|
||
|
||
/**
|
||
* 处理GPS消息
|
||
*
|
||
* @param payload 消息内容
|
||
*/
|
||
public void processGpsMessage(String payload) {
|
||
try {
|
||
logger.debug("开始处理GPS消息: {}", payload);
|
||
|
||
Gps gps = JSONUtil.parseObject(payload, Gps.class);
|
||
if (ObjectUtil.isEmpty(gps)) {
|
||
logger.warn("GPS数据为空,跳过处理");
|
||
return;
|
||
}
|
||
|
||
processGpsData(gps);
|
||
|
||
} catch (Exception e) {
|
||
logger.error("处理GPS数据失败: {}", payload, e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理GPS数据
|
||
*
|
||
* @param gps GPS数据
|
||
*/
|
||
private void processGpsData(Gps gps) {
|
||
try {
|
||
String gpsNo = gps.getImei();
|
||
if (StrUtil.isBlank(gpsNo)) {
|
||
logger.warn("GPS设备编号为空,跳过处理");
|
||
return;
|
||
}
|
||
|
||
// 详细记录GPS数据处理过程
|
||
logger.info("处理GPS数据 - 设备编号: {}, Fixed: {}, 经度: {}, 纬度: {}, 速度: {}",
|
||
gpsNo, gps.getFixed(), gps.getLng(), gps.getLat(), gps.getSpeed());
|
||
|
||
HjmCar car = hjmCarService.getByGpsNo(gpsNo);
|
||
|
||
if (car == null) {
|
||
logger.warn("GPS设备编号: {} 在数据库中未找到对应车辆,请检查车辆配置", gpsNo);
|
||
return;
|
||
}
|
||
|
||
logger.info("GPS设备编号: {} 对应车辆: {} (ID: {})", gpsNo, car.getCode(), car.getId());
|
||
|
||
if (!gps.getFixed()) {
|
||
logger.warn("GPS设备编号: {} 定位未固定(Fixed=false),跳过处理", gpsNo);
|
||
return;
|
||
}
|
||
|
||
// 更新车辆GPS定位信息
|
||
updateCarLocation(car, gps);
|
||
|
||
// 保存GPS轨迹日志
|
||
saveGpsLog(car, gps);
|
||
|
||
// 检查电子围栏
|
||
checkFence(car, gps);
|
||
|
||
} catch (Exception e) {
|
||
logger.error("处理GPS数据时发生错误", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新车辆位置信息
|
||
*/
|
||
private void updateCarLocation(HjmCar car, Gps gps) {
|
||
try {
|
||
car.setLongitude(gps.getLng());
|
||
car.setLatitude(gps.getLat());
|
||
car.setSpeed(gps.getSpeed());
|
||
car.setUpdateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(gps.getTime() * 1000), ZoneId.systemDefault()));
|
||
car.setGpsNo(gps.getImei());
|
||
|
||
if (hjmCarService.updateByGpsNo(car)) {
|
||
logger.debug("更新车辆GPS定位信息成功: {}", car.getCode());
|
||
|
||
// 保存GPS轨迹日志(通过Redis控制频率)
|
||
saveGpsLogRecord(car, gps);
|
||
} else {
|
||
logger.warn("更新车辆GPS定位信息失败: {}", car.getCode());
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
logger.error("更新车辆位置信息失败", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存GPS轨迹日志记录
|
||
* 使用Redis控制保存频率,同一GPS设备2分钟内只保存一次
|
||
*/
|
||
private void saveGpsLogRecord(HjmCar car, Gps gps) {
|
||
try {
|
||
// 只有速度不为0时才保存GPS轨迹
|
||
if (gps.getSpeed().equals("0.000")) {
|
||
logger.debug("GPS设备{}速度为0,跳过保存轨迹日志", gps.getImei());
|
||
return;
|
||
}
|
||
|
||
// 构造Redis key,使用GPS设备编号作为key
|
||
String redisKey = "gps_log:" + gps.getImei();
|
||
|
||
// 使用Redis的setIfAbsent原子操作实现分布式锁
|
||
// 只有key不存在时才设置成功,返回true;key已存在时返回false
|
||
Boolean lockAcquired = stringRedisTemplate.opsForValue()
|
||
.setIfAbsent(redisKey, "locked", 2, TimeUnit.MINUTES);
|
||
|
||
if (Boolean.FALSE.equals(lockAcquired)) {
|
||
// 获取锁失败,说明2分钟内已经保存过
|
||
logger.debug("GPS设备{}在2分钟内已保存过轨迹日志,跳过本次保存", gps.getImei());
|
||
return;
|
||
}
|
||
|
||
// 获取锁成功,执行保存操作
|
||
HjmGpsLog log = new HjmGpsLog();
|
||
log.setTenantId(10519);
|
||
log.setCarId(car.getId());
|
||
log.setGpsNo(gps.getImei());
|
||
log.setLatitude(gps.getLat());
|
||
log.setLongitude(gps.getLng());
|
||
log.setDdmmyy(gps.getDdmmyy());
|
||
log.setHhmmss(gps.getHhmmss());
|
||
log.setSpeed(gps.getSpeed());
|
||
|
||
// 保存数据库
|
||
hjmGpsLogService.save(log);
|
||
|
||
logger.info("保存GPS轨迹日志成功: 设备={}, 速度={}, 经度={}, 纬度={}",
|
||
gps.getImei(), gps.getSpeed(), gps.getLng(), gps.getLat());
|
||
|
||
} catch (Exception e) {
|
||
logger.error("保存GPS轨迹日志失败: 设备={}", gps.getImei(), e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存GPS日志(兼容原有方法)
|
||
*/
|
||
private void saveGpsLog(HjmCar car, Gps gps) {
|
||
// 这里可以添加其他GPS日志相关的处理逻辑
|
||
logger.debug("处理GPS日志: 车辆={}, GPS={}", car.getCode(), gps.getImei());
|
||
}
|
||
|
||
/**
|
||
* 检查电子围栏
|
||
*/
|
||
private void checkFence(HjmCar car, Gps gps) {
|
||
try {
|
||
String gpsNo = gps.getImei();
|
||
|
||
// 检查围栏缓存,避免频繁计算
|
||
String inFenceKey = redisUtil.get("inFence:" + gpsNo);
|
||
if (StrUtil.isNotBlank(inFenceKey)) {
|
||
logger.debug("围栏检查缓存命中,跳过计算: {}", gpsNo);
|
||
return;
|
||
}
|
||
|
||
// 设置围栏检查缓存(1天)
|
||
redisUtil.set("inFence:" + gpsNo, "1", 1L, TimeUnit.DAYS);
|
||
|
||
// 检查是否配置了围栏
|
||
if (car.getFenceId() == null) {
|
||
logger.debug("车辆未配置围栏: {}", car.getCode());
|
||
return;
|
||
}
|
||
|
||
HjmFence fence = hjmFenceService.getById(car.getFenceId());
|
||
if (fence == null) {
|
||
logger.warn("围栏不存在: {}", car.getFenceId());
|
||
return;
|
||
}
|
||
|
||
// 检查坐标有效性
|
||
if (!isValidCoordinate(car.getLongitude(), car.getLatitude())) {
|
||
logger.warn("车辆坐标无效: 经度={}, 纬度={}", car.getLongitude(), car.getLatitude());
|
||
return;
|
||
}
|
||
|
||
// 执行围栏判断
|
||
performFenceCheck(car, fence);
|
||
|
||
} catch (Exception e) {
|
||
logger.error("检查电子围栏时发生错误", e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查坐标有效性
|
||
*/
|
||
private boolean isValidCoordinate(String longitude, String latitude) {
|
||
return longitude != null && latitude != null &&
|
||
!longitude.trim().isEmpty() && !latitude.trim().isEmpty();
|
||
}
|
||
|
||
/**
|
||
* 执行围栏判断
|
||
*/
|
||
private void performFenceCheck(HjmCar car, HjmFence fence) {
|
||
try {
|
||
double lng = Double.parseDouble(car.getLongitude());
|
||
double lat = Double.parseDouble(car.getLatitude());
|
||
|
||
Point carPoint = new Point();
|
||
carPoint.setLongitude(lng);
|
||
carPoint.setLatitude(lat);
|
||
|
||
// 使用多边形围栏判断
|
||
boolean isInFence = hjmCarServiceImpl.checkPolygonFence(carPoint, fence);
|
||
|
||
// 更新围栏状态
|
||
car.setInFence(isInFence);
|
||
car.setUpdateTime(LocalDateTime.now());
|
||
hjmCarService.updateById(car);
|
||
|
||
logger.info("车辆围栏检查完成: 车辆={}, 围栏={}, 是否在围栏内={}",
|
||
car.getCode(), fence.getId(), isInFence);
|
||
|
||
} catch (NumberFormatException e) {
|
||
logger.error("车辆坐标格式错误: 经度={}, 纬度={}", car.getLongitude(), car.getLatitude(), e);
|
||
} catch (Exception e) {
|
||
logger.error("执行围栏判断时发生错误", e);
|
||
}
|
||
}
|
||
}
|