You've already forked FrameTour-BE
Merge branch 'device-microservice'
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
All checks were successful
ZhenTu-BE/pipeline/head This commit looks good
# Conflicts: # src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java # src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java
This commit is contained in:
662
src/main/java/com/ycwl/basic/integration/CLAUDE.md
Normal file
662
src/main/java/com/ycwl/basic/integration/CLAUDE.md
Normal file
@@ -0,0 +1,662 @@
|
||||
# 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
|
||||
|
||||
### Integration Pattern
|
||||
|
||||
Each external service integration follows this structure:
|
||||
```
|
||||
service/
|
||||
├── client/ # Feign clients for HTTP calls
|
||||
├── config/ # Service-specific configuration
|
||||
├── dto/ # Data transfer objects
|
||||
├── service/ # Service layer with business logic
|
||||
└── example/ # Usage examples
|
||||
```
|
||||
|
||||
## Integration Fallback Mechanism
|
||||
|
||||
### Overview
|
||||
The integration package includes a comprehensive fallback mechanism that provides automatic degradation when external microservices are unavailable or return errors. This mechanism ensures system resilience and maintains service availability even when dependencies fail.
|
||||
|
||||
### Core Components
|
||||
|
||||
#### IntegrationFallbackService
|
||||
Universal fallback service that handles failure degradation for all microservice integrations:
|
||||
|
||||
**Key Features:**
|
||||
- **Automatic Fallback**: Transparently handles service failures and returns cached results
|
||||
- **Configurable TTL**: Service-specific cache TTL configuration (default: 7 days)
|
||||
- **Cache Management**: Comprehensive cache statistics, cleanup, and monitoring
|
||||
- **Service Isolation**: Per-service cache namespacing and configuration
|
||||
- **Operation Types**: Supports both query operations (return cached data) and mutation operations (ignore failures with history)
|
||||
|
||||
**Core Methods:**
|
||||
```java
|
||||
// Query operations with fallback
|
||||
<T> 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
|
||||
```yaml
|
||||
integration:
|
||||
fallback:
|
||||
enabled: true # Global fallback switch
|
||||
cachePrefix: "integration:fallback:" # Global cache prefix
|
||||
defaultTtlDays: 7 # Default cache TTL in days
|
||||
|
||||
# Service-specific configurations
|
||||
scenic:
|
||||
enabled: true # Service-specific fallback switch
|
||||
ttlDays: 10 # Custom TTL for scenic service
|
||||
cachePrefix: "scenic:fallback:" # Custom cache prefix (optional)
|
||||
|
||||
device:
|
||||
enabled: true
|
||||
ttlDays: 5 # Custom TTL for device service
|
||||
```
|
||||
|
||||
#### Configuration Priority
|
||||
1. **Service-specific settings**: Override global defaults when specified
|
||||
2. **Global defaults**: Used when service-specific settings are not provided
|
||||
3. **Hardcoded defaults**: Final fallback when no configuration is available
|
||||
|
||||
### Cache Key Strategy
|
||||
|
||||
Cache keys follow a standardized naming convention:
|
||||
```
|
||||
{cachePrefix}{serviceName}:{operationType}:{resourceId}
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
- `integration:fallback:zt-device:device:1001` - Device info cache
|
||||
- `integration:fallback:zt-device:device:config:1001` - Device config cache
|
||||
- `integration:fallback:zt-scenic:scenic:2001` - Scenic info cache
|
||||
- `integration:fallback:zt-scenic:scenic:flat:config:2001` - Scenic flat config cache
|
||||
|
||||
## Scenic Integration (ZT-Scenic Microservice)
|
||||
|
||||
### Key Components
|
||||
|
||||
#### Feign Clients
|
||||
- **ScenicV2Client**: Main scenic operations (CRUD, filtering, listing)
|
||||
- **ScenicConfigV2Client**: Scenic configuration management
|
||||
- **DefaultConfigClient**: Default configuration operations
|
||||
|
||||
#### Services
|
||||
- **ScenicIntegrationService**: High-level scenic operations (with automatic fallback)
|
||||
- **ScenicConfigIntegrationService**: Configuration management (with automatic fallback)
|
||||
- **DefaultConfigIntegrationService**: Default configuration handling
|
||||
|
||||
#### Configuration
|
||||
```yaml
|
||||
integration:
|
||||
scenic:
|
||||
enabled: true
|
||||
serviceName: zt-scenic
|
||||
connectTimeout: 5000
|
||||
readTimeout: 10000
|
||||
retryEnabled: false
|
||||
maxRetries: 3
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Basic Scenic Operations (with Automatic Fallback)
|
||||
```java
|
||||
@Autowired
|
||||
private ScenicIntegrationService scenicService;
|
||||
|
||||
// Get scenic with configuration (automatically falls back to cache on failure)
|
||||
ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId);
|
||||
|
||||
// Get scenic basic info (automatically falls back to cache on failure)
|
||||
ScenicV2DTO scenicInfo = scenicService.getScenic(scenicId);
|
||||
|
||||
// Get flat configuration (automatically falls back to cache on failure)
|
||||
Map<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)
|
||||
```java
|
||||
@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
|
||||
```java
|
||||
@Autowired
|
||||
private IntegrationFallbackService fallbackService;
|
||||
|
||||
// Check fallback cache status
|
||||
boolean hasScenicCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:2001");
|
||||
boolean hasConfigCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:flat:configs:2001");
|
||||
|
||||
// Get cache statistics
|
||||
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-scenic");
|
||||
log.info("Scenic fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays());
|
||||
|
||||
// Clear specific cache
|
||||
fallbackService.clearFallbackCache("zt-scenic", "scenic:2001");
|
||||
|
||||
// Clear all scenic caches
|
||||
fallbackService.clearAllFallbackCache("zt-scenic");
|
||||
```
|
||||
|
||||
## Device Integration (ZT-Device Microservice)
|
||||
|
||||
### Key Components
|
||||
|
||||
#### Feign Clients
|
||||
- **DeviceV2Client**: Main device operations (CRUD, filtering, listing)
|
||||
- **DeviceConfigV2Client**: Device configuration management
|
||||
|
||||
#### Services
|
||||
- **DeviceIntegrationService**: High-level device operations (with automatic fallback)
|
||||
- **DeviceConfigIntegrationService**: Device configuration management (with automatic fallback)
|
||||
|
||||
#### Configuration
|
||||
```yaml
|
||||
integration:
|
||||
device:
|
||||
enabled: true
|
||||
serviceName: zt-device
|
||||
connectTimeout: 5000
|
||||
readTimeout: 10000
|
||||
retryEnabled: false
|
||||
maxRetries: 3
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Basic Device Operations (with Automatic Fallback)
|
||||
```java
|
||||
@Autowired
|
||||
private DeviceIntegrationService deviceService;
|
||||
|
||||
// Create IPC camera device (operation tracked for future fallback)
|
||||
DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId);
|
||||
|
||||
// Get device with configuration (automatically falls back to cache on failure)
|
||||
DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId);
|
||||
|
||||
// Get device by number (automatically falls back to cache on failure)
|
||||
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001");
|
||||
|
||||
// Get basic device info (automatically falls back to cache on failure)
|
||||
DeviceV2DTO basicDevice = deviceService.getDevice(deviceId);
|
||||
|
||||
// List scenic devices (no fallback for list operations)
|
||||
DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10);
|
||||
|
||||
// Enable/disable device (direct operation, fails immediately on error)
|
||||
deviceService.enableDevice(deviceId);
|
||||
deviceService.disableDevice(deviceId);
|
||||
|
||||
// Update device (direct operation, fails immediately on error)
|
||||
UpdateDeviceRequest updateRequest = new UpdateDeviceRequest();
|
||||
updateRequest.setName("更新后的摄像头");
|
||||
deviceService.updateDevice(deviceId, updateRequest);
|
||||
```
|
||||
|
||||
#### Device Configuration Management (with Automatic Fallback)
|
||||
```java
|
||||
@Autowired
|
||||
private DeviceConfigIntegrationService configService;
|
||||
|
||||
// Get device configurations (with fallback)
|
||||
List<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
|
||||
```java
|
||||
// Create and configure camera in one operation (both operations direct, no fallback)
|
||||
DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId);
|
||||
// Use batch configuration to set camera parameters (direct operation)
|
||||
BatchDeviceConfigRequest cameraConfig = configService.createBatchConfigBuilder()
|
||||
.addConfig("ip_address", "192.168.1.100")
|
||||
.addConfig("resolution", "1920x1080")
|
||||
.addConfig("framerate", "30")
|
||||
.addConfig("protocol", "RTSP")
|
||||
.addConfig("username", "admin")
|
||||
.addConfig("password", "password")
|
||||
.build();
|
||||
configService.batchUpdateDeviceConfig(camera.getId(), cameraConfig);
|
||||
|
||||
// Get scenic camera status
|
||||
DeviceV2WithConfigListResponse camerasWithConfig =
|
||||
deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId);
|
||||
|
||||
// Batch update camera resolution (direct operations, no fallback)
|
||||
for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) {
|
||||
BatchDeviceConfigRequest resolutionUpdate = configService.createBatchConfigBuilder()
|
||||
.addConfig("resolution", "2560x1440")
|
||||
.build();
|
||||
configService.batchUpdateDeviceConfig(device.getId(), resolutionUpdate);
|
||||
}
|
||||
|
||||
// Monitor device configuration completeness (with automatic fallback)
|
||||
for (DeviceV2DTO device : activeDevices.getList()) {
|
||||
Map<String, Object> config = configService.getDeviceFlatConfig(device.getId());
|
||||
boolean hasIpConfig = config.containsKey("ip_address");
|
||||
// log configuration status...
|
||||
}
|
||||
```
|
||||
|
||||
#### Fallback Cache Management for Devices
|
||||
```java
|
||||
@Autowired
|
||||
private IntegrationFallbackService fallbackService;
|
||||
|
||||
// Check fallback cache status
|
||||
boolean hasDeviceCache = fallbackService.hasFallbackCache("zt-device", "device:1001");
|
||||
boolean hasConfigCache = fallbackService.hasFallbackCache("zt-device", "device:flat:config:1001");
|
||||
|
||||
// Get cache statistics
|
||||
IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-device");
|
||||
log.info("Device fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays());
|
||||
|
||||
// Clear specific cache
|
||||
fallbackService.clearFallbackCache("zt-device", "device:1001");
|
||||
|
||||
// Clear all device caches
|
||||
fallbackService.clearAllFallbackCache("zt-device");
|
||||
```
|
||||
|
||||
### Enhanced Batch Configuration API
|
||||
|
||||
Device Integration now supports an enhanced batch configuration API that provides detailed processing results and supports default configuration rules.
|
||||
|
||||
#### Default Configuration Rules
|
||||
The system uses `device_id = 0` configurations as default templates:
|
||||
- **Configurations with defaults**: System enforces default `config_type` and `description`, only `configValue` is updated
|
||||
- **Configurations without defaults**: Allows custom `config_type` and `description`
|
||||
|
||||
#### Usage Examples
|
||||
```java
|
||||
// 1. Using Builder Pattern
|
||||
BatchDeviceConfigRequest request = configService.createBatchConfigBuilder()
|
||||
.addVideoConfig("1920x1080", 30, "H264") // Video settings
|
||||
.addNetworkConfig("192.168.1.100", 554, "RTSP") // Network settings
|
||||
.addAuthConfig("admin", "password123") // Authentication
|
||||
.addConfig("recording_enabled", "true") // Use default config rule
|
||||
.addConfig("custom_path", "/data", "string", "Storage path") // Custom config
|
||||
.build();
|
||||
|
||||
BatchUpdateResponse result = configService.batchUpdateDeviceConfigWithResult(deviceId, request);
|
||||
|
||||
// 2. Processing Results
|
||||
log.info("Batch update: {} success, {} failed", result.getSuccess(), result.getFailed());
|
||||
|
||||
for (ProcessedConfigItem item : result.getProcessedItems()) {
|
||||
if ("success".equals(item.getStatus())) {
|
||||
log.info("✅ {} updated (action: {}, hasDefault: {})",
|
||||
item.getConfigKey(), item.getAction(), item.getHasDefault());
|
||||
} else {
|
||||
log.warn("❌ {} failed: {}", item.getConfigKey(), item.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Error Handling
|
||||
if (result.getFailed() > 0) {
|
||||
result.getErrors().forEach(error -> log.warn("Error: {}", error));
|
||||
}
|
||||
```
|
||||
|
||||
#### Response Format
|
||||
- **200 OK**: All configurations updated successfully
|
||||
- **202 Accepted**: Partial success (some configurations failed)
|
||||
- **400 Bad Request**: All configurations failed
|
||||
|
||||
Each processed item includes:
|
||||
- `status`: "success" or "failed"
|
||||
- `action`: "create" or "update"
|
||||
- `hasDefault`: Whether default configuration rules were applied
|
||||
- `finalType`/`finalDescription`: Actually used type and description
|
||||
|
||||
### Device Types
|
||||
- **IPC**: IP Camera devices for video monitoring
|
||||
- **CUSTOM**: Custom device types for sensors, controllers, etc.
|
||||
|
||||
### Common Configuration Keys
|
||||
- `ip_address`: Device IP address
|
||||
- `resolution`: Video resolution (e.g., "1920x1080", "3840x2160")
|
||||
- `framerate`: Video frame rate (integer)
|
||||
- `protocol`: Communication protocol (e.g., "RTSP", "HTTP")
|
||||
- `username`: Authentication username
|
||||
- `password`: Authentication password
|
||||
- `brightness`: Display brightness (0-100)
|
||||
- `contrast`: Display contrast (0-100)
|
||||
- `quality`: Video quality ("low", "medium", "high")
|
||||
|
||||
## Adding New Service Integrations
|
||||
|
||||
### 1. Create Package Structure
|
||||
```
|
||||
com.ycwl.basic.integration.{service-name}/
|
||||
├── client/
|
||||
├── config/
|
||||
├── dto/
|
||||
├── service/
|
||||
└── example/
|
||||
```
|
||||
|
||||
### 2. Add Configuration Properties
|
||||
Update `IntegrationProperties` to include new service configuration:
|
||||
```java
|
||||
@Data
|
||||
public static class NewServiceConfig {
|
||||
private boolean enabled = true;
|
||||
private String serviceName = "service-name";
|
||||
private int connectTimeout = 5000;
|
||||
private int readTimeout = 10000;
|
||||
// other configs...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create Feign Client
|
||||
```java
|
||||
@FeignClient(name = "service-name", contextId = "service-context", path = "/api/path")
|
||||
public interface NewServiceClient {
|
||||
@GetMapping("/endpoint")
|
||||
CommonResponse<ResponseDTO> getData(@PathVariable Long id);
|
||||
// other endpoints...
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Implement Service Layer
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class NewServiceIntegrationService {
|
||||
private final NewServiceClient client;
|
||||
|
||||
public ResponseDTO getData(Long id) {
|
||||
CommonResponse<ResponseDTO> response = client.getData(id);
|
||||
return handleResponse(response, "Failed to get data");
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "service-name");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### IntegrationException
|
||||
All integration failures are wrapped in `IntegrationException`:
|
||||
```java
|
||||
public class IntegrationException extends RuntimeException {
|
||||
private final Integer code;
|
||||
private final String serviceName;
|
||||
// constructors and getters...
|
||||
}
|
||||
```
|
||||
|
||||
### FeignErrorDecoder
|
||||
Automatically converts Feign errors to IntegrationException:
|
||||
- Parses CommonResponse error format
|
||||
- Extracts service name from method key
|
||||
- Provides meaningful error messages
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Properties Structure
|
||||
```yaml
|
||||
integration:
|
||||
# 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
|
||||
connectTimeout: 5000
|
||||
readTimeout: 10000
|
||||
retryEnabled: false
|
||||
maxRetries: 3
|
||||
device:
|
||||
enabled: true
|
||||
serviceName: zt-device
|
||||
connectTimeout: 5000
|
||||
readTimeout: 10000
|
||||
retryEnabled: false
|
||||
maxRetries: 3
|
||||
```
|
||||
|
||||
### Configuration Refresh
|
||||
Uses `@RefreshScope` to support dynamic configuration updates without restart.
|
||||
|
||||
## Testing Integration Services
|
||||
|
||||
### Unit Testing
|
||||
Test service layers by mocking Feign clients:
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ScenicIntegrationServiceTest {
|
||||
@Mock
|
||||
private ScenicV2Client scenicV2Client;
|
||||
|
||||
@InjectMocks
|
||||
private ScenicIntegrationService scenicService;
|
||||
|
||||
@Test
|
||||
void testGetScenic() {
|
||||
// Mock response
|
||||
CommonResponse<ScenicV2DTO> response = new CommonResponse<>();
|
||||
response.setSuccess(true);
|
||||
response.setData(new ScenicV2DTO());
|
||||
|
||||
when(scenicV2Client.getScenic(1L)).thenReturn(response);
|
||||
|
||||
// Test
|
||||
ScenicV2DTO result = scenicService.getScenic(1L);
|
||||
assertNotNull(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
Use `@SpringBootTest` with test profiles and mock external services.
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Running Integration Tests
|
||||
```bash
|
||||
# Run specific integration test class
|
||||
mvn test -Dtest=ScenicIntegrationServiceTest
|
||||
|
||||
# Run all integration tests
|
||||
mvn test -Dtest="com.ycwl.basic.integration.*Test"
|
||||
|
||||
# Run device integration tests
|
||||
mvn test -Dtest=DeviceIntegrationServiceTest
|
||||
mvn test -Dtest="com.ycwl.basic.integration.device.*Test"
|
||||
```
|
||||
|
||||
### Adding New DTOs
|
||||
1. Create DTO classes in the appropriate `dto` package
|
||||
2. Follow existing patterns with proper Jackson annotations
|
||||
3. Use `@JsonProperty` for field mapping when needed
|
||||
|
||||
### Debugging Integration Issues
|
||||
1. Enable Feign client logging in `application-dev.yml`:
|
||||
```yaml
|
||||
logging:
|
||||
level:
|
||||
com.ycwl.basic.integration: DEBUG
|
||||
```
|
||||
|
||||
2. Check Nacos service discovery in development
|
||||
3. Verify service configurations and timeouts
|
||||
4. Review FeignErrorDecoder logs for detailed error information
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Response Handling
|
||||
- Always use the `handleResponse` pattern for consistent error handling
|
||||
- Provide meaningful error messages for business context
|
||||
- Include service name in IntegrationException for debugging
|
||||
|
||||
### 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
|
@@ -11,11 +11,21 @@ import org.springframework.stereotype.Component;
|
||||
@ConfigurationProperties(prefix = "integration")
|
||||
public class IntegrationProperties {
|
||||
|
||||
/**
|
||||
* 降级服务配置
|
||||
*/
|
||||
private FallbackConfig fallback = new FallbackConfig();
|
||||
|
||||
/**
|
||||
* 景区服务配置
|
||||
*/
|
||||
private ScenicConfig scenic = new ScenicConfig();
|
||||
|
||||
/**
|
||||
* 设备服务配置
|
||||
*/
|
||||
private DeviceConfig device = new DeviceConfig();
|
||||
|
||||
@Data
|
||||
public static class ScenicConfig {
|
||||
/**
|
||||
@@ -40,4 +50,71 @@ public class IntegrationProperties {
|
||||
private boolean retryEnabled = false;
|
||||
private int maxRetries = 3;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DeviceConfig {
|
||||
/**
|
||||
* 是否启用设备服务集成
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 服务名称
|
||||
*/
|
||||
private String serviceName = "zt-device";
|
||||
|
||||
/**
|
||||
* 超时配置(毫秒)
|
||||
*/
|
||||
private int connectTimeout = 5000;
|
||||
private int readTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 重试配置
|
||||
*/
|
||||
private boolean retryEnabled = false;
|
||||
private int maxRetries = 3;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,406 @@
|
||||
package com.ycwl.basic.integration.common.manager;
|
||||
|
||||
import com.ycwl.basic.utils.JacksonUtil;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用配置管理器基类
|
||||
* 提供配置查找、类型转换等通用功能
|
||||
*
|
||||
* @param <T> 配置DTO类型
|
||||
*/
|
||||
public abstract class ConfigManager<T> {
|
||||
|
||||
protected List<T> configs;
|
||||
|
||||
public ConfigManager(List<T> configs) {
|
||||
this.configs = configs != null ? configs : new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置项的键
|
||||
* 子类需要实现此方法来提取配置键
|
||||
*/
|
||||
protected abstract String getConfigKey(T config);
|
||||
|
||||
/**
|
||||
* 获取配置项的值
|
||||
* 子类需要实现此方法来提取配置值
|
||||
*/
|
||||
protected abstract Object getConfigValue(T config);
|
||||
|
||||
/**
|
||||
* 根据键查找配置项
|
||||
*/
|
||||
protected T findConfigByKey(String key) {
|
||||
if (key == null || configs == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return configs.stream()
|
||||
.filter(config -> key.equals(getConfigKey(config)))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串配置值
|
||||
*/
|
||||
public String getString(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字符串配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public String getString(String key, String defaultValue) {
|
||||
String value = getString(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取整型配置值
|
||||
*/
|
||||
public Integer getInteger(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Integer) return (Integer) value;
|
||||
if (value instanceof Number) return ((Number) value).intValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Integer.parseInt((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取整型配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public Integer getInteger(String key, Integer defaultValue) {
|
||||
Integer value = getInteger(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长整型配置值
|
||||
*/
|
||||
public Long getLong(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Long) return (Long) value;
|
||||
if (value instanceof Number) return ((Number) value).longValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Long.parseLong((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长整型配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public Long getLong(String key, Long defaultValue) {
|
||||
Long value = getLong(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔型配置值
|
||||
*/
|
||||
public Boolean getBoolean(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Boolean) return (Boolean) value;
|
||||
if (value instanceof String) {
|
||||
String str = ((String) value).toLowerCase().trim();
|
||||
return "true".equals(str) || "1".equals(str) || "yes".equals(str);
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue() != 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔型配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public Boolean getBoolean(String key, Boolean defaultValue) {
|
||||
Boolean value = getBoolean(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点型配置值
|
||||
*/
|
||||
public Float getFloat(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
if (value instanceof Float) return (Float) value;
|
||||
if (value instanceof Number) return ((Number) value).floatValue();
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Float.parseFloat((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点型配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public Float getFloat(String key, Float defaultValue) {
|
||||
Float value = getFloat(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取BigDecimal配置值
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
if (value instanceof BigDecimal) return (BigDecimal) value;
|
||||
if (value instanceof Number) return new BigDecimal(value.toString());
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return new BigDecimal((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取BigDecimal配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
|
||||
BigDecimal value = getBigDecimal(key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取枚举配置值
|
||||
*/
|
||||
public <E extends Enum<E>> E getEnum(String key, Class<E> enumClass) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
return parseEnum(value, enumClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析枚举值
|
||||
*/
|
||||
private <E extends Enum<E>> E parseEnum(Object value, Class<E> enumClass) {
|
||||
if (value == null) return null;
|
||||
try {
|
||||
if (value instanceof String) {
|
||||
return Enum.valueOf(enumClass, (String) value);
|
||||
}
|
||||
return Enum.valueOf(enumClass, value.toString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取枚举配置值,如果不存在则返回默认值
|
||||
*/
|
||||
public <E extends Enum<E>> E getEnum(String key, Class<E> enumClass, E defaultValue) {
|
||||
E value = getEnum(key, enumClass);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定键的配置是否存在
|
||||
*/
|
||||
public boolean hasConfig(String key) {
|
||||
return findConfigByKey(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置项的数量
|
||||
*/
|
||||
public int size() {
|
||||
return configs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置项
|
||||
*/
|
||||
public List<T> getAllConfigs() {
|
||||
return new ArrayList<>(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始对象配置值
|
||||
*/
|
||||
public Object getObject(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
return getConfigValue(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并转换为指定类型的对象
|
||||
* 支持JSON字符串自动反序列化
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R> R getObject(String key, Class<R> clazz) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
|
||||
// 如果类型匹配,直接返回
|
||||
if (clazz.isInstance(value)) {
|
||||
return (R) value;
|
||||
}
|
||||
|
||||
// 如果是String类型的JSON,尝试反序列化
|
||||
if (value instanceof String && !clazz.equals(String.class)) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, clazz);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果目标是String,直接转换
|
||||
if (clazz.equals(String.class)) {
|
||||
return (R) value.toString();
|
||||
}
|
||||
|
||||
// 其他情况尝试JSON转换
|
||||
try {
|
||||
String json = JacksonUtil.toJSONString(value);
|
||||
return JacksonUtil.parseObject(json, clazz);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Map类型的配置值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> getMap(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof Map) {
|
||||
return (Map<String, Object>) value;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, Map.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取List类型的配置值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Object> getList(String key) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof List) {
|
||||
return (List<Object>) value;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseObject((String) value, List.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定元素类型的List配置值
|
||||
*/
|
||||
public <R> List<R> getList(String key, Class<R> elementClass) {
|
||||
T config = findConfigByKey(key);
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = getConfigValue(config);
|
||||
if (value == null) return null;
|
||||
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return JacksonUtil.parseArray((String) value, elementClass);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String json = JacksonUtil.toJSONString(value);
|
||||
return JacksonUtil.parseArray(json, elementClass);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.ycwl.basic.integration.common.manager;
|
||||
|
||||
import com.ycwl.basic.integration.device.dto.config.DeviceConfigV2DTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备配置管理器
|
||||
* 基于通用ConfigManager实现设备配置的管理功能
|
||||
*/
|
||||
public class DeviceConfigManager extends ConfigManager<DeviceConfigV2DTO> {
|
||||
|
||||
public DeviceConfigManager(List<DeviceConfigV2DTO> configs) {
|
||||
super(configs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfigKey(DeviceConfigV2DTO config) {
|
||||
return config != null ? config.getConfigKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getConfigValue(DeviceConfigV2DTO config) {
|
||||
return config != null ? config.getConfigValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备配置类型
|
||||
*/
|
||||
public String getConfigType(String key) {
|
||||
DeviceConfigV2DTO config = findConfigByKey(key);
|
||||
return config != null ? config.getConfigType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备配置描述
|
||||
*/
|
||||
public String getConfigDescription(String key) {
|
||||
DeviceConfigV2DTO config = findConfigByKey(key);
|
||||
return config != null ? config.getDescription() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备配置ID
|
||||
*/
|
||||
public Long getConfigId(String key) {
|
||||
DeviceConfigV2DTO config = findConfigByKey(key);
|
||||
return config != null ? config.getId() : null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,330 @@
|
||||
package com.ycwl.basic.integration.common.manager;
|
||||
|
||||
import com.ycwl.basic.integration.common.util.ConfigValueUtil;
|
||||
import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 景区配置管理器
|
||||
* 基于通用ConfigManager实现景区配置的管理功能
|
||||
*
|
||||
* 提供类型安全的配置值获取功能,支持多种数据类型的自动转换,
|
||||
* 当类型不兼容时返回null而不是抛出异常。
|
||||
*/
|
||||
public class ScenicConfigManager extends ConfigManager<ScenicConfigV2DTO> {
|
||||
|
||||
private final Map<String, Object> configMap;
|
||||
|
||||
/**
|
||||
* 从配置列表构造管理器
|
||||
*
|
||||
* @param configList 配置项列表
|
||||
*/
|
||||
public ScenicConfigManager(List<ScenicConfigV2DTO> configList) {
|
||||
super(configList);
|
||||
this.configMap = new HashMap<>();
|
||||
if (configList != null) {
|
||||
for (ScenicConfigV2DTO config : configList) {
|
||||
if (config.getConfigKey() != null && config.getConfigValue() != null) {
|
||||
this.configMap.put(config.getConfigKey(), config.getConfigValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置Map构造管理器
|
||||
*
|
||||
* @param configMap 配置Map
|
||||
*/
|
||||
public ScenicConfigManager(Map<String, Object> configMap) {
|
||||
super(null); // 使用Map构造时,父类configs为null
|
||||
this.configMap = configMap != null ? new HashMap<>(configMap) : new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfigKey(ScenicConfigV2DTO config) {
|
||||
return config != null ? config.getConfigKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getConfigValue(ScenicConfigV2DTO config) {
|
||||
return config != null ? config.getConfigValue() : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取长整数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Long值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Long getLong(String key) {
|
||||
return ConfigValueUtil.getLongValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取长整数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Long值或默认值
|
||||
*/
|
||||
public Long getLong(String key, Long defaultValue) {
|
||||
Long value = ConfigValueUtil.getLongValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Float值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Float getFloat(String key) {
|
||||
return ConfigValueUtil.getFloatValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浮点数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Float值或默认值
|
||||
*/
|
||||
public Float getFloat(String key, Float defaultValue) {
|
||||
Float value = ConfigValueUtil.getFloatValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双精度浮点数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Double值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Double getDouble(String key) {
|
||||
return ConfigValueUtil.getDoubleValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取双精度浮点数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Double值或默认值
|
||||
*/
|
||||
public Double getDouble(String key, Double defaultValue) {
|
||||
Double value = ConfigValueUtil.getDoubleValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高精度小数值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return BigDecimal值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key) {
|
||||
return ConfigValueUtil.getBigDecimalValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高精度小数值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return BigDecimal值或默认值
|
||||
*/
|
||||
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
|
||||
BigDecimal value = ConfigValueUtil.getBigDecimalValue(configMap, key);
|
||||
return value != null ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return Boolean值,如果键不存在或转换失败返回null
|
||||
*/
|
||||
public Boolean getBoolean(String key) {
|
||||
return ConfigValueUtil.getBooleanValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布尔值,如果为null则返回默认值
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return Boolean值或默认值
|
||||
*/
|
||||
public Boolean getBoolean(String key, Boolean defaultValue) {
|
||||
return ConfigValueUtil.getBooleanValue(configMap, key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return true如果键存在,false如果不存在
|
||||
*/
|
||||
public boolean hasKey(String key) {
|
||||
return ConfigValueUtil.hasKey(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置键是否存在且值不为null
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return true如果键存在且值不为null
|
||||
*/
|
||||
public boolean hasNonNullValue(String key) {
|
||||
return ConfigValueUtil.hasNonNullValue(configMap, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置键
|
||||
*
|
||||
* @return 配置键集合
|
||||
*/
|
||||
public Set<String> getAllKeys() {
|
||||
return new HashSet<>(configMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置项数量
|
||||
*
|
||||
* @return 配置项数量
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return configMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置是否为空
|
||||
*
|
||||
* @return true如果没有配置项
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return configMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置的拷贝
|
||||
*
|
||||
* @return 配置Map的拷贝
|
||||
*/
|
||||
public Map<String, Object> getAllConfigsAsMap() {
|
||||
return new HashMap<>(configMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据键前缀过滤配置
|
||||
*
|
||||
* @param prefix 键前缀
|
||||
* @return 匹配前缀的配置Map
|
||||
*/
|
||||
public Map<String, Object> getConfigsByPrefix(String prefix) {
|
||||
if (prefix == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
return configMap.entrySet().stream()
|
||||
.filter(entry -> entry.getKey() != null && entry.getKey().startsWith(prefix))
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
Map.Entry::getValue
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的ScenicConfigManager,包含当前配置的子集
|
||||
*
|
||||
* @param keys 要包含的配置键
|
||||
* @return 包含指定键配置的新管理器
|
||||
*/
|
||||
public ScenicConfigManager subset(Set<String> keys) {
|
||||
Map<String, Object> subsetMap = new HashMap<>();
|
||||
if (keys != null) {
|
||||
for (String key : keys) {
|
||||
if (configMap.containsKey(key)) {
|
||||
subsetMap.put(key, configMap.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ScenicConfigManager(subsetMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将配置转换为扁平化的Map,键名转换为驼峰形式
|
||||
*
|
||||
* @return 扁平化的配置Map,键为驼峰形式
|
||||
*/
|
||||
public Map<String, Object> toFlatConfig() {
|
||||
Map<String, Object> flatConfig = new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : configMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (key != null) {
|
||||
String camelCaseKey = toCamelCase(key);
|
||||
flatConfig.put(camelCaseKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return flatConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串转换为驼峰形式
|
||||
* 支持下划线、短横线、点号分隔的字符串转换
|
||||
*
|
||||
* @param str 原始字符串
|
||||
* @return 驼峰形式的字符串
|
||||
*/
|
||||
private String toCamelCase(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// 支持下划线、短横线、点号作为分隔符
|
||||
String[] parts = str.split("[_\\-.]");
|
||||
|
||||
if (parts.length <= 1) {
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder camelCase = new StringBuilder();
|
||||
|
||||
// 第一部分保持原样(全小写)
|
||||
camelCase.append(parts[0].toLowerCase());
|
||||
|
||||
// 后续部分首字母大写
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
String part = parts[i];
|
||||
if (!part.isEmpty()) {
|
||||
camelCase.append(Character.toUpperCase(part.charAt(0)));
|
||||
if (part.length() > 1) {
|
||||
camelCase.append(part.substring(1).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return camelCase.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScenicConfigManager{" +
|
||||
"configCount=" + configMap.size() +
|
||||
", keys=" + configMap.keySet() +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -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<String, String> 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 <T> 结果类型
|
||||
* @return 操作结果或缓存的结果
|
||||
*/
|
||||
public <T> T executeWithFallback(String serviceName, String cacheKey, Supplier<T> operation, Class<T> 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> T getFallbackFromCache(String serviceName, String cacheKey, Class<T> 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<String> 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<String> 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package com.ycwl.basic.integration.device.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.device.dto.config.*;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@FeignClient(name = "zt-device", contextId = "device-config-v2", path = "/api/device/config/v2")
|
||||
public interface DeviceConfigV2Client {
|
||||
|
||||
/**
|
||||
* 获取设备所有配置
|
||||
*/
|
||||
@GetMapping("/{deviceId}")
|
||||
CommonResponse<List<DeviceConfigV2DTO>> getDeviceConfigs(@PathVariable("deviceId") Long deviceId);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备所有配置
|
||||
*/
|
||||
@GetMapping("/no/{no}")
|
||||
CommonResponse<List<DeviceConfigV2DTO>> getDeviceConfigsByNo(@PathVariable("no") String no);
|
||||
|
||||
/**
|
||||
* 获取设备特定配置
|
||||
*/
|
||||
@GetMapping("/{deviceId}/key/{configKey}")
|
||||
CommonResponse<DeviceConfigV2DTO> getDeviceConfigByKey(@PathVariable("deviceId") Long deviceId,
|
||||
@PathVariable("configKey") String configKey);
|
||||
|
||||
/**
|
||||
* 获取设备扁平化配置
|
||||
*/
|
||||
@GetMapping("/{deviceId}/flat")
|
||||
CommonResponse<Map<String, Object>> getDeviceFlatConfig(@PathVariable("deviceId") Long deviceId);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备扁平化配置
|
||||
*/
|
||||
@GetMapping("/no/{no}/flat")
|
||||
CommonResponse<Map<String, Object>> getDeviceFlatConfigByNo(@PathVariable("no") String no);
|
||||
|
||||
/**
|
||||
* 创建设备配置
|
||||
*/
|
||||
@PostMapping("/{deviceId}")
|
||||
CommonResponse<DeviceConfigV2DTO> createDeviceConfig(@PathVariable("deviceId") Long deviceId,
|
||||
@RequestBody CreateDeviceConfigRequest request);
|
||||
|
||||
/**
|
||||
* 更新设备配置
|
||||
*/
|
||||
@PutMapping("/{deviceId}/{id}")
|
||||
CommonResponse<String> updateDeviceConfig(@PathVariable("deviceId") Long deviceId,
|
||||
@PathVariable("id") Long id,
|
||||
@RequestBody UpdateDeviceConfigRequest request);
|
||||
|
||||
/**
|
||||
* 删除设备配置
|
||||
*/
|
||||
@DeleteMapping("/{deviceId}/{id}")
|
||||
CommonResponse<String> deleteDeviceConfig(@PathVariable("deviceId") Long deviceId,
|
||||
@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 批量更新设备配置
|
||||
*/
|
||||
@PostMapping("/{deviceId}/batch")
|
||||
CommonResponse<BatchUpdateResponse> batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId,
|
||||
@RequestBody BatchDeviceConfigRequest request);
|
||||
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
package com.ycwl.basic.integration.device.client;
|
||||
|
||||
import com.ycwl.basic.integration.common.response.CommonResponse;
|
||||
import com.ycwl.basic.integration.device.dto.device.*;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@FeignClient(name = "zt-device", contextId = "device-v2", path = "/api/device/v2")
|
||||
public interface DeviceV2Client {
|
||||
|
||||
/**
|
||||
* 获取设备核心信息
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
CommonResponse<DeviceV2DTO> getDevice(@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备核心信息
|
||||
*/
|
||||
@GetMapping("/no/{no}")
|
||||
CommonResponse<DeviceV2DTO> getDeviceByNo(@PathVariable("no") String no);
|
||||
|
||||
/**
|
||||
* 获取设备详细信息(含配置)
|
||||
*/
|
||||
@GetMapping("/{id}/with-config")
|
||||
CommonResponse<DeviceV2WithConfigDTO> getDeviceWithConfig(@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据设备编号获取设备详细信息(含配置)
|
||||
*/
|
||||
@GetMapping("/no/{no}/with-config")
|
||||
CommonResponse<DeviceV2WithConfigDTO> getDeviceByNoWithConfig(@PathVariable("no") String no);
|
||||
|
||||
/**
|
||||
* 创建设备
|
||||
*/
|
||||
@PostMapping("/")
|
||||
CommonResponse<DeviceV2DTO> createDevice(@RequestBody CreateDeviceRequest request);
|
||||
|
||||
/**
|
||||
* 更新设备
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
CommonResponse<String> updateDevice(@PathVariable("id") Long id,
|
||||
@RequestBody UpdateDeviceRequest request);
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
CommonResponse<String> deleteDevice(@PathVariable("id") Long id);
|
||||
|
||||
/**
|
||||
* 分页获取设备列表(核心信息)
|
||||
*/
|
||||
@GetMapping("/")
|
||||
CommonResponse<DeviceV2ListResponse> listDevices(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(value = "name", required = false) String name,
|
||||
@RequestParam(value = "no", required = false) String no,
|
||||
@RequestParam(value = "type", required = false) String type,
|
||||
@RequestParam(value = "isActive", required = false) Integer isActive,
|
||||
@RequestParam(value = "scenicId", required = false) Long scenicId);
|
||||
|
||||
/**
|
||||
* 分页获取设备列表(含配置)
|
||||
*/
|
||||
@GetMapping("/with-config")
|
||||
CommonResponse<DeviceV2WithConfigListResponse> listDevicesWithConfig(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(value = "name", required = false) String name,
|
||||
@RequestParam(value = "no", required = false) String no,
|
||||
@RequestParam(value = "type", required = false) String type,
|
||||
@RequestParam(value = "isActive", required = false) Integer isActive,
|
||||
@RequestParam(value = "scenicId", required = false) Long scenicId);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.ycwl.basic.integration.device.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "integration.device")
|
||||
public class DeviceIntegrationConfig {
|
||||
|
||||
public DeviceIntegrationConfig() {
|
||||
log.info("ZT-Device集成配置初始化完成");
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BatchDeviceConfigRequest {
|
||||
private List<BatchDeviceConfigItem> configs;
|
||||
|
||||
@Data
|
||||
public static class BatchDeviceConfigItem {
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量配置更新响应
|
||||
*/
|
||||
@Data
|
||||
public class BatchUpdateResponse {
|
||||
/**
|
||||
* 成功处理的配置数量
|
||||
*/
|
||||
@JsonProperty("success")
|
||||
private Integer success;
|
||||
|
||||
/**
|
||||
* 失败的配置数量
|
||||
*/
|
||||
@JsonProperty("failed")
|
||||
private Integer failed;
|
||||
|
||||
/**
|
||||
* 处理详情列表
|
||||
*/
|
||||
@JsonProperty("processedItems")
|
||||
private List<ProcessedConfigItem> processedItems;
|
||||
|
||||
/**
|
||||
* 错误信息列表
|
||||
*/
|
||||
@JsonProperty("errors")
|
||||
private List<String> errors;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateDeviceConfigRequest {
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
private Integer isActive;
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class DeviceConfigV2DTO {
|
||||
@JsonProperty("id")
|
||||
private Long id;
|
||||
|
||||
@JsonProperty("deviceId")
|
||||
private Long deviceId;
|
||||
|
||||
@JsonProperty("configKey")
|
||||
private String configKey;
|
||||
|
||||
@JsonProperty("configValue")
|
||||
private String configValue;
|
||||
|
||||
@JsonProperty("configType")
|
||||
private String configType;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("isActive")
|
||||
private Integer isActive;
|
||||
|
||||
@JsonProperty("createTime")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@JsonProperty("updateTime")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 处理的配置项详情
|
||||
*/
|
||||
@Data
|
||||
public class ProcessedConfigItem {
|
||||
/**
|
||||
* 配置键名
|
||||
*/
|
||||
@JsonProperty("configKey")
|
||||
private String configKey;
|
||||
|
||||
/**
|
||||
* 处理状态: success/failed
|
||||
*/
|
||||
@JsonProperty("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 执行动作: create/update
|
||||
*/
|
||||
@JsonProperty("action")
|
||||
private String action;
|
||||
|
||||
/**
|
||||
* 是否有默认配置
|
||||
*/
|
||||
@JsonProperty("hasDefault")
|
||||
private Boolean hasDefault;
|
||||
|
||||
/**
|
||||
* 处理消息
|
||||
*/
|
||||
@JsonProperty("message")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 最终使用的类型
|
||||
*/
|
||||
@JsonProperty("finalType")
|
||||
private String finalType;
|
||||
|
||||
/**
|
||||
* 最终使用的描述
|
||||
*/
|
||||
@JsonProperty("finalDescription")
|
||||
private String finalDescription;
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.ycwl.basic.integration.device.dto.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class UpdateDeviceConfigRequest {
|
||||
private String configKey;
|
||||
private String configValue;
|
||||
private String configType;
|
||||
private String description;
|
||||
private Integer isActive;
|
||||
|
||||
// 支持灵活的字段更新
|
||||
public static UpdateDeviceConfigRequest fromMap(Map<String, Object> updates) {
|
||||
UpdateDeviceConfigRequest request = new UpdateDeviceConfigRequest();
|
||||
if (updates.containsKey("configKey")) {
|
||||
request.setConfigKey((String) updates.get("configKey"));
|
||||
}
|
||||
if (updates.containsKey("configValue")) {
|
||||
request.setConfigValue((String) updates.get("configValue"));
|
||||
}
|
||||
if (updates.containsKey("configType")) {
|
||||
request.setConfigType((String) updates.get("configType"));
|
||||
}
|
||||
if (updates.containsKey("description")) {
|
||||
request.setDescription((String) updates.get("description"));
|
||||
}
|
||||
if (updates.containsKey("isActive")) {
|
||||
request.setIsActive((Integer) updates.get("isActive"));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateDeviceRequest {
|
||||
private String name;
|
||||
private String no;
|
||||
private String type;
|
||||
private Integer isActive;
|
||||
private Long scenicId;
|
||||
private Integer sort;
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class DeviceV2DTO {
|
||||
@JsonProperty("id")
|
||||
private Long id;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("no")
|
||||
private String no;
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type; // IPC, CUSTOM
|
||||
|
||||
@JsonProperty("isActive")
|
||||
private Integer isActive;
|
||||
|
||||
@JsonProperty("scenicId")
|
||||
private Long scenicId;
|
||||
|
||||
@JsonProperty("sort")
|
||||
private Integer sort;
|
||||
|
||||
@JsonProperty("createTime")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@JsonProperty("updateTime")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DeviceV2ListResponse {
|
||||
private List<DeviceV2DTO> list;
|
||||
private Long total;
|
||||
private Integer page;
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DeviceV2WithConfigDTO extends DeviceV2DTO {
|
||||
private Map<String, Object> config;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class DeviceV2WithConfigListResponse {
|
||||
private List<DeviceV2WithConfigDTO> list;
|
||||
private Long total;
|
||||
private Integer page;
|
||||
private Integer pageSize;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.ycwl.basic.integration.device.dto.device;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateDeviceRequest {
|
||||
private String name;
|
||||
private String no;
|
||||
private String type;
|
||||
private Integer isActive;
|
||||
private Long scenicId;
|
||||
private Integer sort;
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
package com.ycwl.basic.integration.device.example;
|
||||
|
||||
import com.ycwl.basic.integration.device.dto.device.*;
|
||||
import com.ycwl.basic.integration.device.dto.config.*;
|
||||
import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService;
|
||||
import com.ycwl.basic.integration.device.service.DeviceIntegrationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Device Integration 使用示例
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceIntegrationExample {
|
||||
|
||||
private final DeviceIntegrationService deviceService;
|
||||
private final DeviceConfigIntegrationService deviceConfigService;
|
||||
|
||||
/**
|
||||
* 基本设备操作
|
||||
*/
|
||||
public void basicDeviceOperations() {
|
||||
log.info("=== 基本设备操作 ===");
|
||||
|
||||
// 创建IPC摄像头设备(默认排序)
|
||||
DeviceV2DTO ipcDevice = deviceService.createIpcDevice(
|
||||
"前门摄像头", "CAM001", 1001L);
|
||||
log.info("创建IPC设备: {}, 排序值: {}", ipcDevice.getName(), ipcDevice.getSort());
|
||||
|
||||
// 根据ID获取设备信息
|
||||
DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId());
|
||||
log.info("获取设备信息: {}, 排序值: {}", device.getName(), device.getSort());
|
||||
|
||||
// 根据设备编号获取设备信息
|
||||
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001");
|
||||
log.info("根据编号获取设备: {}", deviceByNo.getName());
|
||||
|
||||
// 获取设备详细信息(含配置)
|
||||
DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId());
|
||||
log.info("获取设备配置: {}", deviceWithConfig.getName());
|
||||
|
||||
// 分页查询景区设备列表
|
||||
DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10);
|
||||
log.info("景区设备列表: 总数={}", deviceList.getTotal());
|
||||
|
||||
// 启用设备
|
||||
deviceService.enableDevice(ipcDevice.getId());
|
||||
log.info("设备已启用");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备排序功能演示
|
||||
*/
|
||||
public void deviceSortingOperations() {
|
||||
log.info("=== 设备排序功能演示 ===");
|
||||
|
||||
Long scenicId = 1001L;
|
||||
|
||||
// 创建带排序的设备
|
||||
DeviceV2DTO camera1 = deviceService.createIpcDeviceWithSort(
|
||||
"大门摄像头", "CAM_GATE", scenicId, 10);
|
||||
log.info("创建摄像头1: {}, 排序: {}", camera1.getName(), camera1.getSort());
|
||||
|
||||
DeviceV2DTO camera2 = deviceService.createIpcDeviceWithSort(
|
||||
"后门摄像头", "CAM_BACK", scenicId, 20);
|
||||
log.info("创建摄像头2: {}, 排序: {}", camera2.getName(), camera2.getSort());
|
||||
|
||||
DeviceV2DTO sensor1 = deviceService.createCustomDeviceWithSort(
|
||||
"温度传感器", "TEMP_01", scenicId, 5);
|
||||
log.info("创建传感器: {}, 排序: {}", sensor1.getName(), sensor1.getSort());
|
||||
|
||||
// 更新设备排序
|
||||
deviceService.updateDeviceSort(camera1.getId(), 1);
|
||||
log.info("更新摄像头1排序为1(置顶)");
|
||||
|
||||
// 获取排序后的设备列表
|
||||
DeviceV2ListResponse sortedList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId);
|
||||
log.info("排序后的设备列表:");
|
||||
for (DeviceV2DTO device : sortedList.getList()) {
|
||||
log.info(" - {}: 排序={}, 类型={}", device.getName(), device.getSort(), device.getType());
|
||||
}
|
||||
|
||||
// 批量调整排序演示
|
||||
log.info("--- 批量调整排序演示 ---");
|
||||
deviceService.updateDeviceSort(sensor1.getId(), 15); // 传感器排到中间
|
||||
deviceService.updateDeviceSort(camera2.getId(), 30); // 后门摄像头排到最后
|
||||
log.info("批量排序调整完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备配置管理
|
||||
*/
|
||||
public void deviceConfigurationOperations() {
|
||||
log.info("=== 设备配置管理 ===");
|
||||
|
||||
Long deviceId = 1L;
|
||||
|
||||
// 获取设备所有配置
|
||||
List<DeviceConfigV2DTO> configs = deviceConfigService.getDeviceConfigs(deviceId);
|
||||
log.info("设备配置数量: {}", configs.size());
|
||||
|
||||
// 获取扁平化配置
|
||||
Map<String, Object> flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId);
|
||||
log.info("扁平化配置项数: {}", flatConfig.size());
|
||||
|
||||
// 使用批量配置API
|
||||
BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder()
|
||||
.build();
|
||||
|
||||
BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfig(deviceId, builderRequest);
|
||||
log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed());
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序最佳实践演示
|
||||
*/
|
||||
public void sortingBestPractices() {
|
||||
log.info("=== 排序最佳实践演示 ===");
|
||||
|
||||
Long scenicId = 1001L;
|
||||
|
||||
// 推荐使用10的倍数作为排序值
|
||||
DeviceV2DTO device1 = deviceService.createIpcDeviceWithSort(
|
||||
"重要摄像头", "CAM_IMPORTANT", scenicId, 10);
|
||||
|
||||
DeviceV2DTO device2 = deviceService.createIpcDeviceWithSort(
|
||||
"普通摄像头", "CAM_NORMAL", scenicId, 20);
|
||||
|
||||
DeviceV2DTO device3 = deviceService.createIpcDeviceWithSort(
|
||||
"备用摄像头", "CAM_BACKUP", scenicId, 30);
|
||||
|
||||
log.info("使用10的倍数创建设备排序: 10, 20, 30");
|
||||
|
||||
// 在中间插入新设备
|
||||
DeviceV2DTO insertDevice = deviceService.createIpcDeviceWithSort(
|
||||
"中间摄像头", "CAM_MIDDLE", scenicId, 25);
|
||||
log.info("在20和30之间插入设备,排序值: 25");
|
||||
|
||||
// 置顶操作
|
||||
deviceService.updateDeviceSort(device2.getId(), 1);
|
||||
log.info("将普通摄像头置顶(排序值: 1)");
|
||||
|
||||
// 查看最终排序结果
|
||||
DeviceV2ListResponse finalList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId);
|
||||
log.info("最终排序结果:");
|
||||
for (DeviceV2DTO device : finalList.getList()) {
|
||||
log.info(" - {}: 排序={}", device.getName(), device.getSort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行所有示例
|
||||
*/
|
||||
public void runAllExamples() {
|
||||
try {
|
||||
basicDeviceOperations();
|
||||
deviceSortingOperations();
|
||||
sortingBestPractices();
|
||||
deviceConfigurationOperations();
|
||||
log.info("=== 所有示例执行完成 ===");
|
||||
} catch (Exception e) {
|
||||
log.error("示例执行过程中发生错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行基础示例(简化版)
|
||||
*/
|
||||
public void runBasicExamples() {
|
||||
try {
|
||||
basicDeviceOperations();
|
||||
deviceConfigurationOperations();
|
||||
log.info("=== 基础示例执行完成 ===");
|
||||
} catch (Exception e) {
|
||||
log.error("示例执行过程中发生错误", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
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.dto.device.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 设备集成示例(包含降级机制)
|
||||
* 演示设备集成和失败降级策略的使用
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceIntegrationFallbackExample {
|
||||
|
||||
private final DeviceIntegrationService deviceService;
|
||||
private final DeviceConfigIntegrationService configService;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
private static final String SERVICE_NAME = "zt-device";
|
||||
|
||||
/**
|
||||
* 演示设备信息获取的降级机制
|
||||
*/
|
||||
public void deviceInfoFallbackExample() {
|
||||
log.info("=== 设备信息获取降级示例 ===");
|
||||
|
||||
Long deviceId = 1001L;
|
||||
String deviceNo = "CAM001";
|
||||
|
||||
try {
|
||||
// 获取设备信息 - 自动降级
|
||||
DeviceV2DTO device = deviceService.getDevice(deviceId);
|
||||
log.info("获取设备成功: {}", device.getName());
|
||||
|
||||
// 根据设备号获取设备 - 自动降级
|
||||
DeviceV2DTO deviceByNo = deviceService.getDeviceByNo(deviceNo);
|
||||
log.info("根据设备号获取设备成功: {}", deviceByNo.getName());
|
||||
|
||||
// 获取设备配置 - 自动降级
|
||||
DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(deviceId);
|
||||
log.info("获取设备配置成功,配置数量: {}", deviceWithConfig.getConfig().size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("所有降级策略失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示设备操作(无降级机制)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示降级缓存管理
|
||||
*/
|
||||
public void fallbackCacheManagementExample() {
|
||||
log.info("=== 降级缓存管理示例 ===");
|
||||
|
||||
String deviceCacheKey = "device:1001";
|
||||
String configCacheKey = "device:flat:config:1001";
|
||||
|
||||
// 检查降级缓存状态
|
||||
boolean hasDeviceCache = fallbackService.hasFallbackCache(SERVICE_NAME, deviceCacheKey);
|
||||
boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey);
|
||||
|
||||
log.info("设备降级缓存存在: {}", hasDeviceCache);
|
||||
log.info("配置降级缓存存在: {}", hasConfigCache);
|
||||
|
||||
// 清理特定的降级缓存
|
||||
if (hasDeviceCache) {
|
||||
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 runAllExamples() {
|
||||
log.info("开始运行设备集成示例...");
|
||||
|
||||
deviceInfoFallbackExample();
|
||||
deviceOperationExample();
|
||||
fallbackCacheManagementExample();
|
||||
|
||||
log.info("设备集成示例运行完成");
|
||||
}
|
||||
}
|
@@ -0,0 +1,167 @@
|
||||
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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceConfigIntegrationService {
|
||||
|
||||
private final DeviceConfigV2Client deviceConfigV2Client;
|
||||
private final IntegrationFallbackService fallbackService;
|
||||
|
||||
private static final String SERVICE_NAME = "zt-device";
|
||||
|
||||
public List<DeviceConfigV2DTO> getDeviceConfigs(Long deviceId) {
|
||||
log.info("获取设备配置列表, deviceId: {}", deviceId);
|
||||
CommonResponse<List<DeviceConfigV2DTO>> response = deviceConfigV2Client.getDeviceConfigs(deviceId);
|
||||
return handleResponse(response, "获取设备配置列表失败");
|
||||
}
|
||||
|
||||
public List<DeviceConfigV2DTO> getDeviceConfigsByNo(String deviceNo) {
|
||||
log.info("根据设备编号获取配置列表, deviceNo: {}", deviceNo);
|
||||
CommonResponse<List<DeviceConfigV2DTO>> response = deviceConfigV2Client.getDeviceConfigsByNo(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取配置列表失败");
|
||||
}
|
||||
|
||||
public DeviceConfigV2DTO getDeviceConfigByKey(Long deviceId, String configKey) {
|
||||
log.info("根据键获取设备配置, deviceId: {}, configKey: {}", deviceId, configKey);
|
||||
CommonResponse<DeviceConfigV2DTO> response = deviceConfigV2Client.getDeviceConfigByKey(deviceId, configKey);
|
||||
return handleResponse(response, "根据键获取设备配置失败");
|
||||
}
|
||||
|
||||
public Map<String, Object> getDeviceFlatConfig(Long deviceId) {
|
||||
log.info("获取设备扁平化配置, deviceId: {}", deviceId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:flat:config:" + deviceId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId);
|
||||
return handleResponse(response, "获取设备扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> getDeviceFlatConfigByNo(String deviceNo) {
|
||||
log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:flat:config:no:" + deviceNo,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceConfigV2DTO createDeviceConfig(Long deviceId, CreateDeviceConfigRequest request) {
|
||||
log.info("创建设备配置, deviceId: {}, configKey: {}", deviceId, request.getConfigKey());
|
||||
CommonResponse<DeviceConfigV2DTO> response = deviceConfigV2Client.createDeviceConfig(deviceId, request);
|
||||
return handleResponse(response, "创建设备配置失败");
|
||||
}
|
||||
|
||||
public void updateDeviceConfig(Long deviceId, Long configId, UpdateDeviceConfigRequest request) {
|
||||
log.info("更新设备配置, deviceId: {}, configId: {}", deviceId, configId);
|
||||
CommonResponse<String> response = deviceConfigV2Client.updateDeviceConfig(deviceId, configId, request);
|
||||
handleResponse(response, "更新设备配置失败");
|
||||
}
|
||||
|
||||
public void deleteDeviceConfig(Long deviceId, Long configId) {
|
||||
log.info("删除设备配置, deviceId: {}, configId: {}", deviceId, configId);
|
||||
CommonResponse<String> response = deviceConfigV2Client.deleteDeviceConfig(deviceId, configId);
|
||||
handleResponse(response, "删除设备配置失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新设备配置
|
||||
*/
|
||||
public BatchUpdateResponse batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) {
|
||||
log.info("批量更新设备配置, deviceId: {}, configs count: {}", deviceId, request.getConfigs().size());
|
||||
CommonResponse<BatchUpdateResponse> response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request);
|
||||
return handleResponse(response, "批量更新设备配置失败");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取设备特定配置值
|
||||
*/
|
||||
public String getDeviceConfigValue(Long deviceId, String configKey) {
|
||||
DeviceConfigV2DTO config = getDeviceConfigByKey(deviceId, configKey);
|
||||
return config != null ? config.getConfigValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建批量配置请求构建器
|
||||
*/
|
||||
public BatchDeviceConfigRequestBuilder createBatchConfigBuilder() {
|
||||
return new BatchDeviceConfigRequestBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量配置请求构建器
|
||||
*/
|
||||
public static class BatchDeviceConfigRequestBuilder {
|
||||
private final BatchDeviceConfigRequest request = new BatchDeviceConfigRequest();
|
||||
|
||||
public BatchDeviceConfigRequestBuilder() {
|
||||
request.setConfigs(new java.util.ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加配置项(有默认配置的情况,只需提供key和value)
|
||||
*/
|
||||
public BatchDeviceConfigRequestBuilder addConfig(String configKey, String configValue) {
|
||||
BatchDeviceConfigRequest.BatchDeviceConfigItem item = new BatchDeviceConfigRequest.BatchDeviceConfigItem();
|
||||
item.setConfigKey(configKey);
|
||||
item.setConfigValue(configValue);
|
||||
request.getConfigs().add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加配置项(完整参数,用于自定义配置)
|
||||
*/
|
||||
public BatchDeviceConfigRequestBuilder addConfig(String configKey, String configValue,
|
||||
String configType, String description) {
|
||||
BatchDeviceConfigRequest.BatchDeviceConfigItem item = new BatchDeviceConfigRequest.BatchDeviceConfigItem();
|
||||
item.setConfigKey(configKey);
|
||||
item.setConfigValue(configValue);
|
||||
item.setConfigType(configType);
|
||||
item.setDescription(description);
|
||||
request.getConfigs().add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求对象
|
||||
*/
|
||||
public BatchDeviceConfigRequest build() {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "zt-device");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,218 @@
|
||||
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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceIntegrationService {
|
||||
|
||||
private final DeviceV2Client deviceV2Client;
|
||||
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<DeviceV2DTO> response = deviceV2Client.getDevice(deviceId);
|
||||
return handleResponse(response, "获取设备信息失败");
|
||||
},
|
||||
DeviceV2DTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2DTO getDeviceByNo(String deviceNo) {
|
||||
log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:no:" + deviceNo,
|
||||
() -> {
|
||||
CommonResponse<DeviceV2DTO> response = deviceV2Client.getDeviceByNo(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取设备信息失败");
|
||||
},
|
||||
DeviceV2DTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) {
|
||||
log.info("获取设备配置信息, deviceId: {}", deviceId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:config:" + deviceId,
|
||||
() -> {
|
||||
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceWithConfig(deviceId);
|
||||
return handleResponse(response, "获取设备配置信息失败");
|
||||
},
|
||||
DeviceV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) {
|
||||
log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"device:config:no:" + deviceNo,
|
||||
() -> {
|
||||
CommonResponse<DeviceV2WithConfigDTO> response = deviceV2Client.getDeviceByNoWithConfig(deviceNo);
|
||||
return handleResponse(response, "根据设备编号获取设备配置信息失败");
|
||||
},
|
||||
DeviceV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public DeviceV2DTO createDevice(CreateDeviceRequest request) {
|
||||
log.info("创建设备, name: {}, no: {}, type: {}", request.getName(), request.getNo(), request.getType());
|
||||
CommonResponse<DeviceV2DTO> response = deviceV2Client.createDevice(request);
|
||||
return handleResponse(response, "创建设备失败");
|
||||
}
|
||||
|
||||
public void updateDevice(Long deviceId, UpdateDeviceRequest request) {
|
||||
log.info("更新设备信息, deviceId: {}", deviceId);
|
||||
CommonResponse<String> response = deviceV2Client.updateDevice(deviceId, request);
|
||||
handleResponse(response, "更新设备信息失败");
|
||||
}
|
||||
|
||||
public void deleteDevice(Long deviceId) {
|
||||
log.info("删除设备, deviceId: {}", deviceId);
|
||||
CommonResponse<String> response = deviceV2Client.deleteDevice(deviceId);
|
||||
handleResponse(response, "删除设备失败");
|
||||
}
|
||||
|
||||
public DeviceV2ListResponse listDevices(Integer page, Integer pageSize, String name, String no,
|
||||
String type, Integer isActive, Long scenicId) {
|
||||
log.info("分页查询设备列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
CommonResponse<DeviceV2ListResponse> response = deviceV2Client.listDevices(
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
return handleResponse(response, "分页查询设备列表失败");
|
||||
}
|
||||
|
||||
public DeviceV2WithConfigListResponse listDevicesWithConfig(Integer page, Integer pageSize, String name, String no,
|
||||
String type, Integer isActive, Long scenicId) {
|
||||
log.info("分页查询设备带配置列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}",
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
CommonResponse<DeviceV2WithConfigListResponse> response = deviceV2Client.listDevicesWithConfig(
|
||||
page, pageSize, name, no, type, isActive, scenicId);
|
||||
return handleResponse(response, "分页查询设备带配置列表失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建IPC摄像头设备
|
||||
*/
|
||||
public DeviceV2DTO createIpcDevice(String name, String deviceNo, Long scenicId) {
|
||||
CreateDeviceRequest request = new CreateDeviceRequest();
|
||||
request.setName(name);
|
||||
request.setNo(deviceNo);
|
||||
request.setType("IPC");
|
||||
request.setIsActive(1);
|
||||
request.setScenicId(scenicId);
|
||||
request.setSort(0);
|
||||
return createDevice(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建IPC摄像头设备(带排序)
|
||||
*/
|
||||
public DeviceV2DTO createIpcDeviceWithSort(String name, String deviceNo, Long scenicId, Integer sort) {
|
||||
CreateDeviceRequest request = new CreateDeviceRequest();
|
||||
request.setName(name);
|
||||
request.setNo(deviceNo);
|
||||
request.setType("IPC");
|
||||
request.setIsActive(1);
|
||||
request.setScenicId(scenicId);
|
||||
request.setSort(sort != null ? sort : 0);
|
||||
return createDevice(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自定义设备
|
||||
*/
|
||||
public DeviceV2DTO createCustomDevice(String name, String deviceNo, Long scenicId) {
|
||||
CreateDeviceRequest request = new CreateDeviceRequest();
|
||||
request.setName(name);
|
||||
request.setNo(deviceNo);
|
||||
request.setType("CUSTOM");
|
||||
request.setIsActive(1);
|
||||
request.setScenicId(scenicId);
|
||||
request.setSort(0);
|
||||
return createDevice(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自定义设备(带排序)
|
||||
*/
|
||||
public DeviceV2DTO createCustomDeviceWithSort(String name, String deviceNo, Long scenicId, Integer sort) {
|
||||
CreateDeviceRequest request = new CreateDeviceRequest();
|
||||
request.setName(name);
|
||||
request.setNo(deviceNo);
|
||||
request.setType("CUSTOM");
|
||||
request.setIsActive(1);
|
||||
request.setScenicId(scenicId);
|
||||
request.setSort(sort != null ? sort : 0);
|
||||
return createDevice(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用设备
|
||||
*/
|
||||
public void enableDevice(Long deviceId) {
|
||||
UpdateDeviceRequest request = new UpdateDeviceRequest();
|
||||
request.setIsActive(1);
|
||||
updateDevice(deviceId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用设备
|
||||
*/
|
||||
public void disableDevice(Long deviceId) {
|
||||
UpdateDeviceRequest request = new UpdateDeviceRequest();
|
||||
request.setIsActive(0);
|
||||
updateDevice(deviceId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备排序
|
||||
*/
|
||||
public void updateDeviceSort(Long deviceId, Integer sort) {
|
||||
log.info("更新设备排序, deviceId: {}, sort: {}", deviceId, sort);
|
||||
UpdateDeviceRequest request = new UpdateDeviceRequest();
|
||||
request.setSort(sort);
|
||||
updateDevice(deviceId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区的IPC设备列表
|
||||
*/
|
||||
public DeviceV2ListResponse getScenicIpcDevices(Long scenicId, Integer page, Integer pageSize) {
|
||||
return listDevices(page, pageSize, null, null, "IPC", 1, scenicId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取景区的所有激活设备
|
||||
*/
|
||||
public DeviceV2ListResponse getScenicActiveDevices(Long scenicId, Integer page, Integer pageSize) {
|
||||
return listDevices(page, pageSize, null, null, null, 1, scenicId);
|
||||
}
|
||||
|
||||
private <T> T handleResponse(CommonResponse<T> response, String errorMessage) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
String msg = response != null && response.getMessage() != null
|
||||
? response.getMessage()
|
||||
: errorMessage;
|
||||
Integer code = response != null ? response.getCode() : 5000;
|
||||
throw new IntegrationException(code, msg, "zt-device");
|
||||
}
|
||||
return response.getData();
|
||||
}
|
||||
}
|
@@ -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作为默认值
|
||||
}
|
||||
}
|
@@ -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<String, Object> flatConfig = scenicIntegrationService.getScenicFlatConfig(scenicId);
|
||||
log.info("获取扁平化配置成功,配置项数量: {}", flatConfig.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("景区操作降级失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示景区配置管理的降级机制
|
||||
*/
|
||||
public void scenicConfigManagementFallbackExample() {
|
||||
log.info("=== 景区配置管理示例(含降级机制) ===");
|
||||
|
||||
Long scenicId = 2001L;
|
||||
|
||||
try {
|
||||
// 获取扁平化配置 - 自动降级
|
||||
Map<String, Object> flatConfigs = scenicConfigIntegrationService.getFlatConfigs(scenicId);
|
||||
log.info("获取扁平化配置成功,配置项数量: {}", flatConfigs.size());
|
||||
|
||||
// 批量更新配置 - 直接操作,失败时抛出异常
|
||||
Map<String, Object> 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("景区集成示例运行完成");
|
||||
}
|
||||
}
|
@@ -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,43 +18,61 @@ 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<ScenicConfigV2DTO> listConfigs(Long scenicId) {
|
||||
log.info("获取景区配置列表, scenicId: {}", scenicId);
|
||||
CommonResponse<List<ScenicConfigV2DTO>> response = scenicConfigV2Client.listConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区配置列表失败");
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO getConfigByKey(Long scenicId, String configKey) {
|
||||
log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey);
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.getConfigByKey(scenicId, configKey);
|
||||
return handleResponse(response, "根据键获取景区配置失败");
|
||||
}
|
||||
|
||||
public Map<String, Object> getFlatConfigs(Long scenicId) {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:flat:configs:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) {
|
||||
log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey());
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.createConfig(scenicId, request);
|
||||
return handleResponse(response, "创建景区配置失败");
|
||||
}
|
||||
|
||||
public ScenicConfigV2DTO updateConfig(Long scenicId, String id, UpdateConfigRequest request) {
|
||||
log.info("更新景区配置, scenicId: {}, id: {}", scenicId, id);
|
||||
CommonResponse<ScenicConfigV2DTO> response = scenicConfigV2Client.updateConfig(scenicId, id, request);
|
||||
return handleResponse(response, "更新景区配置失败");
|
||||
}
|
||||
|
||||
public void deleteConfig(Long scenicId, String id) {
|
||||
log.info("删除景区配置, scenicId: {}, id: {}", scenicId, id);
|
||||
CommonResponse<Void> response = scenicConfigV2Client.deleteConfig(scenicId, id);
|
||||
handleResponse(response, "删除景区配置失败");
|
||||
}
|
||||
|
||||
public BatchUpdateResponse batchUpdateConfigs(Long scenicId, BatchConfigRequest request) {
|
||||
log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size());
|
||||
CommonResponse<BatchUpdateResponse> response = scenicConfigV2Client.batchUpdateConfigs(scenicId, request);
|
||||
return handleResponse(response, "批量更新景区配置失败");
|
||||
}
|
||||
|
||||
public BatchUpdateResponse batchFlatUpdateConfigs(Long scenicId, Map<String, Object> configs) {
|
||||
log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size());
|
||||
CommonResponse<BatchUpdateResponse> response = scenicConfigV2Client.batchFlatUpdateConfigs(scenicId, configs);
|
||||
return handleResponse(response, "扁平化批量更新景区配置失败");
|
||||
}
|
||||
|
@@ -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,48 +26,81 @@ 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) {
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.getScenic(scenicId);
|
||||
return handleResponse(response, "获取景区信息失败");
|
||||
log.info("获取景区信息, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.getScenic(scenicId);
|
||||
return handleResponse(response, "获取景区信息失败");
|
||||
},
|
||||
ScenicV2DTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) {
|
||||
CommonResponse<ScenicV2WithConfigDTO> response = scenicV2Client.getScenicWithConfig(scenicId);
|
||||
return handleResponse(response, "获取景区配置信息失败");
|
||||
log.info("获取景区配置信息, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:config:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<ScenicV2WithConfigDTO> response = scenicV2Client.getScenicWithConfig(scenicId);
|
||||
return handleResponse(response, "获取景区配置信息失败");
|
||||
},
|
||||
ScenicV2WithConfigDTO.class
|
||||
);
|
||||
}
|
||||
|
||||
public Map<String, Object> getScenicFlatConfig(Long scenicId) {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
log.info("获取景区扁平化配置, scenicId: {}", scenicId);
|
||||
return fallbackService.executeWithFallback(
|
||||
SERVICE_NAME,
|
||||
"scenic:flat:config:" + scenicId,
|
||||
() -> {
|
||||
CommonResponse<Map<String, Object>> response = scenicConfigV2Client.getFlatConfigs(scenicId);
|
||||
return handleResponse(response, "获取景区扁平化配置失败");
|
||||
},
|
||||
Map.class
|
||||
);
|
||||
}
|
||||
|
||||
public ScenicV2DTO createScenic(CreateScenicRequest request) {
|
||||
log.info("创建景区, name: {}", request.getName());
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.createScenic(request);
|
||||
return handleResponse(response, "创建景区失败");
|
||||
}
|
||||
|
||||
public ScenicV2DTO updateScenic(Long scenicId, UpdateScenicRequest request) {
|
||||
log.info("更新景区信息, scenicId: {}", scenicId);
|
||||
CommonResponse<ScenicV2DTO> response = scenicV2Client.updateScenic(scenicId, request);
|
||||
return handleResponse(response, "更新景区信息失败");
|
||||
}
|
||||
|
||||
public void deleteScenic(Long scenicId) {
|
||||
log.info("删除景区, scenicId: {}", scenicId);
|
||||
CommonResponse<Void> response = scenicV2Client.deleteScenic(scenicId);
|
||||
handleResponse(response, "删除景区失败");
|
||||
}
|
||||
|
||||
public ScenicFilterPageResponse filterScenics(ScenicFilterRequest request) {
|
||||
log.info("筛选景区, filters: {}", request.getFilters().size());
|
||||
CommonResponse<ScenicFilterPageResponse> response = scenicV2Client.filterScenics(request);
|
||||
return handleResponse(response, "筛选景区失败");
|
||||
}
|
||||
|
||||
public ScenicV2ListResponse listScenics(Integer page, Integer pageSize, Integer status, String name) {
|
||||
log.info("分页查询景区列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
CommonResponse<ScenicV2ListResponse> response = scenicV2Client.listScenics(page, pageSize, status, name);
|
||||
return handleResponse(response, "分页查询景区列表失败");
|
||||
}
|
||||
|
||||
public ScenicV2WithConfigListResponse listScenicsWithConfig(Integer page, Integer pageSize, Integer status, String name) {
|
||||
log.info("分页查询景区带配置列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name);
|
||||
CommonResponse<ScenicV2WithConfigListResponse> response = scenicV2Client.listScenicsWithConfig(page, pageSize, status, name);
|
||||
return handleResponse(response, "分页查询景区带配置列表失败");
|
||||
}
|
||||
|
Reference in New Issue
Block a user