【同步】BOOT 和 CLOUD 的功能(IoT)

This commit is contained in:
YunaiV
2026-02-14 16:35:48 +08:00
parent 2d4251eda7
commit 92eda45afd
245 changed files with 14927 additions and 7689 deletions

View File

@@ -153,18 +153,6 @@
<optional>true</optional>
</dependency>
<!-- IoT 网络组件:接收来自设备的上行数据 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-iot-net-component-http</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
</dependencies>
<build>

View File

@@ -1,31 +1,41 @@
package cn.iocoder.yudao.module.iot.api.device;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.*;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
/**
* IoT 设备 API 实现类
@@ -41,6 +51,12 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotDeviceModbusConfigService modbusConfigService;
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotDeviceModbusPointService modbusPointService;
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@@ -58,11 +74,57 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
deviceDTO.setProtocolType(product.getProtocolType()).setSerializeType(product.getSerializeType());
}
}));
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/config-list")
@PermitAll
@TenantIgnore
public CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(
@RequestBody IotModbusDeviceConfigListReqDTO listReqDTO) {
// 1. 获取 Modbus 连接配置
List<IotDeviceModbusConfigDO> configList = modbusConfigService.getDeviceModbusConfigList(listReqDTO);
if (CollUtil.isEmpty(configList)) {
return success(new ArrayList<>());
}
// 2. 组装返回结果
Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId);
Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds);
Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds);
Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId));
List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size());
for (IotDeviceModbusConfigDO config : configList) {
// 3.1 获取设备信息
IotDeviceDO device = deviceMap.get(config.getDeviceId());
if (device == null) {
continue;
}
// 3.2 按 protocolType 筛选(如果非空)
if (StrUtil.isNotEmpty(listReqDTO.getProtocolType())) {
IotProductDO product = productMap.get(device.getProductId());
if (product == null || ObjUtil.notEqual(listReqDTO.getProtocolType(), product.getProtocolType())) {
continue;
}
}
// 3.3 获取启用的点位列表
List<IotDeviceModbusPointDO> pointList = pointMap.get(config.getDeviceId());
if (CollUtil.isEmpty(pointList)) {
continue;
}
// 3.4 构建 IotModbusDeviceConfigRespDTO 对象
IotModbusDeviceConfigRespDTO configDTO = BeanUtils.toBean(config, IotModbusDeviceConfigRespDTO.class, o ->
o.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())
.setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class)));
result.add(configDTO);
}
return success(result);
}
@Override
@PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register")
@PermitAll

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备 Modbus 连接配置")
@RestController
@RequestMapping("/iot/device-modbus-config")
@Validated
public class IotDeviceModbusConfigController {
@Resource
private IotDeviceModbusConfigService modbusConfigService;
@PostMapping("/save")
@Operation(summary = "保存设备 Modbus 连接配置")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> saveDeviceModbusConfig(@Valid @RequestBody IotDeviceModbusConfigSaveReqVO saveReqVO) {
modbusConfigService.saveDeviceModbusConfig(saveReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得设备 Modbus 连接配置")
@Parameter(name = "id", description = "编号", example = "1024")
@Parameter(name = "deviceId", description = "设备编号", example = "2048")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<IotDeviceModbusConfigRespVO> getDeviceModbusConfig(
@RequestParam(value = "id", required = false) Long id,
@RequestParam(value = "deviceId", required = false) Long deviceId) {
IotDeviceModbusConfigDO modbusConfig = null;
if (id != null) {
modbusConfig = modbusConfigService.getDeviceModbusConfig(id);
} else if (deviceId != null) {
modbusConfig = modbusConfigService.getDeviceModbusConfigByDeviceId(deviceId);
}
return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class));
}
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT 设备 Modbus 点位配置")
@RestController
@RequestMapping("/iot/device-modbus-point")
@Validated
public class IotDeviceModbusPointController {
@Resource
private IotDeviceModbusPointService modbusPointService;
@PostMapping("/create")
@Operation(summary = "创建设备 Modbus 点位配置")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Long> createDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO createReqVO) {
return success(modbusPointService.createDeviceModbusPoint(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新设备 Modbus 点位配置")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> updateDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO updateReqVO) {
modbusPointService.updateDeviceModbusPoint(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除设备 Modbus 点位配置")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> deleteDeviceModbusPoint(@RequestParam("id") Long id) {
modbusPointService.deleteDeviceModbusPoint(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得设备 Modbus 点位配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<IotDeviceModbusPointRespVO> getDeviceModbusPoint(@RequestParam("id") Long id) {
IotDeviceModbusPointDO modbusPoint = modbusPointService.getDeviceModbusPoint(id);
return success(BeanUtils.toBean(modbusPoint, IotDeviceModbusPointRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得设备 Modbus 点位配置分页")
@PreAuthorize("@ss.hasPermission('iot:device:query')")
public CommonResult<PageResult<IotDeviceModbusPointRespVO>> getDeviceModbusPointPage(@Valid IotDeviceModbusPointPageReqVO pageReqVO) {
PageResult<IotDeviceModbusPointDO> pageResult = modbusPointService.getDeviceModbusPointPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceModbusPointRespVO.class));
}
}

View File

@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

View File

@@ -38,7 +38,7 @@ public class IotDeviceMessageRespVO {
@Schema(description = "请求编号", example = "req_123")
private String requestId;
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.report")
@Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.post")
private String method;
@Schema(description = "请求参数")

View File

@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置 Response VO")
@Data
public class IotDeviceModbusConfigRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long deviceId;
@Schema(description = "设备名称", example = "温湿度传感器")
private String deviceName;
@Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
private String ip;
@Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502")
private Integer port;
@Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer slaveId;
@Schema(description = "连接超时时间(毫秒)", example = "3000")
private Integer timeout;
@Schema(description = "重试间隔(毫秒)", example = "1000")
private Integer retryInterval;
@Schema(description = "工作模式", example = "1")
private Integer mode;
@Schema(description = "数据帧格式", example = "1")
private Integer frameFormat;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置新增/修改 Request VO")
@Data
public class IotDeviceModbusConfigSaveReqVO {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "设备编号不能为空")
private Long deviceId;
@Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100")
private String ip;
@Schema(description = "Modbus 端口", example = "502")
private Integer port;
@Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "从站地址不能为空")
private Integer slaveId;
@Schema(description = "连接超时时间(毫秒)", example = "3000")
private Integer timeout;
@Schema(description = "重试间隔(毫秒)", example = "1000")
private Integer retryInterval;
@Schema(description = "工作模式", example = "1")
@InEnum(IotModbusModeEnum.class)
private Integer mode;
@Schema(description = "数据帧格式", example = "1")
@InEnum(IotModbusFrameFormatEnum.class)
private Integer frameFormat;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IotDeviceModbusPointPageReqVO extends PageParam {
@Schema(description = "设备编号", example = "1024")
private Long deviceId;
@Schema(description = "属性标识符", example = "temperature")
private String identifier;
@Schema(description = "属性名称", example = "温度")
private String name;
@Schema(description = "Modbus 功能码", example = "3")
private Integer functionCode;
@Schema(description = "状态", example = "0")
private Integer status;
}

View File

@@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置 Response VO")
@Data
public class IotDeviceModbusPointRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long deviceId;
@Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long thingModelId;
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
private String identifier;
@Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温度")
private String name;
@Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer functionCode;
@Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer registerAddress;
@Schema(description = "寄存器数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer registerCount;
@Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
private String byteOrder;
@Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
private String rawDataType;
@Schema(description = "缩放因子", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
private BigDecimal scale;
@Schema(description = "轮询间隔(毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000")
private Integer pollInterval;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置新增/修改 Request VO")
@Data
public class IotDeviceModbusPointSaveReqVO {
@Schema(description = "主键", example = "1")
private Long id;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "设备编号不能为空")
private Long deviceId;
@Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
@NotNull(message = "物模型属性编号不能为空")
private Long thingModelId;
@Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "Modbus 功能码不能为空")
private Integer functionCode;
@Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "寄存器起始地址不能为空")
private Integer registerAddress;
@Schema(description = "寄存器数量", example = "1")
private Integer registerCount;
@Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
@NotEmpty(message = "字节序不能为空")
private String byteOrder;
@Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
@NotEmpty(message = "原始数据类型不能为空")
private String rawDataType;
@Schema(description = "缩放因子", example = "1.0")
private BigDecimal scale;
@Schema(description = "轮询间隔(毫秒)", example = "5000")
private Integer pollInterval;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@@ -21,11 +21,7 @@ import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -48,8 +44,8 @@ public class IotOtaTaskRecordController {
@GetMapping("/get-status-statistics")
@Operation(summary = "获得 OTA 升级记录状态统计")
@Parameters({
@Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
@Parameter(name = "taskId", description = "升级任务编号", example = "2048")
@Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
@Parameter(name = "taskId", description = "升级任务编号", example = "2048")
})
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatusStatistics(
@@ -68,17 +64,17 @@ public class IotOtaTaskRecordController {
return success(PageResult.empty());
}
// 批量查询固件信息
Map<Long, IotOtaFirmwareDO> firmwareMap = otaFirmwareService.getOtaFirmwareMap(
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId));
// 批量查询固件信息
Map<Long, IotOtaFirmwareDO> firmwareMap = otaFirmwareService.getOtaFirmwareMap(
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getFromFirmwareId));
Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getDeviceId));
convertSet(pageResult.getList(), IotOtaTaskRecordDO::getDeviceId));
// 转换为响应 VO
return success(BeanUtils.toBean(pageResult, IotOtaTaskRecordRespVO.class, (vo) -> {
MapUtils.findAndThen(firmwareMap, vo.getFromFirmwareId(), firmware ->
vo.setFromFirmwareVersion(firmware.getVersion()));
vo.setFromFirmwareVersion(firmware.getVersion()));
MapUtils.findAndThen(deviceMap, vo.getDeviceId(), device ->
vo.setDeviceName(device.getDeviceName()));
vo.setDeviceName(device.getDeviceName()));
}));
}

View File

@@ -0,0 +1,5 @@
### 请求 /iot/product/sync-property-table 接口 => 成功
POST {{baseUrl}}/iot/product/sync-property-table
Content-Type: application/json
tenant-id: {{adminTenantId}}
Authorization: Bearer {{token}}

View File

@@ -141,6 +141,14 @@ public class IotProductController {
result.getData().getList());
}
@PostMapping("/sync-property-table")
@Operation(summary = "同步产品属性表结构到 TDengine")
@PreAuthorize("@ss.hasPermission('iot:product:update')")
public CommonResult<Boolean> syncProductPropertyTable() {
productService.syncProductPropertyTable();
return success(true);
}
@GetMapping("/simple-list")
@Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
@Parameter(name = "deviceType", description = "设备类型", example = "1")

View File

@@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.iot.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -67,10 +67,15 @@ public class IotProductRespVO {
@DictFormat(DictTypeConstants.NET_TYPE)
private Integer netType;
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty(value = "数据格式", converter = DictConvert.class)
@DictFormat(DictTypeConstants.CODEC_TYPE)
private String codecType;
@Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
@ExcelProperty(value = "协议类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.PROTOCOL_TYPE)
private String protocolType;
@Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
@ExcelProperty(value = "序列化类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.SERIALIZE_TYPE)
private String serializeType;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")

View File

@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -44,9 +46,15 @@ public class IotProductSaveReqVO {
@InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
private Integer netType;
@Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotEmpty(message = "数据格式不能为空")
private String codecType;
@Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
@InEnum(value = IotProtocolTypeEnum.class, message = "协议类型必须是 {value}")
@NotEmpty(message = "协议类型不能为空")
private String protocolType;
@Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
@InEnum(value = IotSerializeTypeEnum.class, message = "序列化类型必须是 {value}")
@NotEmpty(message = "序列化类型不能为空")
private String serializeType;
@Schema(description = "是否开启动态注册", example = "false")
@NotNull(message = "是否开启动态注册不能为空")

View File

@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;

View File

@@ -1,168 +0,0 @@
package cn.iocoder.yudao.module.iot.core.mq.message;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* IoT 设备消息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IotDeviceMessage {
/**
* 【消息总线】应用的设备消息 Topic由 iot-gateway 发给 iot-biz 进行消费
*/
public static final String MESSAGE_BUS_DEVICE_MESSAGE_TOPIC = "iot_device_message";
/**
* 【消息总线】设备消息 Topic由 iot-biz 发送给 iot-gateway 的某个 "server"(protocol) 进行消费
*
* 其中,%s 就是该"server"(protocol) 的标识
*/
public static final String MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC = MESSAGE_BUS_DEVICE_MESSAGE_TOPIC + "_%s";
/**
* 消息编号
*
* 由后端生成,通过 {@link IotDeviceMessageUtils#generateMessageId()}
*/
private String id;
/**
* 上报时间
*
* 由后端生成,当前时间
*/
private LocalDateTime reportTime;
/**
* 设备编号
*/
private Long deviceId;
/**
* 租户编号
*/
private Long tenantId;
/**
* 服务编号,该消息由哪个 server 发送
*/
private String serverId;
// ========== codec编解码字段 ==========
/**
* 请求编号
*
* 由设备生成,对应阿里云 IoT 的 Alink 协议中的 id、华为云 IoTDA 协议的 request_id
*/
private String requestId;
/**
* 请求方法
*
* 枚举 {@link IotDeviceMessageMethodEnum}
* 例如说thing.property.report 属性上报
*/
private String method;
/**
* 请求参数
*
* 例如说:属性上报的 properties、事件上报的 params
*/
private Object params;
/**
* 响应结果
*/
private Object data;
/**
* 响应错误码
*/
private Integer code;
/**
* 返回结果信息
*/
private String msg;
// ========== 基础方法:只传递"codec编解码字段" ==========
public static IotDeviceMessage requestOf(String method) {
return requestOf(null, method, null);
}
public static IotDeviceMessage requestOf(String method, Object params) {
return requestOf(null, method, params);
}
public static IotDeviceMessage requestOf(String requestId, String method, Object params) {
return of(requestId, method, params, null, null, null);
}
/**
* 创建设备请求消息(包含设备信息)
*
* @param deviceId 设备编号
* @param tenantId 租户编号
* @param serverId 服务标识
* @param method 消息方法
* @param params 消息参数
* @return 消息对象
*/
public static IotDeviceMessage requestOf(Long deviceId, Long tenantId, String serverId,
String method, Object params) {
IotDeviceMessage message = of(null, method, params, null, null, null);
return message.setId(IotDeviceMessageUtils.generateMessageId())
.setDeviceId(deviceId).setTenantId(tenantId).setServerId(serverId);
}
public static IotDeviceMessage replyOf(String requestId, String method,
Object data, Integer code, String msg) {
if (code == null) {
code = GlobalErrorCodeConstants.SUCCESS.getCode();
msg = GlobalErrorCodeConstants.SUCCESS.getMsg();
}
return of(requestId, method, null, data, code, msg);
}
public static IotDeviceMessage of(String requestId, String method,
Object params, Object data, Integer code, String msg) {
// 通用参数
IotDeviceMessage message = new IotDeviceMessage()
.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now());
// 当前参数
message.setRequestId(requestId).setMethod(method).setParams(params)
.setData(data).setCode(code).setMsg(msg);
return message;
}
// ========== 核心方法:在 of 基础方法之上,添加对应 method ==========
public static IotDeviceMessage buildStateUpdateOnline() {
return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
MapUtil.of("state", IotDeviceStateEnum.ONLINE.getState()));
}
public static IotDeviceMessage buildStateOffline() {
return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
MapUtil.of("state", IotDeviceStateEnum.OFFLINE.getState()));
}
public static IotDeviceMessage buildOtaUpgrade(String version, String fileUrl, Long fileSize,
String fileDigestAlgorithm, String fileDigestValue) {
return requestOf(IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), MapUtil.builder()
.put("version", version).put("fileUrl", fileUrl).put("fileSize", fileSize)
.put("fileDigestAlgorithm", fileDigestAlgorithm).put("fileDigestValue", fileDigestValue)
.build());
}
}

