diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java index 48df318..cb6de9f 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java @@ -399,21 +399,6 @@ public class DeviceV2Controller { } } - /** - * 扁平化批量更新设备配置 - */ - @PostMapping("/{id}/config/flat-batch") - public ApiResponse batchFlatUpdateDeviceConfig(@PathVariable Long id, - @RequestBody Map configs) { - log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", id, configs.size()); - try { - deviceConfigIntegrationService.batchFlatUpdateDeviceConfig(id, configs); - return ApiResponse.success("设备配置批量更新成功"); - } catch (Exception e) { - log.error("扁平化批量更新设备配置失败, deviceId: {}", id, e); - return ApiResponse.fail("扁平化批量更新设备配置失败: " + e.getMessage()); - } - } /** * 更新设备配置 @@ -431,27 +416,6 @@ public class DeviceV2Controller { } } - /** - * 配置摄像头参数(快捷方法) - */ - @PutMapping("/{id}/config/camera") - public ApiResponse configureCameraParams(@PathVariable Long id, @RequestBody Map request) { - String ipAddress = (String) request.get("ipAddress"); - String resolution = (String) request.get("resolution"); - Integer framerate = request.get("framerate") != null ? Integer.valueOf(request.get("framerate").toString()) : null; - String protocol = (String) request.get("protocol"); - String username = (String) request.get("username"); - String password = (String) request.get("password"); - - log.info("配置摄像头参数, deviceId: {}, ipAddress: {}, resolution: {}", id, ipAddress, resolution); - try { - deviceConfigIntegrationService.configureCameraParams(id, ipAddress, resolution, framerate, protocol, username, password); - return ApiResponse.success("摄像头参数配置成功"); - } catch (Exception e) { - log.error("配置摄像头参数失败, deviceId: {}", id, e); - return ApiResponse.fail("配置摄像头参数失败: " + e.getMessage()); - } - } /** * 删除设备配置 diff --git a/src/main/java/com/ycwl/basic/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md index 173da25..2eb1a1e 100644 --- a/src/main/java/com/ycwl/basic/integration/CLAUDE.md +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -17,6 +17,7 @@ The integration package (`com.ycwl.basic.integration`) is responsible for extern - **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: @@ -35,6 +36,91 @@ service/ └── 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 @@ -45,8 +131,8 @@ service/ - **DefaultConfigClient**: Default configuration operations #### Services -- **ScenicIntegrationService**: High-level scenic operations -- **ScenicConfigIntegrationService**: Configuration management +- **ScenicIntegrationService**: High-level scenic operations (with automatic fallback) +- **ScenicConfigIntegrationService**: Configuration management (with automatic fallback) - **DefaultConfigIntegrationService**: Default configuration handling #### Configuration @@ -63,37 +149,74 @@ integration: ### Usage Examples -#### Basic Scenic Operations +#### Basic Scenic Operations (with Automatic Fallback) ```java @Autowired private ScenicIntegrationService scenicService; -// Get scenic with configuration +// Get scenic with configuration (automatically falls back to cache on failure) ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId); -// Create new scenic +// 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); -// Filter scenics +// 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 +#### Configuration Management (with Automatic Fallback) ```java @Autowired private ScenicConfigIntegrationService configService; -// Get flat configuration -Map config = scenicService.getScenicFlatConfig(scenicId); +// Get flat configuration (automatically falls back to cache on failure) +Map config = configService.getFlatConfigs(scenicId); -// Batch update configurations +// Batch update configurations (direct operation, fails immediately on error) BatchConfigRequest batchRequest = new BatchConfigRequest(); // configure batch updates... -configService.batchUpdateConfigs(scenicId, batchRequest); +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) @@ -105,8 +228,8 @@ configService.batchUpdateConfigs(scenicId, batchRequest); - **DeviceConfigV2Client**: Device configuration management #### Services -- **DeviceIntegrationService**: High-level device operations -- **DeviceConfigIntegrationService**: Device configuration management +- **DeviceIntegrationService**: High-level device operations (with automatic fallback) +- **DeviceConfigIntegrationService**: Device configuration management (with automatic fallback) #### Configuration ```yaml @@ -122,52 +245,56 @@ integration: ### Usage Examples -#### Basic Device Operations +#### Basic Device Operations (with Automatic Fallback) ```java @Autowired private DeviceIntegrationService deviceService; -// Create IPC camera device +// Create IPC camera device (operation tracked for future fallback) DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId); -// Get device with configuration +// Get device with configuration (automatically falls back to cache on failure) DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId); -// Get device by number +// Get device by number (automatically falls back to cache on failure) DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); -// List scenic devices +// 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 +// 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 +#### Device Configuration Management (with Automatic Fallback) ```java @Autowired private DeviceConfigIntegrationService configService; -// Configure camera basic parameters -configService.configureCameraBasicParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP"); +// Get device configurations (with fallback) +List configs = configService.getDeviceConfigs(deviceId); -// Configure camera with authentication -configService.configureCameraFullParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); - -// Set specific configuration -configService.setDeviceIpAddress(deviceId, "192.168.1.101"); -configService.setDeviceResolution(deviceId, "2560x1440"); -configService.setDeviceFramerate(deviceId, 60); - -// Get flat configuration +// Get flat configuration (automatically falls back to cache on failure) Map config = configService.getDeviceFlatConfig(deviceId); -// Batch update configurations (simple way) -Map batchConfigs = new HashMap<>(); -batchConfigs.put("brightness", "50"); -batchConfigs.put("contrast", "80"); -configService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); +// 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() @@ -189,20 +316,32 @@ if (result.getFailed() > 0) { #### Device Management Patterns ```java -// Create and configure camera in one operation +// Create and configure camera in one operation (both operations direct, no fallback) DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId); -configService.configureCameraFullParams(camera.getId(), "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); +// 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 +// Batch update camera resolution (direct operations, no fallback) for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) { - configService.setDeviceResolution(device.getId(), "2560x1440"); + BatchDeviceConfigRequest resolutionUpdate = configService.createBatchConfigBuilder() + .addConfig("resolution", "2560x1440") + .build(); + configService.batchUpdateDeviceConfig(device.getId(), resolutionUpdate); } -// Monitor device configuration completeness +// Monitor device configuration completeness (with automatic fallback) for (DeviceV2DTO device : activeDevices.getList()) { Map config = configService.getDeviceFlatConfig(device.getId()); boolean hasIpConfig = config.containsKey("ip_address"); @@ -210,6 +349,26 @@ for (DeviceV2DTO device : activeDevices.getList()) { } ``` +#### 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"); +``` + ### Enhanced Batch Configuration API Device Integration now supports an enhanced batch configuration API that provides detailed processing results and supports default configuration rules. @@ -359,6 +518,24 @@ Automatically converts Feign errors to IntegrationException: ### 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 + + # Service configurations scenic: enabled: true serviceName: zt-scenic @@ -449,10 +626,23 @@ logging: - 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 @@ -462,4 +652,11 @@ logging: ### Service Layer Design - Keep integration services focused on external service calls - Handle response transformation and error conversion -- Avoid business logic in integration services \ No newline at end of file +- 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 \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java index c4620f8..a25541e 100644 --- a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java +++ b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java @@ -11,6 +11,11 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "integration") public class IntegrationProperties { + /** + * 降级服务配置 + */ + private FallbackConfig fallback = new FallbackConfig(); + /** * 景区服务配置 */ @@ -70,4 +75,46 @@ public class IntegrationProperties { private boolean retryEnabled = false; private int maxRetries = 3; } + + @Data + public static class FallbackConfig { + /** + * 是否启用降级功能 + */ + private boolean enabled = true; + + /** + * 降级缓存前缀 + */ + private String cachePrefix = "integration:fallback:"; + + /** + * 默认降级缓存TTL(天) + */ + private long defaultTtlDays = 1; + + /** + * 服务特定的降级配置 + */ + private ServiceFallbackConfig scenic = new ServiceFallbackConfig(); + private ServiceFallbackConfig device = new ServiceFallbackConfig(); + } + + @Data + public static class ServiceFallbackConfig { + /** + * 是否启用此服务的降级功能 + */ + private boolean enabled = true; + + /** + * 服务特定的缓存TTL(天),如果为0则使用默认TTL + */ + private long ttlDays = 0; + + /** + * 服务特定的缓存前缀,如果为空则使用默认前缀 + */ + private String cachePrefix; + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java b/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java new file mode 100644 index 0000000..ca3a9cd --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java @@ -0,0 +1,257 @@ +package com.ycwl.basic.integration.common.service; + +import com.ycwl.basic.integration.common.config.IntegrationProperties; +import com.ycwl.basic.utils.JacksonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 集成服务通用失败降级处理 + * 提供统一的降级策略,支持所有微服务集成 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class IntegrationFallbackService { + + private final RedisTemplate redisTemplate; + private final IntegrationProperties integrationProperties; + + // 默认降级缓存配置 + private static final String DEFAULT_FALLBACK_PREFIX = "integration:fallback:"; + private static final long DEFAULT_FALLBACK_TTL = 7; // 7天 + + /** + * 执行操作,失败时降级到缓存结果 + * + * @param serviceName 服务名称 (如: zt-device, zt-scenic) + * @param cacheKey 缓存键 + * @param operation 主要操作 + * @param resultClass 结果类型 + * @param 结果类型 + * @return 操作结果或缓存的结果 + */ + public T executeWithFallback(String serviceName, String cacheKey, Supplier operation, Class resultClass) { + try { + T result = operation.get(); + if (result != null) { + // 操作成功,保存结果用于将来的降级 + storeFallbackCache(serviceName, cacheKey, result); + } + return result; + } catch (Exception e) { + log.warn("[{}] 操作失败,尝试降级到缓存结果, cacheKey: {}", serviceName, cacheKey, e); + T fallbackResult = getFallbackFromCache(serviceName, cacheKey, resultClass); + if (fallbackResult == null) { + log.error("[{}] 操作失败且无缓存数据, cacheKey: {}", serviceName, cacheKey); + throw e; + } + log.info("[{}] 成功从降级缓存获取结果, cacheKey: {}", serviceName, cacheKey); + return fallbackResult; + } + } + + /** + * 执行操作,失败时降级到缓存结果,无返回值版本 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + * @param operation 主要操作 + */ + public void executeWithFallback(String serviceName, String cacheKey, Runnable operation) { + try { + operation.run(); + // 操作成功,记录成功状态 + storeFallbackCache(serviceName, cacheKey + ":success", "true"); + log.debug("[{}] 操作成功,已记录成功状态, cacheKey: {}", serviceName, cacheKey); + } catch (Exception e) { + log.warn("[{}] 操作失败,检查是否有历史成功记录, cacheKey: {}", serviceName, cacheKey, e); + String successRecord = getFallbackFromCache(serviceName, cacheKey + ":success", String.class); + if (successRecord == null) { + log.error("[{}] 操作失败且无历史成功记录, cacheKey: {}", serviceName, cacheKey); + throw e; + } + log.info("[{}] 操作失败但有历史成功记录,忽略此次失败, cacheKey: {}", serviceName, cacheKey); + } + } + + /** + * 存储降级缓存 + */ + private void storeFallbackCache(String serviceName, String cacheKey, Object value) { + try { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + String jsonValue = JacksonUtil.toJSONString(value); + long ttl = getFallbackTtl(serviceName); + redisTemplate.opsForValue().set(fullKey, jsonValue, ttl, TimeUnit.DAYS); + log.debug("[{}] 存储降级缓存成功, key: {}", serviceName, fullKey); + } catch (Exception e) { + log.warn("[{}] 存储降级缓存失败, cacheKey: {}", serviceName, cacheKey, e); + } + } + + /** + * 从降级缓存获取结果 + */ + private T getFallbackFromCache(String serviceName, String cacheKey, Class resultClass) { + try { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + String cachedValue = redisTemplate.opsForValue().get(fullKey); + if (cachedValue != null) { + log.debug("[{}] 从降级缓存获取结果, key: {}", serviceName, fullKey); + if (resultClass == String.class) { + return resultClass.cast(cachedValue); + } + return JacksonUtil.parseObject(cachedValue, resultClass); + } + } catch (Exception e) { + log.warn("[{}] 从降级缓存获取结果失败, cacheKey: {}", serviceName, cacheKey, e); + } + return null; + } + + /** + * 清除降级缓存 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + */ + public void clearFallbackCache(String serviceName, String cacheKey) { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + redisTemplate.delete(fullKey); + log.debug("[{}] 清除降级缓存, key: {}", serviceName, fullKey); + } + + /** + * 批量清除服务的所有降级缓存 + * + * @param serviceName 服务名称 + */ + public void clearAllFallbackCache(String serviceName) { + String pattern = buildFullCacheKey(serviceName, "*"); + Set keys = redisTemplate.keys(pattern); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + log.info("[{}] 批量清除降级缓存,共删除 {} 个缓存项", serviceName, keys.size()); + } + } + + /** + * 检查是否有降级缓存 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + * @return 是否存在降级缓存 + */ + public boolean hasFallbackCache(String serviceName, String cacheKey) { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + return Boolean.TRUE.equals(redisTemplate.hasKey(fullKey)); + } + + /** + * 获取服务的降级缓存统计信息 + * + * @param serviceName 服务名称 + * @return 缓存统计信息 + */ + public FallbackCacheStats getFallbackCacheStats(String serviceName) { + String pattern = buildFullCacheKey(serviceName, "*"); + Set keys = redisTemplate.keys(pattern); + int totalCount = keys != null ? keys.size() : 0; + + return FallbackCacheStats.builder() + .serviceName(serviceName) + .totalCacheCount(totalCount) + .cacheKeyPattern(pattern) + .fallbackTtlDays(getFallbackTtl(serviceName)) + .build(); + } + + /** + * 构建完整的缓存键 + */ + private String buildFullCacheKey(String serviceName, String cacheKey) { + String prefix = getFallbackPrefix(serviceName); + return prefix + serviceName + ":" + cacheKey; + } + + /** + * 获取服务的降级缓存前缀 + */ + private String getFallbackPrefix(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return DEFAULT_FALLBACK_PREFIX; + } + + // 获取服务特定的缓存前缀 + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + if (serviceConfig != null && serviceConfig.getCachePrefix() != null) { + return serviceConfig.getCachePrefix(); + } + + // 使用全局配置的前缀 + return integrationProperties.getFallback().getCachePrefix(); + } + + /** + * 获取服务的降级缓存TTL + */ + private long getFallbackTtl(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return DEFAULT_FALLBACK_TTL; + } + + // 获取服务特定的TTL + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + if (serviceConfig != null && serviceConfig.getTtlDays() > 0) { + return serviceConfig.getTtlDays(); + } + + // 使用全局配置的TTL + return integrationProperties.getFallback().getDefaultTtlDays(); + } + + /** + * 获取服务特定的降级配置 + */ + private IntegrationProperties.ServiceFallbackConfig getServiceFallbackConfig(String serviceName) { + switch (serviceName.toLowerCase()) { + case "zt-scenic": + return integrationProperties.getFallback().getScenic(); + case "zt-device": + return integrationProperties.getFallback().getDevice(); + default: + return null; + } + } + + /** + * 检查服务是否启用降级功能 + */ + public boolean isFallbackEnabled(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return false; + } + + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + return serviceConfig == null || serviceConfig.isEnabled(); + } + + /** + * 降级缓存统计信息 + */ + @lombok.Builder + @lombok.Data + public static class FallbackCacheStats { + private String serviceName; + private int totalCacheCount; + private String cacheKeyPattern; + private long fallbackTtlDays; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java index c94ca1e..86afa90 100644 --- a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java @@ -71,10 +71,4 @@ public interface DeviceConfigV2Client { CommonResponse batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, @RequestBody BatchDeviceConfigRequest request); - /** - * 扁平化批量更新设备配置 - */ - @PostMapping("/{deviceId}/batch-flat") - CommonResponse batchFlatUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, - @RequestBody Map configs); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java index df2b0c0..4429f12 100644 --- a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java @@ -1,8 +1,8 @@ package com.ycwl.basic.integration.device.example; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; -import com.ycwl.basic.integration.device.service.DeviceIntegrationFallbackService; import com.ycwl.basic.integration.device.dto.device.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -11,8 +11,8 @@ import org.springframework.stereotype.Component; import java.util.Map; /** - * 设备集成降级机制示例 - * 演示失败降级策略的使用 + * 设备集成示例(包含降级机制) + * 演示设备集成和失败降级策略的使用 */ @Slf4j @Component @@ -21,7 +21,9 @@ public class DeviceIntegrationFallbackExample { private final DeviceIntegrationService deviceService; private final DeviceConfigIntegrationService configService; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; /** * 演示设备信息获取的降级机制 @@ -51,26 +53,26 @@ public class DeviceIntegrationFallbackExample { } /** - * 演示设备操作的降级机制 + * 演示设备操作(无降级机制) */ - public void deviceOperationFallbackExample() { - log.info("=== 设备操作降级示例 ==="); + public void deviceOperationExample() { + log.info("=== 设备操作示例 ==="); Long deviceId = 1001L; try { - // 设备更新操作 - 自动降级 + // 设备更新操作 - 直接操作,失败时抛出异常 UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); updateRequest.setName("更新后的摄像头"); deviceService.updateDevice(deviceId, updateRequest); log.info("设备更新操作完成"); - // 设备排序更新 - 自动降级 + // 设备排序更新 - 直接操作,失败时抛出异常 deviceService.updateDeviceSort(deviceId, 5); log.info("设备排序更新完成"); } catch (Exception e) { - log.error("设备操作降级失败", e); + log.error("设备操作失败", e); } } @@ -84,29 +86,39 @@ public class DeviceIntegrationFallbackExample { String configCacheKey = "device:flat:config:1001"; // 检查降级缓存状态 - boolean hasDeviceCache = fallbackService.hasFallbackCache(deviceCacheKey); - boolean hasConfigCache = fallbackService.hasFallbackCache(configCacheKey); + boolean hasDeviceCache = fallbackService.hasFallbackCache(SERVICE_NAME, deviceCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey); log.info("设备降级缓存存在: {}", hasDeviceCache); log.info("配置降级缓存存在: {}", hasConfigCache); // 清理特定的降级缓存 if (hasDeviceCache) { - fallbackService.clearFallbackCache(deviceCacheKey); + fallbackService.clearFallbackCache(SERVICE_NAME, deviceCacheKey); log.info("已清理设备降级缓存"); } + + // 获取降级缓存统计信息 + IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME); + log.info("设备服务降级缓存统计: {}", stats); + + // 批量清理所有设备降级缓存 + if (stats.getTotalCacheCount() > 10) { + fallbackService.clearAllFallbackCache(SERVICE_NAME); + log.info("已批量清理所有设备降级缓存"); + } } /** - * 运行所有降级示例 + * 运行所有示例 */ - public void runAllFallbackExamples() { - log.info("开始运行设备集成降级示例..."); + public void runAllExamples() { + log.info("开始运行设备集成示例..."); deviceInfoFallbackExample(); - deviceOperationFallbackExample(); + deviceOperationExample(); fallbackCacheManagementExample(); - log.info("设备集成降级示例运行完成"); + log.info("设备集成示例运行完成"); } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java index 2b7b7cb..850c35b 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.device.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.client.DeviceConfigV2Client; import com.ycwl.basic.integration.device.dto.config.*; import lombok.RequiredArgsConstructor; @@ -18,7 +19,9 @@ import java.util.Map; public class DeviceConfigIntegrationService { private final DeviceConfigV2Client deviceConfigV2Client; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; public List getDeviceConfigs(Long deviceId) { log.info("获取设备配置列表, deviceId: {}", deviceId); @@ -41,6 +44,7 @@ public class DeviceConfigIntegrationService { public Map getDeviceFlatConfig(Long deviceId) { log.info("获取设备扁平化配置, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:flat:config:" + deviceId, () -> { CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); @@ -53,6 +57,7 @@ public class DeviceConfigIntegrationService { public Map getDeviceFlatConfigByNo(String deviceNo) { log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:flat:config:no:" + deviceNo, () -> { CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); @@ -89,6 +94,8 @@ public class DeviceConfigIntegrationService { return handleResponse(response, "批量更新设备配置失败"); } + + /** * 获取设备特定配置值 */ diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java deleted file mode 100644 index 59ee0be..0000000 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.ycwl.basic.integration.device.service; - -import com.ycwl.basic.integration.device.dto.device.*; -import com.ycwl.basic.utils.JacksonUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -/** - * 设备集成服务失败降级处理 - * 提供失败时的降级策略,不使用常规缓存 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class DeviceIntegrationFallbackService { - - private final RedisTemplate redisTemplate; - - // 降级缓存键前缀 - private static final String FALLBACK_CACHE_PREFIX = "device:fallback:"; - private static final long FALLBACK_CACHE_TTL = 7; // 7天 - - /** - * 执行操作,失败时降级到缓存结果 - * - * @param cacheKey 缓存键 - * @param operation 主要操作 - * @param resultClass 结果类型 - * @param 结果类型 - * @return 操作结果或缓存的结果 - */ - public T executeWithFallback(String cacheKey, Supplier operation, Class resultClass) { - try { - T result = operation.get(); - if (result != null) { - // 操作成功,保存结果用于将来的降级 - storeFallbackCache(cacheKey, result); - } - return result; - } catch (Exception e) { - log.warn("操作失败,尝试降级到缓存结果, cacheKey: {}", cacheKey, e); - return getFallbackFromCache(cacheKey, resultClass); - } - } - - /** - * 执行操作,失败时降级到缓存结果,无返回值版本 - * - * @param cacheKey 缓存键 - * @param operation 主要操作 - */ - public void executeWithFallback(String cacheKey, Runnable operation) { - try { - operation.run(); - // 操作成功,记录成功状态 - storeFallbackCache(cacheKey + ":success", "true"); - } catch (Exception e) { - log.warn("操作失败,检查是否有历史成功记录, cacheKey: {}", cacheKey, e); - String successRecord = getFallbackFromCache(cacheKey + ":success", String.class); - if (successRecord == null) { - log.error("操作失败且无历史成功记录, cacheKey: {}", cacheKey); - throw e; - } - log.info("操作失败但有历史成功记录,忽略此次失败, cacheKey: {}", cacheKey); - } - } - - /** - * 存储降级缓存 - */ - private void storeFallbackCache(String cacheKey, Object value) { - try { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - String jsonValue = JacksonUtil.toJSONString(value); - redisTemplate.opsForValue().set(key, jsonValue, FALLBACK_CACHE_TTL, TimeUnit.DAYS); - log.debug("存储降级缓存成功, key: {}", key); - } catch (Exception e) { - log.warn("存储降级缓存失败, cacheKey: {}", cacheKey, e); - } - } - - /** - * 从降级缓存获取结果 - */ - private T getFallbackFromCache(String cacheKey, Class resultClass) { - try { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - String cachedValue = redisTemplate.opsForValue().get(key); - if (cachedValue != null) { - log.info("从降级缓存获取结果, key: {}", key); - if (resultClass == String.class) { - return resultClass.cast(cachedValue); - } - return JacksonUtil.parseObject(cachedValue, resultClass); - } - } catch (Exception e) { - log.warn("从降级缓存获取结果失败, cacheKey: {}", cacheKey, e); - } - return null; - } - - /** - * 清除降级缓存 - * - * @param cacheKey 缓存键 - */ - public void clearFallbackCache(String cacheKey) { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - redisTemplate.delete(key); - log.debug("清除降级缓存, key: {}", key); - } - - /** - * 检查是否有降级缓存 - * - * @param cacheKey 缓存键 - * @return 是否存在降级缓存 - */ - public boolean hasFallbackCache(String cacheKey) { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - return Boolean.TRUE.equals(redisTemplate.hasKey(key)); - } -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java index 4e8dcda..3dfe39e 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.device.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.client.DeviceV2Client; import com.ycwl.basic.integration.device.dto.device.*; import lombok.RequiredArgsConstructor; @@ -14,11 +15,14 @@ import org.springframework.stereotype.Service; public class DeviceIntegrationService { private final DeviceV2Client deviceV2Client; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; public DeviceV2DTO getDevice(Long deviceId) { log.info("获取设备信息, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:" + deviceId, () -> { CommonResponse response = deviceV2Client.getDevice(deviceId); @@ -31,6 +35,7 @@ public class DeviceIntegrationService { public DeviceV2DTO getDeviceByNo(String deviceNo) { log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:no:" + deviceNo, () -> { CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); @@ -43,6 +48,7 @@ public class DeviceIntegrationService { public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) { log.info("获取设备配置信息, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:config:" + deviceId, () -> { CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); @@ -55,6 +61,7 @@ public class DeviceIntegrationService { public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) { log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:config:no:" + deviceNo, () -> { CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); @@ -72,24 +79,14 @@ public class DeviceIntegrationService { public void updateDevice(Long deviceId, UpdateDeviceRequest request) { log.info("更新设备信息, deviceId: {}", deviceId); - fallbackService.executeWithFallback( - "device:update:" + deviceId, - () -> { - CommonResponse response = deviceV2Client.updateDevice(deviceId, request); - handleResponse(response, "更新设备信息失败"); - } - ); + CommonResponse response = deviceV2Client.updateDevice(deviceId, request); + handleResponse(response, "更新设备信息失败"); } public void deleteDevice(Long deviceId) { log.info("删除设备, deviceId: {}", deviceId); - fallbackService.executeWithFallback( - "device:delete:" + deviceId, - () -> { - CommonResponse response = deviceV2Client.deleteDevice(deviceId); - handleResponse(response, "删除设备失败"); - } - ); + CommonResponse response = deviceV2Client.deleteDevice(deviceId); + handleResponse(response, "删除设备失败"); } public DeviceV2ListResponse listDevices(Integer page, Integer pageSize, String name, String no, diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java index d3f35b6..ca154ed 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java @@ -13,4 +13,12 @@ public class BatchUpdateResponse { @JsonProperty("message") private String message; + + public Integer getSuccess() { + return (updatedCount != null ? updatedCount : 0) + (createdCount != null ? createdCount : 0); + } + + public Integer getFailed() { + return 0; // 当前响应格式不包含失败计数,返回0作为默认值 + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java index f1f50e3..fe5c875 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java @@ -1,9 +1,9 @@ package com.ycwl.basic.integration.scenic.example; -import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest; -import com.ycwl.basic.integration.scenic.dto.filter.FilterCondition; -import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; -import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.scenic.dto.config.*; +import com.ycwl.basic.integration.scenic.dto.filter.*; +import com.ycwl.basic.integration.scenic.dto.scenic.*; import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; import lombok.RequiredArgsConstructor; @@ -11,10 +11,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** - * ZT-Scenic集成服务使用示例 - * 仅供参考,实际使用时根据业务需要调用相应的服务方法 + * 景区集成示例(包含降级机制) + * 演示景区集成和失败降级策略的使用 */ @Slf4j @Component @@ -23,6 +25,9 @@ public class ScenicIntegrationExample { private final ScenicIntegrationService scenicIntegrationService; private final ScenicConfigIntegrationService scenicConfigIntegrationService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; /** * 示例:创建景区并设置配置 @@ -75,4 +80,105 @@ public class ScenicIntegrationExample { log.error("筛选景区失败", e); } } + + /** + * 演示基础景区操作的降级机制 + */ + public void basicScenicOperationsExample() { + log.info("=== 基础景区操作示例(含降级机制) ==="); + + Long scenicId = 2001L; + + try { + // 获取景区信息 - 自动降级 + ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId); + log.info("获取景区成功: {}", scenic.getName()); + + // 获取景区配置信息 - 自动降级 + ScenicV2WithConfigDTO scenicWithConfig = scenicIntegrationService.getScenicWithConfig(scenicId); + log.info("获取景区配置成功,配置数量: {}", scenicWithConfig.getConfig().size()); + + // 获取扁平化配置 - 自动降级 + Map flatConfig = scenicIntegrationService.getScenicFlatConfig(scenicId); + log.info("获取扁平化配置成功,配置项数量: {}", flatConfig.size()); + + } catch (Exception e) { + log.error("景区操作降级失败", e); + } + } + + /** + * 演示景区配置管理的降级机制 + */ + public void scenicConfigManagementFallbackExample() { + log.info("=== 景区配置管理示例(含降级机制) ==="); + + Long scenicId = 2001L; + + try { + // 获取扁平化配置 - 自动降级 + Map flatConfigs = scenicConfigIntegrationService.getFlatConfigs(scenicId); + log.info("获取扁平化配置成功,配置项数量: {}", flatConfigs.size()); + + // 批量更新配置 - 直接操作,失败时抛出异常 + Map updates = new HashMap<>(); + updates.put("max_visitors", "5000"); + updates.put("opening_hours", "08:00-18:00"); + + BatchUpdateResponse result = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, updates); + log.info("批量更新配置完成: 成功 {}, 失败 {}", result.getSuccess(), result.getFailed()); + + } catch (Exception e) { + log.error("景区配置管理操作失败", e); + } + } + + /** + * 演示降级缓存管理 + */ + public void fallbackCacheManagementExample() { + log.info("=== 景区降级缓存管理示例 ==="); + + String scenicCacheKey = "scenic:2001"; + String configCacheKey = "scenic:flat:configs:2001"; + + // 检查降级缓存状态 + boolean hasScenicCache = fallbackService.hasFallbackCache(SERVICE_NAME, scenicCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey); + + log.info("景区降级缓存存在: {}", hasScenicCache); + log.info("配置降级缓存存在: {}", hasConfigCache); + + // 获取降级缓存统计信息 + IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME); + log.info("景区服务降级缓存统计: 缓存数量={}, TTL={}天", + stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + + // 清理特定的降级缓存 + if (hasScenicCache) { + fallbackService.clearFallbackCache(SERVICE_NAME, scenicCacheKey); + log.info("已清理景区降级缓存"); + } + + // 如果缓存过多,批量清理 + if (stats.getTotalCacheCount() > 50) { + fallbackService.clearAllFallbackCache(SERVICE_NAME); + log.info("已批量清理所有景区降级缓存"); + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + log.info("开始运行景区集成示例(包含降级机制)..."); + + createScenicWithConfig(); + filterScenics(); + basicScenicOperationsExample(); + scenicConfigManagementFallbackExample(); + fallbackCacheManagementExample(); + + log.info("景区集成示例运行完成"); + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java index 39151fd..abb3914 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; import com.ycwl.basic.integration.scenic.dto.config.*; import lombok.RequiredArgsConstructor; @@ -17,6 +18,9 @@ import java.util.Map; public class ScenicConfigIntegrationService { private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public List listConfigs(Long scenicId) { log.info("获取景区配置列表, scenicId: {}", scenicId); @@ -32,8 +36,15 @@ public class ScenicConfigIntegrationService { public Map getFlatConfigs(Long scenicId) { log.info("获取景区扁平化配置, scenicId: {}", scenicId); - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:flat:configs:" + scenicId, + () -> { + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + }, + Map.class + ); } public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) { diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java index e101398..fba59c2 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; import com.ycwl.basic.integration.scenic.client.ScenicV2Client; import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; @@ -25,23 +26,47 @@ public class ScenicIntegrationService { private final ScenicV2Client scenicV2Client; private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public ScenicV2DTO getScenic(Long scenicId) { log.info("获取景区信息, scenicId: {}", scenicId); - CommonResponse response = scenicV2Client.getScenic(scenicId); - return handleResponse(response, "获取景区信息失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenic(scenicId); + return handleResponse(response, "获取景区信息失败"); + }, + ScenicV2DTO.class + ); } public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) { log.info("获取景区配置信息, scenicId: {}", scenicId); - CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); - return handleResponse(response, "获取景区配置信息失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:config:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); + return handleResponse(response, "获取景区配置信息失败"); + }, + ScenicV2WithConfigDTO.class + ); } public Map getScenicFlatConfig(Long scenicId) { log.info("获取景区扁平化配置, scenicId: {}", scenicId); - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:flat:config:" + scenicId, + () -> { + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + }, + Map.class + ); } public ScenicV2DTO createScenic(CreateScenicRequest request) {