feat(integration): 添加设备服务集成模块

- 新增设备服务配置和相关客户端接口
- 实现设备和设备配置的管理功能- 添加设备监控和状态管理示例
- 优化错误处理和故障恢复机制
This commit is contained in:
2025-08-30 23:24:49 +08:00
parent a9d64402f2
commit d34603062a
19 changed files with 1484 additions and 1 deletions

104
CLAUDE.md
View File

@@ -122,6 +122,15 @@ mvn test -DskipTests=false
3. 使用 `@Scheduled` 进行基于 cron 的执行 3. 使用 `@Scheduled` 进行基于 cron 的执行
4. 遵循现有的错误处理和日志记录模式 4. 遵循现有的错误处理和日志记录模式
### 多端API架构
应用程序通过路径前缀区分不同的客户端:
- **移动端**: `/api/mobile/*` - 针对移动应用优化的接口
- **PC管理端**: `/api/*` - Web管理面板接口
- **任务处理**: `/task/*` - 后台任务和渲染服务接口
- **外部集成**: 专用集成接口(打印机、代理、viid、vpt、wvp等)
每个端点都有对应的Controller包结构,确保API的职责分离和维护性。
## 价格查询系统 (Pricing Module) ## 价格查询系统 (Pricing Module)
### 核心架构 ### 核心架构
@@ -187,4 +196,97 @@ ProductType枚举定义了支持的商品类型:
- 单元测试:每个服务类都有对应测试类 - 单元测试:每个服务类都有对应测试类
- 配置验证测试:DefaultConfigValidationTest验证default配置 - 配置验证测试:DefaultConfigValidationTest验证default配置
- JSON序列化测试:验证复杂对象的数据库存储 - JSON序列化测试:验证复杂对象的数据库存储
- 分页功能测试:验证PageHelper集成 - 分页功能测试:验证PageHelper集成
## 关键架构模式
### Repository 层模式
项目使用Repository层抽象数据访问逻辑:
- Repository接口定义数据访问契约
- Mapper接口处理MyBatis Plus的数据库映射
- Service层通过Repository访问数据,避免直接依赖Mapper
### 异常处理架构
- **全局异常处理**: `CustomExceptionHandle` 提供统一的异常处理和响应格式
- **业务异常**: 自定义异常类继承RuntimeException,携带业务错误码
- **集成异常**: `IntegrationException` 专门处理外部服务调用异常
### 配置驱动的扩展性
通过配置文件驱动的多供应商支持:
- 存储:本地、AWS S3、阿里云 OSS
- 支付:微信支付、聪明支付
- 人脸识别:阿里云、百度
每个供应商通过统一接口访问,配置切换无需代码修改。
### 业务层架构
- **Service层**: 核心业务逻辑实现
- **Biz层**: 高级业务流程编排,组合多个Service
- **Controller层**: HTTP请求处理和响应转换
- **Repository层**: 数据访问抽象
### 认证和会话管理
- **JWT**: 使用jjwt库进行身份验证
- **Redis**: 存储会话信息和缓存
- **BaseContextHandler**: 提供当前用户上下文访问
## 微服务集成架构 (Integration Package)
### 核心架构
位于 `com.ycwl.basic.integration` 包,使用 Spring Cloud OpenFeign 和 Nacos 实现外部微服务集成。
#### 通用基础设施
- **IntegrationProperties**: 所有集成的集中配置管理
- **FeignErrorDecoder**: 自定义错误解码器,统一错误处理
- **IntegrationException**: 标准化集成异常
- **CommonResponse/PageResponse**: 外部服务响应包装器
#### 已实现的服务集成
- **Scenic Integration** (`integration.scenic`): ZT-Scenic 微服务集成
- **Device Integration** (`integration.device`): ZT-Device 微服务集成
#### 集成模式
每个外部服务按以下结构组织:
```
service/
├── client/ # Feign 客户端
├── config/ # 服务特定配置
├── dto/ # 数据传输对象
├── service/ # 业务逻辑层
└── example/ # 使用示例
```
### 配置管理
```yaml
integration:
scenic:
enabled: true
serviceName: zt-scenic
connectTimeout: 5000
readTimeout: 10000
device:
enabled: true
serviceName: zt-device
connectTimeout: 5000
readTimeout: 10000
```
### 使用模式
所有集成服务使用统一的 `handleResponse` 模式进行错误处理,确保一致的异常包装和日志记录。
### 测试集成服务
```bash
# 运行特定集成测试
mvn test -Dtest=ScenicIntegrationServiceTest
mvn test -Dtest=DeviceIntegrationServiceTest
# 运行所有集成测试
mvn test -Dtest="com.ycwl.basic.integration.*Test"
```
### 调试集成问题
启用 Feign 客户端日志:
```yaml
logging:
level:
com.ycwl.basic.integration: DEBUG
```

View File