View File

@@ -1,52 +0,0 @@
package cn.iocoder.yudao.module.iot.core.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
/**
* IoT 设备【认证】的工具类,参考阿里云
*
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/how-do-i-obtain-mqtt-parameters-for-authentication">如何计算 MQTT 签名参数</a>
*/
public class IotDeviceAuthUtils {
public static IotDeviceAuthReqDTO getAuthInfo(String productKey, String deviceName, String deviceSecret) {
String clientId = buildClientId(productKey, deviceName);
String username = buildUsername(productKey, deviceName);
String password = buildPassword(deviceSecret,
buildContent(clientId, productKey, deviceName, deviceSecret));
return new IotDeviceAuthReqDTO(clientId, username, password);
}
public static String buildClientId(String productKey, String deviceName) {
return String.format("%s.%s", productKey, deviceName);
}
public static String buildUsername(String productKey, String deviceName) {
return String.format("%s&%s", deviceName, productKey);
}
public static String buildPassword(String deviceSecret, String content) {
return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(deviceSecret))
.digestHex(content);
}
private static String buildContent(String clientId, String productKey, String deviceName, String deviceSecret) {
return "clientId" + clientId +
"deviceName" + deviceName +
"deviceSecret" + deviceSecret +
"productKey" + productKey;
}
public static IotDeviceIdentity parseUsername(String username) {
String[] usernameParts = username.split("&");
if (usernameParts.length != 2) {
return null;
}
return new IotDeviceIdentity(usernameParts[1], usernameParts[0]);
}
}

