diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java index 90584f1..05edb1a 100644 --- a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/receive/ReceiveController.java @@ -1,29 +1,32 @@ package com.ruoyi.web.controller.iot.receive; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.IotConstants; import com.ruoyi.common.constant.ServiceConstants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; -import com.ruoyi.common.utils.CommonUtil; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.map.GeoUtils; import com.ruoyi.common.utils.map.GpsCoordinateUtils; +import com.ruoyi.common.utils.onenet.LogEntry; import com.ruoyi.common.utils.onenet.Token; import com.ruoyi.common.utils.uuid.IdUtils; -import com.ruoyi.system.domain.*; -import com.ruoyi.system.mapper.EtLocationLogMapper; +import com.ruoyi.system.domain.AsDevice; +import com.ruoyi.system.domain.EtAdminOrder; +import com.ruoyi.system.domain.EtOnlineLog; +import com.ruoyi.system.domain.EtOperatingArea; +import com.ruoyi.system.mapper.AsDeviceMapper; import com.ruoyi.system.service.*; import com.ruoyi.web.controller.iot.domain.BodyObj; -import com.ruoyi.web.controller.iot.domain.LogEntry; import com.ruoyi.web.controller.iot.util.Util; import lombok.SneakyThrows; import org.jetbrains.annotations.NotNull; +import org.locationtech.jts.geom.Geometry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -37,7 +40,8 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -56,12 +60,11 @@ public class ReceiveController { @Value(value = "${iot.token}") private String token; - @Resource private IAsDeviceService asDeviceService; - @Autowired - private IEtModelService etModelService; + @Resource + private AsDeviceMapper asDeviceMapper; @Resource private IEtOperatingAreaService etOperatingAreaService; @@ -81,18 +84,21 @@ public class ReceiveController { @Autowired private IEtOnlineLogService etOnlineLogService; - @Resource - private EtLocationLogMapper etLocationLogMapper; - private final Object lock = new Object(); @Autowired private RedisCache redisCache; + @Autowired + private ISysConfigService sysConfigService; + // 用于记录上次发送命令的时间 private static AtomicLong lastCommandTime = new AtomicLong(0); private static final long COMMAND_COOLDOWN_MS = 5 * 60 * 1000; // 5分钟 + private static final Map commandCooldownMap = new ConcurrentHashMap<>(); + private static final long COOLDOWN_PERIOD_MS = 10000; // 冷却时间为 10 秒 + /** * 功能描述:第三方平台数据接收。

@@ -113,29 +119,22 @@ public class ReceiveController { log.info("receive方法接收到参数: body String --- " +body); /************************************************ * 解析数据推送请求,非加密模式。 - * 如果是明文模式使用以下代码 hangdleBody(body, false); ChatGPT is under heavy load + * 如果是明文模式使用以下代码 hangdleBody(body, false); **************************************************/ /*************明文模式 start****************/ BodyObj obj = Util.resolveBody(body, false); log.info("receive方法解析对象: body Object --- " + JSON.toJSONString(obj)); log.info("接收到receive方法时间: " + System.currentTimeMillis()); - /** */ - // 起一个异步线程处理数据 - scheduledExecutorService.schedule(() -> { - new Thread(() -> { - synchronized (lock) { - try { - handleBody(obj); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (InvalidKeyException e) { - e.printStackTrace(); - } + // 使用线程池提交任务 + scheduledExecutorService.submit(() -> { + synchronized (lock) { + try { + handleBody(obj); + } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) { + e.printStackTrace(); } - }).start(); - }, 0, TimeUnit.SECONDS); + } + }); /*************明文模式 end****************/ return "ok"; } @@ -144,160 +143,143 @@ public class ReceiveController { if (obj != null){ boolean dataRight = Util.checkSignature(obj, token); -// elec_device_设备id_info:{ccccccc} - // 1. handleBody中的所有保存数据库的操作全部保存在redis中 - // 2. 定时任务:更新设备的在线状态, 5秒一次 - // 3. 定时任务:更新设备的定位和电压, 5秒一次 - // 4. 定时任务:插入onenet定位信息, 5秒一次 - // 5. 获取最新的定位从redis中获取 - if (dataRight){ + // 记录开始时间 + long startTime = System.nanoTime(); + log.info("receive方法验证签名正确: content" + JSON.toJSONString(obj)); String msg = (String) obj.getMsg(); log.info("receive方法-获取到消息体: msg---" +msg); JSONObject jsonObject = JSONObject.parseObject(msg, JSONObject.class); String devName = (String)jsonObject.get("dev_name"); /*异步更新在线状态*/ - AsDevice asDevice = asDeviceService.selectAsDeviceByMac(devName); + AsDevice asDevice = asDeviceMapper.selectAsDeviceByMac(devName); asynchronousUpdateOnlineStatus(asDevice); if(IotConstants.ONENET_LOCATION.equals(jsonObject.get("ds_id")) && ObjectUtil.isNotNull(jsonObject.get("value"))){ LogEntry logEntry = JSONObject.parseObject(msg, LogEntry.class); log.info("logEntry转换后的对象: logEntry---【{}】" , JSON.toJSONString(logEntry)); LogEntry.LocationValue value = logEntry.getValue(); - /**如果是定位日志则,获取到车辆mac,找到对应车辆 - * 1.更新车辆定位、计算续航里程 - * 2.判断是否在禁行区内,如果在,根据配置‘禁行区内断电配置’进行断电, - * 3.超出运营区外断电 - * 4.行程线路添加,更新订单中的trip_route字段 - * 5.低于电量(%)不得骑行,声音播报 - * 6.低电量 生成换电工单 - * 7.运营边界判断 - * */ - /** 1.更新车辆定位、电压;计算续航里程 */ - AsDevice device = asDeviceService.selectAsDeviceByMac(logEntry.getDevName()); - if(ObjectUtil.isNull(device)){ + if(ObjectUtil.isNull(asDevice)){ throw new ServiceException("未找到车辆信息"); } - if(ObjectUtil.isNotNull(device)){ + if(ObjectUtil.isNotNull(asDevice)){ + // 将msg的定位信息保存到redis中 + redisCache.setCacheObject(CacheConstants.CACHE_DEVICE_KEY+asDevice.getSn(),msg); + log.info("reids更新定位成功==========================>" +asDevice.getSn()); + // 坐标转换 WGS84 转 GCJ02 double[] doubles = coordinateConvert(value); BigDecimal lat = new BigDecimal(doubles[0]).setScale(8, RoundingMode.HALF_UP); BigDecimal lon = new BigDecimal(doubles[1]).setScale(8, RoundingMode.HALF_UP); log.info("转换后的GCJ02经纬度:" + lon + "---" + lat); - asynchronousSaveLog(msg,logEntry.getAt(),devName, lon,lat,device); - if(BigDecimal.ZERO.compareTo(lon) != 0 && BigDecimal.ZERO.compareTo(lat) != 0){ - // 计算电量和里程后更新设备 - int i = updateDevice(value, device, lon, lat); - if(i>0){ - log.info("更新定位成功==========================>" +logEntry.getDevName()); - EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(device.getAreaId()); - if(ObjectUtil.isNotNull(area)){ - /** 2. 判断是否在禁行区内 如果在, 根据配置‘禁行区内断电配置’进行断电 **/ - String isAdminUnlocking = device.getIsAdminUnlocking();// 是否是管理员开锁:0-否;1-是 - boolean noRidingArea = isNoRidingArea(value, device, area, isAdminUnlocking); - /** 3.超出运营区外断电*/ - outAreaOutage(value, device, lon, lat, area, isAdminUnlocking, noRidingArea); - /** 4.锁同步关锁 */ - lockSynchronization(msg, asDevice, logEntry, value, device, lon, lat); - /** 5.低电量 生成换电工单*/ - replacementOrder(device, area); - } - }else{ - log.info("更新定位失败:" +logEntry.getDevName()); - } - }else{ - log.info("----------------无定位更新设备----------------" + logEntry.getDevName()); - noLocationUpdateDevice(logEntry, value, device); + + EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(asDevice.getAreaId()); + // 相差一分钟以上的消息不做处理 + boolean oneMinuteDifference = DateUtils.oneMinuteDifference(logEntry.getAt(), System.currentTimeMillis()); + if(ObjectUtil.isNotNull(area) && !oneMinuteDifference){ + /** 2. 判断是否在禁行区内 如果在, 根据配置‘禁行区内断电配置’进行断电 **/ + String isAdminUnlocking = asDevice.getIsAdminUnlocking();// 是否是管理员开锁:0-否;1-是 + boolean noRidingArea = isNoRidingArea(value, asDevice, area, isAdminUnlocking); + /** 3.超出运营区外断电 包含靠近运营区播报 */ + outAreaOutage(value, asDevice, lon, lat, area, isAdminUnlocking, noRidingArea); + /** 4.锁同步关锁 */ + lockSynchronization(msg, asDevice, logEntry, value, asDevice, lon, lat); + /** 5.低电量 生成换电工单*/ + replacementOrder(asDevice, area); } }else{ log.info("未找到车辆对象:" +logEntry.getDevName()); } } + // 计算执行时间(以毫秒为单位) + long duration = (System.nanoTime() - startTime) / 1_000_000; + // 输出执行时间 + log.info("===============回调方法执行时间============= " + duration + " 毫秒"); }else { log.info("receive方法验证签名错误: signature error"); } - }else { log.info("receive方法参数为空: body empty error"); } } - /** - * 异步保存定位 - */ - private void asynchronousSaveLog(String msg, long at,String mac,BigDecimal lon,BigDecimal lat,AsDevice device) { - //异步保存定位 - scheduledExecutorService.schedule(() -> { - EtLocationLog etLocationLog = new EtLocationLog(); - etLocationLog.setOnenetMsg(msg); - etLocationLog.setCreateTime(DateUtils.getNowDate()); - etLocationLog.setLongitude(lon.toString()); - etLocationLog.setLatitude(lat.toString()); - etLocationLog.setMac(mac); - etLocationLog.setAt(new Date(at)); - etLocationLog.setStatus(device.getStatus()); - etLocationLog.setLockStatus(device.getLockStatus()); - etLocationLogMapper.insertEtLocationLog(etLocationLog); - }, 0, TimeUnit.SECONDS); - } - - /** 无定位更新设备 */ - private void noLocationUpdateDevice(LogEntry logEntry, LogEntry.LocationValue value, AsDevice device) { - Integer bat = value.getBat(); - BigDecimal divide = new BigDecimal(bat).divide(new BigDecimal(10)); - log.info("保存电压:" + divide); - device.setVoltage(divide.toString());//电压 - EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); - // 根据电压计算续航里程 - if(ObjectUtil.isNotNull(model)){ - Integer remainingMileage = 0; - if(StrUtil.isNotBlank(device.getVoltage())){ - remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); - } - Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage());//电量百分百 - device.setRemainingMileage(remainingMileage); - device.setRemainingPower(electricQuantity.toString()); - } - device.setLastTime(DateUtils.getNowDate()); - device.setGps("0"); - device.setSignalStrength(value.getCsq()); - device.setSatellites(0); - device.setQuality(value.getQ()); - int i = asDeviceService.updateLocation(device); - if(i>0){ - log.info("未获取到定位===============保存电压等数值成功===========>" + logEntry.getDevName()); - } - } - - /** 计算电量和里程后更新设备*/ - private int updateDevice(LogEntry.LocationValue value, AsDevice device, BigDecimal lon, BigDecimal lat) { - device.setLatitude(lat.toString()); - device.setLongitude(lon.toString()); - Integer bat = value.getBat(); - BigDecimal divide = new BigDecimal(bat).divide(new BigDecimal(10)); - log.info("保存电压:" + divide); - device.setVoltage(divide.toString());//电压 - // 根据电压计算续航里程 - EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); - if(ObjectUtil.isNotNull(model)){ - Integer remainingMileage = 0; - if(StrUtil.isNotBlank(device.getVoltage())){ - remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); - } - Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage());//电量百分百 - device.setRemainingMileage(remainingMileage); - device.setRemainingPower(electricQuantity.toString()); - } - device.setLastTime(DateUtils.getNowDate()); - device.setLastLocationTime(DateUtils.getNowDate()); - device.setGps("1"); - // 信号强度 - device.setSignalStrength(value.getCsq()); - device.setSatellites(value.getS()); - device.setQuality(value.getQ()); - int i = asDeviceService.updateLocation(device); - return i; - } +// 移动到定时任务中处理 +// /** +// * 异步保存定位 +// */ +// private void asynchronousSaveLog(String msg, long at,String mac,BigDecimal lon,BigDecimal lat,AsDevice device) { +// //异步保存定位 +// scheduledExecutorService.schedule(() -> { +// EtLocationLog etLocationLog = new EtLocationLog(); +// etLocationLog.setOnenetMsg(msg); +// etLocationLog.setCreateTime(DateUtils.getNowDate()); +// etLocationLog.setLongitude(lon.toString()); +// etLocationLog.setLatitude(lat.toString()); +// etLocationLog.setMac(mac); +// etLocationLog.setAt(new Date(at)); +// etLocationLog.setStatus(device.getStatus()); +// etLocationLog.setLockStatus(device.getLockStatus()); +// etLocationLogMapper.insertEtLocationLog(etLocationLog); +// }, 0, TimeUnit.SECONDS); +// } +// +// /** 无定位更新设备 */ +// private void noLocationUpdateDevice(LogEntry logEntry, LogEntry.LocationValue value, AsDevice device) { +// Integer bat = value.getBat(); +// BigDecimal divide = new BigDecimal(bat).divide(new BigDecimal(10)); +// log.info("保存电压:" + divide); +// device.setVoltage(divide.toString());//电压 +// EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); +// // 根据电压计算续航里程 +// if(ObjectUtil.isNotNull(model)){ +// Integer remainingMileage = 0; +// if(StrUtil.isNotBlank(device.getVoltage())){ +// remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); +// } +// Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage());//电量百分百 +// device.setRemainingMileage(remainingMileage); +// device.setRemainingPower(electricQuantity.toString()); +// } +// device.setLastTime(DateUtils.getNowDate()); +// device.setGps("0"); +// device.setSignalStrength(value.getCsq()); +// device.setSatellites(0); +// device.setQuality(value.getQ()); +// int i = asDeviceService.updateLocation(device); +// if(i>0){ +// log.info("未获取到定位===============保存电压等数值成功===========>" + logEntry.getDevName()); +// } +// } +// +// /** 计算电量和里程后更新设备*/ +// private int updateDevice(LogEntry.LocationValue value, AsDevice device, BigDecimal lon, BigDecimal lat) { +// device.setLatitude(lat.toString()); +// device.setLongitude(lon.toString()); +// Integer bat = value.getBat(); +// BigDecimal divide = new BigDecimal(bat).divide(new BigDecimal(10)); +// log.info("保存电压:" + divide); +// device.setVoltage(divide.toString());//电压 +// // 根据电压计算续航里程 +// EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); +// if(ObjectUtil.isNotNull(model)){ +// Integer remainingMileage = 0; +// if(StrUtil.isNotBlank(device.getVoltage())){ +// remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); +// } +// Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage());//电量百分百 +// device.setRemainingMileage(remainingMileage); +// device.setRemainingPower(electricQuantity.toString()); +// } +// device.setLastTime(DateUtils.getNowDate()); +// device.setLastLocationTime(DateUtils.getNowDate()); +// device.setGps("1"); +// // 信号强度 +// device.setSignalStrength(value.getCsq()); +// device.setSatellites(value.getS()); +// device.setQuality(value.getQ()); +// int i = asDeviceService.updateLocation(device); +// return i; +// } /** 坐标转换 */ @NotNull @@ -330,20 +312,36 @@ public class ReceiveController { /** 超出运营区断电*/ private void outAreaOutage(LogEntry.LocationValue value, AsDevice device, BigDecimal lon, BigDecimal lat, EtOperatingArea area, String isAdminUnlocking, boolean noRidingArea) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { - boolean isAreaZone = asDeviceService.isAreaZone(device.getSn(), area); - if(!isAreaZone){ - //是否在30米范围内 - boolean inPolygon = GeoUtils.isInPolygonWithTolerance(lon.toString(), lat.toString(), GeoUtils.fromWkt(area.getBoundary()), 60); - if(inPolygon && !isAdminUnlocking.equals("1")){ + String nearBoundaryDistanceConfig = sysConfigService.selectConfigByKey("near.boundary.distance");// 靠近运营区边界时的播报距离 + log.info("靠近运营区边界时的播报距离==================:" + nearBoundaryDistanceConfig); + double nearBoundaryDistance = Double.parseDouble(nearBoundaryDistanceConfig); + String exceedArea = sysConfigService.selectConfigByKey("exceed.area.distance");// 超出运营区外断电距离 + log.info("超出运营区外断电距离================:" + exceedArea); + int exceedDistance = Integer.parseInt(exceedArea); + + // 创建多边形对象 + Geometry polygon = GeoUtils.fromWkt(area.getBoundary()); + // 是否在缩短后的运营区边界内 + boolean inPolygonWithTolerance = GeoUtils.isInPolygonWithShorten(lon.toString(), lat.toString(), polygon, nearBoundaryDistance); + log.info("=========================是否在缩短后的运营区边界内:{},车辆定位=【{},{}】边界=【{}】",inPolygonWithTolerance,lon,lat,polygon); + if(!inPolygonWithTolerance && !isAdminUnlocking.equals("1") && device.getStatus().equals(ServiceConstants.VEHICLE_STATUS_IN_USING)){//没有在缩短后的运营区边界内 + boolean inPolygonWithTolerance1 = GeoUtils.isInPolygonWithTolerance(lon.toString(), lat.toString(), polygon, 0);// 是否在运营区内 + boolean isNearBoundary = GeoUtils.isInPolygonWithTolerance(lon.toString(), lat.toString(), polygon, exceedDistance ); + if(inPolygonWithTolerance1){//是否在运营区边界内 + // 在靠近运营区边界时发警报 + log.info("靠近运营区边界发警告命令===============--SN:" + device.getSn()); + sendCommandWithCooldown(device, IotConstants.COMMAND_PLAY2, "靠近运营区边界"); + returnPower(device, noRidingArea); + }else if(isNearBoundary){ // 是否在超出运营区边界多少米内 //在20米范围内,发报警 - log.info("超出运营区30米内发送警告命令--SN:" + device.getSn()); - asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_PLAY3, "超出运营区30米内",null,null); + log.info("超出运营区"+exceedDistance+"米内发送警告命令==============--SN:" + device.getSn()); + sendCommandWithCooldown(device, IotConstants.COMMAND_PLAY3, "超出运营区" + exceedDistance + "米内"); }else{ - //超出运营区外断电 + // 超出运营区外断电 String areaOutOutage = area.getAreaOutOutage(); - if (areaOutOutage.equals("1") && value.getStatus() != 3 && !isAdminUnlocking.equals("1")) { // 超出营运区断电 - log.info("超出营运区断电命令--SN:" + device.getSn()); - asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_QLOSE+IotConstants.COMMAND_FREQUENCY_5, "超出营运区断电",null,null); + if (areaOutOutage.equals("1")) { // 超出营运区断电 + log.info("超出营运区断电命令=================--SN:" + device.getSn()); + sendCommandWithCooldown(device, IotConstants.COMMAND_QLOSE + IotConstants.COMMAND_FREQUENCY_5, "超出营运区断电"); device.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); int updateAsDevice = asDeviceService.updateAsDevice(device); if (updateAsDevice > 0) { @@ -352,22 +350,49 @@ public class ReceiveController { } } }else{ - // 判断该车辆是否在进行中的订单,并且车辆的锁状态是关,状态是骑行中 - Boolean inOrderBySn = etOrderService.isInOrderBySn(device.getSn()); - if (inOrderBySn && ServiceConstants.VEHICLE_STATUS_IN_USING.equals(device.getStatus()) && ServiceConstants.LOCK_STATUS_CLOSE.equals(device.getLockStatus())) { // 有正在骑行的订单,给车辆上电 - if(!noRidingArea){ - log.info("返回营运区上电,有正在骑行的订单,给车辆上电--SN:" + device.getSn()); - asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_OPEN+IotConstants.COMMAND_FREQUENCY_5, "返回营运区上电",null,null); - // 更新车辆状态和锁状态 - /** 3.更新车辆状态*/ - device.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); - device.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING); - device.setIsAdminUnlocking("0"); - int i1 = asDeviceService.updateAsDevice(device); - if(i1>1){ - log.info("【返回营运区上电】更新车辆状态成功"); - } - } + log.info("在缩短后的运营区边界内====================:" + device.getSn()); + returnPower(device, noRidingArea); + } + } + + /** 带20秒的冷却时间 */ + private void sendCommandWithCooldown(AsDevice device, String command, String message) { + String deviceKey = device.getSn() + "_" + command; // 使用设备SN和命令作为唯一标识 + long currentTime = System.currentTimeMillis(); + Long lastCommandTime = commandCooldownMap.get(deviceKey); + + if (lastCommandTime == null || (currentTime - lastCommandTime) > COOLDOWN_PERIOD_MS) { + // 冷却时间已过,可以发送命令 + try { + asDeviceService.sendCommand(device.getMac(), Token.getToken(), command, message, null, null); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } + commandCooldownMap.put(deviceKey, currentTime); // 更新命令发送时间 + } else { + log.info("命令在冷却时间内,不执行命令==============--SN:" + device.getSn()); + } + } + + /** 返回运营区上电 */ + private void returnPower(AsDevice device, boolean noRidingArea) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException { + // 判断该车辆是否在进行中的订单,并且车辆的锁状态是关,状态是骑行中 + Boolean inOrderBySn = etOrderService.isInOrderBySn(device.getSn()); + if (inOrderBySn && ServiceConstants.VEHICLE_STATUS_IN_USING.equals(device.getStatus()) && ServiceConstants.LOCK_STATUS_CLOSE.equals(device.getLockStatus()) && !noRidingArea) { // 有正在骑行的订单,给车辆上电 + log.info("返回营运区上电,有正在骑行的订单,给车辆上电--SN:" + device.getSn()); + asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_OPEN+IotConstants.COMMAND_FREQUENCY_5, "返回营运区上电",null,null); + // 更新车辆状态和锁状态 + /** 3.更新车辆状态*/ + device.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN); + device.setStatus(ServiceConstants.VEHICLE_STATUS_IN_USING); + device.setIsAdminUnlocking("0"); + int i1 = asDeviceMapper.updateAsDevice(device); + if(i1>1){ + log.info("【返回营运区上电】更新车辆状态成功"); } } } @@ -379,9 +404,9 @@ public class ReceiveController { String noRidingOutage = area.getNoRidingOutage(); if (noRidingOutage.equals("1") && value.getStatus() != 3 && !isAdminUnlocking.equals("1")) { // 禁行区内断电 log.info("禁行区内断电命令--SN:" + device.getSn()); - asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_QLOSE+IotConstants.COMMAND_FREQUENCY_5, "禁行区内断电",null,null); + sendCommandWithCooldown(device, IotConstants.COMMAND_QLOSE + IotConstants.COMMAND_FREQUENCY_5, "禁行区内断电"); device.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); - int updateAsDevice = asDeviceService.updateAsDevice(device); + int updateAsDevice = asDeviceMapper.updateAsDevice(device); if (updateAsDevice > 0) { log.info("禁行区内断电--更新设备锁状态成功SN:" + device.getSn()); } @@ -391,7 +416,7 @@ public class ReceiveController { boolean inPolygon = asDeviceService.isNoRidingAreaWithTolerance(device.getSn(), device.getAreaId(),30); if (inPolygon && !isAdminUnlocking.equals("1")) { log.info("距离禁行区20米内发送警告命令--SN:" + device.getSn()); - asDeviceService.sendCommand(device.getMac(), Token.getToken(), IotConstants.COMMAND_PLAY2, "距离禁行区30米内",null,null); + sendCommandWithCooldown(device, IotConstants.COMMAND_PLAY2, "距离禁行区30米内"); } } return noRidingArea; diff --git a/electripper-admin/src/main/resources/application-druid.yml b/electripper-admin/src/main/resources/application-druid.yml index e174da8..355b4b5 100644 --- a/electripper-admin/src/main/resources/application-druid.yml +++ b/electripper-admin/src/main/resources/application-druid.yml @@ -6,7 +6,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/electripper?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/ele2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 # url: jdbc:mysql://117.26.179.22:61110/electripper?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 diff --git a/electripper-admin/src/test/java/MainTests.java b/electripper-admin/src/test/java/MainTests.java index cae180b..7e9bd73 100644 --- a/electripper-admin/src/test/java/MainTests.java +++ b/electripper-admin/src/test/java/MainTests.java @@ -1,4 +1,3 @@ -import com.alibaba.fastjson2.JSONObject; import com.ruoyi.RuoYiApplication; import com.ruoyi.common.constant.IotConstants; import com.ruoyi.common.constant.ServiceConstants; @@ -9,13 +8,11 @@ import com.ruoyi.system.domain.EtOperatingArea; import com.ruoyi.system.service.IAsDeviceService; import com.ruoyi.system.service.IEtOperatingAreaService; import com.ruoyi.system.service.ISysConfigService; -import com.ruoyi.web.controller.iot.domain.LogEntry; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.operation.buffer.BufferOp; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -41,6 +38,8 @@ public class MainTests { @Test @SneakyThrows public void testSelectActivityById() { + // 记录开始时间 + long startTime = System.nanoTime(); AsDevice device = asDeviceService.selectAsDeviceByMac("75D30EA71454"); BigDecimal lon = new BigDecimal("120.25709"); @@ -88,6 +87,15 @@ public class MainTests { }else{ log.info("在运营区域内"); } + + // 记录结束时间 + long endTime = System.nanoTime(); + + // 计算执行时间(以毫秒为单位) + long duration = (endTime - startTime) / 1_000_000; + + // 输出执行时间 + System.out.println("Execution time: " + duration + " milliseconds"); } diff --git a/electripper-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/electripper-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java index d2821db..90a837f 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/electripper-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -52,4 +52,9 @@ public class CacheConstants * 登录账户密码错误次数 redis key */ public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; + + /** + * 设备定位信息 redis key + */ + public static final String CACHE_DEVICE_KEY = "device:"; } diff --git a/electripper-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/electripper-common/src/main/java/com/ruoyi/common/utils/DateUtils.java index ad2c212..8ad0186 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/utils/DateUtils.java +++ b/electripper-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -321,4 +321,17 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils ZonedDateTime zonedDateTime = offsetDateTime.toZonedDateTime(); return Date.from(zonedDateTime.toInstant()); } + + /** + * 是否相差一分钟 + * * @param at 第一个时间戳,毫秒级 + * * @param currentTimeMillis 第二个时间戳,毫秒级 + * * @return 如果两个时间戳相差超过一分钟,则返回 true,否则返回 false + */ + public static boolean oneMinuteDifference(long at, long currentTimeMillis) { + // 计算两个时间戳的差值,取绝对值 + long difference = Math.abs(currentTimeMillis - at); + // 判断是否大于 60,000 毫秒(1 分钟) + return difference > 60_000; + } } diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java b/electripper-common/src/main/java/com/ruoyi/common/utils/onenet/LogEntry.java similarity index 94% rename from electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java rename to electripper-common/src/main/java/com/ruoyi/common/utils/onenet/LogEntry.java index 8e61665..ee259c4 100644 --- a/electripper-admin/src/main/java/com/ruoyi/web/controller/iot/domain/LogEntry.java +++ b/electripper-common/src/main/java/com/ruoyi/common/utils/onenet/LogEntry.java @@ -1,4 +1,4 @@ -package com.ruoyi.web.controller.iot.domain; +package com.ruoyi.common.utils.onenet; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java b/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java index 01602a5..8058890 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java +++ b/electripper-system/src/main/java/com/ruoyi/system/task/EtTask.java @@ -3,6 +3,8 @@ package com.ruoyi.system.task; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.constant.CacheConstants; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.ServiceConstants; @@ -10,14 +12,19 @@ import com.ruoyi.common.core.domain.entity.AsUser; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.CommonUtil; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.map.GeoUtils; +import com.ruoyi.common.utils.map.GpsCoordinateUtils; +import com.ruoyi.common.utils.onenet.LogEntry; +import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.system.domain.*; import com.ruoyi.system.mapper.*; import com.ruoyi.system.service.*; import com.wechat.pay.java.service.refund.model.Refund; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -25,12 +32,10 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; @@ -89,6 +94,15 @@ public class EtTask { @Autowired private IAsDeviceService deviceService; + private IEtOnlineLogService etOnlineLogService; + + @Resource + private IAsDeviceService asDeviceService; + + @Autowired + private IEtModelService etModelService; + + /** @@ -524,4 +538,168 @@ public class EtTask { } + + /** + * 更新设备的定位和电压 10秒一次 + * cron: 0 10 0 * * ? + */ + public void updateLocation10(){ + log.info("-------------------【定时任务10秒一次】更新设备的定位和电压-----开始--------------"); + // 记录开始时间 + long startTime = System.nanoTime(); + Collection keys = redisCache.keys(CacheConstants.CACHE_DEVICE_KEY + "*"); + log.info("redis缓存中的数据:" + JSON.toJSONString(keys)); + for(String key:keys){ + String msg = redisCache.getCacheObject(key); + log.info("redis缓存中的数据:" + msg); + LogEntry logEntry = JSONObject.parseObject(msg, LogEntry.class); + log.info("logEntry转换后的对象: logEntry---【{}】" , JSON.toJSONString(logEntry)); + LogEntry.LocationValue value = logEntry.getValue(); + AsDevice device = asDeviceMapper.selectAsDeviceByMac(logEntry.getDevName()); + log.info("device: sn={},【{}】",device.getSn() , JSON.toJSONString(device)); + if(ServiceConstants.LOCK_STATUS_OPEN.equals(device.getLockStatus()) && device.getStatus().equals(ServiceConstants.VEHICLE_STATUS_IN_USING)){ + updateLocationHandle(msg, logEntry, value, device); + } + } + // 计算执行时间(以毫秒为单位) + long duration = (System.nanoTime() - startTime) / 1_000_000; + log.info("-------------------【定时任务10秒一次】更新设备的定位和电压----结束---------------"+duration+ " 毫秒"); + } + + /** + * 更新设备的定位和电压 5分钟一次 + * cron: 0 20 0 * * ? + */ + public void updateLocation300(){ + log.info("-------------------【定时任务5分钟一次】更新设备的定位和电压-----开始--------------"); + // 记录开始时间 + long startTime = System.nanoTime(); + Collection keys = redisCache.keys(CacheConstants.CACHE_DEVICE_KEY + "*"); + log.info("redis缓存中的数据:" + JSON.toJSONString(keys)); + for(String key:keys){ + String msg = redisCache.getCacheObject(key); + log.info("redis缓存中的数据:" + msg); + LogEntry logEntry = JSONObject.parseObject(msg, LogEntry.class); + log.info("logEntry转换后的对象: logEntry---【{}】" , JSON.toJSONString(logEntry)); + LogEntry.LocationValue value = logEntry.getValue(); + AsDevice device = asDeviceMapper.selectAsDeviceByMac(logEntry.getDevName()); + if(ServiceConstants.LOCK_STATUS_CLOSE.equals(device.getLockStatus())){ + updateLocationHandle(msg, logEntry, value, device); + } + } + // 计算执行时间(以毫秒为单位) + long duration = (System.nanoTime() - startTime) / 1_000_000; + log.info("-------------------【定时任务5分钟一次】更新设备的定位和电压----结束---------------"+duration+ " 毫秒"); + } + + private void updateLocationHandle(String msg, LogEntry logEntry, LogEntry.LocationValue value, AsDevice device) { + // 坐标转换 WGS84 转 GCJ02 + double[] doubles = coordinateConvert(value); + BigDecimal lon = new BigDecimal(doubles[1]).setScale(8, RoundingMode.HALF_UP); + BigDecimal lat = new BigDecimal(doubles[0]).setScale(8, RoundingMode.HALF_UP); + + asynchronousSaveLog(msg, logEntry.getAt(), logEntry.getDevName(), lon, lat, device); + + BigDecimal divide = new BigDecimal(value.getBat()).divide(new BigDecimal(10)); + device.setVoltage(divide.toString());//电压 + EtModel model = etModelService.selectEtModelByModelId(device.getModelId()); + device.setLastTime(DateUtils.getNowDate()); + device.setSignalStrength(value.getCsq()); + device.setQuality(value.getQ()); + if(ObjectUtil.isNotNull(model)){ + Integer remainingMileage = 0; + if(StrUtil.isNotBlank(device.getVoltage())){ + remainingMileage = CommonUtil.getRemainingMileage(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage(), model.getFullEndurance()); + } + Integer electricQuantity = CommonUtil.getElectricQuantity(device.getVoltage(), model.getFullVoltage(), model.getLowVoltage());//电量百分百 + device.setRemainingMileage(remainingMileage); + device.setRemainingPower(electricQuantity.toString()); + } + if(BigDecimal.ZERO.compareTo(lon) != 0 && BigDecimal.ZERO.compareTo(lat) != 0){ + device.setLatitude(lat.toString()); + device.setLongitude(lon.toString()); + device.setLastLocationTime(DateUtils.getNowDate()); + device.setGps("1"); + // 信号强度 + device.setSatellites(value.getS()); + }else{ + device.setGps("0"); + device.setSatellites(0); + } + int i = asDeviceService.updateLocation(device); + if(i>0){ + log.info("===============更新设备信息成功===========>" + logEntry.getDevName()); + } + } + + /** + * 异步保存定位 + */ + private void asynchronousSaveLog(String msg, long at,String mac,BigDecimal lon,BigDecimal lat,AsDevice device){ + //异步保存定位 + scheduledExecutorService.schedule(() -> { + EtLocationLog etLocationLog = new EtLocationLog(); + etLocationLog.setOnenetMsg(msg); + etLocationLog.setCreateTime(DateUtils.getNowDate()); + etLocationLog.setLongitude(lon.toString()); + etLocationLog.setLatitude(lat.toString()); + etLocationLog.setMac(mac); + etLocationLog.setAt(new Date(at)); + etLocationLog.setStatus(device.getStatus()); + etLocationLog.setLockStatus(device.getLockStatus()); + etLocationLogMapper.insertEtLocationLog(etLocationLog); + }, 0, TimeUnit.SECONDS); + } + + /** 坐标转换 */ + @NotNull + private double[] coordinateConvert(LogEntry.LocationValue value) { + BigDecimal lon = new BigDecimal(value.getLon()); + BigDecimal lat = new BigDecimal(value.getLat()); +// log.info("WGS84经纬度(未计算):" + lon + "---" + lat); + // 除以100 + lon = lon.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP); + lat = lat.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP); +// log.info("WGS84经纬度(除以100后):" + lon + "---" + lat); + // 取出lon中后面的小数点 + String[] lonStr = getDecimalPart(lon); + String[] latStr = getDecimalPart(lat); +// log.info("WGS84经纬度(截取小数点):" + lonStr[0] + "---" + lonStr[1] + "---"+ latStr[0]+"---"+ latStr[1]); + // 再将结果乘以5/3 + String lon2 = "0."+ lonStr[1]; + String lat2 = "0."+ latStr[1]; + BigDecimal lons = new BigDecimal(lon2).multiply(new BigDecimal(5).divide(new BigDecimal(3), 8, RoundingMode.HALF_UP)); + BigDecimal lats = new BigDecimal(lat2).multiply(new BigDecimal(5).divide(new BigDecimal(3), 8, RoundingMode.HALF_UP)); + BigDecimal lo = new BigDecimal(lonStr[0]).add(lons); + BigDecimal la = new BigDecimal(latStr[0]).add(lats); +// log.info("WGS84经纬度(计算后):" + lo + "---" + la); + lo = lo.setScale(8, RoundingMode.HALF_UP); + la = la.setScale(8, RoundingMode.HALF_UP); +// log.info("WGS84经纬度(保留8为小数):" + lo + "---" + la); + double[] doubles = GpsCoordinateUtils.calWGS84toGCJ02(la.doubleValue(), lo.doubleValue()); + return doubles; + } + + private static String[] getDecimalPart(BigDecimal number) { + // 将BigDecimal转换为字符串 + String numberStr = number.toPlainString(); + + // 找到小数点的位置 + int indexOfDecimal = numberStr.indexOf("."); + + // 初始化结果数组 + String[] parts = new String[2]; + + // 如果有小数点 + if (indexOfDecimal >= 0) { + parts[0] = numberStr.substring(0, indexOfDecimal); // 整数部分 + parts[1] = numberStr.substring(indexOfDecimal + 1); // 小数部分 + } else { + // 如果没有小数点,整数部分为整个字符串,小数部分为空 + parts[0] = numberStr; + parts[1] = ""; + } + + return parts; + } }