@@ -0,0 +1,397 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Integration Package Overview
The integration package (`com.ycwl.basic.integration`) is responsible for external microservice integrations using Spring Cloud OpenFeign and Nacos service discovery. It provides a standardized approach for calling external services with proper error handling, configuration management, and response processing.
## Architecture
### Core Components
#### Common Infrastructure (`com.ycwl.basic.integration.common`)
- **IntegrationProperties**: Centralized configuration properties for all integrations
- **FeignConfig**: Global Feign configuration with error decoder and request interceptors
- **FeignErrorDecoder**: Custom error decoder that converts Feign errors to IntegrationException
- **IntegrationException**: Standardized exception for integration failures
- **CommonResponse/PageResponse**: Standard response wrappers for external service calls
- **ConfigValueUtil**: Utility for handling configuration values
#### Service-Specific Integrations
Currently implemented:
- **Scenic Integration** (`com.ycwl.basic.integration.scenic`): ZT-Scenic microservice integration
- **Device Integration** (`com.ycwl.basic.integration.device`): ZT-Device microservice integration
### Integration Pattern
Each external service integration follows this structure:
```
service/
├── client/ # Feign clients for HTTP calls
├── config/ # Service-specific configuration
├── dto/ # Data transfer objects
├── service/ # Service layer with business logic
└── example/ # Usage examples
```
## Scenic Integration (ZT-Scenic Microservice)
### Key Components
#### Feign Clients
- **ScenicV2Client**: Main scenic operations (CRUD, filtering, listing)
- **ScenicConfigV2Client**: Scenic configuration management
- **DefaultConfigClient**: Default configuration operations
#### Services
- **ScenicIntegrationService**: High-level scenic operations
- **ScenicConfigIntegrationService**: Configuration management
- **DefaultConfigIntegrationService**: Default configuration handling
#### Configuration
```yaml
integration:
scenic:
enabled: true
serviceName: zt-scenic
connectTimeout: 5000
readTimeout: 10000
retryEnabled: false
maxRetries: 3
```
### Usage Examples
#### Basic Scenic Operations
```java
@Autowired
private ScenicIntegrationService scenicService;
// Get scenic with configuration
ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId);
// Create new scenic
CreateScenicRequest request = new CreateScenicRequest();
request.setName("Test Scenic");
ScenicV2DTO result = scenicService.createScenic(request);
// Filter scenics
ScenicFilterRequest filterRequest = new ScenicFilterRequest();
// configure filters...
ScenicFilterPageResponse response = scenicService.filterScenics(filterRequest);
```
#### Configuration Management
```java
@Autowired
private ScenicConfigIntegrationService configService;
// Get flat configuration
Map<String, Object> config = scenicService.getScenicFlatConfig(scenicId);
// Batch update configurations
BatchConfigRequest batchRequest = new BatchConfigRequest();
// configure batch updates...
configService.batchUpdateConfigs(scenicId, batchRequest);
```
## Device Integration (ZT-Device Microservice)
### Key Components
#### Feign Clients
- **DeviceV2Client**: Main device operations (CRUD, filtering, listing)
- **DeviceConfigV2Client**: Device configuration management
#### Services
- **DeviceIntegrationService**: High-level device operations
- **DeviceConfigIntegrationService**: Device configuration management
#### Configuration
```yaml
integration:
device:
enabled: true
serviceName: zt-device
connectTimeout: 5000
readTimeout: 10000
retryEnabled: false
maxRetries: 3
```
### Usage Examples
#### Basic Device Operations
```java
@Autowired
private DeviceIntegrationService deviceService;
// Create IPC camera device
DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId);
// Get device with configuration
DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId);
// Get device by number
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001");
// List scenic devices
DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10);
// Enable/disable device
deviceService.enableDevice(deviceId);
deviceService.disableDevice(deviceId);
```
#### Device Configuration Management
```java
@Autowired
private DeviceConfigIntegrationService configService;
// Configure camera basic parameters
configService.configureCameraBasicParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP");
// Configure camera with authentication
configService.configureCameraFullParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password");
// Set specific configuration
configService.setDeviceIpAddress(deviceId, "192.168.1.101");
configService.setDeviceResolution(deviceId, "2560x1440");
configService.setDeviceFramerate(deviceId, 60);
// Get flat configuration
Map<String, Object> config = configService.getDeviceFlatConfig(deviceId);
// Batch update configurations
Map<String, Object> batchConfigs = new HashMap<>();
batchConfigs.put("brightness", "50");
batchConfigs.put("contrast", "80");
configService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs);
```
#### Device Management Patterns
```java
// Create and configure camera in one operation
DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId);
configService.configureCameraFullParams(camera.getId(), "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password");
// Get scenic camera status
DeviceV2WithConfigListResponse camerasWithConfig =
deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId);
// Batch update camera resolution
for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) {
configService.setDeviceResolution(device.getId(), "2560x1440");
}
// Monitor device configuration completeness
for (DeviceV2DTO device : activeDevices.getList()) {
Map<String, Object> config = configService.getDeviceFlatConfig(device.getId());
boolean hasIpConfig = config.containsKey("ip_address");
// log configuration status...
}
```
### Device Types
- **IPC**: IP Camera devices for video monitoring
- **CUSTOM**: Custom device types for sensors, controllers, etc.
### Common Configuration Keys
- `ip_address`: Device IP address
- `resolution`: Video resolution (e.g., "1920x1080", "3840x2160")
- `framerate`: Video frame rate (integer)
- `protocol`: Communication protocol (e.g., "RTSP", "HTTP")
- `username`: Authentication username
- `password`: Authentication password
- `brightness`: Display brightness (0-100)
- `contrast`: Display contrast (0-100)
- `quality`: Video quality ("low", "medium", "high")
## Adding New Service Integrations
### 1. Create Package Structure
```
com.ycwl.basic.integration.{service-name}/
├── client/
├── config/
├── dto/
├── service/
└── example/
```
### 2. Add Configuration Properties
Update `IntegrationProperties` to include new service configuration:
```java
@Data
public static class NewServiceConfig {
private boolean enabled = true;
private String serviceName = "service-name";
private int connectTimeout = 5000;
private int readTimeout = 10000;
// other configs...
}
```
### 3. Create Feign Client
```java
@FeignClient(name = "service-name", contextId = "service-context", path = "/api/path")
public interface NewServiceClient {
@GetMapping("/endpoint")
CommonResponse<ResponseDTO> getData(@PathVariable Long id);
// other endpoints...
}
```
### 4. Implement Service Layer
```java
@Service
@RequiredArgsConstructor
public class NewServiceIntegrationService {
private final NewServiceClient client;
public ResponseDTO getData(Long id) {
CommonResponse<ResponseDTO> response = client.getData(id);
return handleResponse(response, "Failed to get data");
}
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.isSuccess()) {
String msg = response != null && response.getMessage() != null
? response.getMessage()
: errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, "service-name");
}
return response.getData();
}
}
```
## Error Handling
### IntegrationException
All integration failures are wrapped in `IntegrationException`:
```java
public class IntegrationException extends RuntimeException {
private final Integer code;
private final String serviceName;
// constructors and getters...
}
```
### FeignErrorDecoder
Automatically converts Feign errors to IntegrationException:
- Parses CommonResponse error format
- Extracts service name from method key
- Provides meaningful error messages
## Configuration Management
### Properties Structure
```yaml
integration:
scenic:
enabled: true
serviceName: zt-scenic
connectTimeout: 5000
readTimeout: 10000
retryEnabled: false
maxRetries: 3
device:
enabled: true
serviceName: zt-device
connectTimeout: 5000
readTimeout: 10000
retryEnabled: false
maxRetries: 3
```
### Configuration Refresh
Uses `@RefreshScope` to support dynamic configuration updates without restart.
## Testing Integration Services
### Unit Testing
Test service layers by mocking Feign clients:
```java
@ExtendWith(MockitoExtension.class)
class ScenicIntegrationServiceTest {
@Mock
private ScenicV2Client scenicV2Client;
@InjectMocks
private ScenicIntegrationService scenicService;
@Test
void testGetScenic() {
// Mock response
CommonResponse<ScenicV2DTO> response = new CommonResponse<>();
response.setSuccess(true);
response.setData(new ScenicV2DTO());
when(scenicV2Client.getScenic(1L)).thenReturn(response);
// Test
ScenicV2DTO result = scenicService.getScenic(1L);
assertNotNull(result);
}
}
```
### Integration Testing
Use `@SpringBootTest` with test profiles and mock external services.
## Common Development Tasks
### Running Integration Tests
```bash
# Run specific integration test class
mvn test -Dtest=ScenicIntegrationServiceTest
# Run all integration tests
mvn test -Dtest="com.ycwl.basic.integration.*Test"
# Run device integration tests
mvn test -Dtest=DeviceIntegrationServiceTest
mvn test -Dtest="com.ycwl.basic.integration.device.*Test"
```
### Adding New DTOs
1. Create DTO classes in the appropriate `dto` package
2. Follow existing patterns with proper Jackson annotations
3. Use `@JsonProperty` for field mapping when needed
### Debugging Integration Issues
1. Enable Feign client logging in `application-dev.yml`:
```yaml
logging:
level:
com.ycwl.basic.integration: DEBUG
```
2. Check Nacos service discovery in development
3. Verify service configurations and timeouts
4. Review FeignErrorDecoder logs for detailed error information
## Best Practices
### Response Handling
- Always use the `handleResponse` pattern for consistent error handling
- Provide meaningful error messages for business context
- Include service name in IntegrationException for debugging
### Configuration
- Use environment-specific configuration profiles
- Set appropriate timeouts based on service characteristics
- Enable retries only when safe (idempotent operations)
### DTOs and Mapping
- Keep DTOs simple and focused on data transfer
- Use proper Jackson annotations for field mapping
- Separate request/response DTOs for clarity
### Service Layer Design
- Keep integration services focused on external service calls
- Handle response transformation and error conversion
- Avoid business logic in integration services