View File

@@ -2,14 +2,17 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -108,10 +111,6 @@ public class IotDeviceDO extends TenantBaseDO {
*/
private LocalDateTime activeTime;
/**
* 设备的 IP 地址
*/
private String ip;
/**
* 固件编号
*

View File

@@ -84,7 +84,7 @@ public class IotDeviceMessageDO {
* 请求方法
*
* 枚举 {@link IotDeviceMessageMethodEnum}
* 例如说thing.property.report 属性上报
* 例如说thing.property.post 属性上报
*/
private String method;
/**

View File

@@ -0,0 +1,85 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT 设备 Modbus 连接配置 DO
*
* @author 芋道源码
*/
@TableName("iot_device_modbus_config")
@KeySequence("iot_device_modbus_config_seq")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceModbusConfigDO extends TenantBaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 产品编号
*
* 关联 {@link IotProductDO#getId()}
*/
private Long productId;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* Modbus 服务器 IP 地址
*/
private String ip;
/**
* Modbus 服务器端口
*/
private Integer port;
/**
* 从站地址
*/
private Integer slaveId;
/**
* 连接超时时间,单位:毫秒
*/
private Integer timeout;
/**
* 重试间隔,单位:毫秒
*/
private Integer retryInterval;
/**
* 模式
*
* @see IotModbusModeEnum
*/
private Integer mode;
/**
* 数据帧格式
*
* @see IotModbusFrameFormatEnum
*/
private Integer frameFormat;
/**
* 状态
*
* 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
}

View File

@@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* IoT 设备 Modbus 点位配置 DO
*
* @author 芋道源码
*/
@TableName("iot_device_modbus_point")
@KeySequence("iot_device_modbus_point_seq")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceModbusPointDO extends TenantBaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 设备编号
*
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* 物模型属性编号
*
* 关联 {@link IotThingModelDO#getId()}
*/
private Long thingModelId;
/**
* 属性标识符
*
* 冗余 {@link IotThingModelDO#getIdentifier()}
*/
private String identifier;
/**
* 属性名称
*
* 冗余 {@link IotThingModelDO#getName()}
*/
private String name;
// ========== Modbus 协议配置 ==========
/**
* Modbus 功能码
*
* 取值范围FC01-04读线圈、读离散输入、读保持寄存器、读输入寄存器
*/
private Integer functionCode;
/**
* 寄存器起始地址
*/
private Integer registerAddress;
/**
* 寄存器数量
*/
private Integer registerCount;
/**
* 字节序
*
* 枚举 {@link IotModbusByteOrderEnum}
*/
private String byteOrder;
/**
* 原始数据类型
*
* 枚举 {@link IotModbusRawDataTypeEnum}
*/
private String rawDataType;
/**
* 缩放因子
*/
private BigDecimal scale;
/**
* 轮询间隔(毫秒)
*/
private Integer pollInterval;
/**
* 状态
*
* 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
}

View File

@@ -4,7 +4,10 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* IoT 产品 DO
@@ -78,12 +81,16 @@ public class IotProductDO extends TenantBaseDO {
*/
private Integer netType;
/**
* 数据格式(编解码器类型
* 协议类型
* <p>
* 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}
*
* 目的:用于 gateway-server 解析消息格式
* 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum}
*/
private String codecType;
private String protocolType;
/**
* 序列化类型
* <p>
* 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum}
*/
private String serializeType;
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.iot.dal.mysql.device;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 设备 Modbus 连接配置 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbusConfigDO> {
default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) {
return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId);
}
default List<IotDeviceModbusConfigDO> selectList(IotModbusDeviceConfigListReqDTO reqDTO) {
return selectList(new LambdaQueryWrapperX<IotDeviceModbusConfigDO>()
.eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqDTO.getStatus())
.eqIfPresent(IotDeviceModbusConfigDO::getMode, reqDTO.getMode())
.inIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqDTO.getDeviceIds()));
}
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.iot.dal.mysql.device;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
* IoT 设备 Modbus 点位配置 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotDeviceModbusPointMapper extends BaseMapperX<IotDeviceModbusPointDO> {
default PageResult<IotDeviceModbusPointDO> selectPage(IotDeviceModbusPointPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
.eqIfPresent(IotDeviceModbusPointDO::getDeviceId, reqVO.getDeviceId())
.likeIfPresent(IotDeviceModbusPointDO::getIdentifier, reqVO.getIdentifier())
.likeIfPresent(IotDeviceModbusPointDO::getName, reqVO.getName())
.eqIfPresent(IotDeviceModbusPointDO::getFunctionCode, reqVO.getFunctionCode())
.eqIfPresent(IotDeviceModbusPointDO::getStatus, reqVO.getStatus())
.orderByDesc(IotDeviceModbusPointDO::getId));
}
default List<IotDeviceModbusPointDO> selectListByDeviceIdsAndStatus(Collection<Long> deviceIds, Integer status) {
return selectList(new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
.in(IotDeviceModbusPointDO::getDeviceId, deviceIds)
.eq(IotDeviceModbusPointDO::getStatus, status));
}
default IotDeviceModbusPointDO selectByDeviceIdAndIdentifier(Long deviceId, String identifier) {
return selectOne(IotDeviceModbusPointDO::getDeviceId, deviceId,
IotDeviceModbusPointDO::getIdentifier, identifier);
}
default void updateByThingModelId(Long thingModelId, IotDeviceModbusPointDO updateObj) {
update(updateObj, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
.eq(IotDeviceModbusPointDO::getThingModelId, thingModelId));
}
}

