From 933818d458be7e023814acab466f2a8021fb9cb2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 5 Sep 2025 14:49:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(device):=20=E6=B7=BB=E5=8A=A0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DefaultConfigClient接口,用于与设备微服务进行默认配置相关的操作 - 实现 DefaultConfigIntegrationService 类,提供默认配置管理的高阶服务- 添加批量配置请求构建器 BatchDefaultConfigRequestBuilder,简化批量操作 - 新增 DefaultConfigIntegrationExample 示例类,演示默认配置管理的使用方法 - 更新 CLAUDE.md 文档,增加默认配置管理的详细使用说明和示例代码 --- .../java/com/ycwl/basic/integration/CLAUDE.md | 368 ++++++++++++++++++ .../device/client/DefaultConfigClient.java | 54 +++ .../defaults/BatchDefaultConfigRequest.java | 18 + .../defaults/BatchDefaultConfigResponse.java | 23 ++ .../dto/defaults/DefaultConfigConflict.java | 24 ++ .../dto/defaults/DefaultConfigRequest.java | 26 ++ .../dto/defaults/DefaultConfigResponse.java | 28 ++ .../DefaultConfigIntegrationExample.java | 275 +++++++++++++ .../DefaultConfigIntegrationService.java | 230 +++++++++++ 9 files changed, 1046 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/integration/device/client/DefaultConfigClient.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigConflict.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/example/DefaultConfigIntegrationExample.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/service/DefaultConfigIntegrationService.java diff --git a/src/main/java/com/ycwl/basic/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md index 32f8db0..0047ced 100644 --- a/src/main/java/com/ycwl/basic/integration/CLAUDE.md +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -227,10 +227,12 @@ fallbackService.clearAllFallbackCache("zt-scenic"); #### Feign Clients - **DeviceV2Client**: Main device operations (CRUD, filtering, listing) - **DeviceConfigV2Client**: Device configuration management +- **DefaultConfigClient**: Default configuration management #### Services - **DeviceIntegrationService**: High-level device operations (with automatic fallback) - **DeviceConfigIntegrationService**: Device configuration management (with automatic fallback) +- **DefaultConfigIntegrationService**: Default configuration management (with automatic fallback) #### Configuration ```yaml @@ -377,6 +379,355 @@ fallbackService.clearFallbackCache("zt-device", "device:1001"); fallbackService.clearAllFallbackCache("zt-device"); ``` +## Default Configuration Management (ZT-Device Microservice) + +### Key Components + +#### Default Configuration API +The zt-device microservice provides a comprehensive default configuration management API at `/api/device/config/v2/defaults` that allows: +- Creating and managing default configuration templates +- Batch operations with conflict detection +- Configuration type validation and enforcement +- Usage tracking and analytics + +#### Feign Client +- **DefaultConfigClient**: Default configuration management operations + +#### Service +- **DefaultConfigIntegrationService**: High-level default configuration operations (with automatic fallback for queries) + +### Usage Examples + +#### Basic Default Configuration Operations (with Automatic Fallback) +```java +@Autowired +private DefaultConfigIntegrationService defaultConfigService; + +// Get default configuration list (automatically falls back to cache on failure) +PageResponse configList = defaultConfigService.listDefaultConfigs(1, 10); +log.info("Default configs: total={}, current page={}", + configList.getData().getTotal(), configList.getData().getList().size()); + +// Get specific default configuration (automatically falls back to cache on failure) +DefaultConfigResponse config = defaultConfigService.getDefaultConfig("resolution"); +if (config != null) { + log.info("Default resolution: {}", config.getConfigValue()); +} + +// Create new default configuration (direct operation, fails immediately on error) +DefaultConfigRequest newConfig = new DefaultConfigRequest(); +newConfig.setConfigKey("bitrate"); +newConfig.setConfigValue("2000000"); +newConfig.setConfigType("int"); +newConfig.setDescription("Default video bitrate in bps"); +boolean created = defaultConfigService.createDefaultConfig(newConfig); + +// Update default configuration with conflict detection (direct operation) +Map updates = new HashMap<>(); +updates.put("configValue", "4000000"); +updates.put("description", "Updated default bitrate - high quality"); +DefaultConfigConflict conflict = defaultConfigService.updateDefaultConfig("bitrate", updates); + +if (conflict != null) { + log.warn("Configuration update conflict detected:"); + log.warn(" Key: {}, Type: {}, Affected devices: {}", + conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount()); + log.warn(" Current type: {}, Proposed type: {}", + conflict.getCurrentType(), conflict.getProposedType()); + + // Handle conflict - show affected device IDs + if (conflict.getConflictDevices() != null) { + log.warn(" Conflicted device IDs: {}", conflict.getConflictDevices()); + } +} else { + log.info("Configuration updated successfully without conflicts"); +} + +// Delete default configuration (direct operation, fails immediately on error) +boolean deleted = defaultConfigService.deleteDefaultConfig("bitrate"); +``` + +#### Batch Default Configuration Operations +```java +// Using Builder Pattern for batch operations +BatchDefaultConfigRequest batchRequest = defaultConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 30, "H265") // Add video configuration group + .addNetworkConfig("192.168.1.100", 554, "RTSP") // Add network configuration group + .addConfig("recording_enabled", "true", "bool", "Enable recording by default") + .addConfig("storage_retention", "30", "int", "Storage retention in days") + .addConfig("quality_profile", "high", "string", "Default video quality profile") + .build(); + +// Execute batch update (direct operation, no fallback) +BatchDefaultConfigResponse result = defaultConfigService.batchUpdateDefaultConfigs(batchRequest); + +// Process batch results +log.info("Batch operation completed: {} success, {} failed", result.getSuccess(), result.getFailed()); + +// Handle conflicts +if (result.getConflicts() != null && !result.getConflicts().isEmpty()) { + log.warn("Configuration conflicts detected:"); + for (DefaultConfigConflict conflict : result.getConflicts()) { + log.warn(" Key: {}, Type: {}, Devices affected: {}", + conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount()); + } +} + +// Review processed items +if (result.getProcessedItems() != null) { + result.getProcessedItems().forEach(item -> { + if ("success".equals(item.getStatus())) { + log.info("✅ {} {} successfully (type: {})", + item.getConfigKey(), item.getAction(), item.getFinalType()); + } else { + log.warn("❌ {} failed: {}", item.getConfigKey(), item.getMessage()); + } + }); +} + +// Handle errors +if (result.getErrors() != null && !result.getErrors().isEmpty()) { + result.getErrors().forEach(error -> log.error("Batch operation error: {}", error)); +} +``` + +#### Device Type Specific Default Configuration Patterns +```java +// Create IPC camera default configurations +BatchDefaultConfigRequest ipcDefaults = defaultConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 25, "H264") // Standard HD resolution + .addConfig("night_vision", "true", "bool", "Enable night vision") + .addConfig("motion_detection", "true", "bool", "Enable motion detection") + .addConfig("stream_profile", "main", "string", "Default stream profile") + .addConfig("ptz_enabled", "false", "bool", "PTZ control enabled") + .addConfig("audio_enabled", "true", "bool", "Audio recording enabled") + .build(); + +// Create NVR default configurations +BatchDefaultConfigRequest nvrDefaults = defaultConfigService.createBatchConfigBuilder() + .addConfig("max_channels", "16", "int", "Maximum supported channels") + .addConfig("storage_mode", "continuous", "string", "Recording storage mode") + .addConfig("backup_enabled", "true", "bool", "Enable automatic backup") + .addConfig("raid_level", "5", "int", "Default RAID level") + .addConfig("disk_quota", "80", "int", "Disk usage quota percentage") + .build(); + +// Apply device-type-specific defaults +BatchDefaultConfigResponse ipcResult = defaultConfigService.batchUpdateDefaultConfigs(ipcDefaults); +BatchDefaultConfigResponse nvrResult = defaultConfigService.batchUpdateDefaultConfigs(nvrDefaults); + +log.info("IPC defaults: {} success, {} failed", ipcResult.getSuccess(), ipcResult.getFailed()); +log.info("NVR defaults: {} success, {} failed", nvrResult.getSuccess(), nvrResult.getFailed()); +``` + +#### Configuration Validation and Management Patterns +```java +// Validate system configuration completeness +PageResponse allDefaults = defaultConfigService.listDefaultConfigs(1, 100); + +// Check for required default configurations +String[] requiredDefaults = { + "resolution", "frameRate", "codec", "bitrate", + "protocol", "port", "username", "password" +}; + +Map configStatus = new HashMap<>(); +for (String required : requiredDefaults) { + boolean exists = allDefaults.getData().getList().stream() + .anyMatch(config -> required.equals(config.getConfigKey())); + configStatus.put(required, exists); + + if (!exists) { + log.warn("Missing required default configuration: {}", required); + } +} + +// Generate configuration completeness report +long totalConfigs = allDefaults.getData().getTotal(); +long completeConfigs = configStatus.values().stream().mapToLong(exists -> exists ? 1 : 0).sum(); +double completeness = (double) completeConfigs / requiredDefaults.length * 100; + +log.info("Configuration completeness: {:.1f}% ({}/{} required configs present)", + completeness, completeConfigs, requiredDefaults.length); +log.info("Total default configurations in system: {}", totalConfigs); + +// Analyze configuration type distribution +Map typeDistribution = allDefaults.getData().getList().stream() + .collect(Collectors.groupingBy( + DefaultConfigResponse::getConfigType, + Collectors.counting() + )); + +log.info("Configuration type distribution: {}", typeDistribution); + +// Find most and least used configurations +DefaultConfigResponse mostUsed = allDefaults.getData().getList().stream() + .max(Comparator.comparing(DefaultConfigResponse::getUsageCount)) + .orElse(null); + +DefaultConfigResponse leastUsed = allDefaults.getData().getList().stream() + .min(Comparator.comparing(DefaultConfigResponse::getUsageCount)) + .orElse(null); + +if (mostUsed != null) { + log.info("Most used default config: {} (used {} times)", + mostUsed.getConfigKey(), mostUsed.getUsageCount()); +} +if (leastUsed != null) { + log.info("Least used default config: {} (used {} times)", + leastUsed.getConfigKey(), leastUsed.getUsageCount()); +} +``` + +#### Advanced Batch Configuration with Error Handling +```java +// Complex batch operation with comprehensive error handling +try { + BatchDefaultConfigRequest complexBatch = defaultConfigService.createBatchConfigBuilder() + .addVideoConfig("3840x2160", 60, "H265") // 4K configuration + .addConfig("hdr_enabled", "true", "bool", "HDR video support") + .addConfig("ai_analysis", "enabled", "string", "AI analysis features") + .addConfig("edge_processing", "true", "bool", "Edge computing enabled") + .addConfig("cloud_sync", "auto", "string", "Cloud synchronization mode") + .build(); + + BatchDefaultConfigResponse result = defaultConfigService.batchUpdateDefaultConfigs(complexBatch); + + // Detailed result analysis + if (result.getSuccess() == complexBatch.getConfigs().size()) { + log.info("✅ All {} configurations processed successfully", result.getSuccess()); + } else if (result.getSuccess() > 0) { + log.warn("⚠️ Partial success: {} succeeded, {} failed", result.getSuccess(), result.getFailed()); + + // Analyze what succeeded vs failed + if (result.getProcessedItems() != null) { + Map statusCounts = result.getProcessedItems().stream() + .collect(Collectors.groupingBy( + ProcessedConfigItem::getStatus, + Collectors.counting() + )); + log.info("Processing breakdown: {}", statusCounts); + } + } else { + log.error("❌ All configurations failed to process"); + } + + // Handle different types of conflicts + if (result.getConflicts() != null) { + Map> conflictsByType = result.getConflicts().stream() + .collect(Collectors.groupingBy(DefaultConfigConflict::getConflictType)); + + conflictsByType.forEach((conflictType, conflicts) -> { + log.warn("Conflict type '{}' affects {} configurations:", conflictType, conflicts.size()); + conflicts.forEach(conflict -> + log.warn(" {} affects {} devices", conflict.getConfigKey(), conflict.getDeviceCount()) + ); + }); + } + +} catch (Exception e) { + log.error("Batch default configuration operation failed", e); + // Implement retry logic or fallback behavior as needed +} +``` + +#### Fallback Cache Management for Default Configurations +```java +@Autowired +private IntegrationFallbackService fallbackService; + +// Check fallback cache status for default configurations +boolean hasDefaultListCache = fallbackService.hasFallbackCache("zt-device", "defaults:list:1:10"); +boolean hasSpecificConfigCache = fallbackService.hasFallbackCache("zt-device", "defaults:config:resolution"); + +// Get cache statistics +IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-device"); +log.info("Default config fallback cache: {} items, TTL: {} days", + stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + +// Clear specific default configuration cache +fallbackService.clearFallbackCache("zt-device", "defaults:config:resolution"); + +// Clear all default configuration caches +fallbackService.clearAllFallbackCache("zt-device"); +``` + +### Default Configuration Types and Common Keys + +#### Video Configuration Defaults +- `resolution`: Video resolution ("1920x1080", "3840x2160", "1280x720") +- `frameRate`: Video frame rate (integer: 15, 25, 30, 60) +- `codec`: Video codec ("H264", "H265", "MJPEG") +- `bitrate`: Video bitrate in bps (integer) +- `quality`: Video quality level ("low", "medium", "high", "ultra") + +#### Network Configuration Defaults +- `protocol`: Network protocol ("RTSP", "HTTP", "ONVIF") +- `port`: Network port (integer: 554, 80, 8080) +- `timeout`: Connection timeout in seconds (integer) +- `retry_count`: Maximum retry attempts (integer) + +#### Authentication Defaults +- `username`: Default username (string) +- `password`: Default password (string) +- `auth_method`: Authentication method ("basic", "digest", "none") + +#### Storage and Recording Defaults +- `storage_path`: Default storage path (string) +- `recording_enabled`: Enable recording (boolean) +- `retention_days`: Storage retention period (integer) +- `max_file_size`: Maximum file size in MB (integer) + +#### Feature Control Defaults +- `motion_detection`: Enable motion detection (boolean) +- `night_vision`: Enable night vision (boolean) +- `audio_enabled`: Enable audio recording (boolean) +- `ptz_enabled`: Enable PTZ control (boolean) +- `ai_analysis`: AI analysis features ("enabled", "disabled", "auto") + +### Error Handling and Responses + +#### HTTP Status Codes +- **200 OK**: Configuration retrieved/updated successfully +- **201 Created**: New configuration created successfully +- **202 Accepted**: Operation completed with conflicts (check response for details) +- **400 Bad Request**: Invalid request parameters or validation errors +- **404 Not Found**: Configuration key not found +- **409 Conflict**: Configuration conflicts prevent operation (includes conflict details) + +#### Conflict Resolution +When updating default configurations that are actively used by devices, the API performs conflict detection: + +1. **Type Conflicts**: Changing configuration type when devices use different types +2. **Value Validation**: Ensuring new values are compatible with existing device configurations +3. **Dependency Conflicts**: Checking for configuration dependencies and relationships + +The response includes detailed conflict information to help resolve issues: +```java +// Example conflict handling +DefaultConfigConflict conflict = updateResult; +if (conflict != null) { + switch (conflict.getConflictType()) { + case "TYPE_MISMATCH": + log.warn("Type conflict: {} devices using '{}' type, proposed '{}'type", + conflict.getDeviceCount(), conflict.getCurrentType(), conflict.getProposedType()); + // Handle type conversion or device updates + break; + + case "VALUE_RANGE": + log.warn("Value range conflict for key '{}' affects {} devices", + conflict.getConfigKey(), conflict.getDeviceCount()); + // Handle value range adjustments + break; + + case "DEPENDENCY": + log.warn("Dependency conflict detected for '{}'", conflict.getConfigKey()); + // Handle dependency resolution + break; + } +} +``` + ### Enhanced Batch Configuration API Device Integration now supports an enhanced batch configuration API that provides detailed processing results and supports default configuration rules. @@ -432,6 +783,23 @@ Each processed item includes: - **IPC**: IP Camera devices for video monitoring - **CUSTOM**: Custom device types for sensors, controllers, etc. +### Testing Default Configuration Integration + +```bash +# Run default configuration integration tests +mvn test -Dtest=DefaultConfigIntegrationServiceTest + +# Run all device integration tests (including default configs) +mvn test -Dtest="com.ycwl.basic.integration.device.*Test" + +# Enable example runner in application-dev.yml +integration: + device: + example: + default-config: + enabled: true +``` + ### Common Configuration Keys - `ip_address`: Device IP address - `resolution`: Video resolution (e.g., "1920x1080", "3840x2160") diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DefaultConfigClient.java b/src/main/java/com/ycwl/basic/integration/device/client/DefaultConfigClient.java new file mode 100644 index 0000000..f1571c1 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/client/DefaultConfigClient.java @@ -0,0 +1,54 @@ +package com.ycwl.basic.integration.device.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.response.PageResponse; +import com.ycwl.basic.integration.device.dto.defaults.*; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 默认配置管理 Feign 客户端 + */ +@FeignClient(name = "zt-device", contextId = "device-default-config", path = "/api/device/config/v2/defaults") +public interface DefaultConfigClient { + + /** + * 获取默认配置列表 + */ + @GetMapping + PageResponse listDefaultConfigs(@RequestParam(value = "page", defaultValue = "1") int page, + @RequestParam(value = "pageSize", defaultValue = "10") int pageSize); + + /** + * 根据配置键获取默认配置 + */ + @GetMapping("/{configKey}") + CommonResponse getDefaultConfig(@PathVariable("configKey") String configKey); + + /** + * 创建默认配置 + */ + @PostMapping + CommonResponse createDefaultConfig(@RequestBody DefaultConfigRequest request); + + /** + * 更新默认配置 + */ + @PutMapping("/{configKey}") + CommonResponse> updateDefaultConfig(@PathVariable("configKey") String configKey, + @RequestBody Map updates); + + /** + * 删除默认配置 + */ + @DeleteMapping("/{configKey}") + CommonResponse deleteDefaultConfig(@PathVariable("configKey") String configKey); + + /** + * 批量更新默认配置 + */ + @PostMapping("/batch") + CommonResponse batchUpdateDefaultConfigs(@RequestBody BatchDefaultConfigRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigRequest.java new file mode 100644 index 0000000..081e88e --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigRequest.java @@ -0,0 +1,18 @@ +package com.ycwl.basic.integration.device.dto.defaults; + +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** + * 批量默认配置操作请求模型 + */ +@Data +public class BatchDefaultConfigRequest { + + @NotEmpty(message = "配置列表不能为空") + @Valid + private List configs; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigResponse.java b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigResponse.java new file mode 100644 index 0000000..39ac042 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/BatchDefaultConfigResponse.java @@ -0,0 +1,23 @@ +package com.ycwl.basic.integration.device.dto.defaults; + +import com.ycwl.basic.integration.device.dto.config.ProcessedConfigItem; +import lombok.Data; + +import java.util.List; + +/** + * 批量默认配置操作响应模型 + */ +@Data +public class BatchDefaultConfigResponse { + + private Integer success; + + private Integer failed; + + private List conflicts; + + private List processedItems; + + private List errors; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigConflict.java b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigConflict.java new file mode 100644 index 0000000..7178b45 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigConflict.java @@ -0,0 +1,24 @@ +package com.ycwl.basic.integration.device.dto.defaults; + +import lombok.Data; + +import java.util.List; + +/** + * 冲突信息模型 + */ +@Data +public class DefaultConfigConflict { + + private String configKey; + + private String conflictType; + + private Integer deviceCount; + + private String currentType; + + private String proposedType; + + private List conflictDevices; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigRequest.java new file mode 100644 index 0000000..59e49c5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigRequest.java @@ -0,0 +1,26 @@ +package com.ycwl.basic.integration.device.dto.defaults; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 默认配置请求模型 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DefaultConfigRequest { + + @NotBlank(message = "配置键不能为空") + private String configKey; + + @NotBlank(message = "配置值不能为空") + private String configValue; + + @NotBlank(message = "配置类型不能为空") + private String configType; + + @NotBlank(message = "配置描述不能为空") + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigResponse.java b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigResponse.java new file mode 100644 index 0000000..586c8ac --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/defaults/DefaultConfigResponse.java @@ -0,0 +1,28 @@ +package com.ycwl.basic.integration.device.dto.defaults; + +import lombok.Data; + +/** + * 默认配置响应模型 + */ +@Data +public class DefaultConfigResponse { + + private String id; + + private String configKey; + + private String configValue; + + private String configType; + + private String description; + + private Integer isActive; + + private String createTime; + + private String updateTime; + + private Integer usageCount; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DefaultConfigIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DefaultConfigIntegrationExample.java new file mode 100644 index 0000000..c39b2b9 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/example/DefaultConfigIntegrationExample.java @@ -0,0 +1,275 @@ +package com.ycwl.basic.integration.device.example; + +import com.ycwl.basic.integration.common.response.PageResponse; +import com.ycwl.basic.integration.device.dto.defaults.*; +import com.ycwl.basic.integration.device.service.DefaultConfigIntegrationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 默认配置集成服务使用示例 + * + * 通过在 application.yml 中设置 integration.device.example.default-config.enabled=true 来启用 + */ +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(name = "integration.device.example.default-config.enabled", havingValue = "true") +public class DefaultConfigIntegrationExample implements CommandLineRunner { + + private final DefaultConfigIntegrationService defaultConfigService; + + @Override + public void run(String... args) throws Exception { + log.info("=== 默认配置集成服务使用示例 ==="); + + try { + // 1. 基础查询操作示例(支持自动 Fallback) + basicQueryExamples(); + + // 2. 配置管理操作示例(直接操作) + configManagementExamples(); + + // 3. 批量操作示例 + batchOperationExamples(); + + // 4. 高级使用模式示例 + advancedUsageExamples(); + + } catch (Exception e) { + log.error("默认配置集成示例执行失败", e); + } + } + + /** + * 基础查询操作示例(支持自动 Fallback) + */ + private void basicQueryExamples() { + log.info("--- 基础查询操作示例(支持自动 Fallback)---"); + + try { + // 获取默认配置列表(自动缓存,服务不可用时返回缓存数据) + PageResponse configList = defaultConfigService.listDefaultConfigs(1, 10); + log.info("默认配置列表: 总数={}, 当前页配置数={}", + configList.getData().getTotal(), configList.getData().getList().size()); + + // 显示配置详情 + for (DefaultConfigResponse config : configList.getData().getList()) { + log.info("配置详情: key={}, value={}, type={}, description={}", + config.getConfigKey(), config.getConfigValue(), + config.getConfigType(), config.getDescription()); + + // 获取单个配置详情(自动缓存,服务不可用时返回缓存数据) + DefaultConfigResponse detailConfig = defaultConfigService.getDefaultConfig(config.getConfigKey()); + if (detailConfig != null) { + log.info("配置详情获取成功: usageCount={}, isActive={}", + detailConfig.getUsageCount(), detailConfig.getIsActive()); + } + break; // 只展示第一个 + } + + } catch (Exception e) { + log.error("基础查询操作失败", e); + } + } + + /** + * 配置管理操作示例(直接操作) + */ + private void configManagementExamples() { + log.info("--- 配置管理操作示例(直接操作)---"); + + String testConfigKey = "example_test_config"; + + try { + // 1. 创建默认配置(直接操作,失败时立即报错) + DefaultConfigRequest createRequest = new DefaultConfigRequest(); + createRequest.setConfigKey(testConfigKey); + createRequest.setConfigValue("1920x1080"); + createRequest.setConfigType("string"); + createRequest.setDescription("示例测试配置 - 默认分辨率"); + + boolean createResult = defaultConfigService.createDefaultConfig(createRequest); + log.info("创建默认配置结果: {}", createResult ? "成功" : "失败"); + + // 2. 更新默认配置(直接操作,可能返回冲突信息) + Map updates = new HashMap<>(); + updates.put("configValue", "3840x2160"); + updates.put("description", "更新后的默认分辨率 - 4K"); + + DefaultConfigConflict conflict = defaultConfigService.updateDefaultConfig(testConfigKey, updates); + if (conflict != null) { + log.warn("更新配置存在冲突: configKey={}, conflictType={}, deviceCount={}", + conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount()); + } else { + log.info("配置更新成功,无冲突"); + } + + // 3. 验证更新结果 + DefaultConfigResponse updatedConfig = defaultConfigService.getDefaultConfig(testConfigKey); + if (updatedConfig != null) { + log.info("更新后配置值: {}", updatedConfig.getConfigValue()); + } + + // 4. 删除测试配置(直接操作) + boolean deleteResult = defaultConfigService.deleteDefaultConfig(testConfigKey); + log.info("删除默认配置结果: {}", deleteResult ? "成功" : "失败"); + + } catch (Exception e) { + log.error("配置管理操作失败", e); + } + } + + /** + * 批量操作示例 + */ + private void batchOperationExamples() { + log.info("--- 批量操作示例 ---"); + + try { + // 1. 使用构建器模式创建批量配置 + BatchDefaultConfigRequest batchRequest = defaultConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 30, "H264") // 添加视频配置组 + .addNetworkConfig("192.168.1.100", 554, "RTSP") // 添加网络配置组 + .addConfig("recording_enabled", "true", "bool", "是否启用录制") + .addConfig("storage_path", "/data/recordings", "string", "录制存储路径") + .addConfig("max_file_size", "1024", "int", "最大文件大小(MB)") + .build(); + + log.info("准备批量创建 {} 个默认配置", batchRequest.getConfigs().size()); + + // 2. 执行批量更新(直接操作) + BatchDefaultConfigResponse batchResult = defaultConfigService.batchUpdateDefaultConfigs(batchRequest); + + // 3. 处理批量结果 + log.info("批量操作结果: 成功={}, 失败={}", batchResult.getSuccess(), batchResult.getFailed()); + + if (batchResult.getConflicts() != null && !batchResult.getConflicts().isEmpty()) { + log.warn("发现 {} 个配置冲突:", batchResult.getConflicts().size()); + for (DefaultConfigConflict conflict : batchResult.getConflicts()) { + log.warn("冲突配置: key={}, type={}, deviceCount={}", + conflict.getConfigKey(), conflict.getConflictType(), conflict.getDeviceCount()); + } + } + + if (batchResult.getProcessedItems() != null) { + log.info("处理详情:"); + batchResult.getProcessedItems().forEach(item -> + log.info(" 配置 {}: status={}, action={}, finalType={}", + item.getConfigKey(), item.getStatus(), item.getAction(), item.getFinalType()) + ); + } + + if (batchResult.getErrors() != null && !batchResult.getErrors().isEmpty()) { + log.error("批量操作错误:"); + batchResult.getErrors().forEach(error -> log.error(" {}", error)); + } + + } catch (Exception e) { + log.error("批量操作失败", e); + } + } + + /** + * 高级使用模式示例 + */ + private void advancedUsageExamples() { + log.info("--- 高级使用模式示例 ---"); + + try { + // 1. 设备类型特定的默认配置模式 + createDeviceTypeSpecificConfigs(); + + // 2. 配置验证和完整性检查模式 + validateConfigCompleteness(); + + // 3. 配置迁移和批量更新模式 + configMigrationPattern(); + + } catch (Exception e) { + log.error("高级使用模式示例失败", e); + } + } + + /** + * 创建设备类型特定的默认配置 + */ + private void createDeviceTypeSpecificConfigs() { + log.info("创建设备类型特定的默认配置..."); + + // IPC摄像头默认配置 + BatchDefaultConfigRequest ipcDefaults = defaultConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 25, "H264") + .addConfig("night_vision", "true", "bool", "夜视功能") + .addConfig("motion_detection", "true", "bool", "移动检测") + .addConfig("stream_profile", "main", "string", "码流类型") + .build(); + + // NVR设备默认配置 + BatchDefaultConfigRequest nvrDefaults = defaultConfigService.createBatchConfigBuilder() + .addConfig("max_channels", "16", "int", "最大通道数") + .addConfig("storage_mode", "continuous", "string", "存储模式") + .addConfig("backup_enabled", "true", "bool", "备份启用") + .build(); + + log.info("IPC默认配置项数: {}, NVR默认配置项数: {}", + ipcDefaults.getConfigs().size(), nvrDefaults.getConfigs().size()); + } + + /** + * 配置验证和完整性检查 + */ + private void validateConfigCompleteness() { + log.info("验证配置完整性..."); + + // 获取所有默认配置 + PageResponse allConfigs = defaultConfigService.listDefaultConfigs(1, 100); + + // 检查必需的基础配置是否存在 + String[] requiredConfigs = {"resolution", "frameRate", "codec", "protocol"}; + for (String requiredConfig : requiredConfigs) { + boolean exists = allConfigs.getData().getList().stream() + .anyMatch(config -> requiredConfig.equals(config.getConfigKey())); + log.info("必需配置 {} 存在: {}", requiredConfig, exists ? "✓" : "✗"); + } + + // 统计配置类型分布 + Map typeDistribution = new HashMap<>(); + allConfigs.getData().getList().forEach(config -> + typeDistribution.merge(config.getConfigType(), 1L, Long::sum) + ); + + log.info("配置类型分布: {}", typeDistribution); + } + + /** + * 配置迁移和批量更新模式 + */ + private void configMigrationPattern() { + log.info("配置迁移模式示例..."); + + try { + // 1. 获取需要升级的配置 + PageResponse oldConfigs = defaultConfigService.listDefaultConfigs(1, 50); + + // 2. 创建升级配置批次 + BatchDefaultConfigRequest upgradeRequest = defaultConfigService.createBatchConfigBuilder() + .addConfig("api_version", "v2", "string", "API版本") + .addConfig("security_mode", "enhanced", "string", "安全模式") + .build(); + + // 3. 执行批量升级 + BatchDefaultConfigResponse upgradeResult = defaultConfigService.batchUpdateDefaultConfigs(upgradeRequest); + log.info("配置升级结果: 成功={}, 失败={}", upgradeResult.getSuccess(), upgradeResult.getFailed()); + + } catch (Exception e) { + log.warn("配置迁移示例执行失败(这是正常的,因为是示例代码)", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DefaultConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DefaultConfigIntegrationService.java new file mode 100644 index 0000000..3a996e7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DefaultConfigIntegrationService.java @@ -0,0 +1,230 @@ +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.common.response.PageResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.device.client.DefaultConfigClient; +import com.ycwl.basic.integration.device.dto.defaults.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * 默认配置集成服务 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DefaultConfigIntegrationService { + + private final DefaultConfigClient defaultConfigClient; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; + + /** + * 获取默认配置列表(支持 Fallback) + */ + public PageResponse listDefaultConfigs(int page, int pageSize) { + return fallbackService.executeWithFallback( + SERVICE_NAME, + "defaults:list:" + page + ":" + pageSize, + () -> { + PageResponse response = defaultConfigClient.listDefaultConfigs(page, pageSize); + return handlePageResponse(response, "获取默认配置列表失败"); + }, + PageResponse.class + ); + } + + /** + * 根据配置键获取默认配置(支持 Fallback) + */ + public DefaultConfigResponse getDefaultConfig(String configKey) { + return fallbackService.executeWithFallback( + SERVICE_NAME, + "defaults:config:" + configKey, + () -> { + log.info("获取默认配置, configKey: {}", configKey); + CommonResponse response = defaultConfigClient.getDefaultConfig(configKey); + return handleResponse(response, "获取默认配置失败"); + }, + DefaultConfigResponse.class + ); + } + + /** + * 创建默认配置(直接操作,不支持 Fallback) + */ + public boolean createDefaultConfig(DefaultConfigRequest request) { + log.info("创建默认配置, configKey: {}", request.getConfigKey()); + CommonResponse response = defaultConfigClient.createDefaultConfig(request); + String result = handleResponse(response, "创建默认配置失败"); + return result != null; + } + + /** + * 更新默认配置(直接操作,不支持 Fallback) + */ + public DefaultConfigConflict updateDefaultConfig(String configKey, Map updates) { + log.info("更新默认配置, configKey: {}, updates: {}", configKey, updates); + CommonResponse> response = defaultConfigClient.updateDefaultConfig(configKey, updates); + Map result = handleResponse(response, "更新默认配置失败"); + + // 检查是否有冲突信息 + if (result != null && result.containsKey("conflict")) { + Object conflictObj = result.get("conflict"); + if (conflictObj instanceof Map) { + // 将Map转换为DefaultConfigConflict对象 + return mapToDefaultConfigConflict((Map) conflictObj); + } + } + + return null; + } + + /** + * 删除默认配置(直接操作,不支持 Fallback) + */ + public boolean deleteDefaultConfig(String configKey) { + log.info("删除默认配置, configKey: {}", configKey); + CommonResponse response = defaultConfigClient.deleteDefaultConfig(configKey); + String result = handleResponse(response, "删除默认配置失败"); + return result != null; + } + + /** + * 批量更新默认配置(直接操作,不支持 Fallback) + */ + public BatchDefaultConfigResponse batchUpdateDefaultConfigs(BatchDefaultConfigRequest request) { + log.info("批量更新默认配置, configs count: {}", request.getConfigs().size()); + CommonResponse response = defaultConfigClient.batchUpdateDefaultConfigs(request); + return handleResponse(response, "批量更新默认配置失败"); + } + + /** + * 创建批量配置请求构建器 + */ + public BatchDefaultConfigRequestBuilder createBatchConfigBuilder() { + return new BatchDefaultConfigRequestBuilder(); + } + + /** + * 批量配置请求构建器 + */ + public static class BatchDefaultConfigRequestBuilder { + private final BatchDefaultConfigRequest request = new BatchDefaultConfigRequest(); + + public BatchDefaultConfigRequestBuilder() { + request.setConfigs(new ArrayList<>()); + } + + /** + * 添加配置项 + */ + public BatchDefaultConfigRequestBuilder addConfig(String configKey, String configValue, + String configType, String description) { + DefaultConfigRequest item = new DefaultConfigRequest(); + item.setConfigKey(configKey); + item.setConfigValue(configValue); + item.setConfigType(configType); + item.setDescription(description); + request.getConfigs().add(item); + return this; + } + + /** + * 添加视频配置 + */ + public BatchDefaultConfigRequestBuilder addVideoConfig(String resolution, Integer frameRate, String codec) { + addConfig("resolution", resolution, "string", "视频分辨率"); + addConfig("frameRate", frameRate.toString(), "int", "视频帧率"); + addConfig("codec", codec, "string", "视频编码格式"); + return this; + } + + /** + * 添加网络配置 + */ + public BatchDefaultConfigRequestBuilder addNetworkConfig(String ipAddress, Integer port, String protocol) { + addConfig("ipAddress", ipAddress, "string", "IP地址"); + addConfig("port", port.toString(), "int", "端口号"); + addConfig("protocol", protocol, "string", "网络协议"); + return this; + } + + /** + * 构建请求对象 + */ + public BatchDefaultConfigRequest build() { + return request; + } + } + + /** + * 将Map转换为DefaultConfigConflict对象 + */ + @SuppressWarnings("unchecked") + private DefaultConfigConflict mapToDefaultConfigConflict(Map map) { + DefaultConfigConflict conflict = new DefaultConfigConflict(); + + if (map.containsKey("configKey")) { + conflict.setConfigKey((String) map.get("configKey")); + } + if (map.containsKey("conflictType")) { + conflict.setConflictType((String) map.get("conflictType")); + } + if (map.containsKey("deviceCount")) { + Object deviceCount = map.get("deviceCount"); + if (deviceCount instanceof Number) { + conflict.setDeviceCount(((Number) deviceCount).intValue()); + } + } + if (map.containsKey("currentType")) { + conflict.setCurrentType((String) map.get("currentType")); + } + if (map.containsKey("proposedType")) { + conflict.setProposedType((String) map.get("proposedType")); + } + if (map.containsKey("conflictDevices")) { + Object conflictDevices = map.get("conflictDevices"); + if (conflictDevices instanceof Iterable) { + conflict.setConflictDevices(new ArrayList<>()); + for (Object deviceId : (Iterable) conflictDevices) { + if (deviceId instanceof Number) { + conflict.getConflictDevices().add(((Number) deviceId).longValue()); + } + } + } + } + + return conflict; + } + + private T handleResponse(CommonResponse 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(); + } + + private PageResponse handlePageResponse(PageResponse 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; + } +} \ No newline at end of file