优化在线逻辑

This commit is contained in:
磷叶 2025-05-17 12:41:41 +08:00
parent 33b469cdbc
commit 6be8a70cf4
19 changed files with 248 additions and 74 deletions

View File

@ -127,4 +127,9 @@ public class CacheConstants
* 设备更新物联网状态队列 * 设备更新物联网状态队列
*/ */
public static final String DEVICE_UPDATE_IOT_QUEUE = "device_update_iot_queue"; public static final String DEVICE_UPDATE_IOT_QUEUE = "device_update_iot_queue";
/**
* 设备在线状态
*/
public static final String MAC_ONLINE_STATUS = "mac_online_status:";
} }

View File

@ -341,4 +341,19 @@ public class RedisCache
// 执行 Lua 脚本 // 执行 Lua 脚本
return (List<T>) redisTemplate.<T>execute(redisScript, Collections.singletonList(key)); return (List<T>) redisTemplate.<T>execute(redisScript, Collections.singletonList(key));
} }
/**
* 获取并清空Hash缓存中的所有数据原子性操作
*
* @param key 缓存的键值
* @return Hash中的所有数据
*/
public <T> List<T> getAndClearHashValues(final String key) {
String scriptText =
"local values = redis.call('HVALS', KEYS[1]) " +
"redis.call('DEL', KEYS[1]) " +
"return values";
RedisScript<List<T>> redisScript = new DefaultRedisScript<>(scriptText, (Class<List<T>>) (Class<?>) List.class);
return (List<T>) redisTemplate.execute(redisScript, Collections.singletonList(key));
}
} }

View File

@ -18,7 +18,8 @@ public enum RedisLockKey {
DEVICE_MAC_UNIQUE("device_mac_unique", "设备MAC唯一"), DEVICE_MAC_UNIQUE("device_mac_unique", "设备MAC唯一"),
USER_NAME_UNIQUE("user_name_unique", "用户账号唯一"), USER_NAME_UNIQUE("user_name_unique", "用户账号唯一"),
DEVICE_IOT_REFRESH("device_iot_refresh", "设备物联网信息刷新"), 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 key;
private final String name; private final String name;

View File

@ -64,4 +64,7 @@ public class DeviceQuery extends DeviceVO {
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private List<LocalDate> createDateRange; private List<LocalDate> createDateRange;
@ApiModelProperty("订单设备状态列表")
private List<String> orderDeviceStatusList;
} }

View File