View File

@@ -38,6 +38,10 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
.apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
}
default List<IotProductDO> selectListByStatus(Integer status) {
return selectList(IotProductDO::getStatus, status);
}
default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) {
return selectCount(new LambdaQueryWrapperX<IotProductDO>()
.geIfPresent(IotProductDO::getCreateTime, createTime));

View File

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.job.device;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.framework.iot.config.YudaoIotProperties;

View File

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.job.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;

View File

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -67,7 +67,6 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber<IotDevic
IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
// 1.2 更新设备的连接 server
// TODO 芋艿HTTP 网关的上行消息,不应该更新 serverId会覆盖掉 MQTT 等长连接的 serverId导致下行消息无法发送。
devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());
// 2. 未上线的设备,强制上线

View File

@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
public class IotDataRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
@Resource
private IotDataRuleService dataRuleService;

View File

@@ -9,7 +9,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
// TODO @puhui999后面重构哈
/**
* 针对 {@link IotDeviceMessage} 的消费者处理规则场景
*
@@ -17,7 +16,7 @@ import org.springframework.stereotype.Component;
*/
@Component
@Slf4j
public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
public class IotSceneRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
@Resource
private IotSceneRuleService sceneRuleService;

View File

@@ -41,7 +41,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
public Long createAlertConfig(IotAlertConfigSaveReqVO createReqVO) {
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(createReqVO.getSceneRuleIds());
adminUserApi.validateUserList(createReqVO.getReceiveUserIds()).checkError();
adminUserApi.validateUserList(createReqVO.getReceiveUserIds());
// 插入
IotAlertConfigDO alertConfig = BeanUtils.toBean(createReqVO, IotAlertConfigDO.class);
@@ -55,7 +55,7 @@ public class IotAlertConfigServiceImpl implements IotAlertConfigService {
validateAlertConfigExists(updateReqVO.getId());
// 校验关联数据是否存在
sceneRuleService.validateSceneRuleList(updateReqVO.getSceneRuleIds());
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds()).checkError();
adminUserApi.validateUserList(updateReqVO.getReceiveUserIds());
// 更新
IotAlertConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotAlertConfigDO.class);