View File

@@ -16,6 +16,11 @@ public class IntegrationProperties {
*/ */
private ScenicConfig scenic = new ScenicConfig(); private ScenicConfig scenic = new ScenicConfig();
/**
* 设备服务配置
*/
private DeviceConfig device = new DeviceConfig();
@Data @Data
public static class ScenicConfig { public static class ScenicConfig {
/** /**
@@ -40,4 +45,29 @@ public class IntegrationProperties {
private boolean retryEnabled = false; private boolean retryEnabled = false;
private int maxRetries = 3; private int maxRetries = 3;
} }
@Data
public static class DeviceConfig {
/**
* 是否启用设备服务集成
*/
private boolean enabled = true;
/**
* 服务名称
*/
private String serviceName = "zt-device";
/**
* 超时配置(毫秒)
*/
private int connectTimeout = 5000;
private int readTimeout = 10000;
/**
* 重试配置
*/
private boolean retryEnabled = false;
private int maxRetries = 3;
}
} }

View File

@@ -0,0 +1,80 @@
package com.ycwl.basic.integration.device.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.device.dto.config.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@FeignClient(name = "zt-device", contextId = "device-config-v2", path = "/api/device/config/v2")
public interface DeviceConfigV2Client {
/**
* 获取设备所有配置
*/
@GetMapping("/{deviceId}")
CommonResponse<List<DeviceConfigV2DTO>> getDeviceConfigs(@PathVariable("deviceId") Long deviceId);
/**
* 根据设备编号获取设备所有配置
*/
@GetMapping("/no/{no}")
CommonResponse<List<DeviceConfigV2DTO>> getDeviceConfigsByNo(@PathVariable("no") String no);
/**
* 获取设备特定配置
*/
@GetMapping("/{deviceId}/key/{configKey}")
CommonResponse<DeviceConfigV2DTO> getDeviceConfigByKey(@PathVariable("deviceId") Long deviceId,
@PathVariable("configKey") String configKey);
/**
* 获取设备扁平化配置
*/
@GetMapping("/{deviceId}/flat")
CommonResponse<Map<String, Object>> getDeviceFlatConfig(@PathVariable("deviceId") Long deviceId);
/**
* 根据设备编号获取设备扁平化配置
*/
@GetMapping("/no/{no}/flat")
CommonResponse<Map<String, Object>> getDeviceFlatConfigByNo(@PathVariable("no") String no);
/**
* 创建设备配置
*/
@PostMapping("/{deviceId}")
CommonResponse<DeviceConfigV2DTO> createDeviceConfig(@PathVariable("deviceId") Long deviceId,
@RequestBody CreateDeviceConfigRequest request);
/**
* 更新设备配置
*/
@PutMapping("/{deviceId}/{id}")
CommonResponse<String> updateDeviceConfig(@PathVariable("deviceId") Long deviceId,
@PathVariable("id") Long id,
@RequestBody UpdateDeviceConfigRequest request);
/**
* 删除设备配置
*/
@DeleteMapping("/{deviceId}/{id}")
CommonResponse<String> deleteDeviceConfig(@PathVariable("deviceId") Long deviceId,
@PathVariable("id") Long id);
/**
* 批量更新设备配置
*/
@PostMapping("/{deviceId}/batch")
CommonResponse<String> batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId,
@RequestBody BatchDeviceConfigRequest request);
/**
* 扁平化批量更新设备配置
*/
@PostMapping("/{deviceId}/batch-flat")
CommonResponse<String> batchFlatUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId,
@RequestBody Map<String, Object> configs);
}

