Files
FrameTour-BE/src/main/java/com/ycwl/basic/integration/CLAUDE.md
Jerry Yan a49450b795 feat(integration): 添加问卷服务集成模块
- 新增问卷服务配置和客户端接口
- 实现问卷创建、查询、提交答案和统计分析等功能
- 添加问卷集成示例,演示各项功能的使用- 设计并实现问卷服务的 fallback 缓存管理策略
2025-09-06 00:19:48 +08:00

65 KiB

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:

// Query operations with fallback
<T> T executeWithFallback(String serviceName, String cacheKey, Supplier<T> operation, Class<T> 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

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

integration:
  scenic:
    enabled: true
    serviceName: zt-scenic
    connectTimeout: 5000
    readTimeout: 10000
    retryEnabled: false
    maxRetries: 3

Usage Examples

Basic Scenic Operations (with Automatic Fallback)

@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<String, Object> 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)

@Autowired
private ScenicConfigIntegrationService configService;

// Get flat configuration (automatically falls back to cache on failure)
Map<String, Object> 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<String, Object> 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

@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

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)

@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)

@Autowired
private DeviceConfigIntegrationService configService;

// Get device configurations (with fallback)
List<DeviceConfigV2DTO> configs = configService.getDeviceConfigs(deviceId);

// Get flat configuration (automatically falls back to cache on failure)
Map<String, Object> config = configService.getDeviceFlatConfig(deviceId);

// Get flat configuration by device number (with fallback)
Map<String, Object> 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

// 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<String, Object> config = configService.getDeviceFlatConfig(device.getId());
    boolean hasIpConfig = config.containsKey("ip_address");
    // log configuration status...
}

Fallback Cache Management for Devices

@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)

@Autowired
private DefaultConfigIntegrationService defaultConfigService;

// Get default configuration list (automatically falls back to cache on failure)
PageResponse<DefaultConfigResponse> 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<String, Object> 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

// 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

// 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

// Validate system configuration completeness
PageResponse<DefaultConfigResponse> allDefaults = defaultConfigService.listDefaultConfigs(1, 100);

// Check for required default configurations
String[] requiredDefaults = {
    "resolution", "frameRate", "codec", "bitrate", 
    "protocol", "port", "username", "password"
};

Map<String, Boolean> 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<String, Long> 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

// 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<String, Long> 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<String, List<DefaultConfigConflict>> 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

@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:

// 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

// 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

# 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:

@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

@FeignClient(name = "service-name", contextId = "service-context", path = "/api/path")
public interface NewServiceClient {
    @GetMapping("/endpoint")
    CommonResponse<ResponseDTO> getData(@PathVariable Long id);
    // other endpoints...
}

4. Implement Service Layer

@Service
@RequiredArgsConstructor
public class NewServiceIntegrationService {
    private final NewServiceClient client;
    
    public ResponseDTO getData(Long id) {
        CommonResponse<ResponseDTO> response = client.getData(id);
        return handleResponse(response, "Failed to get data");
    }
    
    private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
        if (response == null || !response.isSuccess()) {
            String msg = response != null && response.getMessage() != null 
                        ? response.getMessage() 
                        : errorMessage;
            Integer code = response != null ? response.getCode() : 5000;
            throw new IntegrationException(code, msg, "service-name");
        }
        return response.getData();
    }
}

Error Handling

IntegrationException

All integration failures are wrapped in IntegrationException:

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

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:

@ExtendWith(MockitoExtension.class)
class ScenicIntegrationServiceTest {
    @Mock
    private ScenicV2Client scenicV2Client;
    
    @InjectMocks
    private ScenicIntegrationService scenicService;
    
    @Test
    void testGetScenic() {
        // Mock response
        CommonResponse<ScenicV2DTO> response = new CommonResponse<>();
        response.setSuccess(true);
        response.setData(new ScenicV2DTO());
        
        when(scenicV2Client.getScenic(1L)).thenReturn(response);
        
        // Test
        ScenicV2DTO result = scenicService.getScenic(1L);
        assertNotNull(result);
    }
}

Integration Testing

Use @SpringBootTest with test profiles and mock external services.

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

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)

@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<RenderWorkerV2DTO> workers = renderWorkerService.listWorkers(1, 10, 1, null);

// List render workers with config (no fallback for list operations)
List<RenderWorkerV2WithConfigDTO> workersWithConfig = 
    renderWorkerService.listWorkersWithConfig(1, 10, 1, null);

Configuration Management (with Automatic Fallback)

@Autowired
private RenderWorkerConfigIntegrationService configService;

// Get worker configurations (with fallback)
List<RenderWorkerConfigV2DTO> configs = configService.getWorkerConfigs(workerId);

// Get flat configuration (automatically falls back to cache on failure)
Map<String, Object> 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<String, Object> 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

// 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<String, Object> 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

// 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<String, Object> 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<RenderWorkerV2WithConfigDTO> activeWorkers = 
    renderWorkerService.listWorkersWithConfig(1, 100, 1, null);

// Check worker health and configuration
for (RenderWorkerV2WithConfigDTO activeWorker : activeWorkers) {
    Map<String, Object> config = activeWorker.getConfig();
    boolean isConfigured = config.containsKey("render_quality") && 
                          config.containsKey("max_concurrent_tasks");
    // log worker status...
}

Fallback Cache Management for Render Workers

@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

# 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:
logging:
  level:
    com.ycwl.basic.integration: DEBUG
  1. Check Nacos service discovery in development
  2. Verify service configurations and timeouts
  3. 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

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)

@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)

// 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<CreateQuestionOptionRequest> 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<CreateQuestionOptionRequest> 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

// Submit questionnaire answers (direct operation, no fallback)
SubmitAnswerRequest answerRequest = new SubmitAnswerRequest();
answerRequest.setQuestionnaireId(questionnaireId);
answerRequest.setUserId("user123");

List<AnswerRequest> 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)
// Creating single-choice question
CreateQuestionRequest singleChoice = new CreateQuestionRequest();
singleChoice.setTitle("您的性别是?");
singleChoice.setType(1);
singleChoice.setIsRequired(true);
singleChoice.setSort(1);

List<CreateQuestionOptionRequest> 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)
// Creating multiple-choice question
CreateQuestionRequest multipleChoice = new CreateQuestionRequest();
multipleChoice.setTitle("您感兴趣的编程语言有哪些?");
multipleChoice.setType(2);
multipleChoice.setIsRequired(false);
multipleChoice.setSort(2);

List<CreateQuestionOptionRequest> 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)
// 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)
// 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)
// 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

// 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

@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

// 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

// 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<CreateQuestionOptionRequest> 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

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

# 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

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