View File

@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
import jakarta.validation.Valid;
import java.util.List;
/**
* IoT 设备 Modbus 连接配置 Service 接口
*
* @author 芋道源码
*/
public interface IotDeviceModbusConfigService {
/**
* 保存设备 Modbus 连接配置(新增或更新)
*
* @param saveReqVO 保存信息
*/
void saveDeviceModbusConfig(@Valid IotDeviceModbusConfigSaveReqVO saveReqVO);
/**
* 获得设备 Modbus 连接配置
*
* @param id 编号
* @return 设备 Modbus 连接配置
*/
IotDeviceModbusConfigDO getDeviceModbusConfig(Long id);
/**
* 根据设备编号获得 Modbus 连接配置
*
* @param deviceId 设备编号
* @return 设备 Modbus 连接配置
*/
IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId);
/**
* 获得 Modbus 连接配置列表
*
* @param listReqDTO 查询参数
* @return Modbus 连接配置列表
*/
List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO);
}

View File

@@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
/**
* IoT 设备 Modbus 连接配置 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigService {
@Resource
private IotDeviceModbusConfigMapper modbusConfigMapper;
@Resource
private IotDeviceService deviceService;
@Resource
private IotProductService productService;
@Override
public void saveDeviceModbusConfig(IotDeviceModbusConfigSaveReqVO saveReqVO) {
// 1.1 校验设备存在
IotDeviceDO device = deviceService.validateDeviceExists(saveReqVO.getDeviceId());
// 1.2 根据产品 protocolType 条件校验
IotProductDO product = productService.getProduct(device.getProductId());
Assert.notNull(product, "产品不存在");
validateModbusConfigByProtocolType(saveReqVO, product.getProtocolType());
// 2. 根据数据库中是否已有配置,决定是新增还是更新
IotDeviceModbusConfigDO existConfig = modbusConfigMapper.selectByDeviceId(saveReqVO.getDeviceId());
if (existConfig == null) {
IotDeviceModbusConfigDO modbusConfig = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class);
modbusConfigMapper.insert(modbusConfig);
} else {
IotDeviceModbusConfigDO updateObj = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class,
o -> o.setId(existConfig.getId()));
modbusConfigMapper.updateById(updateObj);
}
}
@Override
public IotDeviceModbusConfigDO getDeviceModbusConfig(Long id) {
return modbusConfigMapper.selectById(id);
}
@Override
public IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId) {
return modbusConfigMapper.selectByDeviceId(deviceId);
}
@Override
public List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO) {
return modbusConfigMapper.selectList(listReqDTO);
}
private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) {
IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType);
if (protocolTypeEnum == null) {
return;
}
if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_CLIENT) {
Assert.isTrue(StrUtil.isNotEmpty(saveReqVO.getIp()), "Client 模式下IP 地址不能为空");
Assert.notNull(saveReqVO.getPort(), "Client 模式下,端口不能为空");
Assert.notNull(saveReqVO.getTimeout(), "Client 模式下,连接超时时间不能为空");
Assert.notNull(saveReqVO.getRetryInterval(), "Client 模式下,重试间隔不能为空");
} else if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_SERVER) {
Assert.notNull(saveReqVO.getMode(), "Server 模式下,工作模式不能为空");
Assert.notNull(saveReqVO.getFrameFormat(), "Server 模式下,数据帧格式不能为空");
}
}
}

View File

@@ -0,0 +1,75 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* IoT 设备 Modbus 点位配置 Service 接口
*
* @author 芋道源码
*/
public interface IotDeviceModbusPointService {
/**
* 创建设备 Modbus 点位配置
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO createReqVO);
/**
* 更新设备 Modbus 点位配置
*
* @param updateReqVO 更新信息
*/
void updateDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO updateReqVO);
/**
* 删除设备 Modbus 点位配置
*
* @param id 编号
*/
void deleteDeviceModbusPoint(Long id);
/**
* 获得设备 Modbus 点位配置
*
* @param id 编号
* @return 设备 Modbus 点位配置
*/
IotDeviceModbusPointDO getDeviceModbusPoint(Long id);
/**
* 获得设备 Modbus 点位配置分页
*
* @param pageReqVO 分页查询
* @return 设备 Modbus 点位配置分页
*/
PageResult<IotDeviceModbusPointDO> getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO);
/**
* 物模型变更时更新关联点位的冗余字段identifier、name
*
* @param thingModelId 物模型编号
* @param identifier 物模型标识符
* @param name 物模型名称
*/
void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name);
/**
* 根据设备编号批量获得启用的点位配置 Map
*
* @param deviceIds 设备编号集合
* @return 设备点位 Mapkey 为设备编号value 为点位配置列表
*/
Map<Long, List<IotDeviceModbusPointDO>> getEnabledDeviceModbusPointMapByDeviceIds(Collection<Long> deviceIds);
}