View File

@@ -0,0 +1,79 @@
package com.ycwl.basic.integration.device.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.device.dto.device.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(name = "zt-device", contextId = "device-v2", path = "/api/device/v2")
public interface DeviceV2Client {
/**
* 获取设备核心信息
*/
@GetMapping("/{id}")
CommonResponse<DeviceV2DTO> getDevice(@PathVariable("id") Long id);
/**
* 根据设备编号获取设备核心信息
*/
@GetMapping("/no/{no}")
CommonResponse<DeviceV2DTO> getDeviceByNo(@PathVariable("no") String no);
/**
* 获取设备详细信息(含配置)
*/
@GetMapping("/{id}/with-config")
CommonResponse<DeviceV2WithConfigDTO> getDeviceWithConfig(@PathVariable("id") Long id);
/**
* 根据设备编号获取设备详细信息(含配置)
*/
@GetMapping("/no/{no}/with-config")
CommonResponse<DeviceV2WithConfigDTO> getDeviceByNoWithConfig(@PathVariable("no") String no);
/**
* 创建设备
*/
@PostMapping("/")
CommonResponse<DeviceV2DTO> createDevice(@RequestBody CreateDeviceRequest request);
/**
* 更新设备
*/
@PutMapping("/{id}")
CommonResponse<String> updateDevice(@PathVariable("id") Long id,
@RequestBody UpdateDeviceRequest request);
/**
* 删除设备
*/
@DeleteMapping("/{id}")
CommonResponse<String> deleteDevice(@PathVariable("id") Long id);
/**
* 分页获取设备列表(核心信息)
*/
@GetMapping("/")
CommonResponse<DeviceV2ListResponse> listDevices(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "no", required = false) String no,
@RequestParam(value = "type", required = false) String type,
@RequestParam(value = "isActive", required = false) Integer isActive,
@RequestParam(value = "scenicId", required = false) Long scenicId);
/**
* 分页获取设备列表(含配置)
*/
@GetMapping("/with-config")
CommonResponse<DeviceV2WithConfigListResponse> listDevicesWithConfig(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "no", required = false) String no,
@RequestParam(value = "type", required = false) String type,
@RequestParam(value = "isActive", required = false) Integer isActive,
@RequestParam(value = "scenicId", required = false) Long scenicId);
}

View File

@@ -0,0 +1,16 @@
package com.ycwl.basic.integration.device.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "integration.device")
public class DeviceIntegrationConfig {
public DeviceIntegrationConfig() {
log.info("ZT-Device集成配置初始化完成");
}
}

View File

@@ -0,0 +1,18 @@
package com.ycwl.basic.integration.device.dto.config;
import lombok.Data;
import java.util.List;
@Data
public class BatchDeviceConfigRequest {
private List<BatchDeviceConfigItem> configs;
@Data
public static class BatchDeviceConfigItem {
private String configKey;
private String configValue;
private String configType;
private String description;
}
}

View File

@@ -0,0 +1,12 @@
package com.ycwl.basic.integration.device.dto.config;
import lombok.Data;
@Data
public class CreateDeviceConfigRequest {
private String configKey;
private String configValue;
private String configType;
private String description;
private Integer isActive;
}

View File

