From 6be8a70cf41c5a47a3263ade8b927da2644b84e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A3=B7=E5=8F=B6?= <14103883+leaf-phos@user.noreply.gitee.com> Date: Sat, 17 May 2025 12:41:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9C=A8=E7=BA=BF=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/common/constant/CacheConstants.java | 5 ++ .../ruoyi/common/core/redis/RedisCache.java | 15 +++++ .../common/core/redis/enums/RedisLockKey.java | 3 +- .../ruoyi/bst/device/domain/DeviceQuery.java | 3 + .../ruoyi/bst/device/mapper/DeviceMapper.xml | 7 ++ .../bst/device/service/DeviceIotService.java | 17 +++++ .../bst/device/service/DeviceService.java | 5 -- .../service/impl/DeviceIotServiceImpl.java | 64 ++++++++++++++++--- .../service/impl/DeviceServiceImpl.java | 16 ----- .../service/impl/OrderPayHandlerImpl.java | 8 ++- .../service/impl/IotReceiveServiceImpl.java | 3 + .../iot/service/impl/IotServiceImpl.java | 21 ++---- .../com/ruoyi/task/device/DeviceTask.java | 4 +- .../ruoyi/web/app/AppDeviceController.java | 14 +++- .../ruoyi/web/app/AppDeviceIotController.java | 44 +++++++++++-- .../com/ruoyi/web/app/AppOrderController.java | 32 +++++++++- .../com/ruoyi/web/bst/DeviceController.java | 10 ++- .../ruoyi/web/bst/DeviceIotController.java | 49 +++++++++++--- ruoyi-web/src/main/resources/application.yml | 2 +- 19 files changed, 248 insertions(+), 74 deletions(-) diff --git a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java index 2eba440..51b7edc 100644 --- a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -127,4 +127,9 @@ public class CacheConstants * 设备更新物联网状态队列 */ public static final String DEVICE_UPDATE_IOT_QUEUE = "device_update_iot_queue"; + + /** + * 设备在线状态 + */ + public static final String MAC_ONLINE_STATUS = "mac_online_status:"; } diff --git a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java index 9a51658..52303e2 100644 --- a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java +++ b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -341,4 +341,19 @@ public class RedisCache // 执行 Lua 脚本 return (List) redisTemplate.execute(redisScript, Collections.singletonList(key)); } + + /** + * 获取并清空Hash缓存中的所有数据(原子性操作) + * + * @param key 缓存的键值 + * @return Hash中的所有数据 + */ + public List getAndClearHashValues(final String key) { + String scriptText = + "local values = redis.call('HVALS', KEYS[1]) " + + "redis.call('DEL', KEYS[1]) " + + "return values"; + RedisScript> redisScript = new DefaultRedisScript<>(scriptText, (Class>) (Class) List.class); + return (List) redisTemplate.execute(redisScript, Collections.singletonList(key)); + } } diff --git a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/enums/RedisLockKey.java b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/enums/RedisLockKey.java index 0e135b8..be9e240 100644 --- a/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/enums/RedisLockKey.java +++ b/common-ruoyi/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/enums/RedisLockKey.java @@ -18,7 +18,8 @@ public enum RedisLockKey { DEVICE_MAC_UNIQUE("device_mac_unique", "设备MAC唯一"), USER_NAME_UNIQUE("user_name_unique", "用户账号唯一"), DEVICE_IOT_REFRESH("device_iot_refresh", "设备物联网信息刷新"), - DEVICE_UPDATE_IOT_LOCK("device_update_iot_lock", "设备更新物联网信息锁"); + DEVICE_UPDATE_IOT_LOCK("device_update_iot_lock", "设备更新物联网信息锁"), + DEVICE_IOT_OPERATION("device_iot_operation", "设备物联网操作锁"); private final String key; private final String name; diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/domain/DeviceQuery.java b/ruoyi-service/src/main/java/com/ruoyi/bst/device/domain/DeviceQuery.java index 8c893f2..88c39ef 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/domain/DeviceQuery.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/domain/DeviceQuery.java @@ -64,4 +64,7 @@ public class DeviceQuery extends DeviceVO { @DateTimeFormat(pattern = "yyyy-MM-dd") private List createDateRange; + @ApiModelProperty("订单设备状态列表") + private List orderDeviceStatusList; + } diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/mapper/DeviceMapper.xml b/ruoyi-service/src/main/java/com/ruoyi/bst/device/mapper/DeviceMapper.xml index 0187a64..e7d17cb 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/mapper/DeviceMapper.xml +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/mapper/DeviceMapper.xml @@ -131,6 +131,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and bd.last_time <= #{query.lastTimeEnd} and bd.last_time >= #{query.lastTimeStart} and bd.location_type = #{query.locationType} + and bd.order_device_id = #{query.orderDeviceId} and ( bd.sn like concat('%', #{query.keyword}, '%') @@ -155,6 +156,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{item} + + and bod.status in + + #{item} + + and #{query.radius} >= round(st_distance_sphere(point(#{query.center[0]}, #{query.center[1]}), point(bd.longitude, bd.latitude))) diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceIotService.java b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceIotService.java index 6d7493a..481c3db 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceIotService.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceIotService.java @@ -1,5 +1,6 @@ package com.ruoyi.bst.device.service; +import java.time.LocalDateTime; import java.util.Collections; import java.util.List; @@ -8,6 +9,7 @@ import com.ruoyi.bst.device.domain.DeviceVO; import com.ruoyi.bst.device.domain.dto.DeviceBltUploadDTO; import com.ruoyi.bst.device.domain.enums.DeviceUnLockType; import com.ruoyi.bst.device.domain.vo.DeviceIotVO; +import com.ruoyi.bst.device.domain.vo.DeviceOnlineInfo; public interface DeviceIotService { @@ -154,4 +156,19 @@ public interface DeviceIotService { */ int bltUpload(DeviceBltUploadDTO dto); + /** + * 更新设备在线状态 + * @param mac 设备mac地址 + * @param status 在线状态 + * @param at 时间 + */ + int updateDeviceOnlineStatus(String mac, String status, LocalDateTime at); + + /** + * 获取设备在线状态 + * @param mac 设备mac地址 + * @return 设备在线状态 + */ + DeviceOnlineInfo getDeviceOnlineStatus(String mac); + } diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceService.java b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceService.java index 3d1ff3a..dc5f978 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceService.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/DeviceService.java @@ -152,11 +152,6 @@ public interface DeviceService */ public int clearCurrentOrderDevice(Long id, Long orderDeviceId); - /** - * 根据mac更新在线状态 - */ - int updateOnlineStatusByMac(String mac, String onlineStatus); - /** * 划拨运营区 */ diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceIotServiceImpl.java b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceIotServiceImpl.java index aaee3c3..07ac4ee 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceIotServiceImpl.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceIotServiceImpl.java @@ -19,6 +19,7 @@ import com.ruoyi.bst.device.domain.enums.DeviceQuality; import com.ruoyi.bst.device.domain.enums.DeviceStatus; import com.ruoyi.bst.device.domain.enums.DeviceUnLockType; import com.ruoyi.bst.device.domain.vo.DeviceIotVO; +import com.ruoyi.bst.device.domain.vo.DeviceOnlineInfo; import com.ruoyi.bst.device.mapper.DeviceMapper; import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.bst.device.service.DeviceService; @@ -79,6 +80,7 @@ public class DeviceIotServiceImpl implements DeviceIotService { private final static Integer SUB_FAST = 5; // 上报频率(快) private final static Integer SUB_SLOW = 300; // 上报频率(慢) + private final static Integer SUB_X_SLOW = 50; // 上报频率(X开头硬件) @Override public DeviceIotVO unlock(DeviceVO device, DeviceUnLockType type, String reason, boolean requiredIot) { @@ -154,21 +156,27 @@ public class DeviceIotServiceImpl implements DeviceIotService { ServiceUtil.assertion(!DeviceStatus.canUserLock().contains(device.getStatus()), "设备%s当前状态不允许用户锁车", device.getSn()); query.setStatusList(DeviceStatus.canUserLock()); } + if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) { + query.setOrderDeviceId(device.getOrderDeviceId()); + query.setOrderDeviceStatusList(OrderDeviceStatus.inUse()); + } transactionTemplate.execute(status -> { // 更新设备状态 - int rows = deviceMapper.updateByQuerySimple(data, query); + int rows = deviceMapper.updateByQuery(data, query); vo.setDb(rows); if (rows > 0) { // 发送命令锁车 CommandResponse res = null; + + int sub = getDeviceSubSlow(device); if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) { // 临时锁车 - res = iotService.tempLock(device, SUB_SLOW, reason, 3); + res = iotService.tempLock(device, sub, reason, 3); } else { // 锁车 - res = iotService.lock(device, SUB_SLOW, reason, 3); + res = iotService.lock(device, sub, reason, 3); } boolean iot = IotUtil.isSuccess(res); ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED); @@ -181,6 +189,13 @@ public class DeviceIotServiceImpl implements DeviceIotService { return vo; } + public int getDeviceSubSlow(DeviceVO device) { + if (device != null && device.getHardwareVersion() != null && device.getHardwareVersion().startsWith("X")) { + return SUB_X_SLOW; + } + return SUB_SLOW; + } + @Override public DeviceIotVO qLock(DeviceVO device, String reason, boolean requiredIot) { if (device == null || device.getId() == null) { @@ -297,12 +312,10 @@ public class DeviceIotServiceImpl implements DeviceIotService { } public int updateIot(Device device) { - if (device == null) { - return 0; - } - if (StringUtils.isBlank(device.getMac()) && device.getId() == null) { + if (device == null || StringUtils.isBlank(device.getMac())) { return 0; } + Device data = new Device(); data.setMac(device.getMac()); data.setVoltage(device.getVoltage()); @@ -320,7 +333,7 @@ public class DeviceIotServiceImpl implements DeviceIotService { data.setSoftwareVersion(device.getSoftwareVersion()); // 添加到缓存队列 - redisCache.rightPush(CacheConstants.DEVICE_UPDATE_IOT_QUEUE, data); + redisCache.setCacheMapValue(CacheConstants.DEVICE_UPDATE_IOT_QUEUE, data.getMac(), data); return 1; } @@ -385,7 +398,7 @@ public class DeviceIotServiceImpl implements DeviceIotService { } } if (DeviceLockStatus.CLOSE.getCode().equals(device.getLockStatus()) && !DeviceQuality.CLOSE.getCode().equals(device.getQuality())) { - CommandResponse res = iotService.lock(device, SUB_SLOW, "重新尝试锁车", 1); + CommandResponse res = iotService.lock(device, getDeviceSubSlow(device), "重新尝试锁车", 1); if (device.getOrderId() != null) { operLogService.operSysLog("【设备监控】发现未关闭的车辆", IotUtil.isSuccess(res), LogBizType.ORDER, device.getOrderId(), device); } else { @@ -459,4 +472,37 @@ public class DeviceIotServiceImpl implements DeviceIotService { return 1; } + + @Override + public int updateDeviceOnlineStatus(String mac, String status, LocalDateTime at) { + if (StringUtils.isBlank(mac) || StringUtils.isBlank(status)) { + return 0; + } + DeviceOnlineInfo info = this.getDeviceOnlineStatus(mac); + if (info == null) { + info = new DeviceOnlineInfo(); + } + + info.setMac(mac); + info.setStatus(status); + info.setAt(at); + + if (DeviceOnlineStatus.ONLINE.getStatus().equals(status)) { + info.setLastOnlineTime(at); + } + + String key = CacheConstants.MAC_ONLINE_STATUS + mac; + redisCache.setCacheObject(key, info); + return 1; + } + + @Override + public DeviceOnlineInfo getDeviceOnlineStatus(String mac) { + if (StringUtils.isBlank(mac)) { + return null; + } + String key = CacheConstants.MAC_ONLINE_STATUS + mac; + return redisCache.getCacheObject(key); + } + } diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceServiceImpl.java b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceServiceImpl.java index 37fd305..8123cd4 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceServiceImpl.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/device/service/impl/DeviceServiceImpl.java @@ -1,6 +1,5 @@ package com.ruoyi.bst.device.service.impl; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -34,7 +33,6 @@ import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ServiceUtil; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.collection.CollectionUtils; -import com.ruoyi.iot.service.impl.DeviceOnlineStatus; import lombok.extern.slf4j.Slf4j; @@ -480,20 +478,6 @@ public class DeviceServiceImpl implements DeviceService return deviceMapper.clearCurrentOrderDevice(id, orderDeviceId); } - @Override - public int updateOnlineStatusByMac(String mac, String onlineStatus) { - if (StringUtils.isBlank(mac) || StringUtils.isBlank(onlineStatus)) { - return 0; - } - Device data = new Device(); - data.setOnlineStatus(onlineStatus); - if (DeviceOnlineStatus.ONLINE.getStatus().equals(onlineStatus)) { - data.setLastTime(LocalDateTime.now()); - } - data.setMac(mac); - return deviceIotService.updateIot(data); - } - @Override public int transfer(List deviceList, Long areaId) { if (CollectionUtils.isEmptyElement(deviceList) || areaId == null) { diff --git a/ruoyi-service/src/main/java/com/ruoyi/bst/order/service/impl/OrderPayHandlerImpl.java b/ruoyi-service/src/main/java/com/ruoyi/bst/order/service/impl/OrderPayHandlerImpl.java index 79d6656..8d0f4f0 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/bst/order/service/impl/OrderPayHandlerImpl.java +++ b/ruoyi-service/src/main/java/com/ruoyi/bst/order/service/impl/OrderPayHandlerImpl.java @@ -93,13 +93,15 @@ public class OrderPayHandlerImpl implements PayHandler { int start = orderDeviceService.start(orderDevice); ServiceUtil.assertion(start != 1, "开始使用ID为%s的订单设备失败", orderDevice.getId()); + return rows; + }); + + if (result != null && result == 1) { // 解锁设备 DeviceIotVO unlock = deviceIotService.unlock(orderDevice.getDeviceId(), DeviceUnLockType.USER, "订单支付成功开锁:" + order.getNo(), false); operLogService.operSysLog("订单支付成功开锁", unlock.isIotSuccess(), LogBizType.ORDER, order.getId(), orderDevice.getDeviceId(), orderDevice.getDeviceLongitude(), orderDevice.getDeviceLatitude()); ServiceUtil.assertion(unlock.getDb() <= 0, "ID为%s的设备解锁失败", orderDevice.getDeviceId()); - - return rows; - }); + } return result != null && result == 1; } diff --git a/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotReceiveServiceImpl.java b/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotReceiveServiceImpl.java index 274c2e5..f4b08ac 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotReceiveServiceImpl.java +++ b/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotReceiveServiceImpl.java @@ -112,6 +112,9 @@ public class IotReceiveServiceImpl implements IotReceiveService { // 增加更新频率控制 this.updateDeviceIot(device); + // 更新设备在线状态 + deviceIotService.updateDeviceOnlineStatus(device.getMac(), DeviceOnlineStatus.ONLINE.getStatus(), at); + // 定位无效,不处理 if (device.getLongitude() == null || device.getLatitude() == null) { log.info("设备{}定位无效,不处理", device.getMac()); diff --git a/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotServiceImpl.java b/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotServiceImpl.java index b6c5872..d34b3ea 100644 --- a/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotServiceImpl.java +++ b/ruoyi-service/src/main/java/com/ruoyi/iot/service/impl/IotServiceImpl.java @@ -1,5 +1,6 @@ package com.ruoyi.iot.service.impl; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -17,7 +18,7 @@ import com.alibaba.fastjson2.JSONObject; import com.ruoyi.bst.commandLog.domain.CommandLog; import com.ruoyi.bst.commandLog.domain.enums.CommandLogType; import com.ruoyi.bst.commandLog.service.CommandLogService; -import com.ruoyi.bst.device.service.DeviceService; +import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.model.LoginUser; @@ -87,7 +88,7 @@ public class IotServiceImpl implements IotService { private RedisLock redisLock; @Autowired - private DeviceService deviceService; + private DeviceIotService deviceIotService; @Autowired private ScheduledExecutorService scheduledExecutorService; @@ -306,19 +307,9 @@ public class IotServiceImpl implements IotService { String status = this.parseToOnlineStatus(res, deviceName, true); redisCache.setCacheObject(this.getOnlineCacheKey(deviceName), status, 10, TimeUnit.SECONDS); - // 异步更新设备在线状态 - boolean lock = redisLock.lock(RedisLockKey.DEVICE_UPDATE_IOT_LOCK, deviceName, 60L); - if (lock ) { - scheduledExecutorService.execute(() -> { - int update = deviceService.updateOnlineStatusByMac(deviceName, status); - if (update != 1) { - log.error("异步更新设备在线状态失败,MAC={},status={}", deviceName, status); - } - }); - } else { - log.info("设备{}更新太频繁,跳过本次更新", deviceName); - } - + // 更新设备在线状态 + deviceIotService.updateDeviceOnlineStatus(deviceName, status, LocalDateTime.now()); + return res; } catch (Exception e) { this.addCommandLog(device, command, "操作失败:" + e.getMessage(), reason, null, result); diff --git a/ruoyi-web/src/main/java/com/ruoyi/task/device/DeviceTask.java b/ruoyi-web/src/main/java/com/ruoyi/task/device/DeviceTask.java index c4540a6..50cbae6 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/task/device/DeviceTask.java +++ b/ruoyi-web/src/main/java/com/ruoyi/task/device/DeviceTask.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import com.ruoyi.common.utils.collection.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -14,6 +13,7 @@ import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.bst.device.service.DeviceService; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.collection.CollectionUtils; import com.ruoyi.iot.service.impl.DeviceOnlineStatus; import lombok.extern.slf4j.Slf4j; @@ -53,7 +53,7 @@ public class DeviceTask { public void refreshIot() { // 从队列中获取数据 - List deviceList = redisCache.getAndClearCacheList(CacheConstants.DEVICE_UPDATE_IOT_QUEUE); + List deviceList = redisCache.getAndClearHashValues(CacheConstants.DEVICE_UPDATE_IOT_QUEUE); if (CollectionUtils.isEmptyElement(deviceList)) { return ; } diff --git a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceController.java b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceController.java index 488741a..9558e63 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceController.java +++ b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.app; import java.math.BigDecimal; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -9,7 +10,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.bst.device.domain.DeviceQuery; +import com.ruoyi.bst.device.domain.DeviceVO; import com.ruoyi.bst.device.domain.enums.DeviceStatus; +import com.ruoyi.bst.device.service.DeviceAssembler; import com.ruoyi.bst.device.service.DeviceService; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; @@ -24,6 +27,9 @@ public class AppDeviceController extends BaseController { @Autowired private DeviceService deviceService; + @Autowired + private DeviceAssembler deviceAssembler; + @ApiOperation("获取附近可用车辆列表") @GetMapping("/listNearBy") @Anonymous @@ -38,14 +44,18 @@ public class AppDeviceController extends BaseController { return error("中心坐标格式错误,必须包含经度和纬度"); } query.setStatus(DeviceStatus.AVAILABLE.getCode()); - return success(deviceService.selectDeviceList(query)); + List list = deviceService.selectDeviceList(query); + deviceAssembler.assembleOnlineStatus(list); + return success(list); } @ApiOperation("获取可用车辆详情") @GetMapping("/availableDetail") @Anonymous public AjaxResult getAvailableDetail(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { - return success(deviceService.selectAvaliableDevice(id, sn)); + DeviceVO device = deviceService.selectAvaliableDevice(id, sn); + deviceAssembler.assembleOnlineStatus(device); + return success(device); } } diff --git a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceIotController.java b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceIotController.java index 13bc789..986011c 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceIotController.java +++ b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppDeviceIotController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.app; import java.math.BigDecimal; +import java.util.function.Supplier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; @@ -17,6 +18,8 @@ import com.ruoyi.bst.device.service.DeviceService; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisLock; +import com.ruoyi.common.core.redis.enums.RedisLockKey; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.LogBizType; import com.ruoyi.common.utils.MathUtils; @@ -36,6 +39,36 @@ public class AppDeviceIotController extends BaseController { @Autowired private DeviceService deviceService; + @Autowired + private RedisLock redisLock; + + /** + * 执行带设备锁的操作 + * @param device 设备信息 + * @param operation 具体操作 + * @return 操作结果 + */ + private AjaxResult executeWithLock(Long deviceId, Supplier operation) { + ServiceUtil.assertion(deviceId == null, "设备ID不能为空"); + // 获取设备锁 + boolean lock = redisLock.lock(RedisLockKey.DEVICE_IOT_OPERATION, deviceId); + if (!lock) { + return AjaxResult.error("设备操作过于频繁,请稍后再试"); + } + try { + return operation.get(); + } finally { + redisLock.unlock(RedisLockKey.DEVICE_IOT_OPERATION, deviceId); + } + } + + private DeviceVO getDevice(Long id, String sn) { + if (id != null) { + return deviceService.selectDeviceById(id); + } else { + return deviceService.selectDeviceBySn(sn); + } + } @ApiOperation("用户响铃寻车") @PutMapping("/ring") @@ -44,12 +77,7 @@ public class AppDeviceIotController extends BaseController { @RequestParam(required = false) String sn, @RequestParam(required = false) BigDecimal lon, @RequestParam(required = false) BigDecimal lat) { - DeviceVO device = null; - if (id != null) { - device = deviceService.selectDeviceById(id); - } else { - device = deviceService.selectDeviceBySn(sn); - } + DeviceVO device = getDevice(id, sn); ServiceUtil.assertion(device == null, "当前车辆不存在,无法响铃寻车"); if (device.getAreaRequiredRingRadius() != null && device.getAreaRequiredRingRadius()) { @@ -59,7 +87,9 @@ public class AppDeviceIotController extends BaseController { "您当前距离车辆%s米,无法响铃寻车。请距离车辆%s米以内再试", distance, device.getAreaRingRadius()); } - return toAjax(deviceIotService.play(device, IotConstants.PLAY_WARNING, "用户响铃寻车", true)); + return executeWithLock(device.getId(), () -> { + return toAjax(deviceIotService.play(device, IotConstants.PLAY_WARNING, "用户响铃寻车", true)); + }); } @ApiOperation("蓝牙上传设备信息") diff --git a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppOrderController.java b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppOrderController.java index 9632388..32e2849 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/web/app/AppOrderController.java +++ b/ruoyi-web/src/main/java/com/ruoyi/web/app/AppOrderController.java @@ -1,6 +1,7 @@ package com.ruoyi.web.app; import java.util.List; +import java.util.function.Supplier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; @@ -33,6 +34,8 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.redis.RedisLock; +import com.ruoyi.common.core.redis.enums.RedisLockKey; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.LogBizType; import com.ruoyi.common.utils.LogParamHolder; @@ -53,6 +56,27 @@ public class AppOrderController extends BaseController { @Autowired private OrderValidator orderValidator; + @Autowired + private RedisLock redisLock; + + /** + * 执行带设备锁的操作 + * @param device 设备信息 + * @param operation 具体操作 + * @return 操作结果 + */ + private AjaxResult executeWithLock(Long deviceId, Supplier operation) { + ServiceUtil.assertion(deviceId == null, "设备ID不能为空"); + Long lockKey = deviceId; + boolean lock = redisLock.lock(RedisLockKey.DEVICE_IOT_OPERATION, lockKey); + ServiceUtil.assertion(!lock, "设备操作过于频繁,请稍后再试"); + try { + return operation.get(); + } finally { + redisLock.unlock(RedisLockKey.DEVICE_IOT_OPERATION, lockKey); + } + } + @ApiOperation("获取我的订单列表") @GetMapping("/mineList") public TableDataInfo getMineList(OrderQuery query) { @@ -149,7 +173,9 @@ public class AppOrderController extends BaseController { if (dto.getRequiredIot() == null) { dto.setRequiredIot(true); } - return success(orderService.openDevice(dto)); + return executeWithLock(order.getDeviceId(), () -> { + return success(orderService.openDevice(dto)); + }); } @ApiOperation("临时锁车") @@ -167,7 +193,9 @@ public class AppOrderController extends BaseController { if (dto.getRequiredIot() == null) { dto.setRequiredIot(true); } - return success(orderService.closeDevice(dto)); + return executeWithLock(order.getDeviceId(), () -> { + return success(orderService.closeDevice(dto)); + }); } @ApiOperation("换车") diff --git a/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceController.java b/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceController.java index 6f397ac..545b258 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceController.java +++ b/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceController.java @@ -1,12 +1,9 @@ package com.ruoyi.web.bst; -import java.util.Collections; import java.util.List; import javax.servlet.http.HttpServletResponse; -import com.ruoyi.bst.areaJoin.domain.enums.AreaJoinPermission; -import com.ruoyi.common.utils.collection.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -21,10 +18,12 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.bst.area.service.AreaValidator; +import com.ruoyi.bst.areaJoin.domain.enums.AreaJoinPermission; import com.ruoyi.bst.device.domain.Device; import com.ruoyi.bst.device.domain.DeviceQuery; import com.ruoyi.bst.device.domain.DeviceVO; import com.ruoyi.bst.device.domain.dto.DeviceTransferDTO; +import com.ruoyi.bst.device.service.DeviceAssembler; import com.ruoyi.bst.device.service.DeviceConverter; import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.bst.device.service.DeviceService; @@ -68,6 +67,8 @@ public class DeviceController extends BaseController @Autowired private DeviceIotService deviceIotService; + @Autowired + private DeviceAssembler deviceAssembler; /** * 查询设备列表 @@ -84,6 +85,7 @@ public class DeviceController extends BaseController if (query.getRefresh() != null && query.getRefresh()) { deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND); } + deviceAssembler.assembleOnlineStatus(list); return getDataTable(list); } @@ -102,6 +104,7 @@ public class DeviceController extends BaseController if (query.getRefresh() != null && query.getRefresh()) { deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND); } + deviceAssembler.assembleOnlineStatus(list); return success(list); } @@ -134,6 +137,7 @@ public class DeviceController extends BaseController if (refresh) { deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND); } + deviceAssembler.assembleOnlineStatus(device); return success(device); } diff --git a/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceIotController.java b/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceIotController.java index e6031a3..6242d8f 100644 --- a/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceIotController.java +++ b/ruoyi-web/src/main/java/com/ruoyi/web/bst/DeviceIotController.java @@ -1,5 +1,7 @@ package com.ruoyi.web.bst; +import java.util.function.Supplier; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PutMapping; @@ -16,12 +18,18 @@ import com.ruoyi.bst.device.service.DeviceValidator; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisLock; +import com.ruoyi.common.core.redis.enums.RedisLockKey; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.LogBizType; +import com.ruoyi.common.utils.ServiceUtil; import com.ruoyi.iot.constants.IotConstants; +import lombok.extern.slf4j.Slf4j; + @RestController @RequestMapping("/bst/device/iot") +@Slf4j public class DeviceIotController extends BaseController { @Autowired @@ -33,16 +41,39 @@ public class DeviceIotController extends BaseController { @Autowired private DeviceValidator deviceValidator; + @Autowired + private RedisLock redisLock; + private DeviceVO getDevice(Long id, String sn) { return deviceService.selectDevice(id, sn, true, AreaJoinPermission.DEVICE_EDIT); } + /** + * 执行带设备锁的操作 + * @param device 设备信息 + * @param operation 具体操作 + * @return 操作结果 + */ + private AjaxResult executeWithLock(DeviceVO device, Supplier operation) { + if (device == null) { + return toAjax(0); + } + Long lockKey = device.getId(); + boolean lock = redisLock.lock(RedisLockKey.DEVICE_IOT_OPERATION, lockKey); + ServiceUtil.assertion(!lock, "设备操作过于频繁,请稍后再试"); + try { + return operation.get(); + } finally { + redisLock.unlock(RedisLockKey.DEVICE_IOT_OPERATION, lockKey); + } + } + @PreAuthorize("@ss.hasPermi('bst:device:unlock')") @Log(title = "管理员开锁", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.DEVICE) @PutMapping("/unlock") public AjaxResult unlock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - return success(deviceIotService.unlock(device, DeviceUnLockType.ADMIN, "管理员开锁", true)); + return executeWithLock(device, () -> success(deviceIotService.unlock(device, DeviceUnLockType.ADMIN, "管理员开锁", true))); } @PreAuthorize("@ss.hasPermi('bst:device:lock')") @@ -50,7 +81,7 @@ public class DeviceIotController extends BaseController { @PutMapping("/lock") public AjaxResult lock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - return success(deviceIotService.lock(device, true, "管理员锁车", true)); + return executeWithLock(device, () -> success(deviceIotService.lock(device, true, "管理员锁车", true))); } @PreAuthorize("@ss.hasPermi('bst:device:ring')") @@ -58,7 +89,7 @@ public class DeviceIotController extends BaseController { @PutMapping("/ring") public AjaxResult ring(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - return toAjax(deviceIotService.play(device, IotConstants.PLAY_WARNING, "管理员响铃寻车", true)); + return executeWithLock(device, () -> success(deviceIotService.play(device, IotConstants.PLAY_WARNING, "管理员响铃寻车", true))); } @PreAuthorize("@ss.hasPermi('bst:device:reboot')") @@ -66,7 +97,7 @@ public class DeviceIotController extends BaseController { @PutMapping("/reboot") public AjaxResult reboot(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - return toAjax(deviceIotService.reboot(device, "管理员重启", true)); + return executeWithLock(device, () -> success(deviceIotService.reboot(device, "管理员重启", true))); } @PreAuthorize("@ss.hasPermi('bst:device:unlockSeat')") @@ -74,7 +105,7 @@ public class DeviceIotController extends BaseController { @PutMapping("/unlockSeat") public AjaxResult unlockSeat(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - return toAjax(deviceIotService.unlockSeat(device, "管理员开坐垫锁", true)); + return executeWithLock(device, () -> success(deviceIotService.unlockSeat(device, "管理员开坐垫锁", true))); } @PreAuthorize("@ss.hasPermi('bst:device:refresh')") @@ -82,8 +113,10 @@ public class DeviceIotController extends BaseController { @PutMapping("/refresh") public AjaxResult refresh(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { DeviceVO device = this.getDevice(id, sn); - deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND); - return success(device); + return executeWithLock(device, () -> { + deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND); + return success(device); + }); } @PreAuthorize("@ss.hasPermi('bst:device:music')") @@ -91,6 +124,6 @@ public class DeviceIotController extends BaseController { @PutMapping("/music") public AjaxResult music(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn, @RequestParam String music) { DeviceVO device = this.getDevice(id, sn); - return success(deviceIotService.setMusic(device, music, "管理员设置声音", true)); + return executeWithLock(device, () -> success(deviceIotService.setMusic(device, music, "管理员设置声音", true))); } } diff --git a/ruoyi-web/src/main/resources/application.yml b/ruoyi-web/src/main/resources/application.yml index 1adbe7c..94ac0b0 100644 --- a/ruoyi-web/src/main/resources/application.yml +++ b/ruoyi-web/src/main/resources/application.yml @@ -128,7 +128,7 @@ iot: # iot秘钥 accessKey: dJqF0qYhUbK/o1Pr9I5qxNoP14FlJLC+BFK2ZTjUX+lnKwoNYvBYsM/7Xu1ERIzSkUoxVkP/N1RMvGlBKMoBtA== # 超时响应时间(秒) - timeout: 10 + timeout: 5 # token过期时间 daysToExpire: 100 # 推送消息token