View File

@@ -0,0 +1,135 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusPointMapper;
import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
* IoT 设备 Modbus 点位配置 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotDeviceModbusPointServiceImpl implements IotDeviceModbusPointService {
@Resource
private IotDeviceModbusPointMapper modbusPointMapper;
@Resource
private IotDeviceService deviceService;
@Resource
private IotThingModelService thingModelService;
@Override
public Long createDeviceModbusPoint(IotDeviceModbusPointSaveReqVO createReqVO) {
// 1.1 校验设备存在
deviceService.validateDeviceExists(createReqVO.getDeviceId());
// 1.2 校验物模型属性存在
IotThingModelDO thingModel = validateThingModelExists(createReqVO.getThingModelId());
// 1.3 校验同一设备下点位唯一性(基于 identifier
validateDeviceModbusPointUnique(createReqVO.getDeviceId(), thingModel.getIdentifier(), null);
// 2. 插入
IotDeviceModbusPointDO modbusPoint = BeanUtils.toBean(createReqVO, IotDeviceModbusPointDO.class,
o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
modbusPointMapper.insert(modbusPoint);
return modbusPoint.getId();
}
@Override
public void updateDeviceModbusPoint(IotDeviceModbusPointSaveReqVO updateReqVO) {
// 1.1 校验存在
validateDeviceModbusPointExists(updateReqVO.getId());
// 1.2 校验设备存在
deviceService.validateDeviceExists(updateReqVO.getDeviceId());
// 1.3 校验物模型属性存在
IotThingModelDO thingModel = validateThingModelExists(updateReqVO.getThingModelId());
// 1.4 校验同一设备下点位唯一性
validateDeviceModbusPointUnique(updateReqVO.getDeviceId(), thingModel.getIdentifier(), updateReqVO.getId());
// 2. 更新
IotDeviceModbusPointDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceModbusPointDO.class,
o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
modbusPointMapper.updateById(updateObj);
}
@Override
public void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name) {
IotDeviceModbusPointDO updateObj = new IotDeviceModbusPointDO()
.setIdentifier(identifier).setName(name);
modbusPointMapper.updateByThingModelId(thingModelId, updateObj);
}
private IotThingModelDO validateThingModelExists(Long id) {
IotThingModelDO thingModel = thingModelService.getThingModel(id);
if (thingModel == null) {
throw exception(THING_MODEL_NOT_EXISTS);
}
return thingModel;
}
@Override
public void deleteDeviceModbusPoint(Long id) {
// 校验存在
validateDeviceModbusPointExists(id);
// 删除
modbusPointMapper.deleteById(id);
}
private void validateDeviceModbusPointExists(Long id) {
IotDeviceModbusPointDO point = modbusPointMapper.selectById(id);
if (point == null) {
throw exception(DEVICE_MODBUS_POINT_NOT_EXISTS);
}
}
private void validateDeviceModbusPointUnique(Long deviceId, String identifier, Long excludeId) {
IotDeviceModbusPointDO point = modbusPointMapper.selectByDeviceIdAndIdentifier(deviceId, identifier);
if (point != null && ObjUtil.notEqual(point.getId(), excludeId)) {
throw exception(DEVICE_MODBUS_POINT_EXISTS);
}
}
@Override
public IotDeviceModbusPointDO getDeviceModbusPoint(Long id) {
return modbusPointMapper.selectById(id);
}
@Override
public PageResult<IotDeviceModbusPointDO> getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO) {
return modbusPointMapper.selectPage(pageReqVO);
}
@Override
public Map<Long, List<IotDeviceModbusPointDO>> getEnabledDeviceModbusPointMapByDeviceIds(Collection<Long> deviceIds) {
if (CollUtil.isEmpty(deviceIds)) {
return Collections.emptyMap();
}
List<IotDeviceModbusPointDO> pointList = modbusPointMapper.selectListByDeviceIdsAndStatus(
deviceIds, CommonStatusEnum.ENABLE.getStatus());
return convertMultiMap(pointList, IotDeviceModbusPointDO::getDeviceId);
}
}