@@ -0,0 +1,39 @@
package com.ycwl.basic.integration.device.dto.config;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DeviceConfigV2DTO {
@JsonProperty("id")
private Long id;
@JsonProperty("deviceId")
private Long deviceId;
@JsonProperty("configKey")
private String configKey;
@JsonProperty("configValue")
private String configValue;
@JsonProperty("configType")
private String configType;
@JsonProperty("description")
private String description;
@JsonProperty("isActive")
private Integer isActive;
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.ycwl.basic.integration.device.dto.config;
import lombok.Data;
import java.util.Map;
@Data
public class UpdateDeviceConfigRequest {
private String configKey;
private String configValue;
private String configType;
private String description;
private Integer isActive;
// 支持灵活的字段更新
public static UpdateDeviceConfigRequest fromMap(Map<String, Object> updates) {
UpdateDeviceConfigRequest request = new UpdateDeviceConfigRequest();
if (updates.containsKey("configKey")) {
request.setConfigKey((String) updates.get("configKey"));
}
if (updates.containsKey("configValue")) {
request.setConfigValue((String) updates.get("configValue"));
}
if (updates.containsKey("configType")) {
request.setConfigType((String) updates.get("configType"));
}
if (updates.containsKey("description")) {
request.setDescription((String) updates.get("description"));
}
if (updates.containsKey("isActive")) {
request.setIsActive((Integer) updates.get("isActive"));
}
return request;
}
}

View File

@@ -0,0 +1,12 @@
package com.ycwl.basic.integration.device.dto.device;
import lombok.Data;
@Data
public class CreateDeviceRequest {
private String name;
private String no;
private String type;
private Integer isActive;
private Long scenicId;
}

View File

@@ -0,0 +1,36 @@
package com.ycwl.basic.integration.device.dto.device;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DeviceV2DTO {
@JsonProperty("id")
private Long id;
@JsonProperty("name")
private String name;
@JsonProperty("no")
private String no;
@JsonProperty("type")
private String type; // IPC, CUSTOM
@JsonProperty("isActive")
private Integer isActive;
@JsonProperty("scenicId")
private Long scenicId;
@JsonProperty("createTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonProperty("updateTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,13 @@
package com.ycwl.basic.integration.device.dto.device;
import lombok.Data;
import java.util.List;
@Data
public class DeviceV2ListResponse {
private List<DeviceV2DTO> list;
private Long total;
private Integer page;
private Integer pageSize;
}

View File

@@ -0,0 +1,12 @@
package com.ycwl.basic.integration.device.dto.device;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
@Data
@EqualsAndHashCode(callSuper = true)
public class DeviceV2WithConfigDTO extends DeviceV2DTO {
private Map<String, Object> config;
}

View File

@@ -0,0 +1,13 @@
package com.ycwl.basic.integration.device.dto.device;
import lombok.Data;
import java.util.List;
@Data
public class DeviceV2WithConfigListResponse {
private List<DeviceV2WithConfigDTO> list;
private Long total;
private Integer page;
private Integer pageSize;
}

View File

@@ -0,0 +1,12 @@
package com.ycwl.basic.integration.device.dto.device;
import lombok.Data;
@Data
public class UpdateDeviceRequest {
private String name;
private String no;
private String type;
private Integer isActive;
private Long scenicId;
}

View File

@@ -0,0 +1,243 @@
package com.ycwl.basic.integration.device.example;
import com.ycwl.basic.integration.device.dto.device.*;
import com.ycwl.basic.integration.device.dto.config.*;
import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService;
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Device Integration 使用示例
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DeviceIntegrationExample {
private final DeviceIntegrationService deviceService;
private final DeviceConfigIntegrationService deviceConfigService;
/**
* 基本设备操作示例
*/
public void basicDeviceOperations() {
log.info("=== 基本设备操作示例 ===");
// 1. 创建IPC摄像头设备
DeviceV2DTO ipcDevice = deviceService.createIpcDevice(
"前门摄像头", "CAM001", 1001L);
log.info("创建IPC设备: {}", ipcDevice);
// 2. 创建自定义设备
DeviceV2DTO customDevice = deviceService.createCustomDevice(
"温度传感器", "TEMP001", 1001L);
log.info("创建自定义设备: {}", customDevice);
// 3. 根据ID获取设备信息
DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId());
log.info("获取设备信息: {}", device);
// 4. 根据设备编号获取设备信息
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001");
log.info("根据编号获取设备: {}", deviceByNo);
// 5. 获取设备详细信息(含配置)
DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId());
log.info("获取设备配置: {}", deviceWithConfig);
// 6. 分页查询景区设备列表
DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10);
log.info("景区IPC设备列表: 总数={}, 当前页={}", deviceList.getTotal(), deviceList.getList().size());
// 7. 启用/禁用设备
deviceService.enableDevice(ipcDevice.getId());
log.info("设备已启用");
deviceService.disableDevice(customDevice.getId());
log.info("设备已禁用");
}
/**
* 设备配置管理示例
*/
public void deviceConfigurationOperations() {
log.info("=== 设备配置管理示例 ===");
// 假设已有设备ID
Long deviceId = 1L;
// 1. 配置摄像头基本参数
deviceConfigService.configureCameraBasicParams(
deviceId, "192.168.1.100", "1920x1080", 30, "RTSP");
log.info("摄像头基本参数已配置");
// 2. 配置摄像头完整参数(包含认证)
deviceConfigService.configureCameraFullParams(
deviceId, "192.168.1.101", "3840x2160", 25, "RTSP", "admin", "password123");
log.info("摄像头完整参数已配置");
// 3. 单独设置特定配置
deviceConfigService.setDeviceIpAddress(deviceId, "192.168.1.102");
deviceConfigService.setDeviceResolution(deviceId, "2560x1440");
deviceConfigService.setDeviceFramerate(deviceId, 60);
log.info("单独配置项已更新");
// 4. 获取设备所有配置
List<DeviceConfigV2DTO> configs = deviceConfigService.getDeviceConfigs(deviceId);
log.info("设备配置列表: {}", configs.size());
// 5. 获取扁平化配置
Map<String, Object> flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId);
log.info("扁平化配置: {}", flatConfig);
// 6. 获取特定配置值
String ipAddress = deviceConfigService.getDeviceIpAddress(deviceId);
String resolution = deviceConfigService.getDeviceResolution(deviceId);
log.info("IP地址: {}, 分辨率: {}", ipAddress, resolution);
// 7. 批量更新配置
Map<String, Object> batchConfigs = new HashMap<>();
batchConfigs.put("brightness", "50");
batchConfigs.put("contrast", "80");
batchConfigs.put("quality", "high");
deviceConfigService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs);
log.info("批量配置已更新");
}
/**
* 摄像头管理示例
*/
public void cameraManagementExample() {
log.info("=== 摄像头管理示例 ===");
Long scenicId = 1001L;
// 1. 批量创建摄像头
for (int i = 1; i <= 5; i++) {
DeviceV2DTO camera = deviceService.createIpcDevice(
"摄像头" + i, "CAM00" + i, scenicId);
// 配置每个摄像头的基本参数
deviceConfigService.configureCameraFullParams(
camera.getId(),
"192.168.1." + (100 + i),
"1920x1080",
30,
"RTSP",
"admin",
"camera" + i
);
log.info("创建并配置摄像头: {}", camera.getName());
}
// 2. 获取景区所有摄像头状态
DeviceV2WithConfigListResponse camerasWithConfig =
deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId);
log.info("景区摄像头总数: {}", camerasWithConfig.getTotal());
for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) {
log.info("摄像头: {}, IP: {}",
camera.getName(),
camera.getConfig().get("ip_address"));
}
// 3. 批量更新摄像头分辨率
for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) {
deviceConfigService.setDeviceResolution(camera.getId(), "2560x1440");
}
log.info("所有摄像头分辨率已更新为 2560x1440");
}
/**
* 设备监控和状态管理示例
*/
public void deviceMonitoringExample() {
log.info("=== 设备监控和状态管理示例 ===");
Long scenicId = 1001L;
// 1. 获取景区所有激活设备
DeviceV2ListResponse activeDevices = deviceService.getScenicActiveDevices(scenicId, 1, 50);
log.info("景区激活设备数量: {}", activeDevices.getTotal());
// 2. 按设备类型分类统计
Map<String, Integer> deviceTypeCount = new HashMap<>();
for (DeviceV2DTO device : activeDevices.getList()) {
deviceTypeCount.merge(device.getType(), 1, Integer::sum);
}
log.info("设备类型统计: {}", deviceTypeCount);
// 3. 检查设备配置完整性
for (DeviceV2DTO device : activeDevices.getList()) {
try {
Map<String, Object> config = deviceConfigService.getDeviceFlatConfig(device.getId());
boolean hasIpConfig = config.containsKey("ip_address");
log.info("设备 {} 配置状态: IP配置={}", device.getName(), hasIpConfig ? "已配置" : "未配置");
} catch (Exception e) {
log.warn("获取设备 {} 配置失败: {}", device.getName(), e.getMessage());
}
}
// 4. 按设备编号搜索
try {
DeviceV2WithConfigDTO deviceByNo = deviceService.getDeviceWithConfigByNo("CAM001");
log.info("根据编号找到设备: {} (配置项数量: {})",
deviceByNo.getName(),
deviceByNo.getConfig().size());
} catch (Exception e) {
log.warn("未找到设备编号 CAM001: {}", e.getMessage());
}
}
/**
* 错误处理和故障恢复示例
*/
public void errorHandlingExample() {
log.info("=== 错误处理和故障恢复示例 ===");
// 1. 尝试获取不存在的设备
try {
deviceService.getDevice(99999L);
} catch (Exception e) {
log.warn("获取不存在设备的预期错误: {}", e.getMessage());
}
// 2. 尝试获取不存在的配置
try {
deviceConfigService.getDeviceConfigByKey(1L, "non_existent_key");
} catch (Exception e) {
log.warn("获取不存在配置的预期错误: {}", e.getMessage());
}
// 3. 创建设备时的参数验证
try {
CreateDeviceRequest invalidRequest = new CreateDeviceRequest();
// 缺少必要字段
deviceService.createDevice(invalidRequest);
} catch (Exception e) {
log.warn("创建设备参数错误: {}", e.getMessage());
}
}
/**
* 运行所有示例
*/
public void runAllExamples() {
try {
basicDeviceOperations();
deviceConfigurationOperations();
cameraManagementExample();
deviceMonitoringExample();
errorHandlingExample();
log.info("=== 所有示例执行完成 ===");
} catch (Exception e) {
log.error("示例执行过程中发生错误", e);
}
}
}

