# 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 - **IntegrationFallbackService**: Universal fallback service for handling integration failures #### 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 - **Render Worker Integration** (`com.ycwl.basic.integration.render`): ZT-Render-Worker microservice integration - **Questionnaire Integration** (`com.ycwl.basic.integration.questionnaire`): ZT-Questionnaire 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 ``` ## Integration Fallback Mechanism ### Overview The integration package includes a comprehensive fallback mechanism that provides automatic degradation when external microservices are unavailable or return errors. This mechanism ensures system resilience and maintains service availability even when dependencies fail. ### Core Components #### IntegrationFallbackService Universal fallback service that handles failure degradation for all microservice integrations: **Key Features:** - **Automatic Fallback**: Transparently handles service failures and returns cached results - **Configurable TTL**: Service-specific cache TTL configuration (default: 7 days) - **Cache Management**: Comprehensive cache statistics, cleanup, and monitoring - **Service Isolation**: Per-service cache namespacing and configuration - **Operation Types**: Supports both query operations (return cached data) and mutation operations (ignore failures with history) **Core Methods:** ```java // Query operations with fallback T executeWithFallback(String serviceName, String cacheKey, Supplier operation, Class resultClass) // Mutation operations with fallback void executeWithFallback(String serviceName, String cacheKey, Runnable operation) // Cache management boolean hasFallbackCache(String serviceName, String cacheKey) void clearFallbackCache(String serviceName, String cacheKey) void clearAllFallbackCache(String serviceName) FallbackCacheStats getFallbackCacheStats(String serviceName) ``` ### Fallback Strategy #### Query Operations (GET methods) - WITH FALLBACK 1. **Normal Execution**: Execute the primary operation 2. **Success Handling**: Store successful result in fallback cache for future degradation 3. **Failure Handling**: On failure, attempt to retrieve cached result from previous success 4. **Final Fallback**: If no cached result exists, propagate the original exception #### Mutation Operations (PUT/POST/DELETE methods) - NO FALLBACK 1. **Direct Execution**: Execute the primary operation directly without fallback 2. **Success Handling**: Operation completes successfully 3. **Failure Handling**: Immediately propagate the original exception to caller 4. **Rationale**: Users need to know if their create/update/delete operations truly succeeded or failed ### Configuration #### Properties Structure ```yaml integration: fallback: enabled: true # Global fallback switch cachePrefix: "integration:fallback:" # Global cache prefix defaultTtlDays: 7 # Default cache TTL in days # Service-specific configurations scenic: enabled: true # Service-specific fallback switch ttlDays: 10 # Custom TTL for scenic service cachePrefix: "scenic:fallback:" # Custom cache prefix (optional) device: enabled: true ttlDays: 5 # Custom TTL for device service ``` #### Configuration Priority 1. **Service-specific settings**: Override global defaults when specified 2. **Global defaults**: Used when service-specific settings are not provided 3. **Hardcoded defaults**: Final fallback when no configuration is available ### Cache Key Strategy Cache keys follow a standardized naming convention: ``` {cachePrefix}{serviceName}:{operationType}:{resourceId} ``` **Examples:** - `integration:fallback:zt-device:device:1001` - Device info cache - `integration:fallback:zt-device:device:config:1001` - Device config cache - `integration:fallback:zt-scenic:scenic:2001` - Scenic info cache - `integration:fallback:zt-scenic:scenic:flat:config:2001` - Scenic flat config cache ## 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 (with automatic fallback) - **ScenicConfigIntegrationService**: Configuration management (with automatic fallback) - **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 (with Automatic Fallback) ```java @Autowired private ScenicIntegrationService scenicService; // Get scenic with configuration (automatically falls back to cache on failure) ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId); // Get scenic basic info (automatically falls back to cache on failure) ScenicV2DTO scenicInfo = scenicService.getScenic(scenicId); // Get flat configuration (automatically falls back to cache on failure) Map config = scenicService.getScenicFlatConfig(scenicId); // Create new scenic (direct operation, fails immediately on error) CreateScenicRequest request = new CreateScenicRequest(); request.setName("Test Scenic"); ScenicV2DTO result = scenicService.createScenic(request); // Update scenic (direct operation, fails immediately on error) UpdateScenicRequest updateRequest = new UpdateScenicRequest(); updateRequest.setName("Updated Scenic"); ScenicV2DTO updated = scenicService.updateScenic(scenicId, updateRequest); // Filter scenics (no fallback for complex queries) ScenicFilterRequest filterRequest = new ScenicFilterRequest(); // configure filters... ScenicFilterPageResponse response = scenicService.filterScenics(filterRequest); ``` #### Configuration Management (with Automatic Fallback) ```java @Autowired private ScenicConfigIntegrationService configService; // Get flat configuration (automatically falls back to cache on failure) Map config = configService.getFlatConfigs(scenicId); // Batch update configurations (direct operation, fails immediately on error) BatchConfigRequest batchRequest = new BatchConfigRequest(); // configure batch updates... BatchUpdateResponse result = configService.batchUpdateConfigs(scenicId, batchRequest); // Batch flat update configurations (direct operation, fails immediately on error) Map flatUpdates = new HashMap<>(); flatUpdates.put("max_visitors", "5000"); flatUpdates.put("opening_hours", "08:00-18:00"); BatchUpdateResponse flatResult = configService.batchFlatUpdateConfigs(scenicId, flatUpdates); ``` #### Fallback Cache Management for Scenics ```java @Autowired private IntegrationFallbackService fallbackService; // Check fallback cache status boolean hasScenicCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:2001"); boolean hasConfigCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:flat:configs:2001"); // Get cache statistics IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-scenic"); log.info("Scenic fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); // Clear specific cache fallbackService.clearFallbackCache("zt-scenic", "scenic:2001"); // Clear all scenic caches fallbackService.clearAllFallbackCache("zt-scenic"); ``` ## Device Integration (ZT-Device Microservice) ### Key Components #### 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 integration: device: enabled: true serviceName: zt-device connectTimeout: 5000 readTimeout: 10000 retryEnabled: false maxRetries: 3 render: enabled: true serviceName: zt-render-worker connectTimeout: 5000 readTimeout: 10000 retryEnabled: false maxRetries: 3 ``` ### Usage Examples #### Basic Device Operations (with Automatic Fallback) ```java @Autowired private DeviceIntegrationService deviceService; // Create IPC camera device (operation tracked for future fallback) DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId); // Get device with configuration (automatically falls back to cache on failure) DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId); // Get device by number (automatically falls back to cache on failure) DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); // Get basic device info (automatically falls back to cache on failure) DeviceV2DTO basicDevice = deviceService.getDevice(deviceId); // List scenic devices (no fallback for list operations) DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10); // Enable/disable device (direct operation, fails immediately on error) deviceService.enableDevice(deviceId); deviceService.disableDevice(deviceId); // Update device (direct operation, fails immediately on error) UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); updateRequest.setName("更新后的摄像头"); deviceService.updateDevice(deviceId, updateRequest); ``` #### Device Configuration Management (with Automatic Fallback) ```java @Autowired private DeviceConfigIntegrationService configService; // Get device configurations (with fallback) List configs = configService.getDeviceConfigs(deviceId); // Get flat configuration (automatically falls back to cache on failure) Map config = configService.getDeviceFlatConfig(deviceId); // Get flat configuration by device number (with fallback) Map configByNo = configService.getDeviceFlatConfigByNo("CAM001"); // Batch update configurations (direct operation, fails immediately on error) BatchDeviceConfigRequest batchRequest = configService.createBatchConfigBuilder() .addConfig("brightness", "50") .addConfig("contrast", "80") .build(); configService.batchUpdateDeviceConfig(deviceId, batchRequest); // New batch configuration API with detailed results BatchDeviceConfigRequest request = configService.createBatchConfigBuilder() .addVideoConfig("4K", 30, "H265") // Add video configuration .addNetworkConfig("192.168.1.100", 554, "RTSP") // Add network configuration .addAuthConfig("admin", "password") // Add authentication configuration .addConfig("recording_enabled", "true") // Use default configuration .addConfig("custom_setting", "value", "string", "Custom setting") // Custom configuration .build(); BatchUpdateResponse result = configService.batchUpdateDeviceConfigWithResult(deviceId, request); if (result.getFailed() > 0) { // Handle partial failure result.getProcessedItems().stream() .filter(item -> "failed".equals(item.getStatus())) .forEach(item -> log.warn("Config {} failed: {}", item.getConfigKey(), item.getMessage())); } ``` #### Device Management Patterns ```java // Create and configure camera in one operation (both operations direct, no fallback) DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId); // Use batch configuration to set camera parameters (direct operation) BatchDeviceConfigRequest cameraConfig = configService.createBatchConfigBuilder() .addConfig("ip_address", "192.168.1.100") .addConfig("resolution", "1920x1080") .addConfig("framerate", "30") .addConfig("protocol", "RTSP") .addConfig("username", "admin") .addConfig("password", "password") .build(); configService.batchUpdateDeviceConfig(camera.getId(), cameraConfig); // Get scenic camera status DeviceV2WithConfigListResponse camerasWithConfig = deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); // Batch update camera resolution (direct operations, no fallback) for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) { BatchDeviceConfigRequest resolutionUpdate = configService.createBatchConfigBuilder() .addConfig("resolution", "2560x1440") .build(); configService.batchUpdateDeviceConfig(device.getId(), resolutionUpdate); } // Monitor device configuration completeness (with automatic fallback) for (DeviceV2DTO device : activeDevices.getList()) { Map config = configService.getDeviceFlatConfig(device.getId()); boolean hasIpConfig = config.containsKey("ip_address"); // log configuration status... } ``` #### Fallback Cache Management for Devices ```java @Autowired private IntegrationFallbackService fallbackService; // Check fallback cache status boolean hasDeviceCache = fallbackService.hasFallbackCache("zt-device", "device:1001"); boolean hasConfigCache = fallbackService.hasFallbackCache("zt-device", "device:flat:config:1001"); // Get cache statistics IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-device"); log.info("Device fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); // Clear specific cache fallbackService.clearFallbackCache("zt-device", "device:1001"); // Clear all device caches 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. #### Default Configuration Rules The system uses `device_id = 0` configurations as default templates: - **Configurations with defaults**: System enforces default `config_type` and `description`, only `configValue` is updated - **Configurations without defaults**: Allows custom `config_type` and `description` #### Usage Examples ```java // 1. Using Builder Pattern BatchDeviceConfigRequest request = configService.createBatchConfigBuilder() .addVideoConfig("1920x1080", 30, "H264") // Video settings .addNetworkConfig("192.168.1.100", 554, "RTSP") // Network settings .addAuthConfig("admin", "password123") // Authentication .addConfig("recording_enabled", "true") // Use default config rule .addConfig("custom_path", "/data", "string", "Storage path") // Custom config .build(); BatchUpdateResponse result = configService.batchUpdateDeviceConfigWithResult(deviceId, request); // 2. Processing Results log.info("Batch update: {} success, {} failed", result.getSuccess(), result.getFailed()); for (ProcessedConfigItem item : result.getProcessedItems()) { if ("success".equals(item.getStatus())) { log.info("✅ {} updated (action: {}, hasDefault: {})", item.getConfigKey(), item.getAction(), item.getHasDefault()); } else { log.warn("❌ {} failed: {}", item.getConfigKey(), item.getMessage()); } } // 3. Error Handling if (result.getFailed() > 0) { result.getErrors().forEach(error -> log.warn("Error: {}", error)); } ``` #### Response Format - **200 OK**: All configurations updated successfully - **202 Accepted**: Partial success (some configurations failed) - **400 Bad Request**: All configurations failed Each processed item includes: - `status`: "success" or "failed" - `action`: "create" or "update" - `hasDefault`: Whether default configuration rules were applied - `finalType`/`finalDescription`: Actually used type and description ### Device Types - **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") - `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 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 response = client.getData(id); return handleResponse(response, "Failed to get data"); } 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(); } } ``` ## 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: # Fallback configuration fallback: enabled: true # Global fallback switch cachePrefix: "integration:fallback:" # Global cache prefix defaultTtlDays: 7 # Default cache TTL in days # Service-specific fallback configurations scenic: enabled: true # Enable fallback for scenic service ttlDays: 10 # Custom TTL for scenic (longer due to stable data) # cachePrefix: "scenic:fallback:" # Optional custom prefix device: enabled: true # Enable fallback for device service ttlDays: 5 # Custom TTL for device (shorter due to dynamic data) # cachePrefix: "device:fallback:" # Optional custom prefix render: enabled: true # Enable fallback for render worker service ttlDays: 7 # Custom TTL for render worker (medium due to stable worker data) # cachePrefix: "render:fallback:" # Optional custom prefix # Service configurations 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 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. ## Render Worker Integration (ZT-Render-Worker Microservice) ### Key Components #### Feign Clients - **RenderWorkerV2Client**: Main render worker operations (CRUD, listing) - **RenderWorkerConfigV2Client**: Render worker configuration management #### Services - **RenderWorkerIntegrationService**: High-level render worker operations (with automatic fallback) - **RenderWorkerConfigIntegrationService**: Configuration management (with automatic fallback) #### Configuration ```yaml integration: render: enabled: true serviceName: zt-render-worker connectTimeout: 5000 readTimeout: 10000 retryEnabled: false maxRetries: 3 ``` ### Usage Examples #### Basic Render Worker Operations (with Automatic Fallback) ```java @Autowired private RenderWorkerIntegrationService renderWorkerService; // Get render worker basic info (automatically falls back to cache on failure) RenderWorkerV2DTO worker = renderWorkerService.getWorker(workerId); // Get render worker with configuration (automatically falls back to cache on failure) RenderWorkerV2WithConfigDTO workerWithConfig = renderWorkerService.getWorkerWithConfig(workerId); // Get render worker by key (automatically falls back to cache on failure) RenderWorkerV2DTO workerByKey = renderWorkerService.getWorkerByKey("video-renderer-001"); // Get render worker with config by key (automatically falls back to cache on failure) RenderWorkerV2WithConfigDTO workerWithConfigByKey = renderWorkerService.getWorkerWithConfigByKey("video-renderer-001"); // Create new render worker (direct operation, fails immediately on error) CreateRenderWorkerRequest request = new CreateRenderWorkerRequest(); request.setName("Video Renderer"); request.setKey("video-renderer-001"); request.setIsActive(1); RenderWorkerV2DTO newWorker = renderWorkerService.createWorker(request); // Update render worker (direct operation, fails immediately on error) UpdateRenderWorkerRequest updateRequest = new UpdateRenderWorkerRequest(); updateRequest.setName("Updated Video Renderer"); renderWorkerService.updateWorker(workerId, updateRequest); // Delete render worker (direct operation, fails immediately on error) renderWorkerService.deleteWorker(workerId); // List render workers (no fallback for list operations) List workers = renderWorkerService.listWorkers(1, 10, 1, null); // List render workers with config (no fallback for list operations) List workersWithConfig = renderWorkerService.listWorkersWithConfig(1, 10, 1, null); ``` #### Configuration Management (with Automatic Fallback) ```java @Autowired private RenderWorkerConfigIntegrationService configService; // Get worker configurations (with fallback) List configs = configService.getWorkerConfigs(workerId); // Get flat configuration (automatically falls back to cache on failure) Map flatConfig = configService.getWorkerFlatConfig(workerId); // Get specific configuration by key (with fallback) RenderWorkerConfigV2DTO config = configService.getWorkerConfigByKey(workerId, "render_quality"); // Create configuration (direct operation, fails immediately on error) RenderWorkerConfigV2DTO newConfig = new RenderWorkerConfigV2DTO(); newConfig.setConfigKey("max_concurrent_tasks"); newConfig.setConfigValue("4"); newConfig.setConfigType("int"); newConfig.setDescription("Maximum concurrent render tasks"); newConfig.setIsActive(1); RenderWorkerConfigV2DTO created = configService.createWorkerConfig(workerId, newConfig); // Update configuration (direct operation, fails immediately on error) Map updates = new HashMap<>(); updates.put("configValue", "8"); configService.updateWorkerConfig(workerId, configId, updates); // Delete configuration (direct operation, fails immediately on error) configService.deleteWorkerConfig(workerId, configId); ``` #### Enhanced Batch Configuration API ```java // Using Builder Pattern for batch configuration BatchRenderWorkerConfigRequest request = configService.createBatchConfigBuilder() .addRenderConfig("high", "mp4", "1920x1080") // Render settings .addPerformanceConfig(8, 3600) // Performance settings .addConfig("output_path", "/data/renders", "string", "Output directory") .addConfig("enable_gpu", "true", "bool", "Enable GPU acceleration") .build(); // Batch update configurations (direct operation, fails immediately on error) configService.batchUpdateWorkerConfigs(workerId, request); // Batch flat update configurations (direct operation, fails immediately on error) Map flatUpdates = new HashMap<>(); flatUpdates.put("render_quality", "ultra"); flatUpdates.put("max_concurrent_tasks", 12); flatUpdates.put("enable_preview", true); configService.batchFlatUpdateWorkerConfigs(workerId, flatUpdates); ``` #### Render Worker Management Patterns ```java // Create and configure render worker in one operation CreateRenderWorkerRequest workerRequest = new CreateRenderWorkerRequest(); workerRequest.setName("4K Video Renderer"); workerRequest.setKey("4k-video-renderer"); RenderWorkerV2DTO worker = renderWorkerService.createWorker(workerRequest); // Configure the worker for 4K video rendering BatchRenderWorkerConfigRequest config = configService.createBatchConfigBuilder() .addRenderConfig("ultra", "mp4", "3840x2160") .addPerformanceConfig(4, 7200) // 4 concurrent tasks, 2 hour timeout .addConfig("gpu_memory_limit", "8192", "int", "GPU memory limit in MB") .addConfig("codec", "h265", "string", "Video codec") .addConfig("bitrate", "50000", "int", "Video bitrate in kbps") .build(); configService.batchUpdateWorkerConfigs(worker.getId(), config); // Monitor worker configuration completeness (with automatic fallback) Map workerConfig = configService.getWorkerFlatConfig(worker.getId()); boolean hasRenderConfig = workerConfig.containsKey("render_quality"); boolean hasPerformanceConfig = workerConfig.containsKey("max_concurrent_tasks"); // log configuration status... // Get all active workers for monitoring List activeWorkers = renderWorkerService.listWorkersWithConfig(1, 100, 1, null); // Check worker health and configuration for (RenderWorkerV2WithConfigDTO activeWorker : activeWorkers) { Map config = activeWorker.getConfig(); boolean isConfigured = config.containsKey("render_quality") && config.containsKey("max_concurrent_tasks"); // log worker status... } ``` #### Fallback Cache Management for Render Workers ```java @Autowired private IntegrationFallbackService fallbackService; // Check fallback cache status boolean hasWorkerCache = fallbackService.hasFallbackCache("zt-render-worker", "worker:1001"); boolean hasWorkerByKeyCache = fallbackService.hasFallbackCache("zt-render-worker", "worker:key:video-renderer-001"); boolean hasConfigCache = fallbackService.hasFallbackCache("zt-render-worker", "worker:flat:config:1001"); boolean hasConfigByKeyCache = fallbackService.hasFallbackCache("zt-render-worker", "worker:key:config:video-renderer-001"); // Get cache statistics IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-render-worker"); log.info("Render worker fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); // Clear specific cache fallbackService.clearFallbackCache("zt-render-worker", "worker:1001"); // Clear all render worker caches fallbackService.clearAllFallbackCache("zt-render-worker"); ``` ### Render Worker Types and Configuration #### Common Configuration Keys - `render_quality`: Render quality level ("low", "medium", "high", "ultra") - `render_format`: Output format ("mp4", "avi", "mov", "webm") - `render_resolution`: Video resolution ("1920x1080", "3840x2160", "1280x720") - `max_concurrent_tasks`: Maximum concurrent render tasks (integer) - `task_timeout`: Task timeout in seconds (integer) - `output_path`: Render output directory path (string) - `enable_gpu`: Enable GPU acceleration (boolean) - `gpu_memory_limit`: GPU memory limit in MB (integer) - `codec`: Video codec ("h264", "h265", "vp9") - `bitrate`: Video bitrate in kbps (integer) - `enable_preview`: Enable preview generation (boolean) #### Worker Status - **Active (isActive=1)**: Worker is available for tasks - **Inactive (isActive=0)**: Worker is disabled ## 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" # Run render worker integration tests mvn test -Dtest=RenderWorkerIntegrationServiceTest mvn test -Dtest="com.ycwl.basic.integration.render.*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 ### Fallback Strategy Best Practices - **Query operations only**: Only apply fallback to GET operations (data retrieval) - **No fallback for mutations**: CREATE/UPDATE/DELETE operations should fail immediately to provide clear feedback - **Cache key design**: Use clear, hierarchical cache keys for easy identification and cleanup - **TTL management**: Set appropriate TTL based on data freshness requirements - **Monitoring**: Regularly check fallback cache statistics and cleanup stale entries - **Service isolation**: Configure service-specific fallback settings based on reliability needs - **Error transparency**: Let users know exactly when their modification operations fail ### Configuration - Use environment-specific configuration profiles - Set appropriate timeouts based on service characteristics - Enable retries only when safe (idempotent operations) - Configure fallback TTL based on business requirements: - **Critical data**: Longer TTL (7-14 days) for essential operations - **Volatile data**: Shorter TTL (1-3 days) for frequently changing data - **Configuration data**: Medium TTL (5-7 days) for semi-static settings ### 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 - Use fallback service for resilience without changing business logic ### Cache Management Strategies - **Proactive cleanup**: Monitor cache size and clean up periodically - **Service-specific management**: Separate cache management per service - **Debugging support**: Use cache statistics for troubleshooting - **Configuration validation**: Ensure fallback configuration matches service requirements ## Questionnaire Integration (ZT-Questionnaire Microservice) ### Key Components #### Feign Client - **QuestionnaireClient**: Comprehensive questionnaire operations (CRUD, answer submission, statistics) #### Service - **QuestionnaireIntegrationService**: High-level questionnaire operations (with automatic fallback for queries) #### Configuration ```yaml integration: questionnaire: enabled: true serviceName: zt-questionnaire connectTimeout: 5000 readTimeout: 10000 retryEnabled: false maxRetries: 3 fallback: questionnaire: enabled: true ttlDays: 7 ``` ### Usage Examples #### Basic Questionnaire Operations (with Automatic Fallback) ```java @Autowired private QuestionnaireIntegrationService questionnaireService; // Get questionnaire details (automatically falls back to cache on failure) QuestionnaireResponse questionnaire = questionnaireService.getQuestionnaire(questionnaireId); // Get questionnaire list with filters (automatically falls back to cache on failure) QuestionnaireListResponse list = questionnaireService.getQuestionnaireList(1, 10, "客户调查", 2, null); // Get questionnaire statistics (automatically falls back to cache on failure) QuestionnaireStatistics stats = questionnaireService.getStatistics(questionnaireId); // Get response records (automatically falls back to cache on failure) ResponseListResponse responses = questionnaireService.getResponseList(1, 10, questionnaireId, null, null, null); // Get response details (automatically falls back to cache on failure) ResponseDetailResponse responseDetail = questionnaireService.getResponseDetail(responseId); ``` #### Questionnaire Management Operations (Direct Operations) ```java // Create questionnaire (direct operation, fails immediately on error) CreateQuestionnaireRequest request = new CreateQuestionnaireRequest(); request.setName("客户满意度调查"); request.setDescription("收集客户对服务的满意度反馈"); request.setIsAnonymous(true); request.setMaxAnswers(1000); // Add single-choice question CreateQuestionRequest question1 = new CreateQuestionRequest(); question1.setTitle("您对我们的服务满意吗?"); question1.setType(1); // 单选题 question1.setIsRequired(true); question1.setSort(1); List options1 = new ArrayList<>(); options1.add(new CreateQuestionOptionRequest("非常满意", "5", 1)); options1.add(new CreateQuestionOptionRequest("满意", "4", 2)); options1.add(new CreateQuestionOptionRequest("一般", "3", 3)); options1.add(new CreateQuestionOptionRequest("不满意", "2", 4)); options1.add(new CreateQuestionOptionRequest("非常不满意", "1", 5)); question1.setOptions(options1); // Add multiple-choice question CreateQuestionRequest question2 = new CreateQuestionRequest(); question2.setTitle("您感兴趣的服务有哪些?"); question2.setType(2); // 多选题 question2.setIsRequired(false); question2.setSort(2); List options2 = new ArrayList<>(); options2.add(new CreateQuestionOptionRequest("技术支持", "tech_support", 1)); options2.add(new CreateQuestionOptionRequest("产品培训", "training", 2)); options2.add(new CreateQuestionOptionRequest("定制开发", "custom_dev", 3)); options2.add(new CreateQuestionOptionRequest("其他", "others", 4)); question2.setOptions(options2); // Add text area question CreateQuestionRequest question3 = new CreateQuestionRequest(); question3.setTitle("您还有什么建议吗?"); question3.setType(4); // 文本域题 question3.setIsRequired(false); question3.setSort(3); question3.setOptions(null); // 文本域题不需要选项 request.setQuestions(Arrays.asList(question1, question2, question3)); QuestionnaireResponse created = questionnaireService.createQuestionnaire(request, "admin"); // Update questionnaire (direct operation, fails immediately on error) CreateQuestionnaireRequest updateRequest = new CreateQuestionnaireRequest(); updateRequest.setName("更新后的客户满意度调查"); QuestionnaireResponse updated = questionnaireService.updateQuestionnaire(questionnaireId, updateRequest, "admin"); // Publish questionnaire (direct operation, fails immediately on error) QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaireId, "admin"); // Stop questionnaire (direct operation, fails immediately on error) QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaireId, "admin"); // Delete questionnaire (direct operation, fails immediately on error) questionnaireService.deleteQuestionnaire(questionnaireId, "admin"); ``` #### Answer Submission ```java // Submit questionnaire answers (direct operation, no fallback) SubmitAnswerRequest answerRequest = new SubmitAnswerRequest(); answerRequest.setQuestionnaireId(questionnaireId); answerRequest.setUserId("user123"); List answers = new ArrayList<>(); // Single-choice answer answers.add(new AnswerRequest(123L, "4")); // 满意 // Multiple-choice answer answers.add(new AnswerRequest(124L, "tech_support,training")); // 技术支持和产品培训 // Text area answer answers.add(new AnswerRequest(125L, "服务很好,希望能增加更多实用功能")); answerRequest.setAnswers(answers); ResponseDetailResponse response = questionnaireService.submitAnswer(answerRequest); log.info("答案提交成功,回答ID: {}", response.getId()); ``` #### Question Types and Answer Formats ##### 1. Single Choice (Type 1) ```java // Creating single-choice question CreateQuestionRequest singleChoice = new CreateQuestionRequest(); singleChoice.setTitle("您的性别是?"); singleChoice.setType(1); singleChoice.setIsRequired(true); singleChoice.setSort(1); List options = new ArrayList<>(); options.add(new CreateQuestionOptionRequest("男", "male", 1)); options.add(new CreateQuestionOptionRequest("女", "female", 2)); options.add(new CreateQuestionOptionRequest("不愿透露", "prefer_not_to_say", 3)); singleChoice.setOptions(options); // Submitting single-choice answer AnswerRequest singleChoiceAnswer = new AnswerRequest(123L, "male"); ``` ##### 2. Multiple Choice (Type 2) ```java // Creating multiple-choice question CreateQuestionRequest multipleChoice = new CreateQuestionRequest(); multipleChoice.setTitle("您感兴趣的编程语言有哪些?"); multipleChoice.setType(2); multipleChoice.setIsRequired(false); multipleChoice.setSort(2); List options = new ArrayList<>(); options.add(new CreateQuestionOptionRequest("Java", "java", 1)); options.add(new CreateQuestionOptionRequest("Python", "python", 2)); options.add(new CreateQuestionOptionRequest("Go", "go", 3)); options.add(new CreateQuestionOptionRequest("JavaScript", "javascript", 4)); multipleChoice.setOptions(options); // Submitting multiple-choice answer (comma-separated values) AnswerRequest multipleChoiceAnswer = new AnswerRequest(124L, "java,python,go"); ``` ##### 3. Fill in Blank (Type 3) ```java // Creating fill-in-blank question CreateQuestionRequest fillInBlank = new CreateQuestionRequest(); fillInBlank.setTitle("请输入您的姓名"); fillInBlank.setType(3); fillInBlank.setIsRequired(true); fillInBlank.setSort(3); fillInBlank.setOptions(null); // No options needed // Submitting fill-in-blank answer AnswerRequest fillAnswer = new AnswerRequest(125L, "张三"); ``` ##### 4. Text Area (Type 4) ```java // Creating text area question CreateQuestionRequest textArea = new CreateQuestionRequest(); textArea.setTitle("请详细描述您对我们产品的建议"); textArea.setType(4); textArea.setIsRequired(false); textArea.setSort(4); textArea.setOptions(null); // No options needed // Submitting text area answer AnswerRequest textAnswer = new AnswerRequest(126L, "建议增加更多功能,提升用户体验..."); ``` ##### 5. Rating (Type 5) ```java // Creating rating question CreateQuestionRequest rating = new CreateQuestionRequest(); rating.setTitle("请对我们的服务进行评分(1-10分)"); rating.setType(5); rating.setIsRequired(true); rating.setSort(5); rating.setOptions(null); // No options needed, range controlled by frontend // Submitting rating answer AnswerRequest ratingAnswer = new AnswerRequest(127L, "8"); ``` #### Complete Questionnaire Workflow ```java // 1. Create questionnaire CreateQuestionnaireRequest createRequest = buildSampleQuestionnaire(); QuestionnaireResponse questionnaire = questionnaireService.createQuestionnaire(createRequest, "admin"); // 2. Publish questionnaire QuestionnaireResponse published = questionnaireService.publishQuestionnaire(questionnaire.getId(), "admin"); // 3. Users submit answers SubmitAnswerRequest answerRequest = buildSampleAnswers(questionnaire.getId()); ResponseDetailResponse answerResponse = questionnaireService.submitAnswer(answerRequest); // 4. View statistics QuestionnaireStatistics statistics = questionnaireService.getStatistics(questionnaire.getId()); log.info("Statistics - Total responses: {}, Completion rate: {}%", statistics.getTotalResponses(), statistics.getCompletionRate() * 100); // 5. Stop questionnaire when done QuestionnaireResponse stopped = questionnaireService.stopQuestionnaire(questionnaire.getId(), "admin"); ``` #### Fallback Cache Management for Questionnaires ```java @Autowired private IntegrationFallbackService fallbackService; // Check fallback cache status boolean hasQuestionnaireCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:1001"); boolean hasListCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:list:1:10:null:null:null"); boolean hasStatsCache = fallbackService.hasFallbackCache("zt-questionnaire", "questionnaire:statistics:1001"); // Get cache statistics IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-questionnaire"); log.info("Questionnaire fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); // Clear specific cache fallbackService.clearFallbackCache("zt-questionnaire", "questionnaire:1001"); // Clear all questionnaire caches fallbackService.clearAllFallbackCache("zt-questionnaire"); ``` ### Question Types and Validation Rules | Question Type | Type Value | Description | Options Required | Answer Format | |---------------|------------|-------------|------------------|---------------| | Single Choice | 1 | User can select one answer | Yes (2+ options) | Single option value | | Multiple Choice | 2 | User can select multiple answers | Yes (2+ options) | Comma-separated option values | | Fill in Blank | 3 | User inputs short text | No | Text content (1-200 chars) | | Text Area | 4 | User inputs long text | No | Text content (1-2000 chars) | | Rating | 5 | User provides numerical rating | No | Number as string (e.g., "1", "10") | ### Answer Validation Rules | Question Type | Validation Rules | Example | |---------------|------------------|---------| | Single Choice | Must be existing option value | "male", "female" | | Multiple Choice | Comma-separated existing option values | "java,python", "option1,option2,option3" | | Fill in Blank | Non-empty string, 1-200 characters | "张三", "北京市" | | Text Area | String, 1-2000 characters | "这是一段较长的文本内容..." | | Rating | Numeric string, typically 1-10 range | "1", "5", "10" | ### Questionnaire Status - **1**: Draft - Questionnaire is being edited - **2**: Published - Questionnaire is live and accepting responses - **3**: Stopped - Questionnaire is no longer accepting responses - **4**: Deleted - Questionnaire has been deleted ### Common Use Cases #### Customer Satisfaction Survey ```java // Create customer satisfaction questionnaire with rating and feedback CreateQuestionnaireRequest customerSurvey = new CreateQuestionnaireRequest(); customerSurvey.setName("客户满意度调查"); customerSurvey.setDescription("收集客户对服务的满意度反馈"); customerSurvey.setIsAnonymous(true); // Add rating question CreateQuestionRequest ratingQ = new CreateQuestionRequest(); ratingQ.setTitle("整体满意度评分(1-10分)"); ratingQ.setType(5); ratingQ.setIsRequired(true); // Add feedback question CreateQuestionRequest feedbackQ = new CreateQuestionRequest(); feedbackQ.setTitle("请提供具体的改进建议"); feedbackQ.setType(4); feedbackQ.setIsRequired(false); customerSurvey.setQuestions(Arrays.asList(ratingQ, feedbackQ)); ``` #### Product Feature Feedback ```java // Create product feature questionnaire with multiple choice and priorities CreateQuestionnaireRequest featureSurvey = new CreateQuestionnaireRequest(); featureSurvey.setName("产品功能需求调研"); featureSurvey.setIsAnonymous(false); // Priority features question CreateQuestionRequest featuresQ = new CreateQuestionRequest(); featuresQ.setTitle("您最希望我们优先开发哪些功能?"); featuresQ.setType(2); // Multiple choice featuresQ.setIsRequired(true); List featureOptions = new ArrayList<>(); featureOptions.add(new CreateQuestionOptionRequest("移动端适配", "mobile_support", 1)); featureOptions.add(new CreateQuestionOptionRequest("数据导出", "data_export", 2)); featureOptions.add(new CreateQuestionOptionRequest("API集成", "api_integration", 3)); featureOptions.add(new CreateQuestionOptionRequest("高级分析", "advanced_analytics", 4)); featuresQ.setOptions(featureOptions); featureSurvey.setQuestions(Arrays.asList(featuresQ)); ``` ### Error Handling and HTTP Status Codes #### HTTP Status Codes - **200 OK**: Operation successful - **201 Created**: Questionnaire/response created successfully - **400 Bad Request**: Invalid request parameters or validation errors - **404 Not Found**: Questionnaire or response not found - **500 Internal Server Error**: Server error occurred #### Common Error Scenarios ```java try { QuestionnaireResponse questionnaire = questionnaireService.getQuestionnaire(invalidId); } catch (IntegrationException e) { switch (e.getCode()) { case 404: log.warn("问卷不存在: {}", invalidId); break; case 400: log.warn("请求参数错误: {}", e.getMessage()); break; case 500: log.error("服务器内部错误: {}", e.getMessage()); break; default: log.error("未知错误: {}", e.getMessage()); } } ``` ### Testing Questionnaire Integration ```bash # Run questionnaire integration tests mvn test -Dtest=QuestionnaireIntegrationServiceTest # Run all integration tests mvn test -Dtest="com.ycwl.basic.integration.*Test" # Enable example runner in application-dev.yml integration: questionnaire: example: enabled: true ``` ### Configuration Properties ```yaml integration: questionnaire: enabled: true # Enable questionnaire integration serviceName: zt-questionnaire # Service name for Nacos discovery connectTimeout: 5000 # Connection timeout in ms readTimeout: 10000 # Read timeout in ms retryEnabled: false # Enable retry mechanism maxRetries: 3 # Maximum retry attempts fallback: questionnaire: enabled: true # Enable fallback for questionnaire service ttlDays: 7 # Cache TTL in days cachePrefix: "questionnaire:fallback:" # Optional custom prefix ``` ### Best Practices for Questionnaire Integration #### Query vs Mutation Operations - **Query operations (GET)**: Use fallback - questionnaire details, lists, statistics, responses - **Mutation operations (POST/PUT/DELETE)**: No fallback - create, update, delete, publish, stop, submit #### Cache Key Design - `questionnaire:{id}` - Individual questionnaire cache - `questionnaire:list:{page}:{size}:{name}:{status}:{createdBy}` - List cache - `questionnaire:statistics:{id}` - Statistics cache - `response:{id}` - Individual response cache - `responses:list:{page}:{size}:{questionnaireId}:{userId}` - Response list cache #### Answer Submission Best Practices - Validate question types before submission - Handle validation errors gracefully - Provide clear error messages for users - Log submission attempts for audit purposes #### Performance Considerations - Use appropriate page sizes for questionnaire lists - Cache frequently accessed questionnaires - Monitor response submission patterns - Implement rate limiting for public questionnaires