Commit 3a832d9fb46e2078745f8040aee72134a07dc942
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()); |