View File

@@ -0,0 +1,188 @@
package com.ycwl.basic.integration.device.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.device.client.DeviceConfigV2Client;
import com.ycwl.basic.integration.device.dto.config.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceConfigIntegrationService {
private final DeviceConfigV2Client deviceConfigV2Client;
public List<DeviceConfigV2DTO> getDeviceConfigs(Long deviceId) {
log.info("获取设备配置列表, deviceId: {}", deviceId);
CommonResponse<List<DeviceConfigV2DTO>> response = deviceConfigV2Client.getDeviceConfigs(deviceId);
return handleResponse(response, "获取设备配置列表失败");
}
public List<DeviceConfigV2DTO> getDeviceConfigsByNo(String deviceNo) {
log.info("根据设备编号获取配置列表, deviceNo: {}", deviceNo);
CommonResponse<List<DeviceConfigV2DTO>> response = deviceConfigV2Client.getDeviceConfigsByNo(deviceNo);
return handleResponse(response, "根据设备编号获取配置列表失败");
}
public DeviceConfigV2DTO getDeviceConfigByKey(Long deviceId, String configKey) {
log.info("根据键获取设备配置, deviceId: {}, configKey: {}", deviceId, configKey);
CommonResponse<DeviceConfigV2DTO> response = deviceConfigV2Client.getDeviceConfigByKey(deviceId, configKey);
return handleResponse(response, "根据键获取设备配置失败");
}
public Map<String, Object> getDeviceFlatConfig(Long deviceId) {
log.info("获取设备扁平化配置, deviceId: {}", deviceId);
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId);
return handleResponse(response, "获取设备扁平化配置失败");
}
public Map<String, Object> getDeviceFlatConfigByNo(String deviceNo) {
log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo);
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo);
return handleResponse(response, "根据设备编号获取扁平化配置失败");
}
public DeviceConfigV2DTO createDeviceConfig(Long deviceId, CreateDeviceConfigRequest request) {
log.info("创建设备配置, deviceId: {}, configKey: {}", deviceId, request.getConfigKey());
CommonResponse<DeviceConfigV2DTO> response = deviceConfigV2Client.createDeviceConfig(deviceId, request);
return handleResponse(response, "创建设备配置失败");
}
public void updateDeviceConfig(Long deviceId, Long configId, UpdateDeviceConfigRequest request) {
log.info("更新设备配置, deviceId: {}, configId: {}", deviceId, configId);
CommonResponse<String> response = deviceConfigV2Client.updateDeviceConfig(deviceId, configId, request);
handleResponse(response, "更新设备配置失败");
}
public void deleteDeviceConfig(Long deviceId, Long configId) {
log.info("删除设备配置, deviceId: {}, configId: {}", deviceId, configId);
CommonResponse<String> response = deviceConfigV2Client.deleteDeviceConfig(deviceId, configId);
handleResponse(response, "删除设备配置失败");
}
public void batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) {
log.info("批量更新设备配置, deviceId: {}, configs count: {}", deviceId, request.getConfigs().size());
CommonResponse<String> response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request);
handleResponse(response, "批量更新设备配置失败");
}
public void batchFlatUpdateDeviceConfig(Long deviceId, Map<String, Object> configs) {
log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", deviceId, configs.size());
CommonResponse<String> response = deviceConfigV2Client.batchFlatUpdateDeviceConfig(deviceId, configs);
handleResponse(response, "扁平化批量更新设备配置失败");
}
/**
* 设置设备IP地址
*/
public void setDeviceIpAddress(Long deviceId, String ipAddress) {
Map<String, Object> config = new HashMap<>();
config.put("ip_address", ipAddress);
batchFlatUpdateDeviceConfig(deviceId, config);
}
/**
* 设置设备分辨率
*/
public void setDeviceResolution(Long deviceId, String resolution) {
Map<String, Object> config = new HashMap<>();
config.put("resolution", resolution);
batchFlatUpdateDeviceConfig(deviceId, config);
}
/**
* 设置设备帧率
*/
public void setDeviceFramerate(Long deviceId, Integer framerate) {
Map<String, Object> config = new HashMap<>();
config.put("framerate", framerate.toString());
batchFlatUpdateDeviceConfig(deviceId, config);
}
/**
* 设置设备协议
*/
public void setDeviceProtocol(Long deviceId, String protocol) {
Map<String, Object> config = new HashMap<>();
config.put("protocol", protocol);
batchFlatUpdateDeviceConfig(deviceId, config);
}
/**
* 设置设备认证信息
*/
public void setDeviceAuth(Long deviceId, String username, String password) {
Map<String, Object> config = new HashMap<>();
config.put("username", username);
config.put("password", password);
batchFlatUpdateDeviceConfig(deviceId, config);
}
/**
* 配置摄像头基本参数
*/
public void configureCameraBasicParams(Long deviceId, String ipAddress, String resolution,
Integer framerate, String protocol) {
Map<String, Object> configs = new HashMap<>();
configs.put("ip_address", ipAddress);
configs.put("resolution", resolution);
configs.put("framerate", framerate.toString());
configs.put("protocol", protocol);
batchFlatUpdateDeviceConfig(deviceId, configs);
}
/**
* 配置摄像头完整参数
*/
public void configureCameraFullParams(Long deviceId, String ipAddress, String resolution,
Integer framerate, String protocol, String username, String password) {
Map<String, Object> configs = new HashMap<>();
configs.put("ip_address", ipAddress);
configs.put("resolution", resolution);
configs.put("framerate", framerate.toString());
configs.put("protocol", protocol);
configs.put("username", username);
configs.put("password", password);
batchFlatUpdateDeviceConfig(deviceId, configs);
}
/**
* 获取设备特定配置值
*/
public String getDeviceConfigValue(Long deviceId, String configKey) {
DeviceConfigV2DTO config = getDeviceConfigByKey(deviceId, configKey);
return config != null ? config.getConfigValue() : null;
}
/**
* 获取设备IP地址
*/
public String getDeviceIpAddress(Long deviceId) {
return getDeviceConfigValue(deviceId, "ip_address");
}
/**
* 获取设备分辨率
*/
public String getDeviceResolution(Long deviceId) {
return getDeviceConfigValue(deviceId, "resolution");
}
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.isSuccess()) {
String msg = response != null && response.getMessage() != null
? response.getMessage()
: errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, "zt-device");
}
return response.getData();
}
}

