feat(iot): 定义设备控制与属性查询 API

This commit is contained in:
lzh
2026-01-15 16:14:21 +08:00
parent a25c16f151
commit de08aea83f
13 changed files with 517 additions and 64 deletions

View File

@@ -1,48 +1,63 @@
<?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>viewsh-module-iot</artifactId>
<groupId>com.viewsh</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>viewsh-module-iot-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<!-- TODO 芋艿:需要在整理下,特别是 PF4J -->
<description>
物联网 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>com.viewsh</groupId>
<artifactId>viewsh-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
<?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>viewsh-module-iot</artifactId>
<groupId>com.viewsh</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>viewsh-module-iot-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<!-- TODO 芋艿:需要在整理下,特别是 PF4J -->
<description>
物联网 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>com.viewsh</groupId>
<artifactId>viewsh-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- Swagger 注解 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<scope>provided</scope>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,44 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO;
import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeRespDTO;
import com.viewsh.module.iot.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* IoT 设备控制 API
* <p>
* 提供 RPC 接口供其他模块(如 Ops 模块)调用 IoT 设备服务
* <p>
* 支持功能:
* - 服务调用(语音播报、震动提醒等)
* - 批量服务调用
*
* @author lzh
*/
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - IoT 设备服务控制")
public interface IotDeviceControlApi {
String PREFIX = ApiConstants.PREFIX + "/device/control";
// ==================== 服务调用 ====================
@PostMapping(PREFIX + "/invoke-service")
@Operation(summary = "调用设备服务")
CommonResult<IotDeviceServiceInvokeRespDTO> invokeService(@Valid @RequestBody IotDeviceServiceInvokeReqDTO reqDTO);
@PostMapping(PREFIX + "/invoke-service-batch")
@Operation(summary = "批量调用设备服务")
CommonResult<List<IotDeviceServiceInvokeRespDTO>> invokeServiceBatch(@Valid @RequestBody List<IotDeviceServiceInvokeReqDTO> reqDTOList);
}

View File

@@ -0,0 +1,63 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyBatchQueryReqDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyHistoryQueryReqDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyHistoryRespDTO;
import com.viewsh.module.iot.api.device.dto.property.DevicePropertyRespDTO;
import com.viewsh.module.iot.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* IoT 设备属性查询 API
* <p>
* 提供 RPC 接口供其他模块(如 Ops 模块)查询设备属性
*
* @author lzh
*/
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - IoT 设备属性查询")
public interface IotDevicePropertyQueryApi {
String PREFIX = ApiConstants.PREFIX + "/device/property";
// ==================== 单个属性查询 ====================
@GetMapping(PREFIX + "/get")
@Operation(summary = "获取设备单个属性")
@Parameter(name = "deviceId", description = "设备ID", required = true, example = "1")
@Parameter(name = "identifier", description = "属性标识符", required = true, example = "battery_level")
CommonResult<DevicePropertyRespDTO> getProperty(@RequestParam("deviceId") Long deviceId,
@RequestParam("identifier") String identifier);
@GetMapping(PREFIX + "/get-latest")
@Operation(summary = "获取设备最新属性(从缓存)")
@Parameter(name = "deviceId", description = "设备ID", required = true, example = "1")
CommonResult<Map<String, Object>> getLatestProperties(@RequestParam("deviceId") Long deviceId);
// ==================== 批量属性查询 ====================
@PostMapping(PREFIX + "/batch-get")
@Operation(summary = "批量获取多个设备的指定属性")
CommonResult<Map<Long, Map<String, Object>>> batchGetProperties(
@Valid @RequestBody DevicePropertyBatchQueryReqDTO reqDTO);
// ==================== 属性历史查询 ====================
@PostMapping(PREFIX + "/history")
@Operation(summary = "查询设备属性历史数据")
CommonResult<List<DevicePropertyHistoryRespDTO>> getPropertyHistory(
@Valid @RequestBody DevicePropertyHistoryQueryReqDTO reqDTO);
}

View File

@@ -0,0 +1,36 @@
package com.viewsh.module.iot.api.device;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.dto.status.DeviceStatusRespDTO;
import com.viewsh.module.iot.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* IoT 设备状态<E78AB6><E68081>询 API
* <p>
* 提供 RPC 接口供其他模块(如 Ops 模块)查询设备状态
*
* @author lzh
*/
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - IoT 设备状态查询")
public interface IotDeviceStatusQueryApi {
String PREFIX = ApiConstants.PREFIX + "/device/status";
@GetMapping(PREFIX + "/is-online")
@Operation(summary = "检查设备是否在线")
@Parameter(name = "deviceId", description = "设备ID", required = true, example = "1")
CommonResult<Boolean> isDeviceOnline(@RequestParam("deviceId") Long deviceId);
@GetMapping(PREFIX + "/get-status")
@Operation(summary = "获取设备状态")
@Parameter(name = "deviceId", description = "设备ID", required = true, example = "1")
CommonResult<DeviceStatusRespDTO> getDeviceStatus(@RequestParam("deviceId") Long deviceId);
}

View File