@ -131,6 +131,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="query.lastTimeEnd != null">and bd.last_time &lt;= #{query.lastTimeEnd}</if> <if test="query.lastTimeEnd != null">and bd.last_time &lt;= #{query.lastTimeEnd}</if>
<if test="query.lastTimeStart != null">and bd.last_time &gt;= #{query.lastTimeStart}</if> <if test="query.lastTimeStart != null">and bd.last_time &gt;= #{query.lastTimeStart}</if>
<if test="query.locationType != null and query.locationType != ''">and bd.location_type = #{query.locationType}</if> <if test="query.locationType != null and query.locationType != ''">and bd.location_type = #{query.locationType}</if>
<if test="query.orderDeviceId != null">and bd.order_device_id = #{query.orderDeviceId}</if>
<if test="query.keyword != null and query.keyword != ''"> <if test="query.keyword != null and query.keyword != ''">
and ( and (
bd.sn like concat('%', #{query.keyword}, '%') bd.sn like concat('%', #{query.keyword}, '%')
@ -155,6 +156,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{item} #{item}
</foreach> </foreach>
</if> </if>
<if test="query.orderDeviceStatusList != null and query.orderDeviceStatusList.size() > 0">
and bod.status in
<foreach item="item" collection="query.orderDeviceStatusList" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="query.center != null and query.center.size() == 2 and query.radius != null"> <if test="query.center != null and query.center.size() == 2 and query.radius != null">
and #{query.radius} >= round(st_distance_sphere(point(#{query.center[0]}, #{query.center[1]}), point(bd.longitude, bd.latitude))) and #{query.radius} >= round(st_distance_sphere(point(#{query.center[0]}, #{query.center[1]}), point(bd.longitude, bd.latitude)))
</if> </if>

View File

@ -1,5 +1,6 @@
package com.ruoyi.bst.device.service; package com.ruoyi.bst.device.service;
import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.dto.DeviceBltUploadDTO;
import com.ruoyi.bst.device.domain.enums.DeviceUnLockType; import com.ruoyi.bst.device.domain.enums.DeviceUnLockType;
import com.ruoyi.bst.device.domain.vo.DeviceIotVO; import com.ruoyi.bst.device.domain.vo.DeviceIotVO;
import com.ruoyi.bst.device.domain.vo.DeviceOnlineInfo;
public interface DeviceIotService { public interface DeviceIotService {
@ -154,4 +156,19 @@ public interface DeviceIotService {
*/ */
int bltUpload(DeviceBltUploadDTO dto); 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);
} }

View File

@ -152,11 +152,6 @@ public interface DeviceService
*/ */
public int clearCurrentOrderDevice(Long id, Long orderDeviceId); public int clearCurrentOrderDevice(Long id, Long orderDeviceId);
/**
* 根据mac更新在线状态
*/
int updateOnlineStatusByMac(String mac, String onlineStatus);
/** /**
* 划拨运营区 * 划拨运营区
*/ */

View File

@ -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.DeviceStatus;
import com.ruoyi.bst.device.domain.enums.DeviceUnLockType; import com.ruoyi.bst.device.domain.enums.DeviceUnLockType;
import com.ruoyi.bst.device.domain.vo.DeviceIotVO; 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.mapper.DeviceMapper;
import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.bst.device.service.DeviceIotService;
import com.ruoyi.bst.device.service.DeviceService; 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_FAST = 5; // 上报频率
private final static Integer SUB_SLOW = 300; // 上报频率 private final static Integer SUB_SLOW = 300; // 上报频率
private final static Integer SUB_X_SLOW = 50; // 上报频率X开头硬件
@Override @Override
public DeviceIotVO unlock(DeviceVO device, DeviceUnLockType type, String reason, boolean requiredIot) { 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()); ServiceUtil.assertion(!DeviceStatus.canUserLock().contains(device.getStatus()), "设备%s当前状态不允许用户锁车", device.getSn());
query.setStatusList(DeviceStatus.canUserLock()); query.setStatusList(DeviceStatus.canUserLock());
} }
if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) {
query.setOrderDeviceId(device.getOrderDeviceId());
query.setOrderDeviceStatusList(OrderDeviceStatus.inUse());
}
transactionTemplate.execute(status -> { transactionTemplate.execute(status -> {
// 更新设备状态 // 更新设备状态
int rows = deviceMapper.updateByQuerySimple(data, query); int rows = deviceMapper.updateByQuery(data, query);
vo.setDb(rows); vo.setDb(rows);
if (rows > 0) { if (rows > 0) {
// 发送命令锁车 // 发送命令锁车
CommandResponse res = null; CommandResponse res = null;
int sub = getDeviceSubSlow(device);
if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) { if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) {
// 临时锁车 // 临时锁车
res = iotService.tempLock(device, SUB_SLOW, reason, 3); res = iotService.tempLock(device, sub, reason, 3);
} else { } else {
// 锁车 // 锁车
res = iotService.lock(device, SUB_SLOW, reason, 3); res = iotService.lock(device, sub, reason, 3);
} }
boolean iot = IotUtil.isSuccess(res); boolean iot = IotUtil.isSuccess(res);
ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED); ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
@ -181,6 +189,13 @@ public class DeviceIotServiceImpl implements DeviceIotService {
return vo; 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 @Override
public DeviceIotVO qLock(DeviceVO device, String reason, boolean requiredIot) { public DeviceIotVO qLock(DeviceVO device, String reason, boolean requiredIot) {
if (device == null || device.getId() == null) { if (device == null || device.getId() == null) {
@ -297,12 +312,10 @@ public class DeviceIotServiceImpl implements DeviceIotService {
} }
public int updateIot(Device device) { public int updateIot(Device device) {
if (device == null) { if (device == null || StringUtils.isBlank(device.getMac())) {
return 0;
}
if (StringUtils.isBlank(device.getMac()) && device.getId() == null) {
return 0; return 0;
} }
Device data = new Device(); Device data = new Device();
data.setMac(device.getMac()); data.setMac(device.getMac());
data.setVoltage(device.getVoltage()); data.setVoltage(device.getVoltage());
@ -320,7 +333,7 @@ public class DeviceIotServiceImpl implements DeviceIotService {
data.setSoftwareVersion(device.getSoftwareVersion()); data.setSoftwareVersion(device.getSoftwareVersion());
// 添加到缓存队列 // 添加到缓存队列
redisCache.rightPush(CacheConstants.DEVICE_UPDATE_IOT_QUEUE, data); redisCache.setCacheMapValue(CacheConstants.DEVICE_UPDATE_IOT_QUEUE, data.getMac(), data);
return 1; return 1;
} }
@ -385,7 +398,7 @@ public class DeviceIotServiceImpl implements DeviceIotService {
} }
} }
if (DeviceLockStatus.CLOSE.getCode().equals(device.getLockStatus()) && !DeviceQuality.CLOSE.getCode().equals(device.getQuality())) { 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) { if (device.getOrderId() != null) {
operLogService.operSysLog("【设备监控】发现未关闭的车辆", IotUtil.isSuccess(res), LogBizType.ORDER, device.getOrderId(), device); operLogService.operSysLog("【设备监控】发现未关闭的车辆", IotUtil.isSuccess(res), LogBizType.ORDER, device.getOrderId(), device);
} else { } else {
@ -459,4 +472,37 @@ public class DeviceIotServiceImpl implements DeviceIotService {
return 1; 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);
}
} }

View File

@ -1,6 +1,5 @@
package com.ruoyi.bst.device.service.impl; package com.ruoyi.bst.device.service.impl;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; 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.ServiceUtil;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.collection.CollectionUtils; import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.iot.service.impl.DeviceOnlineStatus;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -480,20 +478,6 @@ public class DeviceServiceImpl implements DeviceService
return deviceMapper.clearCurrentOrderDevice(id, orderDeviceId); 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 @Override
public int transfer(List<DeviceVO> deviceList, Long areaId) { public int transfer(List<DeviceVO> deviceList, Long areaId) {
if (CollectionUtils.isEmptyElement(deviceList) || areaId == null) { if (CollectionUtils.isEmptyElement(deviceList) || areaId == null) {

View File

@ -93,13 +93,15 @@ public class OrderPayHandlerImpl implements PayHandler {
int start = orderDeviceService.start(orderDevice); int start = orderDeviceService.start(orderDevice);
ServiceUtil.assertion(start != 1, "开始使用ID为%s的订单设备失败", orderDevice.getId()); 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); 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()); operLogService.operSysLog("订单支付成功开锁", unlock.isIotSuccess(), LogBizType.ORDER, order.getId(), orderDevice.getDeviceId(), orderDevice.getDeviceLongitude(), orderDevice.getDeviceLatitude());
ServiceUtil.assertion(unlock.getDb() <= 0, "ID为%s的设备解锁失败", orderDevice.getDeviceId()); ServiceUtil.assertion(unlock.getDb() <= 0, "ID为%s的设备解锁失败", orderDevice.getDeviceId());
}
return rows;
});
return result != null && result == 1; return result != null && result == 1;
} }

View File

@ -112,6 +112,9 @@ public class IotReceiveServiceImpl implements IotReceiveService {
// 增加更新频率控制 // 增加更新频率控制
this.updateDeviceIot(device); this.updateDeviceIot(device);
// 更新设备在线状态
deviceIotService.updateDeviceOnlineStatus(device.getMac(), DeviceOnlineStatus.ONLINE.getStatus(), at);
// 定位无效不处理 // 定位无效不处理
if (device.getLongitude() == null || device.getLatitude() == null) { if (device.getLongitude() == null || device.getLatitude() == null) {
log.info("设备{}定位无效,不处理", device.getMac()); log.info("设备{}定位无效,不处理", device.getMac());

View File

@ -1,5 +1,6 @@
package com.ruoyi.iot.service.impl; package com.ruoyi.iot.service.impl;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.CommandLog;
import com.ruoyi.bst.commandLog.domain.enums.CommandLogType; import com.ruoyi.bst.commandLog.domain.enums.CommandLogType;
import com.ruoyi.bst.commandLog.service.CommandLogService; 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.CacheConstants;
import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
@ -87,7 +88,7 @@ public class IotServiceImpl implements IotService {
private RedisLock redisLock; private RedisLock redisLock;
@Autowired @Autowired
private DeviceService deviceService; private DeviceIotService deviceIotService;
@Autowired @Autowired
private ScheduledExecutorService scheduledExecutorService; private ScheduledExecutorService scheduledExecutorService;
@ -306,19 +307,9 @@ public class IotServiceImpl implements IotService {
String status = this.parseToOnlineStatus(res, deviceName, true); String status = this.parseToOnlineStatus(res, deviceName, true);
redisCache.setCacheObject(this.getOnlineCacheKey(deviceName), status, 10, TimeUnit.SECONDS); redisCache.setCacheObject(this.getOnlineCacheKey(deviceName), status, 10, TimeUnit.SECONDS);
// 异步更新设备在线状态 // 更新设备在线状态
boolean lock = redisLock.lock(RedisLockKey.DEVICE_UPDATE_IOT_LOCK, deviceName, 60L); deviceIotService.updateDeviceOnlineStatus(deviceName, status, LocalDateTime.now());
if (lock ) {
scheduledExecutorService.execute(() -> {
int update = deviceService.updateOnlineStatusByMac(deviceName, status);
if (update != 1) {
log.error("异步更新设备在线状态失败,MAC={},status={}", deviceName, status);
}
});
} else {
log.info("设备{}更新太频繁,跳过本次更新", deviceName);
}
return res; return res;
} catch (Exception e) { } catch (Exception e) {
this.addCommandLog(device, command, "操作失败:" + e.getMessage(), reason, null, result); this.addCommandLog(device, command, "操作失败:" + e.getMessage(), reason, null, result);

View File

@ -4,7 +4,6 @@ import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.ruoyi.common.utils.collection.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; 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.bst.device.service.DeviceService;
import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.iot.service.impl.DeviceOnlineStatus; import com.ruoyi.iot.service.impl.DeviceOnlineStatus;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -53,7 +53,7 @@ public class DeviceTask {
public void refreshIot() { public void refreshIot() {
// 从队列中获取数据 // 从队列中获取数据
List<Device> deviceList = redisCache.getAndClearCacheList(CacheConstants.DEVICE_UPDATE_IOT_QUEUE); List<Device> deviceList = redisCache.getAndClearHashValues(CacheConstants.DEVICE_UPDATE_IOT_QUEUE);
if (CollectionUtils.isEmptyElement(deviceList)) { if (CollectionUtils.isEmptyElement(deviceList)) {
return ; return ;
} }

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.app; package com.ruoyi.web.app;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; 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 org.springframework.web.bind.annotation.RestController;
import com.ruoyi.bst.device.domain.DeviceQuery; 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.domain.enums.DeviceStatus;
import com.ruoyi.bst.device.service.DeviceAssembler;
import com.ruoyi.bst.device.service.DeviceService; import com.ruoyi.bst.device.service.DeviceService;
import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
@ -24,6 +27,9 @@ public class AppDeviceController extends BaseController {
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@Autowired
private DeviceAssembler deviceAssembler;
@ApiOperation("获取附近可用车辆列表") @ApiOperation("获取附近可用车辆列表")
@GetMapping("/listNearBy") @GetMapping("/listNearBy")
@Anonymous @Anonymous
@ -38,14 +44,18 @@ public class AppDeviceController extends BaseController {
return error("中心坐标格式错误,必须包含经度和纬度"); return error("中心坐标格式错误,必须包含经度和纬度");
} }
query.setStatus(DeviceStatus.AVAILABLE.getCode()); query.setStatus(DeviceStatus.AVAILABLE.getCode());
return success(deviceService.selectDeviceList(query)); List<DeviceVO> list = deviceService.selectDeviceList(query);
deviceAssembler.assembleOnlineStatus(list);
return success(list);
} }
@ApiOperation("获取可用车辆详情") @ApiOperation("获取可用车辆详情")
@GetMapping("/availableDetail") @GetMapping("/availableDetail")
@Anonymous @Anonymous
public AjaxResult getAvailableDetail(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { 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);
} }
} }

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.app; package com.ruoyi.web.app;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; 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.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; 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.BusinessType;
import com.ruoyi.common.enums.LogBizType; import com.ruoyi.common.enums.LogBizType;
import com.ruoyi.common.utils.MathUtils; import com.ruoyi.common.utils.MathUtils;
@ -36,6 +39,36 @@ public class AppDeviceIotController extends BaseController {
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@Autowired
private RedisLock redisLock;
/**
* 执行带设备锁的操作
* @param device 设备信息
* @param operation 具体操作
* @return 操作结果
*/
private AjaxResult executeWithLock(Long deviceId, Supplier<AjaxResult> 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("用户响铃寻车") @ApiOperation("用户响铃寻车")
@PutMapping("/ring") @PutMapping("/ring")
@ -44,12 +77,7 @@ public class AppDeviceIotController extends BaseController {
@RequestParam(required = false) String sn, @RequestParam(required = false) String sn,
@RequestParam(required = false) BigDecimal lon, @RequestParam(required = false) BigDecimal lon,
@RequestParam(required = false) BigDecimal lat) { @RequestParam(required = false) BigDecimal lat) {
DeviceVO device = null; DeviceVO device = getDevice(id, sn);
if (id != null) {
device = deviceService.selectDeviceById(id);
} else {
device = deviceService.selectDeviceBySn(sn);
}
ServiceUtil.assertion(device == null, "当前车辆不存在,无法响铃寻车"); ServiceUtil.assertion(device == null, "当前车辆不存在,无法响铃寻车");
if (device.getAreaRequiredRingRadius() != null && device.getAreaRequiredRingRadius()) { if (device.getAreaRequiredRingRadius() != null && device.getAreaRequiredRingRadius()) {
@ -59,7 +87,9 @@ public class AppDeviceIotController extends BaseController {
"您当前距离车辆%s米无法响铃寻车。请距离车辆%s米以内再试", distance, device.getAreaRingRadius()); "您当前距离车辆%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("蓝牙上传设备信息") @ApiOperation("蓝牙上传设备信息")

View File

@ -1,6 +1,7 @@
package com.ruoyi.web.app; package com.ruoyi.web.app;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; 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.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo; 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.BusinessType;
import com.ruoyi.common.enums.LogBizType; import com.ruoyi.common.enums.LogBizType;
import com.ruoyi.common.utils.LogParamHolder; import com.ruoyi.common.utils.LogParamHolder;
@ -53,6 +56,27 @@ public class AppOrderController extends BaseController {
@Autowired @Autowired
private OrderValidator orderValidator; private OrderValidator orderValidator;
@Autowired
private RedisLock redisLock;
/**
* 执行带设备锁的操作
* @param device 设备信息
* @param operation 具体操作
* @return 操作结果
*/
private AjaxResult executeWithLock(Long deviceId, Supplier<AjaxResult> 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("获取我的订单列表") @ApiOperation("获取我的订单列表")
@GetMapping("/mineList") @GetMapping("/mineList")
public TableDataInfo getMineList(OrderQuery query) { public TableDataInfo getMineList(OrderQuery query) {
@ -149,7 +173,9 @@ public class AppOrderController extends BaseController {
if (dto.getRequiredIot() == null) { if (dto.getRequiredIot() == null) {
dto.setRequiredIot(true); dto.setRequiredIot(true);
} }
return success(orderService.openDevice(dto)); return executeWithLock(order.getDeviceId(), () -> {
return success(orderService.openDevice(dto));
});
} }
@ApiOperation("临时锁车") @ApiOperation("临时锁车")
@ -167,7 +193,9 @@ public class AppOrderController extends BaseController {
if (dto.getRequiredIot() == null) { if (dto.getRequiredIot() == null) {
dto.setRequiredIot(true); dto.setRequiredIot(true);
} }
return success(orderService.closeDevice(dto)); return executeWithLock(order.getDeviceId(), () -> {
return success(orderService.closeDevice(dto));
});
} }
@ApiOperation("换车") @ApiOperation("换车")

View File

@ -1,12 +1,9 @@
package com.ruoyi.web.bst; package com.ruoyi.web.bst;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse; 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.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; 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 org.springframework.web.bind.annotation.RestController;
import com.ruoyi.bst.area.service.AreaValidator; 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.Device;
import com.ruoyi.bst.device.domain.DeviceQuery; import com.ruoyi.bst.device.domain.DeviceQuery;
import com.ruoyi.bst.device.domain.DeviceVO; import com.ruoyi.bst.device.domain.DeviceVO;
import com.ruoyi.bst.device.domain.dto.DeviceTransferDTO; 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.DeviceConverter;
import com.ruoyi.bst.device.service.DeviceIotService; import com.ruoyi.bst.device.service.DeviceIotService;
import com.ruoyi.bst.device.service.DeviceService; import com.ruoyi.bst.device.service.DeviceService;
@ -68,6 +67,8 @@ public class DeviceController extends BaseController
@Autowired @Autowired
private DeviceIotService deviceIotService; private DeviceIotService deviceIotService;
@Autowired
private DeviceAssembler deviceAssembler;
/** /**
* 查询设备列表 * 查询设备列表
@ -84,6 +85,7 @@ public class DeviceController extends BaseController
if (query.getRefresh() != null && query.getRefresh()) { if (query.getRefresh() != null && query.getRefresh()) {
deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND); deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND);
} }
deviceAssembler.assembleOnlineStatus(list);
return getDataTable(list); return getDataTable(list);
} }
@ -102,6 +104,7 @@ public class DeviceController extends BaseController
if (query.getRefresh() != null && query.getRefresh()) { if (query.getRefresh() != null && query.getRefresh()) {
deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND); deviceIotService.refresh(list, IotConstants.ONLINE_TYPE_COMMAND);
} }
deviceAssembler.assembleOnlineStatus(list);
return success(list); return success(list);
} }
@ -134,6 +137,7 @@ public class DeviceController extends BaseController
if (refresh) { if (refresh) {
deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND); deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND);
} }
deviceAssembler.assembleOnlineStatus(device);
return success(device); return success(device);
} }

View File

@ -1,5 +1,7 @@
package com.ruoyi.web.bst; package com.ruoyi.web.bst;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PutMapping; 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.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; 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.BusinessType;
import com.ruoyi.common.enums.LogBizType; import com.ruoyi.common.enums.LogBizType;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.iot.constants.IotConstants; import com.ruoyi.iot.constants.IotConstants;
import lombok.extern.slf4j.Slf4j;
@RestController @RestController
@RequestMapping("/bst/device/iot") @RequestMapping("/bst/device/iot")
@Slf4j
public class DeviceIotController extends BaseController { public class DeviceIotController extends BaseController {
@Autowired @Autowired
@ -33,16 +41,39 @@ public class DeviceIotController extends BaseController {
@Autowired @Autowired
private DeviceValidator deviceValidator; private DeviceValidator deviceValidator;
@Autowired
private RedisLock redisLock;
private DeviceVO getDevice(Long id, String sn) { private DeviceVO getDevice(Long id, String sn) {
return deviceService.selectDevice(id, sn, true, AreaJoinPermission.DEVICE_EDIT); return deviceService.selectDevice(id, sn, true, AreaJoinPermission.DEVICE_EDIT);
} }
/**
* 执行带设备锁的操作
* @param device 设备信息
* @param operation 具体操作
* @return 操作结果
*/
private AjaxResult executeWithLock(DeviceVO device, Supplier<AjaxResult> 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')") @PreAuthorize("@ss.hasPermi('bst:device:unlock')")
@Log(title = "管理员开锁", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.DEVICE) @Log(title = "管理员开锁", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.DEVICE)
@PutMapping("/unlock") @PutMapping("/unlock")
public AjaxResult unlock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult unlock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, 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')") @PreAuthorize("@ss.hasPermi('bst:device:lock')")
@ -50,7 +81,7 @@ public class DeviceIotController extends BaseController {
@PutMapping("/lock") @PutMapping("/lock")
public AjaxResult lock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult lock(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, 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')") @PreAuthorize("@ss.hasPermi('bst:device:ring')")
@ -58,7 +89,7 @@ public class DeviceIotController extends BaseController {
@PutMapping("/ring") @PutMapping("/ring")
public AjaxResult ring(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult ring(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, 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')") @PreAuthorize("@ss.hasPermi('bst:device:reboot')")
@ -66,7 +97,7 @@ public class DeviceIotController extends BaseController {
@PutMapping("/reboot") @PutMapping("/reboot")
public AjaxResult reboot(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult reboot(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, 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')") @PreAuthorize("@ss.hasPermi('bst:device:unlockSeat')")
@ -74,7 +105,7 @@ public class DeviceIotController extends BaseController {
@PutMapping("/unlockSeat") @PutMapping("/unlockSeat")
public AjaxResult unlockSeat(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult unlockSeat(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, 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')") @PreAuthorize("@ss.hasPermi('bst:device:refresh')")
@ -82,8 +113,10 @@ public class DeviceIotController extends BaseController {
@PutMapping("/refresh") @PutMapping("/refresh")
public AjaxResult refresh(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) { public AjaxResult refresh(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn) {
DeviceVO device = this.getDevice(id, sn); DeviceVO device = this.getDevice(id, sn);
deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND); return executeWithLock(device, () -> {
return success(device); deviceIotService.refresh(device, IotConstants.ONLINE_TYPE_COMMAND);
return success(device);
});
} }
@PreAuthorize("@ss.hasPermi('bst:device:music')") @PreAuthorize("@ss.hasPermi('bst:device:music')")
@ -91,6 +124,6 @@ public class DeviceIotController extends BaseController {
@PutMapping("/music") @PutMapping("/music")
public AjaxResult music(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn, @RequestParam String music) { public AjaxResult music(@RequestParam(required = false) Long id, @RequestParam(required = false) String sn, @RequestParam String music) {
DeviceVO device = this.getDevice(id, sn); DeviceVO device = this.getDevice(id, sn);
return success(deviceIotService.setMusic(device, music, "管理员设置声音", true)); return executeWithLock(device, () -> success(deviceIotService.setMusic(device, music, "管理员设置声音", true)));
} }
} }

View File

@ -128,7 +128,7 @@ iot:
# iot秘钥 # iot秘钥
accessKey: dJqF0qYhUbK/o1Pr9I5qxNoP14FlJLC+BFK2ZTjUX+lnKwoNYvBYsM/7Xu1ERIzSkUoxVkP/N1RMvGlBKMoBtA== accessKey: dJqF0qYhUbK/o1Pr9I5qxNoP14FlJLC+BFK2ZTjUX+lnKwoNYvBYsM/7Xu1ERIzSkUoxVkP/N1RMvGlBKMoBtA==
# 超时响应时间(秒) # 超时响应时间(秒)
timeout: 10 timeout: 5
# token过期时间 # token过期时间
daysToExpire: 100 daysToExpire: 100
# 推送消息token # 推送消息token