Commit 3a832d9fb46e2078745f8040aee72134a07dc942

Authored by YunaiV
1 parent 28a30d4b

feat(iot):【设备订单:50%】简化设备定位功能,支持 GeoLocation 自动更新,基于 calm-roaming-pillow.md

Showing 14 changed files with 109 additions and 89 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
@@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
11 -import cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum;  
12 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 11 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
13 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
14 import io.swagger.v3.oas.annotations.Parameter; 13 import io.swagger.v3.oas.annotations.Parameter;
@@ -153,10 +152,9 @@ public class IotDeviceController { @@ -153,10 +152,9 @@ public class IotDeviceController {
153 // 手动创建导出 demo 152 // 手动创建导出 demo
154 List<IotDeviceImportExcelVO> list = Arrays.asList( 153 List<IotDeviceImportExcelVO> list = Arrays.asList(
155 IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110") 154 IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
156 - .productKey("1de24640dfe").groupNames("灰度分组,生产分组")  
157 - .locationType(IotLocationTypeEnum.IP.getType()).build(), 155 + .productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
158 IotDeviceImportExcelVO.builder().deviceName("biubiu").productKey("YzvHxd4r67sT4s2B") 156 IotDeviceImportExcelVO.builder().deviceName("biubiu").productKey("YzvHxd4r67sT4s2B")
159 - .groupNames("").locationType(IotLocationTypeEnum.MANUAL.getType()).build()); 157 + .groupNames("").build());
160 // 输出 158 // 输出
161 ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list); 159 ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
162 } 160 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceImportExcelVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; 1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
2 2
3 import cn.idev.excel.annotation.ExcelProperty; 3 import cn.idev.excel.annotation.ExcelProperty;
4 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
5 -import cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum;  
6 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
7 import jakarta.validation.constraints.NotEmpty; 5 import jakarta.validation.constraints.NotEmpty;
8 -import jakarta.validation.constraints.NotNull;  
9 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
10 import lombok.Builder; 7 import lombok.Builder;
11 import lombok.Data; 8 import lombok.Data;
@@ -35,9 +32,4 @@ public class IotDeviceImportExcelVO { @@ -35,9 +32,4 @@ public class IotDeviceImportExcelVO {
35 @ExcelProperty("设备分组") 32 @ExcelProperty("设备分组")
36 private String groupNames; 33 private String groupNames;
37 34
38 - @ExcelProperty("上报方式(1:IP 定位, 2:设备上报,3:手动定位)")  
39 - @NotNull(message = "上报方式不能为空")  
40 - @InEnum(IotLocationTypeEnum.class)  
41 - private Integer locationType;  
42 -  
43 } 35 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
@@ -84,11 +84,6 @@ public class IotDeviceRespVO { @@ -84,11 +84,6 @@ public class IotDeviceRespVO {
84 @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}") 84 @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
85 private String config; 85 private String config;
86 86
87 - @Schema(description = "定位方式", example = "2")  
88 - @ExcelProperty(value = "定位方式", converter = DictConvert.class)  
89 - @DictFormat(DictTypeConstants.LOCATION_TYPE)  
90 - private Integer locationType;  
91 -  
92 @Schema(description = "设备位置的纬度", example = "45.000000") 87 @Schema(description = "设备位置的纬度", example = "45.000000")
93 private BigDecimal latitude; 88 private BigDecimal latitude;
94 89
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceSaveReqVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; 1 package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
2 2
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 5
@@ -39,10 +37,6 @@ public class IotDeviceSaveReqVO { @@ -39,10 +37,6 @@ public class IotDeviceSaveReqVO {
39 @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}") 37 @Schema(description = "设备配置", example = "{\"abc\": \"efg\"}")
40 private String config; 38 private String config;
41 39
42 - @Schema(description = "定位类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
43 - @InEnum(value = IotLocationTypeEnum.class, message = "定位方式必须是 {value}")  
44 - private Integer locationType;  
45 -  
46 @Schema(description = "设备位置的纬度", example = "16380") 40 @Schema(description = "设备位置的纬度", example = "16380")
47 private BigDecimal latitude; 41 private BigDecimal latitude;
48 42
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java
@@ -147,7 +147,7 @@ public class IotProductController { @@ -147,7 +147,7 @@ public class IotProductController {
147 List<IotProductDO> list = productService.getProductList(); 147 List<IotProductDO> list = productService.getProductList();
148 return success(convertList(list, product -> // 只返回 id、name 字段 148 return success(convertList(list, product -> // 只返回 id、name 字段
149 new IotProductRespVO().setId(product.getId()).setName(product.getName()).setStatus(product.getStatus()) 149 new IotProductRespVO().setId(product.getId()).setName(product.getName()).setStatus(product.getStatus())
150 - .setDeviceType(product.getDeviceType()).setLocationType(product.getLocationType()))); 150 + .setDeviceType(product.getDeviceType())));
151 } 151 }
152 152
153 } 153 }
154 \ No newline at end of file 154 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
@@ -61,11 +61,6 @@ public class IotProductRespVO { @@ -61,11 +61,6 @@ public class IotProductRespVO {
61 @DictFormat(DictTypeConstants.NET_TYPE) 61 @DictFormat(DictTypeConstants.NET_TYPE)
62 private Integer netType; 62 private Integer netType;
63 63
64 - @Schema(description = "定位方式", example = "2")  
65 - @ExcelProperty(value = "定位方式", converter = DictConvert.class)  
66 - @DictFormat(DictTypeConstants.LOCATION_TYPE)  
67 - private Integer locationType;  
68 -  
69 @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 64 @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
70 @ExcelProperty(value = "数据格式", converter = DictConvert.class) 65 @ExcelProperty(value = "数据格式", converter = DictConvert.class)
71 @DictFormat(DictTypeConstants.CODEC_TYPE) 66 @DictFormat(DictTypeConstants.CODEC_TYPE)
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product; 1 package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 -import cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum;  
5 import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum; 4 import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
6 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; 5 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
7 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
@@ -45,10 +44,6 @@ public class IotProductSaveReqVO { @@ -45,10 +44,6 @@ public class IotProductSaveReqVO {
45 @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}") 44 @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
46 private Integer netType; 45 private Integer netType;
47 46
48 - @Schema(description = "定位类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
49 - @InEnum(value = IotLocationTypeEnum.class, message = "定位方式必须是 {value}")  
50 - private Integer locationType;  
51 -  
52 @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 47 @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
53 @NotEmpty(message = "数据格式不能为空") 48 @NotEmpty(message = "数据格式不能为空")
54 private String codecType; 49 private String codecType;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
@@ -130,12 +130,6 @@ public class IotDeviceDO extends TenantBaseDO { @@ -130,12 +130,6 @@ public class IotDeviceDO extends TenantBaseDO {
130 private String authType; 130 private String authType;
131 131
132 /** 132 /**
133 - * 定位方式  
134 - * <p>  
135 - * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum}  
136 - */  
137 - private Integer locationType;  
138 - /**  
139 * 设备位置的纬度 133 * 设备位置的纬度
140 */ 134 */
141 private BigDecimal latitude; 135 private BigDecimal latitude;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
@@ -70,12 +70,6 @@ public class IotProductDO extends TenantBaseDO { @@ -70,12 +70,6 @@ public class IotProductDO extends TenantBaseDO {
70 */ 70 */
71 private Integer netType; 71 private Integer netType;
72 /** 72 /**
73 - * 定位方式  
74 - * <p>  
75 - * 枚举 {@link cn.iocoder.yudao.module.iot.enums.product.IotLocationTypeEnum}  
76 - */  
77 - private Integer locationType;  
78 - /**  
79 * 数据格式(编解码器类型) 73 * 数据格式(编解码器类型)
80 * <p> 74 * <p>
81 * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE} 75 * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotLocationTypeEnum.java deleted
1 -package cn.iocoder.yudao.module.iot.enums.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.ArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -/**  
10 - * IoT 定位方式枚举类  
11 - *  
12 - * @author alwayssuper  
13 - */  
14 -@AllArgsConstructor  
15 -@Getter  
16 -public enum IotLocationTypeEnum implements ArrayValuable<Integer> {  
17 -  
18 - IP(1, "IP 定位"),  
19 - DEVICE(2, "设备上报"),  
20 - MANUAL(3, "手动定位");  
21 -  
22 - public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotLocationTypeEnum::getType).toArray(Integer[]::new);  
23 -  
24 - /**  
25 - * 类型  
26 - */  
27 - private final Integer type;  
28 - /**  
29 - * 描述  
30 - */  
31 - private final String description;  
32 -  
33 - @Override  
34 - public Integer[] array() {  
35 - return ARRAYS;  
36 - }  
37 -  
38 -}  
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
9 9
10 import javax.annotation.Nullable; 10 import javax.annotation.Nullable;
  11 +import java.math.BigDecimal;
11 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
12 import java.util.Collection; 13 import java.util.Collection;
13 import java.util.List; 14 import java.util.List;
@@ -271,4 +272,13 @@ public interface IotDeviceService { @@ -271,4 +272,13 @@ public interface IotDeviceService {
271 */ 272 */
272 void updateDeviceFirmware(Long deviceId, Long firmwareId); 273 void updateDeviceFirmware(Long deviceId, Long firmwareId);
273 274
  275 + /**
  276 + * 更新设备定位信息(GeoLocation 上报时调用)
  277 + *
  278 + * @param device 设备信息(用于清除缓存)
  279 + * @param longitude 经度
  280 + * @param latitude 纬度
  281 + */
  282 + void updateDeviceLocation(IotDeviceDO device, BigDecimal longitude, BigDecimal latitude);
  283 +
274 } 284 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -34,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; @@ -34,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
34 import org.springframework.validation.annotation.Validated; 34 import org.springframework.validation.annotation.Validated;
35 35
36 import javax.annotation.Nullable; 36 import javax.annotation.Nullable;
  37 +import java.math.BigDecimal;
37 import java.time.LocalDateTime; 38 import java.time.LocalDateTime;
38 import java.util.*; 39 import java.util.*;
39 40
@@ -376,8 +377,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -376,8 +377,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
376 if (existDevice == null) { 377 if (existDevice == null) {
377 createDevice(new IotDeviceSaveReqVO() 378 createDevice(new IotDeviceSaveReqVO()
378 .setDeviceName(importDevice.getDeviceName()) 379 .setDeviceName(importDevice.getDeviceName())
379 - .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds)  
380 - .setLocationType(importDevice.getLocationType())); 380 + .setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds));
381 respVO.getCreateDeviceNames().add(importDevice.getDeviceName()); 381 respVO.getCreateDeviceNames().add(importDevice.getDeviceName());
382 return; 382 return;
383 } 383 }
@@ -386,7 +386,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -386,7 +386,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
386 throw exception(DEVICE_KEY_EXISTS); 386 throw exception(DEVICE_KEY_EXISTS);
387 } 387 }
388 updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId()) 388 updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId())
389 - .setGatewayId(gatewayId).setGroupIds(groupIds).setLocationType(importDevice.getLocationType())); 389 + .setGatewayId(gatewayId).setGroupIds(groupIds));
390 respVO.getUpdateDeviceNames().add(importDevice.getDeviceName()); 390 respVO.getUpdateDeviceNames().add(importDevice.getDeviceName());
391 } catch (ServiceException ex) { 391 } catch (ServiceException ex) {
392 respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage()); 392 respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
@@ -490,15 +490,25 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -490,15 +490,25 @@ public class IotDeviceServiceImpl implements IotDeviceService {
490 public void updateDeviceFirmware(Long deviceId, Long firmwareId) { 490 public void updateDeviceFirmware(Long deviceId, Long firmwareId) {
491 // 1. 校验设备是否存在 491 // 1. 校验设备是否存在
492 IotDeviceDO device = validateDeviceExists(deviceId); 492 IotDeviceDO device = validateDeviceExists(deviceId);
493 - 493 +
494 // 2. 更新设备固件版本 494 // 2. 更新设备固件版本
495 IotDeviceDO updateObj = new IotDeviceDO().setId(deviceId).setFirmwareId(firmwareId); 495 IotDeviceDO updateObj = new IotDeviceDO().setId(deviceId).setFirmwareId(firmwareId);
496 deviceMapper.updateById(updateObj); 496 deviceMapper.updateById(updateObj);
497 - 497 +
498 // 3. 清空对应缓存 498 // 3. 清空对应缓存
499 deleteDeviceCache(device); 499 deleteDeviceCache(device);
500 } 500 }
501 501
  502 + @Override
  503 + public void updateDeviceLocation(IotDeviceDO device, BigDecimal longitude, BigDecimal latitude) {
  504 + // 1. 更新定位信息
  505 + deviceMapper.updateById(new IotDeviceDO().setId(device.getId())
  506 + .setLongitude(longitude).setLatitude(latitude));
  507 +
  508 + // 2. 清空对应缓存
  509 + deleteDeviceCache(device);
  510 + }
  511 +
502 private IotDeviceServiceImpl getSelf() { 512 private IotDeviceServiceImpl getSelf() {
503 return SpringUtil.getBean(getClass()); 513 return SpringUtil.getBean(getClass());
504 } 514 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/property/IotDevicePropertyServiceImpl.java
@@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper; @@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyMapper;
22 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum; 22 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
23 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum; 23 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotThingModelTypeEnum;
24 import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField; 24 import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  25 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
25 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 26 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
26 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; 27 import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
27 import jakarta.annotation.Resource; 28 import jakarta.annotation.Resource;
@@ -30,6 +31,7 @@ import org.springframework.context.annotation.Lazy; @@ -30,6 +31,7 @@ import org.springframework.context.annotation.Lazy;
30 import org.springframework.scheduling.annotation.Async; 31 import org.springframework.scheduling.annotation.Async;
31 import org.springframework.stereotype.Service; 32 import org.springframework.stereotype.Service;
32 33
  34 +import java.math.BigDecimal;
33 import java.time.LocalDateTime; 35 import java.time.LocalDateTime;
34 import java.util.*; 36 import java.util.*;
35 37
@@ -66,6 +68,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -66,6 +68,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
66 @Resource 68 @Resource
67 @Lazy // 延迟加载,解决循环依赖 69 @Lazy // 延迟加载,解决循环依赖
68 private IotProductService productService; 70 private IotProductService productService;
  71 + @Resource
  72 + @Lazy // 延迟加载,解决循环依赖
  73 + private IotDeviceService deviceService;
69 74
70 @Resource 75 @Resource
71 private DevicePropertyRedisDAO deviceDataRedisDAO; 76 private DevicePropertyRedisDAO deviceDataRedisDAO;
@@ -168,6 +173,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -168,6 +173,9 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
168 Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry -> 173 Map<String, IotDevicePropertyDO> properties2 = convertMap(properties.entrySet(), Map.Entry::getKey, entry ->
169 IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build()); 174 IotDevicePropertyDO.builder().value(entry.getValue()).updateTime(message.getReportTime()).build());
170 deviceDataRedisDAO.putAll(device.getId(), properties2); 175 deviceDataRedisDAO.putAll(device.getId(), properties2);
  176 +
  177 + // 2.3 提取 GeoLocation 并更新设备定位
  178 + extractAndUpdateDeviceLocation(device, (Map<?, ?>) message.getParams());
171 } 179 }
172 180
173 @Override 181 @Override
@@ -213,4 +221,73 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService { @@ -213,4 +221,73 @@ public class IotDevicePropertyServiceImpl implements IotDevicePropertyService {
213 return deviceServerIdRedisDAO.get(id); 221 return deviceServerIdRedisDAO.get(id);
214 } 222 }
215 223
  224 + // ========== 设备定位相关操作 ==========
  225 +
  226 + /**
  227 + * 从属性中提取 GeoLocation 并更新设备定位
  228 + *
  229 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/device-geolocation">阿里云规范</a>
  230 + * GeoLocation 结构体包含:Longitude, Latitude, Altitude, CoordinateSystem
  231 + */
  232 + private void extractAndUpdateDeviceLocation(IotDeviceDO device, Map<?, ?> params) {
  233 + // 1. 解析 GeoLocation 经纬度坐标
  234 + Double[] location = parseGeoLocation(params);
  235 + if (location == null) {
  236 + return;
  237 + }
  238 +
  239 + // 2. 更新设备定位
  240 + deviceService.updateDeviceLocation(device,
  241 + BigDecimal.valueOf(location[0]), BigDecimal.valueOf(location[1]));
  242 + log.info("[extractAndUpdateGeoLocation][设备({}) 定位更新: lng={}, lat={}]",
  243 + device.getId(), location[0], location[1]);
  244 + }
  245 +
  246 + /**
  247 + * 从属性参数中解析 GeoLocation,返回经纬度坐标数组 [longitude, latitude]
  248 + *
  249 + * @param params 属性参数
  250 + * @return [经度, 纬度],解析失败返回 null
  251 + */
  252 + @SuppressWarnings("unchecked")
  253 + // TODO @AI:返回 BigDecimal 数组;
  254 + private Double[] parseGeoLocation(Map<?, ?> params) {
  255 + if (params == null) {
  256 + return null;
  257 + }
  258 + // 1. 查找 GeoLocation 属性(标识符为 GeoLocation 或 geoLocation)
  259 + Object geoValue = params.get("GeoLocation");
  260 + if (geoValue == null) {
  261 + geoValue = params.get("geoLocation");
  262 + }
  263 + if (geoValue == null) {
  264 + return null;
  265 + }
  266 +
  267 + // 2. 转换为 Map
  268 + Map<String, Object> geoLocation = null;
  269 + if (geoValue instanceof Map) {
  270 + geoLocation = (Map<String, Object>) geoValue;
  271 + } else if (geoValue instanceof String) {
  272 + geoLocation = JsonUtils.parseObject((String) geoValue, Map.class);
  273 + }
  274 + if (geoLocation == null) {
  275 + return null;
  276 + }
  277 +
  278 + // 3. 提取经纬度(支持阿里云命名规范:首字母大写)
  279 + Double longitude = MapUtil.getDouble(geoLocation, "Longitude");
  280 + if (longitude == null) {
  281 + longitude = MapUtil.getDouble(geoLocation, "longitude");
  282 + }
  283 + Double latitude = MapUtil.getDouble(geoLocation, "Latitude");
  284 + if (latitude == null) {
  285 + latitude = MapUtil.getDouble(geoLocation, "latitude");
  286 + }
  287 + if (longitude == null || latitude == null) {
  288 + return null;
  289 + }
  290 + return new Double[]{longitude, latitude};
  291 + }
  292 +
216 } 293 }
217 \ No newline at end of file 294 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/IotDataRuleServiceImpl.java
@@ -271,6 +271,10 @@ public class IotDataRuleServiceImpl implements IotDataRuleService { @@ -271,6 +271,10 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
271 if (ObjUtil.notEqual(action.getType(), dataSink.getType())) { 271 if (ObjUtil.notEqual(action.getType(), dataSink.getType())) {
272 return; 272 return;
273 } 273 }
  274 + if (CommonStatusEnum.isDisable(dataSink.getStatus())) {
  275 + log.warn("[executeDataRuleAction][消息({}) 数据目的({}) 状态为禁用]", message.getId(), dataSink.getId());
  276 + return;
  277 + }
274 try { 278 try {
275 action.execute(message, dataSink); 279 action.execute(message, dataSink);
276 log.info("[executeDataRuleAction][消息({}) 数据目的({}) 执行成功]", message.getId(), dataSink.getId()); 280 log.info("[executeDataRuleAction][消息({}) 数据目的({}) 执行成功]", message.getId(), dataSink.getId());