Merge remote-tracking branch 'yudao/feature/iot' into iot

This commit is contained in:
puhui999 2024-12-16 10:51:02 +08:00
commit c5894765b5
58 changed files with 2533 additions and 265 deletions

View File

@ -66,6 +66,7 @@
<ip2region.version>2.7.0</ip2region.version>
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
<mqtt.version>1.2.5</mqtt.version>
<pf4j-spring.version>0.9.0</pf4j-spring.version>
<!-- 三方云服务相关 -->
<okio.version>3.5.0</okio.version>
<okhttp3.version>4.11.0</okhttp3.version>
@ -605,6 +606,13 @@
<version>${mqtt.version}</version>
</dependency>
<!-- PF4J -->
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>${pf4j-spring.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.framework.mybatis.core.type;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
/**
* Set<Long> 的类型转换器实现类对应数据库的 varchar 类型
*
* @author 芋道源码
*/
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(List.class)
public class LongSetTypeHandler implements TypeHandler<Set<Long>> {
private static final String COMMA = ",";
@Override
public void setParameter(PreparedStatement ps, int i, Set<Long> strings, JdbcType jdbcType) throws SQLException {
// 设置占位符
ps.setString(i, CollUtil.join(strings, COMMA));
}
@Override
public Set<Long> getResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return getResult(value);
}
@Override
public Set<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return getResult(value);
}
@Override
public Set<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return getResult(value);
}
private Set<Long> getResult(String value) {
if (value == null) {
return null;
}
return StrUtils.splitToLongSet(value, COMMA);
}
}

View File

@ -10,6 +10,7 @@
<modules>
<module>yudao-module-iot-api</module>
<module>yudao-module-iot-biz</module>
<module>yudao-module-iot-plugin</module>
</modules>
<modelVersion>4.0.0</modelVersion>

View File

@ -13,4 +13,7 @@ public class DictTypeConstants {
public static final String PROTOCOL_TYPE = "iot_protocol_type";
public static final String DATA_FORMAT = "iot_data_format";
public static final String VALIDATE_TYPE = "iot_validate_type";
public static final String DEVICE_STATUS = "iot_device_status";
}

View File

@ -26,11 +26,26 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_NOT_EXISTS = new ErrorCode(1_050_003_000, "设备不存在");
ErrorCode DEVICE_NAME_EXISTS = new ErrorCode(1_050_003_001, "设备名称在同一产品下必须唯一");
ErrorCode DEVICE_HAS_CHILDREN = new ErrorCode(1_050_003_002, "有子设备,不允许删除");
ErrorCode DEVICE_NAME_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_003, "设备名称不能修改");
ErrorCode DEVICE_PRODUCT_CANNOT_BE_MODIFIED = new ErrorCode(1_050_003_004, "产品不能修改");
ErrorCode DEVICE_INVALID_DEVICE_STATUS = new ErrorCode(1_050_003_005, "无效的设备状态");
ErrorCode DEVICE_KEY_EXISTS = new ErrorCode(1_050_003_003, "设备标识已经存在");
ErrorCode DEVICE_GATEWAY_NOT_EXISTS = new ErrorCode(1_050_003_004, "网关设备不存在");
ErrorCode DEVICE_NOT_GATEWAY = new ErrorCode(1_050_003_005, "设备不是网关设备");
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
// ========== 产品分类 1-050-004-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");
// ========== 设备分组 1-050-005-000 ==========
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
// ========== 插件信息 1-050-006-000 ==========
ErrorCode PLUGIN_INFO_NOT_EXISTS = new ErrorCode(1_050_006_000, "插件信息不存在");
ErrorCode PLUGIN_INSTALL_FAILED = new ErrorCode(1_050_006_001, "插件安装失败");
ErrorCode PLUGIN_INSTALL_FAILED_FILE_NAME_NOT_MATCH = new ErrorCode(1_050_006_002, "插件安装失败文件名与原插件id不匹配");
ErrorCode PLUGIN_INFO_DELETE_FAILED_RUNNING = new ErrorCode(1_050_006_003, "请先停止插件");
ErrorCode PLUGIN_STATUS_INVALID = new ErrorCode(1_050_006_004, "插件状态无效");
// ========== 插件实例 1-050-007-000 ==========
ErrorCode PLUGIN_INSTANCE_NOT_EXISTS = new ErrorCode(1_050_007_000, "插件实例不存在");
}

View File

@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 部署方式枚举
*
* @author haohao
*/
@Getter
public enum IotPluginDeployTypeEnum implements IntArrayValuable {
UPLOAD(0, "上传 jar"), // TODO @haohaoUPLOAD ALONE 感觉有点冲突前者是部署方式后者是运行方式这个后续再讨论下哈
ALONE(1, "独立运行");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray();
/**
* 部署方式
*/
private final Integer deployType;
/**
* 部署方式名
*/
private final String name;
IotPluginDeployTypeEnum(Integer deployType, String name) {
this.deployType = deployType;
this.name = name;
}
public static IotPluginDeployTypeEnum fromDeployType(Integer deployType) {
for (IotPluginDeployTypeEnum value : values()) {
if (value.getDeployType().equals(deployType)) {
return value;
}
}
return null;
}
public static boolean isValidDeployType(Integer deployType) {
return fromDeployType(deployType) != null;
}
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 插件状态枚举
*
* @author haohao
*/
@Getter
public enum IotPluginStatusEnum implements IntArrayValuable {
STOPPED(0, "停止"),
RUNNING(1, "运行");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginStatusEnum::getStatus).toArray();
/**
* 状态
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
IotPluginStatusEnum(Integer status, String name) {
this.status = status;
this.name = name;
}
public static IotPluginStatusEnum fromState(Integer state) {
for (IotPluginStatusEnum value : values()) {
if (value.getStatus().equals(state)) {
return value;
}
}
return null;
}
@Override
public int[] array() {
return ARRAYS;
}
public static boolean isValidState(Integer state) {
return fromState(state) != null;
}
public static boolean contains(Integer status) {
return Arrays.stream(values()).anyMatch(e -> e.getStatus().equals(status));
}
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.iot.enums.plugin;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* IoT 插件类型枚举
*
* @author haohao
*/
@AllArgsConstructor
@Getter
public enum IotPluginTypeEnum implements IntArrayValuable {
NORMAL(0, "普通插件"),
DEVICE(1, "设备插件");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginTypeEnum::getType).toArray();
/**
* 类型
*/
private final Integer type;
/**
* 类型名
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
// TODO @haohao可以使用 hutool 简化
public static IotPluginTypeEnum fromType(Integer type) {
for (IotPluginTypeEnum value : values()) {
if (value.getType().equals(type)) {
return value;
}
}
return null;
}
public static boolean isValidType(Integer type) {
return fromType(type) != null;
}
}

View File

@ -36,4 +36,14 @@ public enum IotProductDeviceTypeEnum implements IntArrayValuable {
return ARRAYS;
}
/**
* 判断是否是网关
*
* @param type 类型
* @return 是否是网关
*/
public static boolean isGateway(Integer type) {
return GATEWAY.getType().equals(type);
}
}

View File