View File

@@ -0,0 +1,146 @@
package com.ycwl.basic.integration.device.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.device.client.DeviceV2Client;
import com.ycwl.basic.integration.device.dto.device.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceIntegrationService {
private final DeviceV2Client deviceV2Client;
public DeviceV2DTO getDevice(Long deviceId) {
log.info("获取设备信息, deviceId: {}", deviceId);
CommonResponse<DeviceV2DTO> response = deviceV2Client.getDevice(deviceId);
return handleResponse(response, "获取设备信息失败");
}
public DeviceV2DTO getDeviceByNo(String deviceNo) {
log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo);
CommonResponse<DeviceV2DTO> response = deviceV2Client.getDeviceByNo(deviceNo);
return handleResponse(response, "根据设备编号获取设备信息失败");
}
public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) {
log.info("获取设备配置信息, deviceId: {}", deviceId);
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceWithConfig(deviceId);
return handleResponse(response, "获取设备配置信息失败");
}
public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) {
log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo);
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceByNoWithConfig(deviceNo);
return handleResponse(response, "根据设备编号获取设备配置信息失败");
}
public DeviceV2DTO createDevice(CreateDeviceRequest request) {
log.info("创建设备, name: {}, no: {}, type: {}", request.getName(), request.getNo(), request.getType());
CommonResponse<DeviceV2DTO> response = deviceV2Client.createDevice(request);
return handleResponse(response, "创建设备失败");
}
public void updateDevice(Long deviceId, UpdateDeviceRequest request) {
log.info("更新设备信息, deviceId: {}", deviceId);
CommonResponse<String> response = deviceV2Client.updateDevice(deviceId, request);
handleResponse(response, "更新设备信息失败");
}
public void deleteDevice(Long deviceId) {
log.info("删除设备, deviceId: {}", deviceId);
CommonResponse<String> response = deviceV2Client.deleteDevice(deviceId);
handleResponse(response, "删除设备失败");
}
public DeviceV2ListResponse listDevices(Integer page, Integer pageSize, String name, String no,
String type, Integer isActive, Long scenicId) {
log.info("分页查询设备列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
page, pageSize, name, no, type, isActive, scenicId);
CommonResponse<DeviceV2ListResponse> response = deviceV2Client.listDevices(
page, pageSize, name, no, type, isActive, scenicId);
return handleResponse(response, "分页查询设备列表失败");
}
public DeviceV2WithConfigListResponse listDevicesWithConfig(Integer page, Integer pageSize, String name, String no,
String type, Integer isActive, Long scenicId) {
log.info("分页查询设备带配置列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
page, pageSize, name, no, type, isActive, scenicId);
CommonResponse<DeviceV2WithConfigListResponse> response = deviceV2Client.listDevicesWithConfig(
page, pageSize, name, no, type, isActive, scenicId);
return handleResponse(response, "分页查询设备带配置列表失败");
}
/**
* 创建IPC摄像头设备
*/
public DeviceV2DTO createIpcDevice(String name, String deviceNo, Long scenicId) {
CreateDeviceRequest request = new CreateDeviceRequest();
request.setName(name);
request.setNo(deviceNo);
request.setType("IPC");
request.setIsActive(1);
request.setScenicId(scenicId);
return createDevice(request);
}
/**
* 创建自定义设备
*/
public DeviceV2DTO createCustomDevice(String name, String deviceNo, Long scenicId) {
CreateDeviceRequest request = new CreateDeviceRequest();
request.setName(name);
request.setNo(deviceNo);
request.setType("CUSTOM");
request.setIsActive(1);
request.setScenicId(scenicId);
return createDevice(request);
}
/**
* 启用设备
*/
public void enableDevice(Long deviceId) {
UpdateDeviceRequest request = new UpdateDeviceRequest();
request.setIsActive(1);
updateDevice(deviceId, request);
}
/**
* 禁用设备
*/
public void disableDevice(Long deviceId) {
UpdateDeviceRequest request = new UpdateDeviceRequest();
request.setIsActive(0);
updateDevice(deviceId, request);
}
/**
* 获取景区的IPC设备列表
*/
public DeviceV2ListResponse getScenicIpcDevices(Long scenicId, Integer page, Integer pageSize) {
return listDevices(page, pageSize, null, null, "IPC", 1, scenicId);
}
/**
* 获取景区的所有激活设备
*/
public DeviceV2ListResponse getScenicActiveDevices(Long scenicId, Integer page, Integer pageSize) {
return listDevices(page, pageSize, null, null, null, 1, scenicId);
}
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
if (response == null || !response.isSuccess()) {
String msg = response != null && response.getMessage() != null
? response.getMessage()
: errorMessage;
Integer code = response != null ? response.getCode() : 5000;
throw new IntegrationException(code, msg, "zt-device");
}
return response.getData();
}
}