View File

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;

View File

@@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
@@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
throw exception(DEVICE_REGISTER_DISABLED);
}
// 1.3 验证 productSecret
if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) {
// 1.3 【重要!!!】验证签名
if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(),
product.getProductSecret(), reqDTO.getSign())) {
throw exception(DEVICE_REGISTER_SECRET_INVALID);
}
return TenantUtils.execute(product.getTenantId(), () -> {

View File

@@ -3,11 +3,15 @@ package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaProgressReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaUpgradeReqDTO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
@@ -133,9 +137,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
try {
// 1. 推送 OTA 任务记录
IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(
fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),
fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue());
IotDeviceOtaUpgradeReqDTO params = BeanUtils.toBean(fireware, IotDeviceOtaUpgradeReqDTO.class);
IotDeviceMessage message = IotDeviceMessage.requestOf(
IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), params);
deviceMessageService.sendDeviceMessage(message, device);
// 2. 更新 OTA 升级记录状态为进行中
@@ -163,17 +167,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
@SuppressWarnings("unchecked")
public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
// 1.1 参数解析
Map<String, Object> params = (Map<String, Object>) message.getParams();
String version = MapUtil.getStr(params, "version");
IotDeviceOtaProgressReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceOtaProgressReqDTO.class);
String version = params.getVersion();
Assert.notBlank(version, "version 不能为空");
Integer status = MapUtil.getInt(params, "status");
Integer status = params.getStatus();
Assert.notNull(status, "status 不能为空");
Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确");
String description = MapUtil.getStr(params, "description");
Integer progress = MapUtil.getInt(params, "progress");
String description = params.getDescription();
Integer progress = params.getProgress();
Assert.notNull(progress, "progress 不能为空");
Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
// 1.2 查询 OTA 升级记录