@ -69,6 +69,12 @@
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
<!-- PF4J -->
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,24 +1,33 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
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.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
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.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - IoT 设备")
@RestController
@ -52,8 +61,16 @@ public class IotDeviceController {
return success(true);
}
@PutMapping("/update-group")
@Operation(summary = "更新设备分组")
@PreAuthorize("@ss.hasPermission('iot:device:update')")
public CommonResult<Boolean> updateDeviceGroup(@Valid @RequestBody IotDeviceUpdateGroupReqVO updateReqVO) {
deviceService.updateDeviceGroup(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除设备")
@Operation(summary = "删除单个设备")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:device:delete')")
public CommonResult<Boolean> deleteDevice(@RequestParam("id") Long id) {
@ -61,6 +78,15 @@ public class IotDeviceController {
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "删除多个设备")
@Parameter(name = "ids", description = "编号数组", required = true)
@PreAuthorize("@ss.hasPermission('iot:device:delete')")
public CommonResult<Boolean> deleteDeviceList(@RequestParam("ids") Collection<Long> ids) {
deviceService.deleteDeviceList(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得设备")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@ -78,6 +104,19 @@ public class IotDeviceController {
return success(BeanUtils.toBean(pageResult, IotDeviceRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出设备 Excel")
@PreAuthorize("@ss.hasPermission('iot:device:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportDeviceExcel(@Valid IotDevicePageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
CommonResult<PageResult<IotDeviceRespVO>> result = getDevicePage(exportReqVO);
// 导出 Excel
ExcelUtils.write(response, "设备.xls", "数据", IotDeviceRespVO.class,
result.getData().getList());
}
@GetMapping("/count")
@Operation(summary = "获得设备数量")
@Parameter(name = "productId", description = "产品编号", example = "1")
@ -86,4 +125,38 @@ public class IotDeviceController {
return success(deviceService.getDeviceCountByProductId(productId));
}
@GetMapping("/simple-list")
@Operation(summary = "获取设备的精简信息列表", description = "主要用于前端的下拉选项")
@Parameter(name = "deviceType", description = "设备类型", example = "1")
public CommonResult<List<IotDeviceRespVO>> getSimpleDeviceList(
@RequestParam(value = "deviceType", required = false) Integer deviceType) {
List<IotDeviceDO> list = deviceService.getDeviceList(deviceType);
return success(convertList(list, device -> // 只返回 idname 字段
new IotDeviceRespVO().setId(device.getId()).setDeviceName(device.getDeviceName())));
}
@PostMapping("/import")
@Operation(summary = "导入设备")
@PreAuthorize("@ss.hasPermission('iot:device:import')")
public CommonResult<IotDeviceImportRespVO> importDevice(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport)
throws Exception {
List<IotDeviceImportExcelVO> list = ExcelUtils.read(file, IotDeviceImportExcelVO.class);
return success(deviceService.importDevice(list, updateSupport));
}
@GetMapping("/get-import-template")
@Operation(summary = "获得导入设备模板")
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<IotDeviceImportExcelVO> list = Arrays.asList(
IotDeviceImportExcelVO.builder().deviceName("温度传感器001").parentDeviceName("gateway110")
.productKey("1de24640dfe").groupNames("灰度分组,生产分组").build(),
IotDeviceImportExcelVO.builder().deviceName("biubiu")
.productKey("YzvHxd4r67sT4s2B").groupNames("").build());
// 输出
ExcelUtils.write(response, "设备导入模板.xls", "数据", IotDeviceImportExcelVO.class, list);
}
}

View File

@ -0,0 +1,88 @@
package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
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.group.IotDeviceGroupPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceGroupService;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
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 java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - IoT 设备分组")
@RestController
@RequestMapping("/iot/device-group")
@Validated
public class IotDeviceGroupController {
@Resource
private IotDeviceGroupService deviceGroupService;
@Resource
private IotDeviceService deviceService;
@PostMapping("/create")
@Operation(summary = "创建设备分组")
@PreAuthorize("@ss.hasPermission('iot:device-group:create')")
public CommonResult<Long> createDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO createReqVO) {
return success(deviceGroupService.createDeviceGroup(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新设备分组")
@PreAuthorize("@ss.hasPermission('iot:device-group:update')")
public CommonResult<Boolean> updateDeviceGroup(@Valid @RequestBody IotDeviceGroupSaveReqVO updateReqVO) {
deviceGroupService.updateDeviceGroup(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除设备分组")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:device-group:delete')")
public CommonResult<Boolean> deleteDeviceGroup(@RequestParam("id") Long id) {
deviceGroupService.deleteDeviceGroup(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得设备分组")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:device-group:query')")
public CommonResult<IotDeviceGroupRespVO> getDeviceGroup(@RequestParam("id") Long id) {
IotDeviceGroupDO deviceGroup = deviceGroupService.getDeviceGroup(id);
return success(BeanUtils.toBean(deviceGroup, IotDeviceGroupRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得设备分组分页")
@PreAuthorize("@ss.hasPermission('iot:device-group:query')")
public CommonResult<PageResult<IotDeviceGroupRespVO>> getDeviceGroupPage(@Valid IotDeviceGroupPageReqVO pageReqVO) {
PageResult<IotDeviceGroupDO> pageResult = deviceGroupService.getDeviceGroupPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, IotDeviceGroupRespVO.class,
group -> group.setDeviceCount(deviceService.getDeviceCountByGroupId(group.getId()))));
}
@GetMapping("/simple-list")
@Operation(summary = "获取设备分组的精简信息列表", description = "只包含被开启的分组,主要用于前端的下拉选项")
public CommonResult<List<IotDeviceGroupRespVO>> getSimpleDeviceGroupList() {
List<IotDeviceGroupDO> list = deviceGroupService.getDeviceGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
return success(convertList(list, group -> // 只返回 idname 字段
new IotDeviceGroupRespVO().setId(group.getId()).setName(group.getName())));
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 设备 Excel 导入 VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false避免设备导入有问题
public class IotDeviceImportExcelVO {
@ExcelProperty("设备名称")
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
@ExcelProperty("父设备名称")
@Schema(description = "父设备名称", example = "网关001")
private String parentDeviceName;
@ExcelProperty("产品标识")
@NotEmpty(message = "产品标识不能为空")
private String productKey;
@ExcelProperty("设备分组")
private String groupNames;
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - IoT 设备导入 Response VO")
@Data
@Builder
public class IotDeviceImportRespVO {
@Schema(description = "创建成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> createDeviceNames;
@Schema(description = "更新成功的设备名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> updateDeviceNames;
@Schema(description = "导入失败的设备集合,key为设备名称,value为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, String> failureDeviceNames;
}

View File

@ -8,11 +8,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - IoT 设备分页 Request VO")
@Data
@ -20,11 +15,6 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true)
public class IotDevicePageReqVO extends PageParam {
// TODO @芋艿需要去掉一些多余的字段
@Schema(description = "设备唯一标识符", example = "24602")
private String deviceKey;
@Schema(description = "设备名称", example = "王五")
private String deviceName;
@ -34,53 +24,15 @@ public class IotDevicePageReqVO extends PageParam {
@Schema(description = "产品编号", example = "26202")
private Long productId;
@Schema(description = "产品标识")
private String productKey;
@Schema(description = "设备类型", example = "1")
@InEnum(IotProductDeviceTypeEnum.class)
private Integer deviceType;
@Schema(description = "网关设备 ID", example = "16380")
private Long gatewayId;
@Schema(description = "设备状态", example = "1")
@InEnum(IotDeviceStatusEnum.class)
private Integer status;
@Schema(description = "设备状态最后更新时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] statusLastUpdateTime;
@Schema(description = "最后上线时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastOnlineTime;
@Schema(description = "最后离线时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] lastOfflineTime;
@Schema(description = "设备激活时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activeTime;
@Schema(description = "设备密钥,用于设备认证,需安全存储")
private String deviceSecret;
@Schema(description = "MQTT 客户端 ID", example = "24602")
private String mqttClientId;
@Schema(description = "MQTT 用户名", example = "芋艿")
private String mqttUsername;
@Schema(description = "MQTT 密码")
private String mqttPassword;
@Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
private String authType;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
@Schema(description = "设备分组编号", example = "1024")
private Long groupId;
}

View File

@ -1,11 +1,16 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Set;
import static cn.iocoder.yudao.module.iot.enums.DictTypeConstants.DEVICE_STATUS;
@Schema(description = "管理后台 - IoT 设备 Response VO")
@Data
@ -20,9 +25,24 @@ public class IotDeviceRespVO {
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
@ExcelProperty("设备名称")
@ExcelProperty("设备名称")
private String deviceName;
@Schema(description = "设备备注名称", example = "张三")
@ExcelProperty("设备备注名称")
private String nickname;
@Schema(description = "设备序列号", example = "1024")
@ExcelProperty("设备序列号")
private String serialNumber;
@Schema(description = "设备图片", example = "我是一名码农")
@ExcelProperty("设备图片")
private String picUrl;
@Schema(description = "设备分组编号数组", example = "1,2")
private Set<Long> groupIds;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
@ExcelProperty("产品编号")
private Long productId;
@ -35,15 +55,12 @@ public class IotDeviceRespVO {
@ExcelProperty("设备类型")
private Integer deviceType;
@Schema(description = "设备备注名称", example = "张三")
@ExcelProperty("设备备注名称")
private String nickname;
@Schema(description = "网关设备 ID", example = "16380")
private Long gatewayId;
@Schema(description = "设备状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("设备状态")
@ExcelProperty(value = "设备状态", converter = DictConvert.class)
@DictFormat(DEVICE_STATUS)
private Integer status;
@Schema(description = "设备状态最后更新时间")

View File

@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Set;
@Schema(description = "管理后台 - IoT 设备新增/修改 Request VO")
@Data
public class IotDeviceSaveReqVO {
@ -10,13 +12,28 @@ public class IotDeviceSaveReqVO {
@Schema(description = "设备编号", example = "177")
private Long id;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.AUTO, example = "177")
private String deviceKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
private String deviceName;
@Schema(description = "备注名称", example = "张三")
private String nickname;
@Schema(description = "设备序列号", example = "123456")
private String serialNumber;
@Schema(description = "设备图片", example = "https://iocoder.cn/1.png")
private String picUrl;
@Schema(description = "设备分组编号数组", example = "1,2")
private Set<Long> groupIds;
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26202")
private Long productId;
@Schema(description = "网关设备 ID", example = "16380")
private Long gatewayId;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Set;
@Schema(description = "管理后台 - IoT 设备更新分组 Request VO")
@Data
public class IotDeviceUpdateGroupReqVO {
@Schema(description = "设备编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
@NotEmpty(message = "设备编号列表不能为空")
private Set<Long> ids;
@Schema(description = "分组编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
@NotEmpty(message = "分组编号列表不能为空")
private Set<Long> groupIds;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
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;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - IoT 设备分组分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class IotDeviceGroupPageReqVO extends PageParam {
@Schema(description = "分组名字", example = "李四")
private String name;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 设备分组 Response VO")
@Data
public class IotDeviceGroupRespVO {
@Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583")
private Long id;
@Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
private String name;
@Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "分组描述", example = "你说的对")
private String description;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "设备数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Long deviceCount;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - IoT 设备分组新增/修改 Request VO")
@Data
public class IotDeviceGroupSaveReqVO {
@Schema(description = "分组 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3583")
private Long id;
@Schema(description = "分组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
@NotEmpty(message = "分组名字不能为空")
private String name;
@Schema(description = "分组状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "分组状态不能为空")
private Integer status;
@Schema(description = "分组描述", example = "你说的对")
private String description;
}

View File

@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
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.plugininfo.vo.PluginInfoPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import cn.iocoder.yudao.module.iot.service.plugininfo.PluginInfoService;
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 org.springframework.web.multipart.MultipartFile;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
@Tag(name = "管理后台 - IoT 插件信息")
@RestController
@RequestMapping("/iot/plugin-info")
@Validated
public class PluginInfoController {
@Resource
private PluginInfoService pluginInfoService;
@PostMapping("/create")
@Operation(summary = "创建插件信息")
@PreAuthorize("@ss.hasPermission('iot:plugin-info:create')")
public CommonResult<Long> createPluginInfo(@Valid @RequestBody PluginInfoSaveReqVO createReqVO) {
return success(pluginInfoService.createPluginInfo(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新插件信息")
@PreAuthorize("@ss.hasPermission('iot:plugin-info:update')")
public CommonResult<Boolean> updatePluginInfo(@Valid @RequestBody PluginInfoSaveReqVO updateReqVO) {
pluginInfoService.updatePluginInfo(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除插件信息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:plugin-info:delete')")
public CommonResult<Boolean> deletePluginInfo(@RequestParam("id") Long id) {
pluginInfoService.deletePluginInfo(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得插件信息")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:plugin-info:query')")
public CommonResult<PluginInfoRespVO> getPluginInfo(@RequestParam("id") Long id) {
PluginInfoDO pluginInfo = pluginInfoService.getPluginInfo(id);
return success(BeanUtils.toBean(pluginInfo, PluginInfoRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得插件信息分页")
@PreAuthorize("@ss.hasPermission('iot:plugin-info:query')")
public CommonResult<PageResult<PluginInfoRespVO>> getPluginInfoPage(@Valid PluginInfoPageReqVO pageReqVO) {
PageResult<PluginInfoDO> pageResult = pluginInfoService.getPluginInfoPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
}
@RequestMapping(value = "/update-jar",
method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
@Operation(summary = "上传Jar包")
public CommonResult<Boolean> uploadJar(
@RequestParam("id") Long id,
@RequestParam("jar") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw exception(FILE_IS_EMPTY);
}
pluginInfoService.uploadJar(id, file);
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "修改插件状态")
@PreAuthorize("@ss.hasPermission('iot:plugin-info:update')")
public CommonResult<Boolean> updateUserStatus(@Valid @RequestBody PluginInfoSaveReqVO reqVO) {
pluginInfoService.updatePluginStatus(reqVO.getId(), reqVO.getStatus());
return success(true);
}
}

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginTypeEnum;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @haohao只查询必要字段哈
@Schema(description = "管理后台 - IoT 插件信息分页 Request VO")
@Data
public class PluginInfoPageReqVO extends PageParam {
@Schema(description = "插件包 ID ", example = "24627")
private String pluginId;
@Schema(description = "插件名称", example = "赵六")
private String name;
@Schema(description = "描述", example = "你猜")
private String description;
@Schema(description = "部署方式", example = "2")
private Integer deployType;
@Schema(description = "插件包文件名")
private String file;
@Schema(description = "插件版本")
private String version;
@Schema(description = "插件类型", example = "2")
@InEnum(IotPluginTypeEnum.class)
private Integer type;
@Schema(description = "设备插件协议类型")
private String protocol;
@Schema(description = "状态")
private Integer status;
@Schema(description = "插件配置项描述信息")
private String configSchema;
@Schema(description = "插件配置信息")
private String config;
@Schema(description = "插件脚本")
private String script;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT 插件信息 Response VO")
@Data
@ExcelIgnoreUnannotated
public class PluginInfoRespVO {
@Schema(description = "主键 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
@ExcelProperty("主键 ID")
private Long id;
@Schema(description = "插件包 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
@ExcelProperty("插件包 ID")
private String pluginId;
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@ExcelProperty("插件名称")
private String name;
@Schema(description = "描述", example = "你猜")
@ExcelProperty("描述")
private String description;
@Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("部署方式")
private Integer deployType;
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("插件包文件名")
private String file;
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("插件版本")
private String version;
@Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty("插件类型")
private Integer type;
@Schema(description = "设备插件协议类型")
@ExcelProperty("设备插件协议类型")
private String protocol;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("状态")
private Integer status;
@Schema(description = "插件配置项描述信息")
@ExcelProperty("插件配置项描述信息")
private String configSchema;
@Schema(description = "插件配置信息")
@ExcelProperty("插件配置信息")
private String config;
@Schema(description = "插件脚本")
@ExcelProperty("插件脚本")
private String script;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
@Schema(description = "管理后台 - IoT 插件信息新增/修改 Request VO")
@Data
public class PluginInfoSaveReqVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
private Long id;
@Schema(description = "插件包id", requiredMode = Schema.RequiredMode.REQUIRED, example = "24627")
private String pluginId;
@Schema(description = "插件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
private String name;
@Schema(description = "描述", example = "你猜")
private String description;
@Schema(description = "部署方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer deployType;
@Schema(description = "插件包文件名", requiredMode = Schema.RequiredMode.REQUIRED)
private String file;
@Schema(description = "插件版本", requiredMode = Schema.RequiredMode.REQUIRED)
private String version;
@Schema(description = "插件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer type;
@Schema(description = "设备插件协议类型")
private String protocol;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer status;
@Schema(description = "插件配置项描述信息")
private String configSchema;
@Schema(description = "插件配置信息")
private String config;
@Schema(description = "插件脚本")
private String script;
}

View File

@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.*;
import jakarta.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
import cn.iocoder.yudao.module.iot.service.plugininstance.PluginInstanceService;
@Tag(name = "管理后台 - IoT 插件实例")
@RestController
@RequestMapping("/iot/plugin-instance")
@Validated
public class PluginInstanceController {
@Resource
private PluginInstanceService pluginInstanceService;
@PostMapping("/create")
@Operation(summary = "创建IoT 插件实例")
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:create')")
public CommonResult<Long> createPluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO createReqVO) {
return success(pluginInstanceService.createPluginInstance(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新IoT 插件实例")
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:update')")
public CommonResult<Boolean> updatePluginInstance(@Valid @RequestBody PluginInstanceSaveReqVO updateReqVO) {
pluginInstanceService.updatePluginInstance(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除IoT 插件实例")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:delete')")
public CommonResult<Boolean> deletePluginInstance(@RequestParam("id") Long id) {
pluginInstanceService.deletePluginInstance(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得IoT 插件实例")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
public CommonResult<PluginInstanceRespVO> getPluginInstance(@RequestParam("id") Long id) {
PluginInstanceDO pluginInstance = pluginInstanceService.getPluginInstance(id);
return success(BeanUtils.toBean(pluginInstance, PluginInstanceRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得IoT 插件实例分页")
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:query')")
public CommonResult<PageResult<PluginInstanceRespVO>> getPluginInstancePage(@Valid PluginInstancePageReqVO pageReqVO) {
PageResult<PluginInstanceDO> pageResult = pluginInstanceService.getPluginInstancePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, PluginInstanceRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出IoT 插件实例 Excel")
@PreAuthorize("@ss.hasPermission('iot:plugin-instance:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportPluginInstanceExcel(@Valid PluginInstancePageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<PluginInstanceDO> list = pluginInstanceService.getPluginInstancePage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "IoT 插件实例.xls", "数据", PluginInstanceRespVO.class,
BeanUtils.toBean(list, PluginInstanceRespVO.class));
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @haohao建议搞个 plugin然后里面分 info instance另外是不是 info => config 会好点插件配置
@Schema(description = "管理后台 - IoT 插件实例分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PluginInstancePageReqVO extends PageParam {
@Schema(description = "插件主程序id", example = "23738")
private String mainId;
@Schema(description = "插件id", example = "26498")
private Long pluginId;
@Schema(description = "插件主程序所在ip")
private String ip;
@Schema(description = "插件主程序端口")
private Integer port;
@Schema(description = "心跳时间心路时间超过30秒需要剔除")
private Long heartbeatAt;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
@Schema(description = "管理后台 - IoT 插件实例 Response VO")
@Data
@ExcelIgnoreUnannotated
public class PluginInstanceRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23864")
@ExcelProperty("主键ID")
private Long id;
@Schema(description = "插件主程序id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23738")
@ExcelProperty("插件主程序id")
private String mainId;
@Schema(description = "插件id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26498")
@ExcelProperty("插件id")
private Long pluginId;
@Schema(description = "插件主程序所在ip", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("插件主程序所在ip")
private String ip;
@Schema(description = "插件主程序端口", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("插件主程序端口")
private Integer port;
@Schema(description = "心跳时间心路时间超过30秒需要剔除", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("心跳时间心路时间超过30秒需要剔除")
private Long heartbeatAt;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import jakarta.validation.constraints.*;
@Schema(description = "管理后台 - IoT 插件实例新增/修改 Request VO")
@Data
public class PluginInstanceSaveReqVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "23864")
private Long id;
@Schema(description = "插件主程序id", requiredMode = Schema.RequiredMode.REQUIRED, example = "23738")
@NotEmpty(message = "插件主程序id不能为空")
private String mainId;
@Schema(description = "插件id", requiredMode = Schema.RequiredMode.REQUIRED, example = "26498")
@NotNull(message = "插件id不能为空")
private Long pluginId;
@Schema(description = "插件主程序所在ip", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "插件主程序所在ip不能为空")
private String ip;
@Schema(description = "插件主程序端口", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "插件主程序端口不能为空")
private Integer port;
@Schema(description = "心跳时间心路时间超过30秒需要剔除", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "心跳时间心路时间超过30秒需要剔除不能为空")
private Long heartbeatAt;
}

View File

@ -121,12 +121,12 @@ public class IotProductController {
}
@GetMapping("/simple-list")
@Operation(summary = "获得所有产品列表")
@PreAuthorize("@ss.hasPermission('iot:product:query')")
@Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
public CommonResult<List<IotProductRespVO>> getSimpleProductList() {
List<IotProductDO> list = productService.getProductList();
return success(convertList(list, product -> // 只返回 idname 字段
new IotProductRespVO().setId(product.getId()).setName(product.getName())));
new IotProductRespVO().setId(product.getId()).setName(product.getName())
.setDeviceType(product.getDeviceType())));
}
}

View File

@ -38,8 +38,8 @@ public class IotProductRespVO {
@ExcelProperty("产品图标")
private String icon;
@Schema(description = "产品图", example = "https://iocoder.cn/1.png")
@ExcelProperty("产品图")
@Schema(description = "产品图", example = "https://iocoder.cn/1.png")
@ExcelProperty("产品图")
private String picUrl;
@Schema(description = "产品描述", example = "你猜")

View File

@ -28,7 +28,7 @@ public class IotProductSaveReqVO {
@Schema(description = "产品图标", example = "https://iocoder.cn/1.svg")
private String icon;
@Schema(description = "产品图", example = "https://iocoder.cn/1.png")
@Schema(description = "产品图", example = "https://iocoder.cn/1.png")
private String picUrl;
@Schema(description = "产品描述", example = "描述")

View File

@ -1,22 +1,25 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Set;
/**
* IoT 设备 DO
*
* @author haohao
*/
@TableName("iot_device")
@TableName(value = "iot_device", autoResultMap = true)
@KeySequence("iot_device_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ -47,6 +50,17 @@ public class IotDeviceDO extends BaseDO {
* 设备序列号
*/
private String serialNumber;
/**
* 设备图片
*/
private String picUrl;
/**
* 设备分组编号集合
*
* 关联 {@link IotDeviceGroupDO#getId()}
*/
@TableField(typeHandler = LongSetTypeHandler.class)
private Set<Long> groupIds;
/**
* 产品编号
@ -66,13 +80,6 @@ public class IotDeviceDO extends BaseDO {
* 冗余 {@link IotProductDO#getDeviceType()}
*/
private Integer deviceType;
/**
* 设备状态
* <p>
* 枚举 {@link IotDeviceStatusEnum}
*/
private Integer status;
/**
* 网关设备编号
* <p>
@ -82,6 +89,13 @@ public class IotDeviceDO extends BaseDO {
*/
private Long gatewayId;
/**
* 设备状态
* <p>
* 枚举 {@link IotDeviceStatusEnum}
*/
private Integer status;
/**
* 设备状态最后更新时间
*/

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* IoT 设备分组 DO
*
* @author 芋道源码
*/
@TableName("iot_device_group")
@KeySequence("iot_device_group_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IotDeviceGroupDO extends BaseDO {
/**
* 分组 ID
*/
@TableId
private Long id;
/**
* 分组名字
*/
private String name;
/**
* 分组状态
*
* 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
/**
* 分组描述
*/
private String description;
}

View File

@ -0,0 +1,83 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* IoT 插件信息 DO
*
* @author 芋道源码
*/
@TableName("iot_plugin_info")
@KeySequence("iot_plugin_info_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PluginInfoDO extends BaseDO {
/**
* 主键 ID
*/
@TableId
private Long id;
// TODO @haohao这个是不是改成类似 key 之类的字段哈
/**
* 插件包 ID
*/
private String pluginId;
/**
* 插件名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 部署方式
*/
// TODO @haohao枚举
private Integer deployType;
/**
* 插件包文件名
*/
// TODO @haohao是不是叫 fileName 避免后续有别的字段类似 fileUrl
private String file;
/**
* 插件版本
*/
private String version;
/**
* 插件类型
*/
// TODO @haohao枚举
private Integer type;
/**
* 设备插件协议类型
*/
private String protocol;
/**
* 状态
*/
// TODO @haohao枚举
private Integer status;
/**
* 插件配置项描述信息
*/
private String configSchema;
/**
* 插件配置信息
*/
private String config;
/**
* 插件脚本
*/
private String script;
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
// TODO @haohao一些必要的关联枚举
/**
* IoT 插件实例 DO
*
* @author 芋道源码
*/
@TableName("iot_plugin_instance")
@KeySequence("iot_plugin_instance_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PluginInstanceDO extends BaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 插件主程序 ID
*/
private String mainId;
/**
* 插件id
*/
private Long pluginId;
/**
* 插件主程序所在ip
*/
private String ip;
/**
* 插件主程序端口
*/
private Integer port;
/**
* 心跳时间心路时间超过 30 秒需要剔除
*/
private Long heartbeatAt;
}

View File

@ -0,0 +1,35 @@
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.group.IotDeviceGroupPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 设备分组 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface IotDeviceGroupMapper extends BaseMapperX<IotDeviceGroupDO> {
default PageResult<IotDeviceGroupDO> selectPage(IotDeviceGroupPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceGroupDO>()
.likeIfPresent(IotDeviceGroupDO::getName, reqVO.getName())
.betweenIfPresent(IotDeviceGroupDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotDeviceGroupDO::getId));
}
default List<IotDeviceGroupDO> selectListByStatus(Integer status) {
return selectList(IotDeviceGroupDO::getStatus, status);
}
default IotDeviceGroupDO selectByName(String name) {
return selectOne(IotDeviceGroupDO::getName, name);
}
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.dal.mysql.device;
import cn.hutool.core.util.ObjectUtil;
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;
@ -7,6 +8,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePa
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* IoT 设备 Mapper
*
@ -15,30 +18,21 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
// TODO @haohao可能多余的查询条件要去掉哈
default PageResult<IotDeviceDO> selectPage(IotDevicePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceDO>()
.eqIfPresent(IotDeviceDO::getDeviceKey, reqVO.getDeviceKey())
.likeIfPresent(IotDeviceDO::getDeviceName, reqVO.getDeviceName())
.eqIfPresent(IotDeviceDO::getProductId, reqVO.getProductId())
.eqIfPresent(IotDeviceDO::getProductKey, reqVO.getProductKey())
.eqIfPresent(IotDeviceDO::getDeviceType, reqVO.getDeviceType())
.likeIfPresent(IotDeviceDO::getNickname, reqVO.getNickname())
.eqIfPresent(IotDeviceDO::getGatewayId, reqVO.getGatewayId())
.eqIfPresent(IotDeviceDO::getStatus, reqVO.getStatus())
.betweenIfPresent(IotDeviceDO::getStatusLastUpdateTime, reqVO.getStatusLastUpdateTime())
.betweenIfPresent(IotDeviceDO::getLastOnlineTime, reqVO.getLastOnlineTime())
.betweenIfPresent(IotDeviceDO::getLastOfflineTime, reqVO.getLastOfflineTime())
.betweenIfPresent(IotDeviceDO::getActiveTime, reqVO.getActiveTime())
.eqIfPresent(IotDeviceDO::getDeviceSecret, reqVO.getDeviceSecret())
.eqIfPresent(IotDeviceDO::getMqttClientId, reqVO.getMqttClientId())
.likeIfPresent(IotDeviceDO::getMqttUsername, reqVO.getMqttUsername())
.eqIfPresent(IotDeviceDO::getMqttPassword, reqVO.getMqttPassword())
.eqIfPresent(IotDeviceDO::getAuthType, reqVO.getAuthType())
.betweenIfPresent(IotDeviceDO::getCreateTime, reqVO.getCreateTime())
.apply(ObjectUtil.isNotNull(reqVO.getGroupId()), "FIND_IN_SET(" + reqVO.getGroupId() + ",group_ids) > 0")
.orderByDesc(IotDeviceDO::getId));
}
default IotDeviceDO selectByDeviceName(String deviceName) {
return selectOne(IotDeviceDO::getDeviceName, deviceName);
}
default IotDeviceDO selectByProductKeyAndDeviceName(String productKey, String deviceName) {
return selectOne(IotDeviceDO::getProductKey, productKey,
IotDeviceDO::getDeviceName, deviceName);
@ -51,4 +45,19 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
default Long selectCountByProductId(Long productId) {
return selectCount(IotDeviceDO::getProductId, productId);
}
default IotDeviceDO selectByDeviceKey(String deviceKey) {
return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
}
default List<IotDeviceDO> selectList(Integer deviceType) {
return selectList(IotDeviceDO::getDeviceType, deviceType);
}
default Long selectCountByGroupId(Long groupId) {
return selectCount(new LambdaQueryWrapperX<IotDeviceDO>()
.apply("FIND_IN_SET(" + groupId + ",group_ids) > 0")
.orderByDesc(IotDeviceDO::getId));
}
}

View File

@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.iot.dal.mysql.plugininfo;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.*;
/**
* IoT 插件信息 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface PluginInfoMapper extends BaseMapperX<PluginInfoDO> {
default PageResult<PluginInfoDO> selectPage(PluginInfoPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<PluginInfoDO>()
.eqIfPresent(PluginInfoDO::getPluginId, reqVO.getPluginId())
.likeIfPresent(PluginInfoDO::getName, reqVO.getName())
.eqIfPresent(PluginInfoDO::getDescription, reqVO.getDescription())
.eqIfPresent(PluginInfoDO::getDeployType, reqVO.getDeployType())
.eqIfPresent(PluginInfoDO::getFile, reqVO.getFile())
.eqIfPresent(PluginInfoDO::getVersion, reqVO.getVersion())
.eqIfPresent(PluginInfoDO::getType, reqVO.getType())
.eqIfPresent(PluginInfoDO::getProtocol, reqVO.getProtocol())
.eqIfPresent(PluginInfoDO::getStatus, reqVO.getStatus())
.eqIfPresent(PluginInfoDO::getConfigSchema, reqVO.getConfigSchema())
.eqIfPresent(PluginInfoDO::getConfig, reqVO.getConfig())
.eqIfPresent(PluginInfoDO::getScript, reqVO.getScript())
.betweenIfPresent(PluginInfoDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(PluginInfoDO::getId));
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.iot.dal.mysql.plugininstance;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.*;
/**
* IoT 插件实例 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface PluginInstanceMapper extends BaseMapperX<PluginInstanceDO> {
default PageResult<PluginInstanceDO> selectPage(PluginInstancePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<PluginInstanceDO>()
.eqIfPresent(PluginInstanceDO::getMainId, reqVO.getMainId())
.eqIfPresent(PluginInstanceDO::getPluginId, reqVO.getPluginId())
.eqIfPresent(PluginInstanceDO::getIp, reqVO.getIp())
.eqIfPresent(PluginInstanceDO::getPort, reqVO.getPort())
.eqIfPresent(PluginInstanceDO::getHeartbeatAt, reqVO.getHeartbeatAt())
.betweenIfPresent(PluginInstanceDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(PluginInstanceDO::getId));
}
}

View File

@ -21,7 +21,7 @@ public interface IotProductCategoryMapper extends BaseMapperX<IotProductCategory
return selectPage(reqVO, new LambdaQueryWrapperX<IotProductCategoryDO>()
.likeIfPresent(IotProductCategoryDO::getName, reqVO.getName())
.betweenIfPresent(IotProductCategoryDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(IotProductCategoryDO::getId));
.orderByAsc(IotProductCategoryDO::getSort));
}
default List<IotProductCategoryDO> selectListByStatus(Integer status) {

View File

@ -23,7 +23,7 @@ public interface IotProductMapper extends BaseMapperX<IotProductDO> {
}
default IotProductDO selectByProductKey(String productKey) {
return selectOne(new LambdaQueryWrapperX<IotProductDO>().eq(IotProductDO::getProductKey, productKey));
return selectOne(IotProductDO::getProductKey, productKey);
}
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.iot.framework.plugin;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfiguration {
@Bean
public SpringPluginManager pluginManager() {
return new SpringPluginManager();
}
}

View File

@ -15,7 +15,6 @@ import java.util.Map;
*/
public interface IotDeviceDataService {
/**
* 保存设备数据
*

View File

@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* IoT 设备分组 Service 接口
*
* @author 芋道源码
*/
public interface IotDeviceGroupService {
/**
* 创建设备分组
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createDeviceGroup(@Valid IotDeviceGroupSaveReqVO createReqVO);
/**
* 更新设备分组
*
* @param updateReqVO 更新信息
*/
void updateDeviceGroup(@Valid IotDeviceGroupSaveReqVO updateReqVO);
/**
* 删除设备分组
*
* @param id 编号
*/
void deleteDeviceGroup(Long id);
/**
* 校验设备分组是否存在
*
* @param ids 设备分组 ID 数组
*/
default List<IotDeviceGroupDO> validateDeviceGroupExists(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return List.of();
}
return convertList(ids, this::validateDeviceGroupExists);
}
/**
* 校验设备分组是否存在
*
* @param id 设备分组 ID
* @return 设备分组
*/
IotDeviceGroupDO validateDeviceGroupExists(Long id);
/**
* 获得设备分组
*
* @param id 编号
* @return 设备分组
*/
IotDeviceGroupDO getDeviceGroup(Long id);
/**
* 获得设备分组
*
* @param name 名称
* @return 设备分组
*/
IotDeviceGroupDO getDeviceGroupByName(String name);
/**
* 获得设备分组分页
*
* @param pageReqVO 分页查询
* @return 设备分组分页
*/
PageResult<IotDeviceGroupDO> getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO);
/**
* 获得设备分组列表
*
* @param status 状态
* @return 设备分组列表
*/
List<IotDeviceGroupDO> getDeviceGroupListByStatus(Integer status);
}

View File

@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.iot.service.device;
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.group.IotDeviceGroupPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.group.IotDeviceGroupSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceGroupMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_GROUP_NOT_EXISTS;
/**
* IoT 设备分组 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class IotDeviceGroupServiceImpl implements IotDeviceGroupService {
@Resource
private IotDeviceGroupMapper deviceGroupMapper;
@Resource
private IotDeviceService deviceService;
@Override
public Long createDeviceGroup(IotDeviceGroupSaveReqVO createReqVO) {
// 插入
IotDeviceGroupDO deviceGroup = BeanUtils.toBean(createReqVO, IotDeviceGroupDO.class);
deviceGroupMapper.insert(deviceGroup);
// 返回
return deviceGroup.getId();
}
@Override
public void updateDeviceGroup(IotDeviceGroupSaveReqVO updateReqVO) {
// 校验存在
validateDeviceGroupExists(updateReqVO.getId());
// 更新
IotDeviceGroupDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceGroupDO.class);
deviceGroupMapper.updateById(updateObj);
}
@Override
public void deleteDeviceGroup(Long id) {
// 1.1 校验存在
validateDeviceGroupExists(id);
// 1.2 校验是否存在设备
if (deviceService.getDeviceCountByGroupId(id) > 0) {
throw exception(DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS);
}
// 删除
deviceGroupMapper.deleteById(id);
}
@Override
public IotDeviceGroupDO validateDeviceGroupExists(Long id) {
IotDeviceGroupDO group = deviceGroupMapper.selectById(id);
if (group == null) {
throw exception(DEVICE_GROUP_NOT_EXISTS);
}
return group;
}
@Override
public IotDeviceGroupDO getDeviceGroup(Long id) {
return deviceGroupMapper.selectById(id);
}
@Override
public IotDeviceGroupDO getDeviceGroupByName(String name) {
return deviceGroupMapper.selectByName(name);
}
@Override
public PageResult<IotDeviceGroupDO> getDeviceGroupPage(IotDeviceGroupPageReqVO pageReqVO) {
return deviceGroupMapper.selectPage(pageReqVO);
}
@Override
public List<IotDeviceGroupDO> getDeviceGroupListByStatus(Integer status) {
return deviceGroupMapper.selectListByStatus(status);
}
}

View File

@ -1,11 +1,18 @@
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.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import jakarta.validation.*;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceUpdateGroupReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportRespVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceImportExcelVO;
import jakarta.validation.Valid;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
/**
* IoT 设备 Service 接口
@ -30,12 +37,33 @@ public interface IotDeviceService {
void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO);
/**
* 删除设备
* 更新设备状态
*
* @param updateReqVO 更新信息
*/
void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO);
/**
* 更新设备分组
*
* @param updateReqVO 更新信息
*/
void updateDeviceGroup(@Valid IotDeviceUpdateGroupReqVO updateReqVO);
/**
* 删除单个设备
*
* @param id 编号
*/
void deleteDevice(Long id);
/**
* 删除多个设备
*
* @param ids 编号数组
*/
void deleteDeviceList(Collection<Long> ids);
/**
* 获得设备
*
@ -45,7 +73,7 @@ public interface IotDeviceService {
IotDeviceDO getDevice(Long id);
/**
* 得设备分页
* <EFBFBD><EFBFBD>得设备分页
*
* @param pageReqVO 分页查询
* @return IoT 设备分页
@ -53,11 +81,12 @@ public interface IotDeviceService {
PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO);
/**
* 更新设备状态
* 获得设备列表
*
* @param updateReqVO 更新信息
* @param deviceType 设备类型
* @return 设备列表
*/
void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO);
List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType);
/**
* 获得设备数量
@ -67,6 +96,14 @@ public interface IotDeviceService {
*/
Long getDeviceCountByProductId(Long productId);
/**
* 获得设备数量
*
* @param groupId 分组编号
* @return 设备数量
*/
Long getDeviceCountByGroupId(Long groupId);
/**
* 根据产品 key 和设备名称获得设备信息
*
@ -75,4 +112,14 @@ public interface IotDeviceService {
* @return 设备信息
*/
IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName);
/**
* 导入设备
*
* @param importDevices 导入设备列表
* @param updateSupport 是否支持更新
* @return 导入结果
*/
IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport);
}

View File

@ -1,31 +1,36 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceSaveReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
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;
import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceMapper;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.security.SecureRandom;
import javax.annotation.Nullable;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Objects;
import java.util.UUID;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
@ -40,68 +45,212 @@ public class IotDeviceServiceImpl implements IotDeviceService {
@Resource
private IotDeviceMapper deviceMapper;
@Resource
private IotProductService productService;
@Resource
@Lazy // 延迟加载解决循环依赖
private IotDeviceGroupService deviceGroupService;
/**
* 创建 IoT 设备
*
* @param createReqVO 创建请求 VO
* @return 设备 ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long createDevice(IotDeviceSaveReqVO createReqVO) {
// 1.1 校验产品是否存在
IotProductDO product = productService.getProduct(createReqVO.getProductId());
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
// 1.2 校验设备名称在同一产品下是否唯一
if (StrUtil.isBlank(createReqVO.getDeviceName())) {
createReqVO.setDeviceName(generateUniqueDeviceName(product.getProductKey()));
} else {
validateDeviceNameUnique(product.getProductKey(), createReqVO.getDeviceName());
// 1.2 校验设备标识是否唯一
if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
throw exception(DEVICE_KEY_EXISTS);
}
// 1.3 校验设备名称在同一产品下是否唯一
if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
throw exception(DEVICE_NAME_EXISTS);
}
// 1.4 校验父设备是否为合法网关
if (IotProductDeviceTypeEnum.isGateway(product.getDeviceType())
&& createReqVO.getGatewayId() != null) {
validateGatewayDeviceExists(createReqVO.getGatewayId());
}
// 1.5 校验分组存在
deviceGroupService.validateDeviceGroupExists(createReqVO.getGroupIds());
// 2.1 转换 VO DO
IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class)
.setProductKey(product.getProductKey())
.setDeviceType(product.getDeviceType());
// 2.2 生成并设置必要的字段
device.setDeviceKey(generateUniqueDeviceKey());
device.setDeviceSecret(generateDeviceSecret());
device.setMqttClientId(generateMqttClientId());
device.setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()));
device.setMqttPassword(generateMqttPassword());
// 2.3 设置设备状态为未激活
device.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus());
device.setStatusLastUpdateTime(LocalDateTime.now());
// 2.4 插入到数据库
IotDeviceDO device = BeanUtils.toBean(createReqVO, IotDeviceDO.class, o -> {
o.setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
// 生成并设置必要的字段
o.setDeviceSecret(generateDeviceSecret())
.setMqttClientId(generateMqttClientId())
.setMqttUsername(generateMqttUsername(o.getDeviceName(), o.getProductKey()))
.setMqttPassword(generateMqttPassword());
// 设置设备状态为未激活
o.setStatus(IotDeviceStatusEnum.INACTIVE.getStatus()).setStatusLastUpdateTime(LocalDateTime.now());
});
// 2.2 插入到数据库
deviceMapper.insert(device);
return device.getId();
}
/**
* 校验设备名称在同一产品下是否唯一
*
* @param productKey 产品 Key
* @param deviceName 设备名称
*/
private void validateDeviceNameUnique(String productKey, String deviceName) {
IotDeviceDO existingDevice = deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
if (existingDevice != null) {
throw exception(DEVICE_NAME_EXISTS);
@Override
public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
updateReqVO.setDeviceKey(null).setDeviceName(null).setProductId(null); // 不允许更新
// 1.1 校验存在
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
// 1.2 校验父设备是否为合法网关
if (IotProductDeviceTypeEnum.isGateway(device.getDeviceType())
&& updateReqVO.getGatewayId() != null) {
validateGatewayDeviceExists(updateReqVO.getGatewayId());
}
// 1.3 校验分组存在
deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds());
// 2. 更新到数据库
IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
deviceMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateDeviceGroup(IotDeviceUpdateGroupReqVO updateReqVO) {
// 1.1 校验设备存在
List<IotDeviceDO> devices = deviceMapper.selectBatchIds(updateReqVO.getIds());
if (CollUtil.isEmpty(devices)) {
return;
}
// 1.2 校验分组存在
deviceGroupService.validateDeviceGroupExists(updateReqVO.getGroupIds());
// 3. 更新设备分组
deviceMapper.updateBatch(convertList(devices, device -> new IotDeviceDO()
.setId(device.getId()).setGroupIds(updateReqVO.getGroupIds())));
}
@Override
public void deleteDevice(Long id) {
// 1.1 校验存在
IotDeviceDO device = validateDeviceExists(id);
// 1.2 如果是网关设备检查是否有子设备
if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
throw exception(DEVICE_HAS_CHILDREN);
}
// 2. 删除设备
deviceMapper.deleteById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteDeviceList(Collection<Long> ids) {
// 1.1 校验存在
if (CollUtil.isEmpty(ids)) {
return;
}
List<IotDeviceDO> devices = deviceMapper.selectBatchIds(ids);
if (CollUtil.isEmpty(devices)) {
return;
}
// 1.2 校验网关设备是否存在
for (IotDeviceDO device : devices) {
if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(device.getId()) > 0) {
throw exception(DEVICE_HAS_CHILDREN);
}
}
// 2. 删除设备
deviceMapper.deleteByIds(ids);
}
/**
* 生成唯一的 deviceKey
* 校验设备是否存在
*
* @param id 设备 ID
* @return 设备对象
*/
private IotDeviceDO validateDeviceExists(Long id) {
IotDeviceDO device = deviceMapper.selectById(id);
if (device == null) {
throw exception(DEVICE_NOT_EXISTS);
}
return device;
}
/**
* 校验网关设备是否存在
*
* @param id 设备 ID
*/
private void validateGatewayDeviceExists(Long id) {
IotDeviceDO device = deviceMapper.selectById(id);
if (device == null) {
throw exception(DEVICE_GATEWAY_NOT_EXISTS);
}
if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
throw exception(DEVICE_NOT_GATEWAY);
}
}
@Override
public IotDeviceDO getDevice(Long id) {
return deviceMapper.selectById(id);
}
@Override
public PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO) {
return deviceMapper.selectPage(pageReqVO);
}
@Override
public List<IotDeviceDO> getDeviceList(@Nullable Integer deviceType) {
return deviceMapper.selectList(deviceType);
}
@Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
// 1. 校验存在
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
// 2.1 更新状态和更新时间
IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
.setStatusLastUpdateTime(LocalDateTime.now());
// 2.2 更新状态相关时间
if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
&& Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 从未激活到在线设置激活时间和最后上线时间
updateDevice.setActiveTime(LocalDateTime.now()).setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 如果是上线设置最后上线时间
updateDevice.setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.OFFLINE.getStatus())) {
// 如果是离线设置最后离线时间
updateDevice.setLastOfflineTime(LocalDateTime.now());
}
// 2.3 更新到数据库
deviceMapper.updateById(updateDevice);
}
@Override
public Long getDeviceCountByProductId(Long productId) {
return deviceMapper.selectCountByProductId(productId);
}
@Override
public Long getDeviceCountByGroupId(Long groupId) {
return deviceMapper.selectCountByGroupId(groupId);
}
@Override
@TenantIgnore
public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) {
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
}
/**
* 生成 deviceKey
*
* @return 生成的 deviceKey
*/
private String generateUniqueDeviceKey() {
return UUID.randomUUID().toString();
private String generateDeviceKey() {
return RandomUtil.randomString(16);
}
/**
@ -119,7 +268,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
* @return 生成的 MQTT Client ID
*/
private String generateMqttClientId() {
return UUID.randomUUID().toString();
return IdUtil.fastSimpleUUID();
}
/**
@ -139,119 +288,77 @@ public class IotDeviceServiceImpl implements IotDeviceService {
* @return 生成的 MQTT Password
*/
private String generateMqttPassword() {
// TODO @浩浩这里的 StrUtil 随机字符串
SecureRandom secureRandom = new SecureRandom();
byte[] passwordBytes = new byte[32]; // 256 位的随机数
secureRandom.nextBytes(passwordBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(passwordBytes);
}
/**
* 生成唯一的 DeviceName
*
* @param productKey 产品标识
* @return 生成的唯一 DeviceName
*/
private String generateUniqueDeviceName(String productKey) {
for (int i = 0; i < Short.MAX_VALUE; i++) {
String deviceName = IdUtil.fastSimpleUUID().substring(0, 20);
if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
return deviceName;
}
}
throw new IllegalArgumentException("生成 DeviceName 失败");
return RandomUtil.randomString(32);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateDevice(IotDeviceSaveReqVO updateReqVO) {
// 1. 校验存在
validateDeviceExists(updateReqVO.getId());
// 2. 更新到数据库
IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class)
.setDeviceName(null).setProductId(null); // 设备名称 产品 ID 不能修改
deviceMapper.updateById(updateObj);
@Transactional(rollbackFor = Exception.class) // 添加事务异常则回滚所有导入
public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
// 1. 参数校验
if (CollUtil.isEmpty(importDevices)) {
throw exception(DEVICE_IMPORT_LIST_IS_EMPTY);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteDevice(Long id) {
// 1.1 校验存在
IotDeviceDO device = validateDeviceExists(id);
// 1.2 如果是网关设备检查是否有子设备
if (device.getGatewayId() != null && deviceMapper.selectCountByGatewayId(id) > 0) {
throw exception(DEVICE_HAS_CHILDREN);
// 2. 遍历逐个创建 or 更新
IotDeviceImportRespVO respVO = IotDeviceImportRespVO.builder().createDeviceNames(new ArrayList<>())
.updateDeviceNames(new ArrayList<>()).failureDeviceNames(new LinkedHashMap<>()).build();
importDevices.forEach(importDevice -> {
try {
// 2.1.1 校验字段是否符合要求
try {
ValidationUtils.validate(importDevice);
} catch (ConstraintViolationException ex){
respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
return;
}
// 2.1.2 校验产品是否存在
IotProductDO product = productService.validateProductExists(importDevice.getProductKey());
// 2.1.3 校验父设备是否存在
Long gatewayId = null;
if (StrUtil.isNotEmpty(importDevice.getParentDeviceName())) {
IotDeviceDO gatewayDevice = deviceMapper.selectByDeviceName(importDevice.getParentDeviceName());
if (gatewayDevice == null) {
throw exception(DEVICE_GATEWAY_NOT_EXISTS);
}
if (!IotProductDeviceTypeEnum.isGateway(gatewayDevice.getDeviceType())) {
throw exception(DEVICE_NOT_GATEWAY);
}
gatewayId = gatewayDevice.getId();
}
// 2.1.4 校验设备分组是否存在
Set<Long> groupIds = new HashSet<>();
if (StrUtil.isNotEmpty(importDevice.getGroupNames())) {
String[] groupNames = importDevice.getGroupNames().split(",");
for (String groupName : groupNames) {
IotDeviceGroupDO group = deviceGroupService.getDeviceGroupByName(groupName);
if (group == null) {
throw exception(DEVICE_GROUP_NOT_EXISTS);
}
groupIds.add(group.getId());
}
}
// 2. 删除设备
deviceMapper.deleteById(id);
// 2.2.1 判断如果不存在在进行插入
IotDeviceDO existDevice = deviceMapper.selectByDeviceName(importDevice.getDeviceName());
if (existDevice == null) {
createDevice(new IotDeviceSaveReqVO()
.setDeviceName(importDevice.getDeviceName()).setDeviceKey(generateDeviceKey())
.setProductId(product.getId()).setGatewayId(gatewayId).setGroupIds(groupIds));
respVO.getCreateDeviceNames().add(importDevice.getDeviceName());
return;
}
/**
* 校验设备是否存在
*
* @param id 设备 ID
* @return 设备对象
*/
private IotDeviceDO validateDeviceExists(Long id) {
IotDeviceDO device = deviceMapper.selectById(id);
if (device == null) {
throw exception(DEVICE_NOT_EXISTS);
// 2.2.2 如果存在判断是否允许更新
if (updateSupport) {
throw exception(DEVICE_KEY_EXISTS);
}
return device;
updateDevice(new IotDeviceSaveReqVO().setId(existDevice.getId())
.setGatewayId(gatewayId).setGroupIds(groupIds));
respVO.getUpdateDeviceNames().add(importDevice.getDeviceName());
} catch (ServiceException ex) {
respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
}
@Override
public IotDeviceDO getDevice(Long id) {
IotDeviceDO device = deviceMapper.selectById(id);
if (device == null) {
throw exception(DEVICE_NOT_EXISTS);
}
return device;
}
@Override
public PageResult<IotDeviceDO> getDevicePage(IotDevicePageReqVO pageReqVO) {
return deviceMapper.selectPage(pageReqVO);
}
@Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
// 1. 校验存在
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
// 2.1 更新状态和更新时间
IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
// 2.2 更新状态相关时间
if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
&& Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 从未激活到在线设置激活时间和最后上线时间
updateDevice.setActiveTime(LocalDateTime.now());
updateDevice.setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 如果是上线设置最后上线时间
updateDevice.setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.OFFLINE.getStatus())) {
// 如果是离线设置最后离线时间
updateDevice.setLastOfflineTime(LocalDateTime.now());
}
// 2.3 设置状态更新时间
updateDevice.setStatusLastUpdateTime(LocalDateTime.now());
// 2.4 更新到数据库
deviceMapper.updateById(updateDevice);
}
@Override
public Long getDeviceCountByProductId(Long productId) {
return deviceMapper.selectCountByProductId(productId);
}
@Override
@TenantIgnore
public IotDeviceDO getDeviceByProductKeyAndDeviceName(String productKey, String deviceName) {
return deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName);
});
return respVO;
}
}

View File

@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.iot.service.plugininfo;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import jakarta.validation.Valid;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
/**
* IoT 插件信息 Service 接口
*
* @author 芋道源码
*/
public interface PluginInfoService {
/**
* 创建IoT 插件信息
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createPluginInfo(@Valid PluginInfoSaveReqVO createReqVO);
/**
* 更新IoT 插件信息
*
* @param updateReqVO 更新信息
*/
void updatePluginInfo(@Valid PluginInfoSaveReqVO updateReqVO);
/**
* 删除IoT 插件信息
*
* @param id 编号
*/
void deletePluginInfo(Long id);
/**
* 获得IoT 插件信息
*
* @param id 编号
* @return IoT 插件信息
*/
PluginInfoDO getPluginInfo(Long id);
/**
* 获得IoT 插件信息分页
*
* @param pageReqVO 分页查询
* @return IoT 插件信息分页
*/
PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO);
/**
* 上传插件的 JAR
*
* @param id 插件id
* @param file 文件
*/
void uploadJar(Long id, MultipartFile file);
/**
* 更新插件的状态
*
* @param id 插件id
* @param status 状态
*/
void updatePluginStatus(Long id, Integer status);
}

View File

@ -0,0 +1,267 @@
package cn.iocoder.yudao.module.iot.service.plugininfo;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginDescriptor;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/**
* IoT 插件信息 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class PluginInfoServiceImpl implements PluginInfoService {
@Resource
private PluginInfoMapper pluginInfoMapper;
@Resource
private SpringPluginManager pluginManager;
@Resource
private FileApi fileApi;
@Override
public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
// 插入
PluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, PluginInfoDO.class);
pluginInfoMapper.insert(pluginInfo);
// 返回
return pluginInfo.getId();
}
@Override
public void updatePluginInfo(PluginInfoSaveReqVO updateReqVO) {
// 校验存在
validatePluginInfoExists(updateReqVO.getId());
// 更新
PluginInfoDO updateObj = BeanUtils.toBean(updateReqVO, PluginInfoDO.class);
pluginInfoMapper.updateById(updateObj);
}
@Override
public void deletePluginInfo(Long id) {
// 校验存在
PluginInfoDO pluginInfoDO = validatePluginInfoExists(id);
// 停止插件
if (IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
throw exception(PLUGIN_INFO_DELETE_FAILED_RUNNING);
}
// 卸载插件
PluginWrapper plugin = pluginManager.getPlugin(pluginInfoDO.getPluginId());
if (plugin != null) {
// 查询插件是否是启动状态
if (plugin.getPluginState().equals(PluginState.STARTED)) {
// 停止插件
pluginManager.stopPlugin(plugin.getPluginId());
}
// 卸载插件
pluginManager.unloadPlugin(plugin.getPluginId());
}
// 删除
pluginInfoMapper.deleteById(id);
}
private PluginInfoDO validatePluginInfoExists(Long id) {
PluginInfoDO pluginInfo = pluginInfoMapper.selectById(id);
if (pluginInfo == null) {
throw exception(PLUGIN_INFO_NOT_EXISTS);
}
return pluginInfo;
}
@Override
public PluginInfoDO getPluginInfo(Long id) {
return pluginInfoMapper.selectById(id);
}
@Override
public PageResult<PluginInfoDO> getPluginInfoPage(PluginInfoPageReqVO pageReqVO) {
return pluginInfoMapper.selectPage(pageReqVO);
}
@Override
public void uploadJar(Long id, MultipartFile file) {
// 1. 校验存在
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
// 2. 判断文件名称与插件 ID 是否匹配
String pluginId = pluginInfoDo.getPluginId();
// 3. 停止卸载旧的插件
// 3.1. 获取插件信息
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
if (plugin != null) {
// 3.2. 如果插件状态是启动的停止插件
if (plugin.getPluginState().equals(PluginState.STARTED)) {
pluginManager.stopPlugin(pluginId);
}
// 3.3. 卸载插件
pluginManager.unloadPlugin(pluginId);
}
// 4. 上传插件
String pluginIdNew;
try {
String path = fileApi.createFile(IoUtil.readBytes(file.getInputStream()));
Path pluginPath = Path.of(path);
pluginIdNew = pluginManager.loadPlugin(pluginPath);
} catch (Exception e) {
throw exception(PLUGIN_INSTALL_FAILED);
}
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
if (pluginWrapper == null) {
throw exception(PLUGIN_INSTALL_FAILED);
}
// 5. 读取配置文件和脚本
String configJson = "";
String script = "";
pluginInfoDo.setPluginId(pluginIdNew);
pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
pluginInfoDo.setFile(file.getOriginalFilename());
pluginInfoDo.setConfigSchema(configJson);
pluginInfoDo.setScript(script);
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
pluginInfoDo.setVersion(pluginDescriptor.getVersion());
pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
pluginInfoMapper.updateById(pluginInfoDo);
// 5. 读取配置文件和脚本
// String configJson = "";
// String script = "";
// try (JarFile jarFile = new JarFile(pluginInfoUpdate.getPluginPath())) {
// // 5.1 获取config文件在jar包中的路径
// String configFile = "classes/config.json";
// JarEntry configEntry = jarFile.getJarEntry(configFile);
//
// if (configEntry != null) {
// // 5.2 读取配置文件
// configJson = IoUtil.read(jarFile.getInputStream(configEntry), Charset.defaultCharset());
// log.info("configJson:{}", configJson);
// }
//
// // 5.3 读取script.js脚本
// String scriptFile = "classes/script.js";
// JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
// if (scriptEntity != null) {
// // 5.4 读取脚本文件
// script = IoUtil.read(jarFile.getInputStream(scriptEntity), Charset.defaultCharset());
// log.info("script:{}", script);
// }
// } catch (Exception e) {
// throw exception(PLUGIN_INSTALL_FAILED);
// }
// PluginState pluginState = pluginInfoUpdate.getPluginState();
// if (pluginState == PluginState.STARTED) {
// pluginInfoDo.setStatus(IotPluginStatusEnum.RUNNING.getStatus());
// }
// pluginInfoDo.setPluginId(pluginInfoUpdate.getPluginId());
// pluginInfoDo.setFile(file.getOriginalFilename());
// pluginInfoDo.setConfigSchema(configJson);
// pluginInfoDo.setScript(script);
//
// PluginDescriptor pluginDescriptor = pluginInfoUpdate.getPluginDescriptor();
// pluginInfoDo.setVersion(pluginDescriptor.getPluginVersion());
// pluginInfoDo.setDescription(pluginDescriptor.getDescription());
// pluginInfoMapper.updateById(pluginInfoDo);
}
@Override
public void updatePluginStatus(Long id, Integer status) {
// 1. 校验存在
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
// 插件状态无效
if (!IotPluginStatusEnum.contains(status)) {
throw exception(PLUGIN_STATUS_INVALID);
}
// 插件包为空
// String pluginId = pluginInfoDo.getPluginId();
// if (StrUtil.isBlank(pluginId)) {
// throw exception(PLUGIN_INFO_NOT_EXISTS);
// }
// com.gitee.starblues.core.PluginInfo pluginInfo = pluginOperator.getPluginInfo(pluginId);
// if (pluginInfo != null) {
// if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.RUNNING.getStatus()) && pluginInfo.getPluginState() != PluginState.STARTED) {
// // 启动插件
// pluginOperator.start(pluginId);
// } else if (pluginInfoDo.getStatus().equals(IotPluginStatusEnum.STOPPED.getStatus()) && pluginInfo.getPluginState() == PluginState.STARTED) {
// // 停止插件
// pluginOperator.stop(pluginId);
// }
// } else {
// // 已经停止未获取到插件
// if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
// throw exception(PLUGIN_STATUS_INVALID);
// }
// }
pluginInfoDo.setStatus(status);
pluginInfoMapper.updateById(pluginInfoDo);
}
@PostConstruct
public void init() {
Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
}
@SneakyThrows
private void startPlugins() {
// while (!pluginOperator.inited()) {
// Thread.sleep(1000L);
// }
for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
continue;
}
log.info("start plugin:{}", pluginInfoDO.getPluginId());
try {
// com.gitee.starblues.core.PluginInfo plugin = pluginOperator.getPluginInfo(pluginInfoDO.getPluginId());
// if (plugin != null) {
// pluginOperator.start(plugin.getPluginId());
// }
} catch (Exception e) {
log.error("start plugin error", e);
}
}
}
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.iot.service.plugininstance;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstancePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
import jakarta.validation.Valid;
/**
* IoT 插件实例 Service 接口
*
* @author 芋道源码
*/
public interface PluginInstanceService {
/**
* 创建IoT 插件实例
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createPluginInstance(@Valid PluginInstanceSaveReqVO createReqVO);
/**
* 更新IoT 插件实例
*
* @param updateReqVO 更新信息
*/
void updatePluginInstance(@Valid PluginInstanceSaveReqVO updateReqVO);
/**
* 删除IoT 插件实例
*
* @param id 编号
*/
void deletePluginInstance(Long id);
/**
* 获得IoT 插件实例
*
* @param id 编号
* @return IoT 插件实例
*/
PluginInstanceDO getPluginInstance(Long id);
/**
* 获得IoT 插件实例分页
*
* @param pageReqVO 分页查询
* @return IoT 插件实例分页
*/
PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO);
}

View File

@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.iot.service.plugininstance;
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.plugininstance.vo.PluginInstancePageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo.PluginInstanceSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininstance.PluginInstanceDO;
import cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
/**
* IoT 插件实例 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class PluginInstanceServiceImpl implements PluginInstanceService {
@Resource
private PluginInstanceMapper pluginInstanceMapper;
@Override
public Long createPluginInstance(PluginInstanceSaveReqVO createReqVO) {
// 插入
PluginInstanceDO pluginInstance = BeanUtils.toBean(createReqVO, PluginInstanceDO.class);
pluginInstanceMapper.insert(pluginInstance);
// 返回
return pluginInstance.getId();
}
@Override
public void updatePluginInstance(PluginInstanceSaveReqVO updateReqVO) {
// 校验存在
validatePluginInstanceExists(updateReqVO.getId());
// 更新
PluginInstanceDO updateObj = BeanUtils.toBean(updateReqVO, PluginInstanceDO.class);
pluginInstanceMapper.updateById(updateObj);
}
@Override
public void deletePluginInstance(Long id) {
// 校验存在
validatePluginInstanceExists(id);
// 删除
pluginInstanceMapper.deleteById(id);
}
private void validatePluginInstanceExists(Long id) {
if (pluginInstanceMapper.selectById(id) == null) {
throw exception(PLUGIN_INSTANCE_NOT_EXISTS);
}
}
@Override
public PluginInstanceDO getPluginInstance(Long id) {
return pluginInstanceMapper.selectById(id);
}
@Override
public PageResult<PluginInstanceDO> getPluginInstancePage(PluginInstancePageReqVO pageReqVO) {
return pluginInstanceMapper.selectPage(pageReqVO);
}
}

View File

@ -45,6 +45,22 @@ public interface IotProductService {
*/
IotProductDO getProduct(Long id);
/**
* 校验产品存在
*
* @param id 编号
* @return 产品
*/
IotProductDO validateProductExists(Long id);
/**
* 校验产品存在
*
* @param productKey 产品 key
* @return 产品
*/
IotProductDO validateProductExists(String productKey);
/**
* 获得产品分页
*

View File

@ -71,16 +71,26 @@ public class IotProductServiceImpl implements IotProductService {
productMapper.deleteById(id);
}
private IotProductDO validateProductExists(Long id) {
IotProductDO iotProductDO = productMapper.selectById(id);
if (iotProductDO == null) {
@Override
public IotProductDO validateProductExists(Long id) {
IotProductDO product = productMapper.selectById(id);
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
return iotProductDO;
return product;
}
private void validateProductStatus(IotProductDO iotProductDO) {
if (Objects.equals(iotProductDO.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
@Override
public IotProductDO validateProductExists(String productKey) {
IotProductDO product = productMapper.selectByProductKey(productKey);
if (product == null) {
throw exception(PRODUCT_NOT_EXISTS);
}
return product;
}
private void validateProductStatus(IotProductDO product) {
if (Objects.equals(product.getStatus(), IotProductStatusEnum.PUBLISHED.getStatus())) {
throw exception(PRODUCT_STATUS_NOT_DELETE);
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.plugininstance.PluginInstanceMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-module-iot</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-iot-plugin</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
物联网 模块 - 插件
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,6 @@
/**
* 占位
*
* TODO 芋艿后续删除
*/
package cn.iocoder.yudao.module.iot.plugin;