【同步】BOOT 和 CLOUD 的功能(IoT)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "请求参数")
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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}}
|
||||
@@ -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")
|
||||
|
||||
@@ -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("创建时间")
|
||||
|
||||
@@ -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 = "是否开启动态注册不能为空")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
/**
|
||||
* 固件编号
|
||||
*
|
||||
|
||||
@@ -84,7 +84,7 @@ public class IotDeviceMessageDO {
|
||||
* 请求方法
|
||||
*
|
||||
* 枚举 {@link IotDeviceMessageMethodEnum}
|
||||
* 例如说:thing.property.report 属性上报
|
||||
* 例如说:thing.property.post 属性上报
|
||||
*/
|
||||
private String method;
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. 未上线的设备,强制上线
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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 模式下,数据帧格式不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 设备点位 Map,key 为设备编号,value 为点位配置列表
|
||||
*/
|
||||
Map<Long, List<IotDeviceModbusPointDO>> getEnabledDeviceModbusPointMapByDeviceIds(Collection<Long> deviceIds);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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(), () -> {
|
||||
|
||||
@@ -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 升级记录
|
||||
|
||||
@@ -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 产品 Map(key: 产品编号, 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();
|
||||
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 实现类
|
||||
|
||||
@@ -14,8 +14,6 @@ import java.time.Duration;
|
||||
|
||||
// TODO @芋艿:数据库
|
||||
// TODO @芋艿:mqtt
|
||||
// TODO @芋艿:tcp
|
||||
// TODO @芋艿:websocket
|
||||
|
||||
/**
|
||||
* 可缓存的 {@link IotDataRuleAction} 抽象实现
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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} 实现类
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user