View File

@@ -10,6 +10,9 @@ import javax.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* IoT 产品 Service 接口
@@ -121,6 +124,24 @@ public interface IotProductService {
*/
Long getProductCount(@Nullable LocalDateTime createTime);
/**
* 批量获得产品列表
*
* @param ids 产品编号集合
* @return 产品列表
*/
List<IotProductDO> getProductList(Collection<Long> ids);
/**
* 批量获得产品 Map
*
* @param ids 产品编号集合
* @return 产品 Mapkey: 产品编号, value: 产品)
*/
default Map<Long, IotProductDO> getProductMap(Collection<Long> ids) {
return convertMap(getProductList(ids), IotProductDO::getId);
}
/**
* 批量校验产品存在
*
@@ -128,4 +149,11 @@ public interface IotProductService {
*/
void validateProductsExist(Collection<Long> ids);
/**
* 同步产品的 TDengine 表结构
*
* 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中
*/
void syncProductPropertyTable();
}

View File

@@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.iot.service.product;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
@@ -15,8 +15,10 @@ import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -33,6 +35,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
*
* @author ahh
*/
@Slf4j
@Service
@Validated
public class IotProductServiceImpl implements IotProductService {
@@ -40,10 +43,11 @@ public class IotProductServiceImpl implements IotProductService {
@Resource
private IotProductMapper productMapper;
@Resource
private IotDevicePropertyService devicePropertyDataService;
@Resource
private IotDeviceService deviceService;
@Resource
@Lazy // 延迟加载,避免循环依赖
private IotDevicePropertyService devicePropertyDataService;
@Override
public Long createProduct(IotProductSaveReqVO createReqVO) {
@@ -171,6 +175,32 @@ public class IotProductServiceImpl implements IotProductService {
return productMapper.selectCountByCreateTime(createTime);
}
@Override
public List<IotProductDO> getProductList(Collection<Long> ids) {
return productMapper.selectByIds(ids);
}
@Override
public void syncProductPropertyTable() {
// 1. 获取所有已发布的产品
List<IotProductDO> products = productMapper.selectListByStatus(
IotProductStatusEnum.PUBLISHED.getStatus());
log.info("[syncProductPropertyTable][开始同步,已发布产品数量({})]", products.size());
// 2. 遍历同步 TDengine 表结构(创建产品超级表数据模型)
int successCount = 0;
for (IotProductDO product : products) {
try {
devicePropertyDataService.defineDevicePropertyData(product.getId());
successCount++;
log.info("[syncProductPropertyTable][产品({}/{}) 同步成功]", product.getId(), product.getName());
} catch (Exception e) {
log.error("[syncProductPropertyTable][产品({}/{}) 同步失败]", product.getId(), product.getName(), e);
}
}
log.info("[syncProductPropertyTable][同步完成,成功({}/{})个]", successCount, products.size());
}
@Override
public void validateProductsExist(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {

View File

@@ -19,9 +19,7 @@ import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_DELETE_FAIL_USED_BY_RULE;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_NAME_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DATA_SINK_NOT_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
* IoT 数据流转目的 Service 实现类

View File

@@ -14,8 +14,6 @@ import java.time.Duration;
// TODO @芋艿:数据库
// TODO @芋艿mqtt
// TODO @芋艿tcp
// TODO @芋艿websocket
/**
* 可缓存的 {@link IotDataRuleAction} 抽象实现

View File

@@ -1,8 +1,8 @@
package cn.iocoder.yudao.module.iot.service.rule.data.action;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkKafkaConfig;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkKafkaConfig;
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.producer.ProducerConfig;

View File

@@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类
@@ -24,7 +23,7 @@ import java.util.Map;
*/
@Component
@Slf4j
public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
public class IotDevicePropertySetSceneRuleAction implements IotSceneRuleAction {
@Resource
private IotDeviceService deviceService;

View File

@@ -15,9 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* IoT 设备服务调用的 {@link IotSceneRuleAction} 实现类

View File

@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -50,6 +51,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotProductService productService;
@Resource
@Lazy // 延迟加载,解决循环依赖
private IotDeviceModbusPointService deviceModbusPointService;
@Override
@Transactional(rollbackFor = Exception.class)
@@ -84,7 +88,11 @@ public class IotThingModelServiceImpl implements IotThingModelService {
IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
thingModelMapper.updateById(thingModel);
// 3. 删除缓存
// 3. 同步更新 Modbus 点位的冗余字段identifier、name
deviceModbusPointService.updateDeviceModbusPointByThingModel(
updateReqVO.getId(), updateReqVO.getIdentifier(), updateReqVO.getName());
// 4. 删除缓存
deleteThingModelListCache(updateReqVO.getProductId());
}