@@ -0,0 +1,41 @@
package com.viewsh.module.iot.api.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* IoT 设备服务调用请求 DTO
* <p>
* 用于调用设备服务<E69C8D><E58AA1>如语音播报、震动提醒等
*
* @author lzh
*/
@Schema(description = "IoT 设备服务调用请求")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IotDeviceServiceInvokeReqDTO {
@Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备ID不能为空")
private Long deviceId;
@Schema(description = "服务标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "playVoice")
@NotEmpty(message = "服务标识符不能为空")
private String identifier;
@Schema(description = "服务参数", example = "{\"text\":\"您有新的工单\",\"volume\":80}")
private Map<String, Object> params;
@Schema(description = "超时时间默认30秒", example = "30")
private Integer timeoutSeconds;
}

View File

@@ -0,0 +1,41 @@
package com.viewsh.module.iot.api.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* IoT 设备服务调用响应 DTO
*
* @author lzh
*/
@Schema(description = "IoT 设备服务调用响应")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class IotDeviceServiceInvokeRespDTO {
@Schema(description = "消息ID", example = "msg_123456")
private String messageId;
@Schema(description = "是否成功", example = "true")
private Boolean success;
@Schema(description = "响应数据")
private Object data;
@Schema(description = "错误码", example = "500")
private Integer code;
@Schema(description = "错误信息")
private String errorMsg;
@Schema(description = "响应时间", example = "2024-01-01T12:00:00")
private LocalDateTime responseTime;
}

View File

@@ -0,0 +1,25 @@
package com.viewsh.module.iot.api.device.dto.property;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 设备属性批量查询请求 DTO
*
* @author lzh
*/
@Schema(description = "设备属性批量查询请求")
@Data
public class DevicePropertyBatchQueryReqDTO {
@Schema(description = "设备ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "设备ID列表不能为空")
private List<Long> deviceIds;
@Schema(description = "属性标识符列表,为空则返回所有属性", example = "[\"battery_level\", \"signal_strength\"]")
private List<String> identifiers;
}

View File

@@ -0,0 +1,40 @@
package com.viewsh.module.iot.api.device.dto.property;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 设备属性历史查询请求 DTO
*
* @author lzh
*/
@Schema(description = "设备属性历史查询请求")
@Data
public class DevicePropertyHistoryQueryReqDTO {
@Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "设备ID不能为空")
private Long deviceId;
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "battery_level")
@NotBlank(message = "属性标识符不能为空")
private String identifier;
@Schema(description = "开始时间", example = "2024-01-01T00:00:00")
private LocalDateTime startTime;
@Schema(description = "结束时间", example = "2024-01-01T23:59:59")
private LocalDateTime endTime;
@Schema(description = "限制条数", example = "100")
@Min(value = 1, message = "限制条数至少为1")
@Max(value = 1000, message = "限制条数最多为1000")
private Integer limit = 100;
}

View File

@@ -0,0 +1,30 @@
package com.viewsh.module.iot.api.device.dto.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 设备属性历史响应 DTO
*
* @author lzh
*/
@Schema(description = "设备属性历史响应")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DevicePropertyHistoryRespDTO {
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "battery_level")
private String identifier;
@Schema(description = "属性值", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
private Object value;
@Schema(description = "时间戳(毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1704067200000")
private Long timestamp;
}

View File

@@ -0,0 +1,30 @@
package com.viewsh.module.iot.api.device.dto.property;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 设备属性响应 DTO
*
* @author lzh
*/
@Schema(description = "设备属性响应")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DevicePropertyRespDTO {
@Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "battery_level")
private String identifier;
@Schema(description = "属性值", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
private Object value;
@Schema(description = "更新时间(时间戳毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1704067200000")
private Long updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.viewsh.module.iot.api.device.dto.status;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 设备状态响应 DTO
*
* @author lzh
*/
@Schema(description = "设备状态响应")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DeviceStatusRespDTO {
@Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deviceId;
@Schema(description = "设备编码", example = "BADGE001")
private String deviceCode;
@Schema(description = "设备状态0=INACTIVE, 1=ONLINE, 2=OFFLINE", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "状态变更时间", example = "2024-01-01T12:00:00")
private LocalDateTime statusChangeTime;
}

View File

@@ -0,0 +1,46 @@
package com.viewsh.module.iot.api.enums;
import com.viewsh.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* IoT 设备状态枚举
*
* @author haohao
*/
@RequiredArgsConstructor
@Getter
public enum IotDeviceStateEnum implements ArrayValuable<Integer> {
INACTIVE(0, "未激活"),
ONLINE(1, "在线"),
OFFLINE(2, "离线");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(IotDeviceStateEnum::getState).toArray(Integer[]::new);
/**
* 状态
*/
private final Integer state;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isOnline(Integer state) {
return ONLINE.getState().equals(state);
}
public static boolean isNotOnline(Integer state) {
return !isOnline(state);
}
}

View File

@@ -1,16 +1,23 @@
package com.viewsh.module.iot.enums;
import com.viewsh.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*
* @author 芋道源码
*/
public class ApiConstants {
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot";
public static final String VERSION = "1.0.0";
}
package com.viewsh.module.iot.enums;
import com.viewsh.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*
* @author 芋道源码
*/
public class ApiConstants {
/**
* 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
public static final String NAME = "iot-server";
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/iot";
public static final String VERSION = "1.0.0";
}