From d34603062a9ee13a14ff7e42ee15da82b580277d Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sat, 30 Aug 2025 23:24:49 +0800 Subject: [PATCH 01/18] =?UTF-8?q?feat(integration):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=9C=8D=E5=8A=A1=E9=9B=86=E6=88=90=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增设备服务配置和相关客户端接口 - 实现设备和设备配置的管理功能- 添加设备监控和状态管理示例 - 优化错误处理和故障恢复机制 --- CLAUDE.md | 104 ++++- .../java/com/ycwl/basic/integration/CLAUDE.md | 397 ++++++++++++++++++ .../common/config/IntegrationProperties.java | 30 ++ .../device/client/DeviceConfigV2Client.java | 80 ++++ .../device/client/DeviceV2Client.java | 79 ++++ .../config/DeviceIntegrationConfig.java | 16 + .../dto/config/BatchDeviceConfigRequest.java | 18 + .../dto/config/CreateDeviceConfigRequest.java | 12 + .../device/dto/config/DeviceConfigV2DTO.java | 39 ++ .../dto/config/UpdateDeviceConfigRequest.java | 35 ++ .../dto/device/CreateDeviceRequest.java | 12 + .../device/dto/device/DeviceV2DTO.java | 36 ++ .../dto/device/DeviceV2ListResponse.java | 13 + .../dto/device/DeviceV2WithConfigDTO.java | 12 + .../DeviceV2WithConfigListResponse.java | 13 + .../dto/device/UpdateDeviceRequest.java | 12 + .../example/DeviceIntegrationExample.java | 243 +++++++++++ .../DeviceConfigIntegrationService.java | 188 +++++++++ .../service/DeviceIntegrationService.java | 146 +++++++ 19 files changed, 1484 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ycwl/basic/integration/CLAUDE.md create mode 100644 src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/client/DeviceV2Client.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/BatchDeviceConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/CreateDeviceConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/DeviceConfigV2DTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/UpdateDeviceConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2ListResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigDTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigListResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java diff --git a/CLAUDE.md b/CLAUDE.md index 6c92571..c51a066 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,6 +122,15 @@ mvn test -DskipTests=false 3. 使用 `@Scheduled` 进行基于 cron 的执行 4. 遵循现有的错误处理和日志记录模式 +### 多端API架构 +应用程序通过路径前缀区分不同的客户端: +- **移动端**: `/api/mobile/*` - 针对移动应用优化的接口 +- **PC管理端**: `/api/*` - Web管理面板接口 +- **任务处理**: `/task/*` - 后台任务和渲染服务接口 +- **外部集成**: 专用集成接口(打印机、代理、viid、vpt、wvp等) + +每个端点都有对应的Controller包结构,确保API的职责分离和维护性。 + ## 价格查询系统 (Pricing Module) ### 核心架构 @@ -187,4 +196,97 @@ ProductType枚举定义了支持的商品类型: - 单元测试:每个服务类都有对应测试类 - 配置验证测试:DefaultConfigValidationTest验证default配置 - JSON序列化测试:验证复杂对象的数据库存储 -- 分页功能测试:验证PageHelper集成 \ No newline at end of file +- 分页功能测试:验证PageHelper集成 + +## 关键架构模式 + +### Repository 层模式 +项目使用Repository层抽象数据访问逻辑: +- Repository接口定义数据访问契约 +- Mapper接口处理MyBatis Plus的数据库映射 +- Service层通过Repository访问数据,避免直接依赖Mapper + +### 异常处理架构 +- **全局异常处理**: `CustomExceptionHandle` 提供统一的异常处理和响应格式 +- **业务异常**: 自定义异常类继承RuntimeException,携带业务错误码 +- **集成异常**: `IntegrationException` 专门处理外部服务调用异常 + +### 配置驱动的扩展性 +通过配置文件驱动的多供应商支持: +- 存储:本地、AWS S3、阿里云 OSS +- 支付:微信支付、聪明支付 +- 人脸识别:阿里云、百度 +每个供应商通过统一接口访问,配置切换无需代码修改。 + +### 业务层架构 +- **Service层**: 核心业务逻辑实现 +- **Biz层**: 高级业务流程编排,组合多个Service +- **Controller层**: HTTP请求处理和响应转换 +- **Repository层**: 数据访问抽象 + +### 认证和会话管理 +- **JWT**: 使用jjwt库进行身份验证 +- **Redis**: 存储会话信息和缓存 +- **BaseContextHandler**: 提供当前用户上下文访问 + +## 微服务集成架构 (Integration Package) + +### 核心架构 +位于 `com.ycwl.basic.integration` 包,使用 Spring Cloud OpenFeign 和 Nacos 实现外部微服务集成。 + +#### 通用基础设施 +- **IntegrationProperties**: 所有集成的集中配置管理 +- **FeignErrorDecoder**: 自定义错误解码器,统一错误处理 +- **IntegrationException**: 标准化集成异常 +- **CommonResponse/PageResponse**: 外部服务响应包装器 + +#### 已实现的服务集成 +- **Scenic Integration** (`integration.scenic`): ZT-Scenic 微服务集成 +- **Device Integration** (`integration.device`): ZT-Device 微服务集成 + +#### 集成模式 +每个外部服务按以下结构组织: +``` +service/ +├── client/ # Feign 客户端 +├── config/ # 服务特定配置 +├── dto/ # 数据传输对象 +├── service/ # 业务逻辑层 +└── example/ # 使用示例 +``` + +### 配置管理 +```yaml +integration: + scenic: + enabled: true + serviceName: zt-scenic + connectTimeout: 5000 + readTimeout: 10000 + device: + enabled: true + serviceName: zt-device + connectTimeout: 5000 + readTimeout: 10000 +``` + +### 使用模式 +所有集成服务使用统一的 `handleResponse` 模式进行错误处理,确保一致的异常包装和日志记录。 + +### 测试集成服务 +```bash +# 运行特定集成测试 +mvn test -Dtest=ScenicIntegrationServiceTest +mvn test -Dtest=DeviceIntegrationServiceTest + +# 运行所有集成测试 +mvn test -Dtest="com.ycwl.basic.integration.*Test" +``` + +### 调试集成问题 +启用 Feign 客户端日志: +```yaml +logging: + level: + com.ycwl.basic.integration: DEBUG +``` \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md new file mode 100644 index 0000000..2120cad --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -0,0 +1,397 @@ +# 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 + +#### 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 +``` + +## 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 +- **ScenicConfigIntegrationService**: Configuration management +- **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 +```java +@Autowired +private ScenicIntegrationService scenicService; + +// Get scenic with configuration +ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId); + +// Create new scenic +CreateScenicRequest request = new CreateScenicRequest(); +request.setName("Test Scenic"); +ScenicV2DTO result = scenicService.createScenic(request); + +// Filter scenics +ScenicFilterRequest filterRequest = new ScenicFilterRequest(); +// configure filters... +ScenicFilterPageResponse response = scenicService.filterScenics(filterRequest); +``` + +#### Configuration Management +```java +@Autowired +private ScenicConfigIntegrationService configService; + +// Get flat configuration +Map config = scenicService.getScenicFlatConfig(scenicId); + +// Batch update configurations +BatchConfigRequest batchRequest = new BatchConfigRequest(); +// configure batch updates... +configService.batchUpdateConfigs(scenicId, batchRequest); +``` + +## 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 +- **DeviceConfigIntegrationService**: Device configuration management + +#### Configuration +```yaml +integration: + device: + enabled: true + serviceName: zt-device + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 +``` + +### Usage Examples + +#### Basic Device Operations +```java +@Autowired +private DeviceIntegrationService deviceService; + +// Create IPC camera device +DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId); + +// Get device with configuration +DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId); + +// Get device by number +DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); + +// List scenic devices +DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10); + +// Enable/disable device +deviceService.enableDevice(deviceId); +deviceService.disableDevice(deviceId); +``` + +#### Device Configuration Management +```java +@Autowired +private DeviceConfigIntegrationService configService; + +// Configure camera basic parameters +configService.configureCameraBasicParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP"); + +// Configure camera with authentication +configService.configureCameraFullParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); + +// Set specific configuration +configService.setDeviceIpAddress(deviceId, "192.168.1.101"); +configService.setDeviceResolution(deviceId, "2560x1440"); +configService.setDeviceFramerate(deviceId, 60); + +// Get flat configuration +Map config = configService.getDeviceFlatConfig(deviceId); + +// Batch update configurations +Map batchConfigs = new HashMap<>(); +batchConfigs.put("brightness", "50"); +batchConfigs.put("contrast", "80"); +configService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); +``` + +#### Device Management Patterns +```java +// Create and configure camera in one operation +DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId); +configService.configureCameraFullParams(camera.getId(), "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); + +// Get scenic camera status +DeviceV2WithConfigListResponse camerasWithConfig = + deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); + +// Batch update camera resolution +for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) { + configService.setDeviceResolution(device.getId(), "2560x1440"); +} + +// Monitor device configuration completeness +for (DeviceV2DTO device : activeDevices.getList()) { + Map config = configService.getDeviceFlatConfig(device.getId()); + boolean hasIpConfig = config.containsKey("ip_address"); + // log configuration status... +} +``` + +### 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 getData(@PathVariable Long id); + // other endpoints... +} +``` + +### 4. Implement Service Layer +```java +@Service +@RequiredArgsConstructor +public class NewServiceIntegrationService { + private final NewServiceClient client; + + public ResponseDTO getData(Long id) { + CommonResponse response = client.getData(id); + return handleResponse(response, "Failed to get data"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "service-name"); + } + return response.getData(); + } +} +``` + +## Error Handling + +### IntegrationException +All integration failures are wrapped in `IntegrationException`: +```java +public class IntegrationException extends RuntimeException { + private final Integer code; + private final String serviceName; + // constructors and getters... +} +``` + +### FeignErrorDecoder +Automatically converts Feign errors to IntegrationException: +- Parses CommonResponse error format +- Extracts service name from method key +- Provides meaningful error messages + +## Configuration Management + +### Properties Structure +```yaml +integration: + scenic: + enabled: true + serviceName: zt-scenic + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 + device: + enabled: true + serviceName: zt-device + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 +``` + +### Configuration Refresh +Uses `@RefreshScope` to support dynamic configuration updates without restart. + +## Testing Integration Services + +### Unit Testing +Test service layers by mocking Feign clients: +```java +@ExtendWith(MockitoExtension.class) +class ScenicIntegrationServiceTest { + @Mock + private ScenicV2Client scenicV2Client; + + @InjectMocks + private ScenicIntegrationService scenicService; + + @Test + void testGetScenic() { + // Mock response + CommonResponse response = new CommonResponse<>(); + response.setSuccess(true); + response.setData(new ScenicV2DTO()); + + when(scenicV2Client.getScenic(1L)).thenReturn(response); + + // Test + ScenicV2DTO result = scenicService.getScenic(1L); + assertNotNull(result); + } +} +``` + +### Integration Testing +Use `@SpringBootTest` with test profiles and mock external services. + +## 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 + +### Configuration +- Use environment-specific configuration profiles +- Set appropriate timeouts based on service characteristics +- Enable retries only when safe (idempotent operations) + +### 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 \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java index 8baa61a..c4620f8 100644 --- a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java +++ b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java @@ -16,6 +16,11 @@ public class IntegrationProperties { */ private ScenicConfig scenic = new ScenicConfig(); + /** + * 设备服务配置 + */ + private DeviceConfig device = new DeviceConfig(); + @Data public static class ScenicConfig { /** @@ -40,4 +45,29 @@ 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; + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java new file mode 100644 index 0000000..28046b8 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java @@ -0,0 +1,80 @@ +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> getDeviceConfigs(@PathVariable("deviceId") Long deviceId); + + /** + * 根据设备编号获取设备所有配置 + */ + @GetMapping("/no/{no}") + CommonResponse> getDeviceConfigsByNo(@PathVariable("no") String no); + + /** + * 获取设备特定配置 + */ + @GetMapping("/{deviceId}/key/{configKey}") + CommonResponse getDeviceConfigByKey(@PathVariable("deviceId") Long deviceId, + @PathVariable("configKey") String configKey); + + /** + * 获取设备扁平化配置 + */ + @GetMapping("/{deviceId}/flat") + CommonResponse> getDeviceFlatConfig(@PathVariable("deviceId") Long deviceId); + + /** + * 根据设备编号获取设备扁平化配置 + */ + @GetMapping("/no/{no}/flat") + CommonResponse> getDeviceFlatConfigByNo(@PathVariable("no") String no); + + /** + * 创建设备配置 + */ + @PostMapping("/{deviceId}") + CommonResponse createDeviceConfig(@PathVariable("deviceId") Long deviceId, + @RequestBody CreateDeviceConfigRequest request); + + /** + * 更新设备配置 + */ + @PutMapping("/{deviceId}/{id}") + CommonResponse updateDeviceConfig(@PathVariable("deviceId") Long deviceId, + @PathVariable("id") Long id, + @RequestBody UpdateDeviceConfigRequest request); + + /** + * 删除设备配置 + */ + @DeleteMapping("/{deviceId}/{id}") + CommonResponse deleteDeviceConfig(@PathVariable("deviceId") Long deviceId, + @PathVariable("id") Long id); + + /** + * 批量更新设备配置 + */ + @PostMapping("/{deviceId}/batch") + CommonResponse batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, + @RequestBody BatchDeviceConfigRequest request); + + /** + * 扁平化批量更新设备配置 + */ + @PostMapping("/{deviceId}/batch-flat") + CommonResponse batchFlatUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, + @RequestBody Map configs); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DeviceV2Client.java b/src/main/java/com/ycwl/basic/integration/device/client/DeviceV2Client.java new file mode 100644 index 0000000..106c982 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceV2Client.java @@ -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 getDevice(@PathVariable("id") Long id); + + /** + * 根据设备编号获取设备核心信息 + */ + @GetMapping("/no/{no}") + CommonResponse getDeviceByNo(@PathVariable("no") String no); + + /** + * 获取设备详细信息(含配置) + */ + @GetMapping("/{id}/with-config") + CommonResponse getDeviceWithConfig(@PathVariable("id") Long id); + + /** + * 根据设备编号获取设备详细信息(含配置) + */ + @GetMapping("/no/{no}/with-config") + CommonResponse getDeviceByNoWithConfig(@PathVariable("no") String no); + + /** + * 创建设备 + */ + @PostMapping("/") + CommonResponse createDevice(@RequestBody CreateDeviceRequest request); + + /** + * 更新设备 + */ + @PutMapping("/{id}") + CommonResponse updateDevice(@PathVariable("id") Long id, + @RequestBody UpdateDeviceRequest request); + + /** + * 删除设备 + */ + @DeleteMapping("/{id}") + CommonResponse deleteDevice(@PathVariable("id") Long id); + + /** + * 分页获取设备列表(核心信息) + */ + @GetMapping("/") + CommonResponse 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 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); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java b/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java new file mode 100644 index 0000000..6b4c6de --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java @@ -0,0 +1,16 @@ +package com.ycwl.basic.integration.device.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@ConditionalOnProperty(prefix = "integration.device") +public class DeviceIntegrationConfig { + + public DeviceIntegrationConfig() { + log.info("ZT-Device集成配置初始化完成"); + + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchDeviceConfigRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchDeviceConfigRequest.java new file mode 100644 index 0000000..ac0b7d6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchDeviceConfigRequest.java @@ -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 configs; + + @Data + public static class BatchDeviceConfigItem { + private String configKey; + private String configValue; + private String configType; + private String description; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/CreateDeviceConfigRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/CreateDeviceConfigRequest.java new file mode 100644 index 0000000..6bfb523 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/CreateDeviceConfigRequest.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/DeviceConfigV2DTO.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/DeviceConfigV2DTO.java new file mode 100644 index 0000000..5fd54a5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/DeviceConfigV2DTO.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/UpdateDeviceConfigRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/UpdateDeviceConfigRequest.java new file mode 100644 index 0000000..190e7b4 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/UpdateDeviceConfigRequest.java @@ -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 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java new file mode 100644 index 0000000..dc5328a --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java @@ -0,0 +1,12 @@ +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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java new file mode 100644 index 0000000..75ce26d --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java @@ -0,0 +1,36 @@ +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("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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2ListResponse.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2ListResponse.java new file mode 100644 index 0000000..819834f --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2ListResponse.java @@ -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 list; + private Long total; + private Integer page; + private Integer pageSize; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigDTO.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigDTO.java new file mode 100644 index 0000000..766677a --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigDTO.java @@ -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 config; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigListResponse.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigListResponse.java new file mode 100644 index 0000000..aec44ff --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2WithConfigListResponse.java @@ -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 list; + private Long total; + private Integer page; + private Integer pageSize; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java new file mode 100644 index 0000000..ab818d9 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java @@ -0,0 +1,12 @@ +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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java new file mode 100644 index 0000000..ce2de85 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java @@ -0,0 +1,243 @@ +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.HashMap; +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("=== 基本设备操作示例 ==="); + + // 1. 创建IPC摄像头设备 + DeviceV2DTO ipcDevice = deviceService.createIpcDevice( + "前门摄像头", "CAM001", 1001L); + log.info("创建IPC设备: {}", ipcDevice); + + // 2. 创建自定义设备 + DeviceV2DTO customDevice = deviceService.createCustomDevice( + "温度传感器", "TEMP001", 1001L); + log.info("创建自定义设备: {}", customDevice); + + // 3. 根据ID获取设备信息 + DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId()); + log.info("获取设备信息: {}", device); + + // 4. 根据设备编号获取设备信息 + DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); + log.info("根据编号获取设备: {}", deviceByNo); + + // 5. 获取设备详细信息(含配置) + DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId()); + log.info("获取设备配置: {}", deviceWithConfig); + + // 6. 分页查询景区设备列表 + DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10); + log.info("景区IPC设备列表: 总数={}, 当前页={}", deviceList.getTotal(), deviceList.getList().size()); + + // 7. 启用/禁用设备 + deviceService.enableDevice(ipcDevice.getId()); + log.info("设备已启用"); + + deviceService.disableDevice(customDevice.getId()); + log.info("设备已禁用"); + } + + /** + * 设备配置管理示例 + */ + public void deviceConfigurationOperations() { + log.info("=== 设备配置管理示例 ==="); + + // 假设已有设备ID + Long deviceId = 1L; + + // 1. 配置摄像头基本参数 + deviceConfigService.configureCameraBasicParams( + deviceId, "192.168.1.100", "1920x1080", 30, "RTSP"); + log.info("摄像头基本参数已配置"); + + // 2. 配置摄像头完整参数(包含认证) + deviceConfigService.configureCameraFullParams( + deviceId, "192.168.1.101", "3840x2160", 25, "RTSP", "admin", "password123"); + log.info("摄像头完整参数已配置"); + + // 3. 单独设置特定配置 + deviceConfigService.setDeviceIpAddress(deviceId, "192.168.1.102"); + deviceConfigService.setDeviceResolution(deviceId, "2560x1440"); + deviceConfigService.setDeviceFramerate(deviceId, 60); + log.info("单独配置项已更新"); + + // 4. 获取设备所有配置 + List configs = deviceConfigService.getDeviceConfigs(deviceId); + log.info("设备配置列表: {}", configs.size()); + + // 5. 获取扁平化配置 + Map flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId); + log.info("扁平化配置: {}", flatConfig); + + // 6. 获取特定配置值 + String ipAddress = deviceConfigService.getDeviceIpAddress(deviceId); + String resolution = deviceConfigService.getDeviceResolution(deviceId); + log.info("IP地址: {}, 分辨率: {}", ipAddress, resolution); + + // 7. 批量更新配置 + Map batchConfigs = new HashMap<>(); + batchConfigs.put("brightness", "50"); + batchConfigs.put("contrast", "80"); + batchConfigs.put("quality", "high"); + deviceConfigService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); + log.info("批量配置已更新"); + } + + /** + * 摄像头管理示例 + */ + public void cameraManagementExample() { + log.info("=== 摄像头管理示例 ==="); + + Long scenicId = 1001L; + + // 1. 批量创建摄像头 + for (int i = 1; i <= 5; i++) { + DeviceV2DTO camera = deviceService.createIpcDevice( + "摄像头" + i, "CAM00" + i, scenicId); + + // 配置每个摄像头的基本参数 + deviceConfigService.configureCameraFullParams( + camera.getId(), + "192.168.1." + (100 + i), + "1920x1080", + 30, + "RTSP", + "admin", + "camera" + i + ); + log.info("创建并配置摄像头: {}", camera.getName()); + } + + // 2. 获取景区所有摄像头状态 + DeviceV2WithConfigListResponse camerasWithConfig = + deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); + + log.info("景区摄像头总数: {}", camerasWithConfig.getTotal()); + for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) { + log.info("摄像头: {}, IP: {}", + camera.getName(), + camera.getConfig().get("ip_address")); + } + + // 3. 批量更新摄像头分辨率 + for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) { + deviceConfigService.setDeviceResolution(camera.getId(), "2560x1440"); + } + log.info("所有摄像头分辨率已更新为 2560x1440"); + } + + /** + * 设备监控和状态管理示例 + */ + public void deviceMonitoringExample() { + log.info("=== 设备监控和状态管理示例 ==="); + + Long scenicId = 1001L; + + // 1. 获取景区所有激活设备 + DeviceV2ListResponse activeDevices = deviceService.getScenicActiveDevices(scenicId, 1, 50); + log.info("景区激活设备数量: {}", activeDevices.getTotal()); + + // 2. 按设备类型分类统计 + Map deviceTypeCount = new HashMap<>(); + for (DeviceV2DTO device : activeDevices.getList()) { + deviceTypeCount.merge(device.getType(), 1, Integer::sum); + } + log.info("设备类型统计: {}", deviceTypeCount); + + // 3. 检查设备配置完整性 + for (DeviceV2DTO device : activeDevices.getList()) { + try { + Map config = deviceConfigService.getDeviceFlatConfig(device.getId()); + boolean hasIpConfig = config.containsKey("ip_address"); + log.info("设备 {} 配置状态: IP配置={}", device.getName(), hasIpConfig ? "已配置" : "未配置"); + } catch (Exception e) { + log.warn("获取设备 {} 配置失败: {}", device.getName(), e.getMessage()); + } + } + + // 4. 按设备编号搜索 + try { + DeviceV2WithConfigDTO deviceByNo = deviceService.getDeviceWithConfigByNo("CAM001"); + log.info("根据编号找到设备: {} (配置项数量: {})", + deviceByNo.getName(), + deviceByNo.getConfig().size()); + } catch (Exception e) { + log.warn("未找到设备编号 CAM001: {}", e.getMessage()); + } + } + + /** + * 错误处理和故障恢复示例 + */ + public void errorHandlingExample() { + log.info("=== 错误处理和故障恢复示例 ==="); + + // 1. 尝试获取不存在的设备 + try { + deviceService.getDevice(99999L); + } catch (Exception e) { + log.warn("获取不存在设备的预期错误: {}", e.getMessage()); + } + + // 2. 尝试获取不存在的配置 + try { + deviceConfigService.getDeviceConfigByKey(1L, "non_existent_key"); + } catch (Exception e) { + log.warn("获取不存在配置的预期错误: {}", e.getMessage()); + } + + // 3. 创建设备时的参数验证 + try { + CreateDeviceRequest invalidRequest = new CreateDeviceRequest(); + // 缺少必要字段 + deviceService.createDevice(invalidRequest); + } catch (Exception e) { + log.warn("创建设备参数错误: {}", e.getMessage()); + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + try { + basicDeviceOperations(); + deviceConfigurationOperations(); + cameraManagementExample(); + deviceMonitoringExample(); + errorHandlingExample(); + log.info("=== 所有示例执行完成 ==="); + } catch (Exception e) { + log.error("示例执行过程中发生错误", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java new file mode 100644 index 0000000..0e2359a --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -0,0 +1,188 @@ +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.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; + + public List getDeviceConfigs(Long deviceId) { + log.info("获取设备配置列表, deviceId: {}", deviceId); + CommonResponse> response = deviceConfigV2Client.getDeviceConfigs(deviceId); + return handleResponse(response, "获取设备配置列表失败"); + } + + public List getDeviceConfigsByNo(String deviceNo) { + log.info("根据设备编号获取配置列表, deviceNo: {}", deviceNo); + CommonResponse> response = deviceConfigV2Client.getDeviceConfigsByNo(deviceNo); + return handleResponse(response, "根据设备编号获取配置列表失败"); + } + + public DeviceConfigV2DTO getDeviceConfigByKey(Long deviceId, String configKey) { + log.info("根据键获取设备配置, deviceId: {}, configKey: {}", deviceId, configKey); + CommonResponse response = deviceConfigV2Client.getDeviceConfigByKey(deviceId, configKey); + return handleResponse(response, "根据键获取设备配置失败"); + } + + public Map getDeviceFlatConfig(Long deviceId) { + log.info("获取设备扁平化配置, deviceId: {}", deviceId); + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); + return handleResponse(response, "获取设备扁平化配置失败"); + } + + public Map getDeviceFlatConfigByNo(String deviceNo) { + log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo); + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); + return handleResponse(response, "根据设备编号获取扁平化配置失败"); + } + + public DeviceConfigV2DTO createDeviceConfig(Long deviceId, CreateDeviceConfigRequest request) { + log.info("创建设备配置, deviceId: {}, configKey: {}", deviceId, request.getConfigKey()); + CommonResponse response = deviceConfigV2Client.createDeviceConfig(deviceId, request); + return handleResponse(response, "创建设备配置失败"); + } + + public void updateDeviceConfig(Long deviceId, Long configId, UpdateDeviceConfigRequest request) { + log.info("更新设备配置, deviceId: {}, configId: {}", deviceId, configId); + CommonResponse response = deviceConfigV2Client.updateDeviceConfig(deviceId, configId, request); + handleResponse(response, "更新设备配置失败"); + } + + public void deleteDeviceConfig(Long deviceId, Long configId) { + log.info("删除设备配置, deviceId: {}, configId: {}", deviceId, configId); + CommonResponse response = deviceConfigV2Client.deleteDeviceConfig(deviceId, configId); + handleResponse(response, "删除设备配置失败"); + } + + public void batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) { + log.info("批量更新设备配置, deviceId: {}, configs count: {}", deviceId, request.getConfigs().size()); + CommonResponse response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request); + handleResponse(response, "批量更新设备配置失败"); + } + + public void batchFlatUpdateDeviceConfig(Long deviceId, Map configs) { + log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", deviceId, configs.size()); + CommonResponse response = deviceConfigV2Client.batchFlatUpdateDeviceConfig(deviceId, configs); + handleResponse(response, "扁平化批量更新设备配置失败"); + } + + /** + * 设置设备IP地址 + */ + public void setDeviceIpAddress(Long deviceId, String ipAddress) { + Map config = new HashMap<>(); + config.put("ip_address", ipAddress); + batchFlatUpdateDeviceConfig(deviceId, config); + } + + /** + * 设置设备分辨率 + */ + public void setDeviceResolution(Long deviceId, String resolution) { + Map config = new HashMap<>(); + config.put("resolution", resolution); + batchFlatUpdateDeviceConfig(deviceId, config); + } + + /** + * 设置设备帧率 + */ + public void setDeviceFramerate(Long deviceId, Integer framerate) { + Map config = new HashMap<>(); + config.put("framerate", framerate.toString()); + batchFlatUpdateDeviceConfig(deviceId, config); + } + + /** + * 设置设备协议 + */ + public void setDeviceProtocol(Long deviceId, String protocol) { + Map config = new HashMap<>(); + config.put("protocol", protocol); + batchFlatUpdateDeviceConfig(deviceId, config); + } + + /** + * 设置设备认证信息 + */ + public void setDeviceAuth(Long deviceId, String username, String password) { + Map config = new HashMap<>(); + config.put("username", username); + config.put("password", password); + batchFlatUpdateDeviceConfig(deviceId, config); + } + + /** + * 配置摄像头基本参数 + */ + public void configureCameraBasicParams(Long deviceId, String ipAddress, String resolution, + Integer framerate, String protocol) { + Map configs = new HashMap<>(); + configs.put("ip_address", ipAddress); + configs.put("resolution", resolution); + configs.put("framerate", framerate.toString()); + configs.put("protocol", protocol); + batchFlatUpdateDeviceConfig(deviceId, configs); + } + + /** + * 配置摄像头完整参数 + */ + public void configureCameraFullParams(Long deviceId, String ipAddress, String resolution, + Integer framerate, String protocol, String username, String password) { + Map configs = new HashMap<>(); + configs.put("ip_address", ipAddress); + configs.put("resolution", resolution); + configs.put("framerate", framerate.toString()); + configs.put("protocol", protocol); + configs.put("username", username); + configs.put("password", password); + batchFlatUpdateDeviceConfig(deviceId, configs); + } + + /** + * 获取设备特定配置值 + */ + public String getDeviceConfigValue(Long deviceId, String configKey) { + DeviceConfigV2DTO config = getDeviceConfigByKey(deviceId, configKey); + return config != null ? config.getConfigValue() : null; + } + + /** + * 获取设备IP地址 + */ + public String getDeviceIpAddress(Long deviceId) { + return getDeviceConfigValue(deviceId, "ip_address"); + } + + /** + * 获取设备分辨率 + */ + public String getDeviceResolution(Long deviceId) { + return getDeviceConfigValue(deviceId, "resolution"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-device"); + } + return response.getData(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java new file mode 100644 index 0000000..418496d --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -0,0 +1,146 @@ +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.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; + + public DeviceV2DTO getDevice(Long deviceId) { + log.info("获取设备信息, deviceId: {}", deviceId); + CommonResponse response = deviceV2Client.getDevice(deviceId); + return handleResponse(response, "获取设备信息失败"); + } + + public DeviceV2DTO getDeviceByNo(String deviceNo) { + log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo); + CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); + return handleResponse(response, "根据设备编号获取设备信息失败"); + } + + public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) { + log.info("获取设备配置信息, deviceId: {}", deviceId); + CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); + return handleResponse(response, "获取设备配置信息失败"); + } + + public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) { + log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo); + CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); + return handleResponse(response, "根据设备编号获取设备配置信息失败"); + } + + public DeviceV2DTO createDevice(CreateDeviceRequest request) { + log.info("创建设备, name: {}, no: {}, type: {}", request.getName(), request.getNo(), request.getType()); + CommonResponse response = deviceV2Client.createDevice(request); + return handleResponse(response, "创建设备失败"); + } + + public void updateDevice(Long deviceId, UpdateDeviceRequest request) { + log.info("更新设备信息, deviceId: {}", deviceId); + CommonResponse response = deviceV2Client.updateDevice(deviceId, request); + handleResponse(response, "更新设备信息失败"); + } + + public void deleteDevice(Long deviceId) { + log.info("删除设备, deviceId: {}", deviceId); + CommonResponse 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 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 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); + 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); + 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); + } + + /** + * 获取景区的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 handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-device"); + } + return response.getData(); + } +} \ No newline at end of file From 98e5c3dc39e2ad57669a8dd897f5c12fa9a482b6 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 1 Sep 2025 18:45:26 +0800 Subject: [PATCH 02/18] =?UTF-8?q?fix(device):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E9=85=8D=E7=BD=AE=20deviceNo=20=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E8=A2=AB=E5=8A=A8=E5=AD=98=E5=82=A8=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 VptPassiveStorageOperator 和 WvpPassiveStorageOperator 中添加日志警告 - 返回空列表以避免使用未配置的 deviceNo 进行查询 - 移除 DeviceEntity 中的 no2 字段 - 更新 DeviceServiceImpl 中的缓存清除逻辑 --- .../ycwl/basic/device/operator/VptPassiveStorageOperator.java | 3 ++- .../ycwl/basic/device/operator/WvpPassiveStorageOperator.java | 3 ++- .../com/ycwl/basic/model/pc/device/entity/DeviceEntity.java | 1 - .../java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java | 1 - 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ycwl/basic/device/operator/VptPassiveStorageOperator.java b/src/main/java/com/ycwl/basic/device/operator/VptPassiveStorageOperator.java index 83bb864..f9877d0 100644 --- a/src/main/java/com/ycwl/basic/device/operator/VptPassiveStorageOperator.java +++ b/src/main/java/com/ycwl/basic/device/operator/VptPassiveStorageOperator.java @@ -84,7 +84,8 @@ public class VptPassiveStorageOperator extends ADeviceStorageOperator { if (StringUtils.isNotBlank(config.getDeviceNo())) { task.deviceNo = config.getDeviceNo(); } else { - task.deviceNo = device.getNo2(); + log.warn("设备未配置deviceNo:{}", device); + return Collections.emptyList(); } task.startTime = startDate; task.endTime = endDate; diff --git a/src/main/java/com/ycwl/basic/device/operator/WvpPassiveStorageOperator.java b/src/main/java/com/ycwl/basic/device/operator/WvpPassiveStorageOperator.java index cd3ad21..86192cd 100644 --- a/src/main/java/com/ycwl/basic/device/operator/WvpPassiveStorageOperator.java +++ b/src/main/java/com/ycwl/basic/device/operator/WvpPassiveStorageOperator.java @@ -81,7 +81,8 @@ public class WvpPassiveStorageOperator extends ADeviceStorageOperator { if (StringUtils.isNotBlank(config.getDeviceNo())) { task.deviceNo = config.getDeviceNo(); } else { - task.deviceNo = device.getNo2(); + log.warn("设备未配置deviceNo:{}", device); + return Collections.emptyList(); } task.startTime = startDate; task.endTime = endDate; diff --git a/src/main/java/com/ycwl/basic/model/pc/device/entity/DeviceEntity.java b/src/main/java/com/ycwl/basic/model/pc/device/entity/DeviceEntity.java index 88d9bfa..58730f9 100644 --- a/src/main/java/com/ycwl/basic/model/pc/device/entity/DeviceEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/device/entity/DeviceEntity.java @@ -28,7 +28,6 @@ public class DeviceEntity { * 设备编号 */ private String no; - private String no2; /** * 经度 */ diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java index 1a42ea2..9235cea 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java @@ -160,7 +160,6 @@ public class DeviceServiceImpl implements DeviceService { deviceMapper.updateSort(item.getId(), item.getSort()); deviceRepository.clearDeviceCache(item.getId()); deviceRepository.clearDeviceCache(item.getNo()); - deviceRepository.clearDeviceCache(item.getNo2()); }); return ApiResponse.success(true); } From 0bcf2aaccfa4aad047b65dba07f1bf753520cbcf Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 1 Sep 2025 23:14:45 +0800 Subject: [PATCH 03/18] =?UTF-8?q?refactor(device):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=BC=93=E5=AD=98=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DeviceV2DTO 转换为 DeviceEntity 的方法 - 引入成功结果缓存,用于失败时降级 - 优化 getDevice 和 getDeviceByDeviceNo 方法,增加异常处理和缓存逻辑 - 清理缓存时增加成功结果缓存的清理 --- .../basic/repository/DeviceRepository.java | 99 +++++++++++++++---- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java index 210af67..77d9121 100644 --- a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java +++ b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java @@ -5,52 +5,107 @@ import com.ycwl.basic.mapper.DeviceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.utils.SnowFlakeUtil; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; +import java.time.ZoneId; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +@Slf4j @Component public class DeviceRepository { @Autowired private DeviceMapper deviceMapper; @Autowired private RedisTemplate redisTemplate; + @Autowired + private DeviceIntegrationService deviceIntegrationService; public static final String DEVICE_ONLINE_CACHE_KEY = "device:online_status:%s"; public static final String DEVICE_CACHE_KEY = "device:%s"; public static final String DEVICE_CONFIG_CACHE_KEY = "device:%s:config"; + public static final String DEVICE_CACHE_SUCCESS_KEY = "device:success:%s"; + public static final String DEVICE_NO_CACHE_SUCCESS_KEY = "device:success:no:%s"; + + /** + * 将DeviceV2DTO转换为DeviceEntity + */ + private DeviceEntity convertToEntity(DeviceV2DTO dto) { + if (dto == null) { + return null; + } + DeviceEntity entity = new DeviceEntity(); + entity.setId(dto.getId()); + entity.setName(dto.getName()); + entity.setNo(dto.getNo()); + entity.setScenicId(dto.getScenicId()); + // DeviceV2DTO中的isActive对应DeviceEntity中的status + entity.setStatus(dto.getIsActive()); + // 转换时间格式:LocalDateTime -> Date + if (dto.getCreateTime() != null) { + entity.setCreateAt(Date.from(dto.getCreateTime().atZone(ZoneId.systemDefault()).toInstant())); + } + if (dto.getUpdateTime() != null) { + entity.setUpdateAt(Date.from(dto.getUpdateTime().atZone(ZoneId.systemDefault()).toInstant())); + } + return entity; + } public DeviceEntity getDevice(Long deviceId) { - if (redisTemplate.hasKey(String.format(DEVICE_CACHE_KEY, deviceId))) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(String.format(DEVICE_CACHE_KEY, deviceId)), DeviceEntity.class); + log.debug("获取设备信息, deviceId: {}", deviceId); + try { + DeviceV2DTO deviceDto = deviceIntegrationService.getDevice(deviceId); + DeviceEntity device = convertToEntity(deviceDto); + if (device != null) { + // 存储到常规缓存 + redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); + // 存储到成功结果缓存,用于失败时降级 + redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); + } + return device; + } catch (Exception e) { + log.warn("调用zt-device服务失败, deviceId: {}, 尝试返回缓存结果", deviceId, e); + String cachedDevice = redisTemplate.opsForValue().get(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId)); + if (cachedDevice != null) { + log.info("返回缓存的设备信息, deviceId: {}", deviceId); + return JacksonUtil.parseObject(cachedDevice, DeviceEntity.class); + } + log.error("无法获取设备信息且无缓存数据, deviceId: {}", deviceId); + throw e; } - DeviceEntity device = deviceMapper.getByDeviceId(deviceId); - if (null != device) { - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - } - return device; } public DeviceEntity getDeviceByDeviceNo(String deviceNo) { - if (redisTemplate.hasKey(String.format(DEVICE_CACHE_KEY, deviceNo))) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(String.format(DEVICE_CACHE_KEY, deviceNo)), DeviceEntity.class); + log.debug("根据设备编号获取设备信息, deviceNo: {}", deviceNo); + try { + DeviceV2DTO deviceDto = deviceIntegrationService.getDeviceByNo(deviceNo); + DeviceEntity device = convertToEntity(deviceDto); + if (device != null) { + // 存储到常规缓存 + redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); + redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getId()), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); + // 存储到成功结果缓存,用于失败时降级 + redisTemplate.opsForValue().set(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); + redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_SUCCESS_KEY, device.getId()), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); + } + return device; + } catch (Exception e) { + log.warn("调用zt-device服务失败, deviceNo: {}, 尝试返回缓存结果", deviceNo, e); + String cachedDevice = redisTemplate.opsForValue().get(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo)); + if (cachedDevice != null) { + log.info("返回缓存的设备信息, deviceNo: {}", deviceNo); + return JacksonUtil.parseObject(cachedDevice, DeviceEntity.class); + } + log.error("无法获取设备信息且无缓存数据, deviceNo: {}", deviceNo); + throw e; } - DeviceEntity device = deviceMapper.getByDeviceNo(deviceNo); - if (null == device) { - device = deviceMapper.getByDeviceNo2(deviceNo); - } - if (null != device) { - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getId()), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - } else { - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), "null", 60L, TimeUnit.SECONDS); - } - return device; } public DeviceConfigEntity getDeviceConfig(Long deviceId) { @@ -82,6 +137,8 @@ public class DeviceRepository { } } redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceNo)); + // 清理成功结果缓存 + redisTemplate.delete(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo)); return true; } public boolean clearDeviceCache(Long deviceId) { @@ -92,6 +149,8 @@ public class DeviceRepository { } redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceId)); redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)); + // 清理成功结果缓存 + redisTemplate.delete(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId)); return true; } From ad7d1042f4133ae3e2bf9ce153ba736aee2e8af0 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 1 Sep 2025 23:15:02 +0800 Subject: [PATCH 04/18] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E9=85=8D=E7=BD=AE=E8=AE=BE=E5=A4=87=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 BatchUpdateResponse 和 ProcessedConfigItem 类用于批量更新响应 - 修改 DeviceConfigV2Client 接口返回类型为 BatchUpdateResponse - 在 DeviceConfigIntegrationService 中实现新的批量更新逻辑 - 更新 DeviceIntegrationExample 和 CLAUDE.md 文档,添加新的批量配置示例 --- .../java/com/ycwl/basic/integration/CLAUDE.md | 70 +++++++- .../device/client/DeviceConfigV2Client.java | 4 +- .../dto/config/BatchUpdateResponse.java | 36 +++++ .../dto/config/ProcessedConfigItem.java | 52 ++++++ .../example/DeviceIntegrationExample.java | 151 +++++++++++++++++- .../DeviceConfigIntegrationService.java | 121 +++++++++++--- 6 files changed, 409 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/BatchUpdateResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/dto/config/ProcessedConfigItem.java diff --git a/src/main/java/com/ycwl/basic/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md index 2120cad..173da25 100644 --- a/src/main/java/com/ycwl/basic/integration/CLAUDE.md +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -163,11 +163,28 @@ configService.setDeviceFramerate(deviceId, 60); // Get flat configuration Map config = configService.getDeviceFlatConfig(deviceId); -// Batch update configurations +// Batch update configurations (simple way) Map batchConfigs = new HashMap<>(); batchConfigs.put("brightness", "50"); batchConfigs.put("contrast", "80"); configService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); + +// 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 @@ -193,6 +210,57 @@ for (DeviceV2DTO device : activeDevices.getList()) { } ``` +### 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. diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java index 28046b8..c94ca1e 100644 --- a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java @@ -68,8 +68,8 @@ public interface DeviceConfigV2Client { * 批量更新设备配置 */ @PostMapping("/{deviceId}/batch") - CommonResponse batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, - @RequestBody BatchDeviceConfigRequest request); + CommonResponse batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, + @RequestBody BatchDeviceConfigRequest request); /** * 扁平化批量更新设备配置 diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchUpdateResponse.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchUpdateResponse.java new file mode 100644 index 0000000..bd4b756 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/BatchUpdateResponse.java @@ -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 processedItems; + + /** + * 错误信息列表 + */ + @JsonProperty("errors") + private List errors; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/config/ProcessedConfigItem.java b/src/main/java/com/ycwl/basic/integration/device/dto/config/ProcessedConfigItem.java new file mode 100644 index 0000000..6911163 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/config/ProcessedConfigItem.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java index ce2de85..04e01a9 100644 --- a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java @@ -101,13 +101,49 @@ public class DeviceIntegrationExample { String resolution = deviceConfigService.getDeviceResolution(deviceId); log.info("IP地址: {}, 分辨率: {}", ipAddress, resolution); - // 7. 批量更新配置 + // 7. 批量更新配置(扁平化方式) Map batchConfigs = new HashMap<>(); batchConfigs.put("brightness", "50"); batchConfigs.put("contrast", "80"); batchConfigs.put("quality", "high"); deviceConfigService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); log.info("批量配置已更新"); + + // 8. 使用新的批量配置API(返回详细结果) + BatchDeviceConfigRequest.BatchDeviceConfigItem item1 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item1.setConfigKey("resolution"); + item1.setConfigValue("4K"); + + BatchDeviceConfigRequest.BatchDeviceConfigItem item2 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item2.setConfigKey("custom_setting"); + item2.setConfigValue("custom_value"); + item2.setConfigType("string"); + item2.setDescription("自定义设置"); + + BatchDeviceConfigRequest batchRequest = new BatchDeviceConfigRequest(); + batchRequest.setConfigs(List.of(item1, item2)); + + BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, batchRequest); + log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed()); + + for (ProcessedConfigItem processedItem : result.getProcessedItems()) { + log.info("配置项 {} 处理状态: {}, 动作: {}, 有默认配置: {}", + processedItem.getConfigKey(), + processedItem.getStatus(), + processedItem.getAction(), + processedItem.getHasDefault()); + } + + // 9. 使用配置构建器简化批量配置 + BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 30, "H264") + .addNetworkConfig("192.168.1.100", 554, "RTSP") + .addAuthConfig("admin", "password123") + .addConfig("custom_key", "custom_value", "string", "自定义配置") + .build(); + + BatchUpdateResponse builderResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, builderRequest); + log.info("构建器批量配置更新结果: 成功={}, 失败={}", builderResult.getSuccess(), builderResult.getFailed()); } /** @@ -195,6 +231,118 @@ public class DeviceIntegrationExample { } } + /** + * 新批量配置API使用示例 + */ + public void batchConfigurationExample() { + log.info("=== 新批量配置API使用示例 ==="); + + Long deviceId = 1L; + + // 1. 使用基础批量配置API + try { + // 创建批量配置请求 + BatchDeviceConfigRequest.BatchDeviceConfigItem item1 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item1.setConfigKey("resolution"); + item1.setConfigValue("3840x2160"); + + BatchDeviceConfigRequest.BatchDeviceConfigItem item2 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item2.setConfigKey("framerate"); + item2.setConfigValue("60"); + + BatchDeviceConfigRequest.BatchDeviceConfigItem item3 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item3.setConfigKey("custom_quality"); + item3.setConfigValue("ultra"); + item3.setConfigType("string"); + item3.setDescription("自定义画质设置"); + + BatchDeviceConfigRequest request = new BatchDeviceConfigRequest(); + request.setConfigs(List.of(item1, item2, item3)); + + // 调用批量配置API并获取详细结果 + BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, request); + + // 分析结果 + log.info("批量配置更新完成 - 成功: {}, 失败: {}", result.getSuccess(), result.getFailed()); + + // 详细处理结果 + for (ProcessedConfigItem item : result.getProcessedItems()) { + if ("success".equals(item.getStatus())) { + log.info("✅ 配置 {} 更新成功 - 动作: {}, 默认配置: {}, 最终类型: {}", + item.getConfigKey(), + item.getAction(), + item.getHasDefault() ? "是" : "否", + item.getFinalType()); + } else { + log.warn("❌ 配置 {} 更新失败 - 原因: {}", + item.getConfigKey(), + item.getMessage()); + } + } + + // 错误信息 + if (!result.getErrors().isEmpty()) { + log.warn("批量更新错误列表:"); + result.getErrors().forEach(error -> log.warn(" - {}", error)); + } + + } catch (Exception e) { + log.error("批量配置更新异常", e); + } + + // 2. 使用构建器模式简化批量配置 + try { + log.info("--- 使用构建器模式 ---"); + + BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 30, "H265") // 添加视频配置 + .addNetworkConfig("192.168.1.200", 8554, "RTSP") // 添加网络配置 + .addAuthConfig("operator", "newpassword") // 添加认证配置 + .addConfig("recording_enabled", "true") // 使用默认配置的项 + .addConfig("storage_path", "/data/recordings", "string", "录像存储路径") // 自定义配置项 + .build(); + + BatchUpdateResponse builderResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, builderRequest); + log.info("构建器模式批量配置结果 - 成功: {}, 失败: {}", + builderResult.getSuccess(), builderResult.getFailed()); + + } catch (Exception e) { + log.error("构建器模式批量配置异常", e); + } + + // 3. 处理部分成功的情况 + try { + log.info("--- 处理部分成功场景 ---"); + + BatchDeviceConfigRequest mixedRequest = deviceConfigService.createBatchConfigBuilder() + .addConfig("resolution", "1920x1080") // 正常配置 + .addConfig("invalid_key_format", "test_value") // 可能失败的配置 + .addConfig("", "empty_key_value") // 肯定失败的配置 + .addConfig("max_connections", "100") // 正常配置 + .build(); + + BatchUpdateResponse mixedResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, mixedRequest); + + if (mixedResult.getFailed() > 0) { + log.warn("存在部分失败的配置项:"); + mixedResult.getProcessedItems().stream() + .filter(item -> "failed".equals(item.getStatus())) + .forEach(item -> log.warn(" - {}: {}", item.getConfigKey(), item.getMessage())); + + log.info("成功配置的项:"); + mixedResult.getProcessedItems().stream() + .filter(item -> "success".equals(item.getStatus())) + .forEach(item -> log.info(" - {}: {} ({})", + item.getConfigKey(), + item.getAction(), + item.getHasDefault() ? "使用默认规则" : "自定义配置")); + } + + } catch (Exception e) { + log.error("混合配置场景异常", e); + } + } + /** * 错误处理和故障恢复示例 */ @@ -232,6 +380,7 @@ public class DeviceIntegrationExample { try { basicDeviceOperations(); deviceConfigurationOperations(); + batchConfigurationExample(); // 新增的批量配置示例 cameraManagementExample(); deviceMonitoringExample(); errorHandlingExample(); diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java index 0e2359a..4be322d 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -67,10 +67,13 @@ public class DeviceConfigIntegrationService { handleResponse(response, "删除设备配置失败"); } - public void batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) { + /** + * 批量更新设备配置 + */ + public BatchUpdateResponse batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) { log.info("批量更新设备配置, deviceId: {}, configs count: {}", deviceId, request.getConfigs().size()); - CommonResponse response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request); - handleResponse(response, "批量更新设备配置失败"); + CommonResponse response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request); + return handleResponse(response, "批量更新设备配置失败"); } public void batchFlatUpdateDeviceConfig(Long deviceId, Map configs) { @@ -126,30 +129,17 @@ public class DeviceConfigIntegrationService { } /** - * 配置摄像头基本参数 + * 配置摄像头参数 */ - public void configureCameraBasicParams(Long deviceId, String ipAddress, String resolution, - Integer framerate, String protocol) { + public void configureCameraParams(Long deviceId, String ipAddress, String resolution, + Integer framerate, String protocol, String username, String password) { Map configs = new HashMap<>(); configs.put("ip_address", ipAddress); configs.put("resolution", resolution); configs.put("framerate", framerate.toString()); configs.put("protocol", protocol); - batchFlatUpdateDeviceConfig(deviceId, configs); - } - - /** - * 配置摄像头完整参数 - */ - public void configureCameraFullParams(Long deviceId, String ipAddress, String resolution, - Integer framerate, String protocol, String username, String password) { - Map configs = new HashMap<>(); - configs.put("ip_address", ipAddress); - configs.put("resolution", resolution); - configs.put("framerate", framerate.toString()); - configs.put("protocol", protocol); - configs.put("username", username); - configs.put("password", password); + if (username != null) configs.put("username", username); + if (password != null) configs.put("password", password); batchFlatUpdateDeviceConfig(deviceId, configs); } @@ -174,6 +164,95 @@ public class DeviceConfigIntegrationService { public String getDeviceResolution(Long deviceId) { return getDeviceConfigValue(deviceId, "resolution"); } + + /** + * 创建批量配置请求构建器 + */ + 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 BatchDeviceConfigRequestBuilder addVideoConfig(String resolution, Integer framerate, String codec) { + addConfig("resolution", resolution); + addConfig("framerate", framerate.toString()); + if (codec != null) { + addConfig("codec", codec); + } + return this; + } + + /** + * 添加网络配置 + */ + public BatchDeviceConfigRequestBuilder addNetworkConfig(String ipAddress, Integer port, String protocol) { + addConfig("ip_address", ipAddress); + if (port != null) { + addConfig("port", port.toString()); + } + if (protocol != null) { + addConfig("protocol", protocol); + } + return this; + } + + /** + * 添加认证配置 + */ + public BatchDeviceConfigRequestBuilder addAuthConfig(String username, String password) { + if (username != null) { + addConfig("username", username); + } + if (password != null) { + addConfig("password", password); + } + return this; + } + + /** + * 构建请求对象 + */ + public BatchDeviceConfigRequest build() { + return request; + } + } private T handleResponse(CommonResponse response, String errorMessage) { if (response == null || !response.isSuccess()) { From b475e38018889034a47f86f2601aeb6bb584786a Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 01:32:12 +0800 Subject: [PATCH 05/18] =?UTF-8?q?feat(device):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 CreateDeviceRequest 和 UpdateDeviceRequest 中添加 sort 字段 - 在 DeviceV2DTO 中添加 sort 属性- 更新 DeviceIntegrationExample 中的示例代码,演示设备排序功能- 新增设备排序相关的服务方法,如 createIpcDeviceWithSort 和 updateDeviceSort - 优化 runAllExamples 方法,移除部分冗余示例 - 新增 runBasicExamples 方法,用于运行基础示例 --- .../dto/device/CreateDeviceRequest.java | 1 + .../device/dto/device/DeviceV2DTO.java | 3 + .../dto/device/UpdateDeviceRequest.java | 1 + .../example/DeviceIntegrationExample.java | 408 +++++------------- .../service/DeviceIntegrationService.java | 40 ++ 5 files changed, 149 insertions(+), 304 deletions(-) diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java index dc5328a..26425da 100644 --- a/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java @@ -9,4 +9,5 @@ public class CreateDeviceRequest { private String type; private Integer isActive; private Long scenicId; + private Integer sort; } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java index 75ce26d..9ac6b8c 100644 --- a/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java @@ -26,6 +26,9 @@ public class DeviceV2DTO { @JsonProperty("scenicId") private Long scenicId; + @JsonProperty("sort") + private Integer sort; + @JsonProperty("createTime") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; diff --git a/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java b/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java index ab818d9..a3a39b5 100644 --- a/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java @@ -9,4 +9,5 @@ public class UpdateDeviceRequest { private String type; private Integer isActive; private Long scenicId; + private Integer sort; } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java index 04e01a9..84d2231 100644 --- a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java @@ -8,7 +8,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,352 +23,142 @@ public class DeviceIntegrationExample { private final DeviceConfigIntegrationService deviceConfigService; /** - * 基本设备操作示例 + * 基本设备操作 */ public void basicDeviceOperations() { - log.info("=== 基本设备操作示例 ==="); + log.info("=== 基本设备操作 ==="); - // 1. 创建IPC摄像头设备 + // 创建IPC摄像头设备(默认排序) DeviceV2DTO ipcDevice = deviceService.createIpcDevice( "前门摄像头", "CAM001", 1001L); - log.info("创建IPC设备: {}", ipcDevice); + log.info("创建IPC设备: {}, 排序值: {}", ipcDevice.getName(), ipcDevice.getSort()); - // 2. 创建自定义设备 - DeviceV2DTO customDevice = deviceService.createCustomDevice( - "温度传感器", "TEMP001", 1001L); - log.info("创建自定义设备: {}", customDevice); - - // 3. 根据ID获取设备信息 + // 根据ID获取设备信息 DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId()); - log.info("获取设备信息: {}", device); + log.info("获取设备信息: {}, 排序值: {}", device.getName(), device.getSort()); - // 4. 根据设备编号获取设备信息 + // 根据设备编号获取设备信息 DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); - log.info("根据编号获取设备: {}", deviceByNo); + log.info("根据编号获取设备: {}", deviceByNo.getName()); - // 5. 获取设备详细信息(含配置) + // 获取设备详细信息(含配置) DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId()); - log.info("获取设备配置: {}", deviceWithConfig); + log.info("获取设备配置: {}", deviceWithConfig.getName()); - // 6. 分页查询景区设备列表 + // 分页查询景区设备列表 DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10); - log.info("景区IPC设备列表: 总数={}, 当前页={}", deviceList.getTotal(), deviceList.getList().size()); + log.info("景区设备列表: 总数={}", deviceList.getTotal()); - // 7. 启用/禁用设备 + // 启用设备 deviceService.enableDevice(ipcDevice.getId()); log.info("设备已启用"); - - deviceService.disableDevice(customDevice.getId()); - log.info("设备已禁用"); } /** - * 设备配置管理示例 + * 设备排序功能演示 */ - public void deviceConfigurationOperations() { - log.info("=== 设备配置管理示例 ==="); + public void deviceSortingOperations() { + log.info("=== 设备排序功能演示 ==="); - // 假设已有设备ID - Long deviceId = 1L; + Long scenicId = 1001L; - // 1. 配置摄像头基本参数 - deviceConfigService.configureCameraBasicParams( - deviceId, "192.168.1.100", "1920x1080", 30, "RTSP"); - log.info("摄像头基本参数已配置"); + // 创建带排序的设备 + DeviceV2DTO camera1 = deviceService.createIpcDeviceWithSort( + "大门摄像头", "CAM_GATE", scenicId, 10); + log.info("创建摄像头1: {}, 排序: {}", camera1.getName(), camera1.getSort()); - // 2. 配置摄像头完整参数(包含认证) - deviceConfigService.configureCameraFullParams( - deviceId, "192.168.1.101", "3840x2160", 25, "RTSP", "admin", "password123"); - log.info("摄像头完整参数已配置"); + DeviceV2DTO camera2 = deviceService.createIpcDeviceWithSort( + "后门摄像头", "CAM_BACK", scenicId, 20); + log.info("创建摄像头2: {}, 排序: {}", camera2.getName(), camera2.getSort()); - // 3. 单独设置特定配置 - deviceConfigService.setDeviceIpAddress(deviceId, "192.168.1.102"); - deviceConfigService.setDeviceResolution(deviceId, "2560x1440"); - deviceConfigService.setDeviceFramerate(deviceId, 60); - log.info("单独配置项已更新"); + DeviceV2DTO sensor1 = deviceService.createCustomDeviceWithSort( + "温度传感器", "TEMP_01", scenicId, 5); + log.info("创建传感器: {}, 排序: {}", sensor1.getName(), sensor1.getSort()); - // 4. 获取设备所有配置 - List configs = deviceConfigService.getDeviceConfigs(deviceId); - log.info("设备配置列表: {}", configs.size()); + // 更新设备排序 + deviceService.updateDeviceSort(camera1.getId(), 1); + log.info("更新摄像头1排序为1(置顶)"); - // 5. 获取扁平化配置 - Map flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId); - log.info("扁平化配置: {}", flatConfig); - - // 6. 获取特定配置值 - String ipAddress = deviceConfigService.getDeviceIpAddress(deviceId); - String resolution = deviceConfigService.getDeviceResolution(deviceId); - log.info("IP地址: {}, 分辨率: {}", ipAddress, resolution); - - // 7. 批量更新配置(扁平化方式) - Map batchConfigs = new HashMap<>(); - batchConfigs.put("brightness", "50"); - batchConfigs.put("contrast", "80"); - batchConfigs.put("quality", "high"); - deviceConfigService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); - log.info("批量配置已更新"); - - // 8. 使用新的批量配置API(返回详细结果) - BatchDeviceConfigRequest.BatchDeviceConfigItem item1 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); - item1.setConfigKey("resolution"); - item1.setConfigValue("4K"); - - BatchDeviceConfigRequest.BatchDeviceConfigItem item2 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); - item2.setConfigKey("custom_setting"); - item2.setConfigValue("custom_value"); - item2.setConfigType("string"); - item2.setDescription("自定义设置"); - - BatchDeviceConfigRequest batchRequest = new BatchDeviceConfigRequest(); - batchRequest.setConfigs(List.of(item1, item2)); - - BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, batchRequest); - log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed()); - - for (ProcessedConfigItem processedItem : result.getProcessedItems()) { - log.info("配置项 {} 处理状态: {}, 动作: {}, 有默认配置: {}", - processedItem.getConfigKey(), - processedItem.getStatus(), - processedItem.getAction(), - processedItem.getHasDefault()); + // 获取排序后的设备列表 + 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()); } - // 9. 使用配置构建器简化批量配置 + // 批量调整排序演示 + log.info("--- 批量调整排序演示 ---"); + deviceService.updateDeviceSort(sensor1.getId(), 15); // 传感器排到中间 + deviceService.updateDeviceSort(camera2.getId(), 30); // 后门摄像头排到最后 + log.info("批量排序调整完成"); + } + + /** + * 设备配置管理 + */ + public void deviceConfigurationOperations() { + log.info("=== 设备配置管理 ==="); + + Long deviceId = 1L; + + // 配置摄像头基本参数 + deviceConfigService.configureCameraParams( + deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password123"); + log.info("摄像头基本参数已配置"); + + // 获取设备所有配置 + List configs = deviceConfigService.getDeviceConfigs(deviceId); + log.info("设备配置数量: {}", configs.size()); + + // 获取扁平化配置 + Map flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId); + log.info("扁平化配置项数: {}", flatConfig.size()); + + // 使用批量配置API BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() .addVideoConfig("1920x1080", 30, "H264") .addNetworkConfig("192.168.1.100", 554, "RTSP") .addAuthConfig("admin", "password123") - .addConfig("custom_key", "custom_value", "string", "自定义配置") .build(); - BatchUpdateResponse builderResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, builderRequest); - log.info("构建器批量配置更新结果: 成功={}, 失败={}", builderResult.getSuccess(), builderResult.getFailed()); + BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfig(deviceId, builderRequest); + log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed()); } /** - * 摄像头管理示例 + * 排序最佳实践演示 */ - public void cameraManagementExample() { - log.info("=== 摄像头管理示例 ==="); + public void sortingBestPractices() { + log.info("=== 排序最佳实践演示 ==="); Long scenicId = 1001L; - // 1. 批量创建摄像头 - for (int i = 1; i <= 5; i++) { - DeviceV2DTO camera = deviceService.createIpcDevice( - "摄像头" + i, "CAM00" + i, scenicId); - - // 配置每个摄像头的基本参数 - deviceConfigService.configureCameraFullParams( - camera.getId(), - "192.168.1." + (100 + i), - "1920x1080", - 30, - "RTSP", - "admin", - "camera" + i - ); - log.info("创建并配置摄像头: {}", camera.getName()); - } + // 推荐使用10的倍数作为排序值 + DeviceV2DTO device1 = deviceService.createIpcDeviceWithSort( + "重要摄像头", "CAM_IMPORTANT", scenicId, 10); - // 2. 获取景区所有摄像头状态 - DeviceV2WithConfigListResponse camerasWithConfig = - deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); + DeviceV2DTO device2 = deviceService.createIpcDeviceWithSort( + "普通摄像头", "CAM_NORMAL", scenicId, 20); - log.info("景区摄像头总数: {}", camerasWithConfig.getTotal()); - for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) { - log.info("摄像头: {}, IP: {}", - camera.getName(), - camera.getConfig().get("ip_address")); - } + DeviceV2DTO device3 = deviceService.createIpcDeviceWithSort( + "备用摄像头", "CAM_BACKUP", scenicId, 30); - // 3. 批量更新摄像头分辨率 - for (DeviceV2WithConfigDTO camera : camerasWithConfig.getList()) { - deviceConfigService.setDeviceResolution(camera.getId(), "2560x1440"); - } - log.info("所有摄像头分辨率已更新为 2560x1440"); - } - - /** - * 设备监控和状态管理示例 - */ - public void deviceMonitoringExample() { - log.info("=== 设备监控和状态管理示例 ==="); + log.info("使用10的倍数创建设备排序: 10, 20, 30"); - Long scenicId = 1001L; + // 在中间插入新设备 + DeviceV2DTO insertDevice = deviceService.createIpcDeviceWithSort( + "中间摄像头", "CAM_MIDDLE", scenicId, 25); + log.info("在20和30之间插入设备,排序值: 25"); - // 1. 获取景区所有激活设备 - DeviceV2ListResponse activeDevices = deviceService.getScenicActiveDevices(scenicId, 1, 50); - log.info("景区激活设备数量: {}", activeDevices.getTotal()); + // 置顶操作 + deviceService.updateDeviceSort(device2.getId(), 1); + log.info("将普通摄像头置顶(排序值: 1)"); - // 2. 按设备类型分类统计 - Map deviceTypeCount = new HashMap<>(); - for (DeviceV2DTO device : activeDevices.getList()) { - deviceTypeCount.merge(device.getType(), 1, Integer::sum); - } - log.info("设备类型统计: {}", deviceTypeCount); - - // 3. 检查设备配置完整性 - for (DeviceV2DTO device : activeDevices.getList()) { - try { - Map config = deviceConfigService.getDeviceFlatConfig(device.getId()); - boolean hasIpConfig = config.containsKey("ip_address"); - log.info("设备 {} 配置状态: IP配置={}", device.getName(), hasIpConfig ? "已配置" : "未配置"); - } catch (Exception e) { - log.warn("获取设备 {} 配置失败: {}", device.getName(), e.getMessage()); - } - } - - // 4. 按设备编号搜索 - try { - DeviceV2WithConfigDTO deviceByNo = deviceService.getDeviceWithConfigByNo("CAM001"); - log.info("根据编号找到设备: {} (配置项数量: {})", - deviceByNo.getName(), - deviceByNo.getConfig().size()); - } catch (Exception e) { - log.warn("未找到设备编号 CAM001: {}", e.getMessage()); - } - } - - /** - * 新批量配置API使用示例 - */ - public void batchConfigurationExample() { - log.info("=== 新批量配置API使用示例 ==="); - - Long deviceId = 1L; - - // 1. 使用基础批量配置API - try { - // 创建批量配置请求 - BatchDeviceConfigRequest.BatchDeviceConfigItem item1 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); - item1.setConfigKey("resolution"); - item1.setConfigValue("3840x2160"); - - BatchDeviceConfigRequest.BatchDeviceConfigItem item2 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); - item2.setConfigKey("framerate"); - item2.setConfigValue("60"); - - BatchDeviceConfigRequest.BatchDeviceConfigItem item3 = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); - item3.setConfigKey("custom_quality"); - item3.setConfigValue("ultra"); - item3.setConfigType("string"); - item3.setDescription("自定义画质设置"); - - BatchDeviceConfigRequest request = new BatchDeviceConfigRequest(); - request.setConfigs(List.of(item1, item2, item3)); - - // 调用批量配置API并获取详细结果 - BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, request); - - // 分析结果 - log.info("批量配置更新完成 - 成功: {}, 失败: {}", result.getSuccess(), result.getFailed()); - - // 详细处理结果 - for (ProcessedConfigItem item : result.getProcessedItems()) { - if ("success".equals(item.getStatus())) { - log.info("✅ 配置 {} 更新成功 - 动作: {}, 默认配置: {}, 最终类型: {}", - item.getConfigKey(), - item.getAction(), - item.getHasDefault() ? "是" : "否", - item.getFinalType()); - } else { - log.warn("❌ 配置 {} 更新失败 - 原因: {}", - item.getConfigKey(), - item.getMessage()); - } - } - - // 错误信息 - if (!result.getErrors().isEmpty()) { - log.warn("批量更新错误列表:"); - result.getErrors().forEach(error -> log.warn(" - {}", error)); - } - - } catch (Exception e) { - log.error("批量配置更新异常", e); - } - - // 2. 使用构建器模式简化批量配置 - try { - log.info("--- 使用构建器模式 ---"); - - BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() - .addVideoConfig("1920x1080", 30, "H265") // 添加视频配置 - .addNetworkConfig("192.168.1.200", 8554, "RTSP") // 添加网络配置 - .addAuthConfig("operator", "newpassword") // 添加认证配置 - .addConfig("recording_enabled", "true") // 使用默认配置的项 - .addConfig("storage_path", "/data/recordings", "string", "录像存储路径") // 自定义配置项 - .build(); - - BatchUpdateResponse builderResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, builderRequest); - log.info("构建器模式批量配置结果 - 成功: {}, 失败: {}", - builderResult.getSuccess(), builderResult.getFailed()); - - } catch (Exception e) { - log.error("构建器模式批量配置异常", e); - } - - // 3. 处理部分成功的情况 - try { - log.info("--- 处理部分成功场景 ---"); - - BatchDeviceConfigRequest mixedRequest = deviceConfigService.createBatchConfigBuilder() - .addConfig("resolution", "1920x1080") // 正常配置 - .addConfig("invalid_key_format", "test_value") // 可能失败的配置 - .addConfig("", "empty_key_value") // 肯定失败的配置 - .addConfig("max_connections", "100") // 正常配置 - .build(); - - BatchUpdateResponse mixedResult = deviceConfigService.batchUpdateDeviceConfigWithResult(deviceId, mixedRequest); - - if (mixedResult.getFailed() > 0) { - log.warn("存在部分失败的配置项:"); - mixedResult.getProcessedItems().stream() - .filter(item -> "failed".equals(item.getStatus())) - .forEach(item -> log.warn(" - {}: {}", item.getConfigKey(), item.getMessage())); - - log.info("成功配置的项:"); - mixedResult.getProcessedItems().stream() - .filter(item -> "success".equals(item.getStatus())) - .forEach(item -> log.info(" - {}: {} ({})", - item.getConfigKey(), - item.getAction(), - item.getHasDefault() ? "使用默认规则" : "自定义配置")); - } - - } catch (Exception e) { - log.error("混合配置场景异常", e); - } - } - - /** - * 错误处理和故障恢复示例 - */ - public void errorHandlingExample() { - log.info("=== 错误处理和故障恢复示例 ==="); - - // 1. 尝试获取不存在的设备 - try { - deviceService.getDevice(99999L); - } catch (Exception e) { - log.warn("获取不存在设备的预期错误: {}", e.getMessage()); - } - - // 2. 尝试获取不存在的配置 - try { - deviceConfigService.getDeviceConfigByKey(1L, "non_existent_key"); - } catch (Exception e) { - log.warn("获取不存在配置的预期错误: {}", e.getMessage()); - } - - // 3. 创建设备时的参数验证 - try { - CreateDeviceRequest invalidRequest = new CreateDeviceRequest(); - // 缺少必要字段 - deviceService.createDevice(invalidRequest); - } catch (Exception e) { - log.warn("创建设备参数错误: {}", e.getMessage()); + // 查看最终排序结果 + DeviceV2ListResponse finalList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId); + log.info("最终排序结果:"); + for (DeviceV2DTO device : finalList.getList()) { + log.info(" - {}: 排序={}", device.getName(), device.getSort()); } } @@ -379,14 +168,25 @@ public class DeviceIntegrationExample { public void runAllExamples() { try { basicDeviceOperations(); + deviceSortingOperations(); + sortingBestPractices(); deviceConfigurationOperations(); - batchConfigurationExample(); // 新增的批量配置示例 - cameraManagementExample(); - deviceMonitoringExample(); - errorHandlingExample(); log.info("=== 所有示例执行完成 ==="); } catch (Exception e) { log.error("示例执行过程中发生错误", e); } } + + /** + * 运行基础示例(简化版) + */ + public void runBasicExamples() { + try { + basicDeviceOperations(); + deviceConfigurationOperations(); + log.info("=== 基础示例执行完成 ==="); + } catch (Exception e) { + log.error("示例执行过程中发生错误", e); + } + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java index 418496d..bef9d9f 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -85,6 +85,21 @@ public class DeviceIntegrationService { 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); } @@ -98,6 +113,21 @@ public class DeviceIntegrationService { 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); } @@ -119,6 +149,16 @@ public class DeviceIntegrationService { 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设备列表 */ From ec24464cba1c4d05b77324e6f27b706b78f15ae2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 01:43:31 +0800 Subject: [PATCH 06/18] =?UTF-8?q?feat(device):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86V2=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加设备基础 CRUD 操作接口 - 实现设备配置管理相关接口- 提供景区设备管理功能接口 - 优化参数验证和错误处理 --- .../controller/pc/DeviceV2Controller.java | 523 ++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java new file mode 100644 index 0000000..48df318 --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java @@ -0,0 +1,523 @@ +package com.ycwl.basic.controller.pc; + +import com.ycwl.basic.integration.device.dto.config.*; +import com.ycwl.basic.integration.device.dto.device.*; +import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.utils.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 设备管理 V2 版本控制器 - 基于 zt-device 集成服务 + * + * @author Claude Code + * @date 2025-09-01 + */ +@Slf4j +@RestController +@RequestMapping("/api/device/v2") +@RequiredArgsConstructor +public class DeviceV2Controller { + + private final DeviceIntegrationService deviceIntegrationService; + private final DeviceConfigIntegrationService deviceConfigIntegrationService; + + // ========== 设备基础 CRUD 操作 ========== + + /** + * 设备V2核心信息分页列表 + */ + @GetMapping("/") + public ApiResponse listDevices(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String name, + @RequestParam(required = false) String no, + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer isActive, + @RequestParam(required = false) Long scenicId) { + log.info("分页查询设备核心信息列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}", + page, pageSize, name, no, type, isActive, scenicId); + + // 参数验证:限制pageSize最大值为100 + if (pageSize > 100) { + pageSize = 100; + } + + try { + DeviceV2ListResponse response = deviceIntegrationService.listDevices(page, pageSize, name, no, type, isActive, scenicId); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("分页查询设备核心信息列表失败", e); + return ApiResponse.fail("分页查询设备列表失败: " + e.getMessage()); + } + } + + /** + * 设备V2带配置信息分页列表 + */ + @GetMapping("/with-config") + public ApiResponse listDevicesWithConfig(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) String name, + @RequestParam(required = false) String no, + @RequestParam(required = false) String type, + @RequestParam(required = false) Integer isActive, + @RequestParam(required = false) Long scenicId) { + log.info("分页查询设备带配置信息列表, page: {}, pageSize: {}, name: {}, no: {}, type: {}, isActive: {}, scenicId: {}", + page, pageSize, name, no, type, isActive, scenicId); + + // 参数验证:限制pageSize最大值为100 + if (pageSize > 100) { + pageSize = 100; + } + + try { + DeviceV2WithConfigListResponse response = deviceIntegrationService.listDevicesWithConfig(page, pageSize, name, no, type, isActive, scenicId); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("分页查询设备带配置信息列表失败", e); + return ApiResponse.fail("分页查询设备带配置信息列表失败: " + e.getMessage()); + } + } + + /** + * 根据ID获取设备信息 + */ + @GetMapping("/{id}") + public ApiResponse getDevice(@PathVariable Long id) { + log.info("获取设备信息, id: {}", id); + try { + DeviceV2DTO device = deviceIntegrationService.getDevice(id); + return ApiResponse.success(device); + } catch (Exception e) { + log.error("获取设备信息失败, id: {}", id, e); + return ApiResponse.fail("获取设备信息失败: " + e.getMessage()); + } + } + + /** + * 根据ID获取设备带配置信息 + */ + @GetMapping("/{id}/with-config") + public ApiResponse getDeviceWithConfig(@PathVariable Long id) { + log.info("获取设备配置信息, id: {}", id); + try { + DeviceV2WithConfigDTO device = deviceIntegrationService.getDeviceWithConfig(id); + return ApiResponse.success(device); + } catch (Exception e) { + log.error("获取设备配置信息失败, id: {}", id, e); + return ApiResponse.fail("获取设备配置信息失败: " + e.getMessage()); + } + } + + /** + * 根据设备编号获取设备信息 + */ + @GetMapping("/no/{no}") + public ApiResponse getDeviceByNo(@PathVariable String no) { + log.info("根据设备编号获取设备信息, no: {}", no); + try { + DeviceV2DTO device = deviceIntegrationService.getDeviceByNo(no); + return ApiResponse.success(device); + } catch (Exception e) { + log.error("根据设备编号获取设备信息失败, no: {}", no, e); + return ApiResponse.fail("根据设备编号获取设备信息失败: " + e.getMessage()); + } + } + + /** + * 根据设备编号获取设备带配置信息 + */ + @GetMapping("/no/{no}/with-config") + public ApiResponse getDeviceWithConfigByNo(@PathVariable String no) { + log.info("根据设备编号获取设备配置信息, no: {}", no); + try { + DeviceV2WithConfigDTO device = deviceIntegrationService.getDeviceWithConfigByNo(no); + return ApiResponse.success(device); + } catch (Exception e) { + log.error("根据设备编号获取设备配置信息失败, no: {}", no, e); + return ApiResponse.fail("根据设备编号获取设备配置信息失败: " + e.getMessage()); + } + } + + /** + * 创建设备 + */ + @PostMapping("/") + public ApiResponse createDevice(@Valid @RequestBody CreateDeviceRequest request) { + log.info("创建设备, name: {}, no: {}, type: {}, sort: {}", + request.getName(), request.getNo(), request.getType(), request.getSort()); + try { + DeviceV2DTO device = deviceIntegrationService.createDevice(request); + return ApiResponse.success(device); + } catch (Exception e) { + log.error("创建设备失败", e); + return ApiResponse.fail("创建设备失败: " + e.getMessage()); + } + } + + /** + * 创建IPC摄像头设备(快捷方法) + */ + @PostMapping("/ipc") + public ApiResponse createIpcDevice(@RequestBody Map request) { + String name = (String) request.get("name"); + String deviceNo = (String) request.get("no"); + Long scenicId = Long.valueOf(request.get("scenicId").toString()); + Integer sort = request.get("sort") != null ? Integer.valueOf(request.get("sort").toString()) : null; + + log.info("创建IPC摄像头设备, name: {}, no: {}, scenicId: {}, sort: {}", name, deviceNo, scenicId, sort); + try { + DeviceV2DTO device; + if (sort != null) { + device = deviceIntegrationService.createIpcDeviceWithSort(name, deviceNo, scenicId, sort); + } else { + device = deviceIntegrationService.createIpcDevice(name, deviceNo, scenicId); + } + return ApiResponse.success(device); + } catch (Exception e) { + log.error("创建IPC摄像头设备失败", e); + return ApiResponse.fail("创建IPC摄像头设备失败: " + e.getMessage()); + } + } + + /** + * 创建自定义设备(快捷方法) + */ + @PostMapping("/custom") + public ApiResponse createCustomDevice(@RequestBody Map request) { + String name = (String) request.get("name"); + String deviceNo = (String) request.get("no"); + Long scenicId = Long.valueOf(request.get("scenicId").toString()); + Integer sort = request.get("sort") != null ? Integer.valueOf(request.get("sort").toString()) : null; + + log.info("创建自定义设备, name: {}, no: {}, scenicId: {}, sort: {}", name, deviceNo, scenicId, sort); + try { + DeviceV2DTO device; + if (sort != null) { + device = deviceIntegrationService.createCustomDeviceWithSort(name, deviceNo, scenicId, sort); + } else { + device = deviceIntegrationService.createCustomDevice(name, deviceNo, scenicId); + } + return ApiResponse.success(device); + } catch (Exception e) { + log.error("创建自定义设备失败", e); + return ApiResponse.fail("创建自定义设备失败: " + e.getMessage()); + } + } + + /** + * 更新设备信息 + */ + @PutMapping("/{id}") + public ApiResponse updateDevice(@PathVariable Long id, @Valid @RequestBody UpdateDeviceRequest request) { + log.info("更新设备信息, id: {}", id); + try { + deviceIntegrationService.updateDevice(id, request); + return ApiResponse.success("设备信息更新成功"); + } catch (Exception e) { + log.error("更新设备信息失败, id: {}", id, e); + return ApiResponse.fail("更新设备信息失败: " + e.getMessage()); + } + } + + /** + * 更新设备排序 + */ + @PutMapping("/{id}/sort") + public ApiResponse updateDeviceSort(@PathVariable Long id, @RequestBody Map request) { + Integer sort = request.get("sort"); + log.info("更新设备排序, id: {}, sort: {}", id, sort); + try { + deviceIntegrationService.updateDeviceSort(id, sort); + return ApiResponse.success("设备排序更新成功"); + } catch (Exception e) { + log.error("更新设备排序失败, id: {}, sort: {}", id, sort, e); + return ApiResponse.fail("更新设备排序失败: " + e.getMessage()); + } + } + + /** + * 启用设备 + */ + @PutMapping("/{id}/enable") + public ApiResponse enableDevice(@PathVariable Long id) { + log.info("启用设备, id: {}", id); + try { + deviceIntegrationService.enableDevice(id); + return ApiResponse.success("设备启用成功"); + } catch (Exception e) { + log.error("启用设备失败, id: {}", id, e); + return ApiResponse.fail("启用设备失败: " + e.getMessage()); + } + } + + /** + * 禁用设备 + */ + @PutMapping("/{id}/disable") + public ApiResponse disableDevice(@PathVariable Long id) { + log.info("禁用设备, id: {}", id); + try { + deviceIntegrationService.disableDevice(id); + return ApiResponse.success("设备禁用成功"); + } catch (Exception e) { + log.error("禁用设备失败, id: {}", id, e); + return ApiResponse.fail("禁用设备失败: " + e.getMessage()); + } + } + + /** + * 删除设备 + */ + @DeleteMapping("/{id}") + public ApiResponse deleteDevice(@PathVariable Long id) { + log.info("删除设备, id: {}", id); + try { + deviceIntegrationService.deleteDevice(id); + return ApiResponse.success("设备删除成功"); + } catch (Exception e) { + log.error("删除设备失败, id: {}", id, e); + return ApiResponse.fail("删除设备失败: " + e.getMessage()); + } + } + + // ========== 设备配置管理操作 ========== + + /** + * 获取设备配置列表 + */ + @GetMapping("/{id}/config") + public ApiResponse> getDeviceConfigs(@PathVariable Long id) { + log.info("获取设备配置列表, deviceId: {}", id); + try { + List configs = deviceConfigIntegrationService.getDeviceConfigs(id); + return ApiResponse.success(configs); + } catch (Exception e) { + log.error("获取设备配置列表失败, deviceId: {}", id, e); + return ApiResponse.fail("获取设备配置列表失败: " + e.getMessage()); + } + } + + /** + * 获取设备扁平化配置 + */ + @GetMapping("/{id}/flat-config") + public ApiResponse> getDeviceFlatConfig(@PathVariable Long id) { + log.info("获取设备扁平化配置, deviceId: {}", id); + try { + Map config = deviceConfigIntegrationService.getDeviceFlatConfig(id); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("获取设备扁平化配置失败, deviceId: {}", id, e); + return ApiResponse.fail("获取设备扁平化配置失败: " + e.getMessage()); + } + } + + /** + * 根据配置键获取配置 + */ + @GetMapping("/{id}/config/{configKey}") + public ApiResponse getDeviceConfigByKey(@PathVariable Long id, + @PathVariable String configKey) { + log.info("根据键获取设备配置, deviceId: {}, configKey: {}", id, configKey); + try { + DeviceConfigV2DTO config = deviceConfigIntegrationService.getDeviceConfigByKey(id, configKey); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("根据键获取设备配置失败, deviceId: {}, configKey: {}", id, configKey, e); + return ApiResponse.fail("根据键获取设备配置失败: " + e.getMessage()); + } + } + + /** + * 根据设备编号获取配置列表 + */ + @GetMapping("/no/{no}/config") + public ApiResponse> getDeviceConfigsByNo(@PathVariable String no) { + log.info("根据设备编号获取配置列表, deviceNo: {}", no); + try { + List configs = deviceConfigIntegrationService.getDeviceConfigsByNo(no); + return ApiResponse.success(configs); + } catch (Exception e) { + log.error("根据设备编号获取配置列表失败, deviceNo: {}", no, e); + return ApiResponse.fail("根据设备编号获取配置列表失败: " + e.getMessage()); + } + } + + /** + * 根据设备编号获取扁平化配置 + */ + @GetMapping("/no/{no}/flat-config") + public ApiResponse> getDeviceFlatConfigByNo(@PathVariable String no) { + log.info("根据设备编号获取扁平化配置, deviceNo: {}", no); + try { + Map config = deviceConfigIntegrationService.getDeviceFlatConfigByNo(no); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("根据设备编号获取扁平化配置失败, deviceNo: {}", no, e); + return ApiResponse.fail("根据设备编号获取扁平化配置失败: " + e.getMessage()); + } + } + + /** + * 创建设备配置 + */ + @PostMapping("/{id}/config") + public ApiResponse createDeviceConfig(@PathVariable Long id, + @Valid @RequestBody CreateDeviceConfigRequest request) { + log.info("创建设备配置, deviceId: {}, configKey: {}", id, request.getConfigKey()); + try { + DeviceConfigV2DTO config = deviceConfigIntegrationService.createDeviceConfig(id, request); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("创建设备配置失败, deviceId: {}, configKey: {}", id, request.getConfigKey(), e); + return ApiResponse.fail("创建设备配置失败: " + e.getMessage()); + } + } + + /** + * 批量创建/更新设备配置 + */ + @PostMapping("/{id}/config/batch") + public ApiResponse batchUpdateDeviceConfig(@PathVariable Long id, + @Valid @RequestBody BatchDeviceConfigRequest request) { + log.info("批量更新设备配置, deviceId: {}, configs count: {}", id, request.getConfigs().size()); + try { + BatchUpdateResponse result = deviceConfigIntegrationService.batchUpdateDeviceConfig(id, request); + return ApiResponse.success(result); + } catch (Exception e) { + log.error("批量更新设备配置失败, deviceId: {}", id, e); + return ApiResponse.fail("批量更新设备配置失败: " + e.getMessage()); + } + } + + /** + * 扁平化批量更新设备配置 + */ + @PostMapping("/{id}/config/flat-batch") + public ApiResponse batchFlatUpdateDeviceConfig(@PathVariable Long id, + @RequestBody Map configs) { + log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", id, configs.size()); + try { + deviceConfigIntegrationService.batchFlatUpdateDeviceConfig(id, configs); + return ApiResponse.success("设备配置批量更新成功"); + } catch (Exception e) { + log.error("扁平化批量更新设备配置失败, deviceId: {}", id, e); + return ApiResponse.fail("扁平化批量更新设备配置失败: " + e.getMessage()); + } + } + + /** + * 更新设备配置 + */ + @PutMapping("/{id}/config/{configId}") + public ApiResponse updateDeviceConfig(@PathVariable Long id, @PathVariable Long configId, + @Valid @RequestBody UpdateDeviceConfigRequest request) { + log.info("更新设备配置, deviceId: {}, configId: {}", id, configId); + try { + deviceConfigIntegrationService.updateDeviceConfig(id, configId, request); + return ApiResponse.success("设备配置更新成功"); + } catch (Exception e) { + log.error("更新设备配置失败, deviceId: {}, configId: {}", id, configId, e); + return ApiResponse.fail("更新设备配置失败: " + e.getMessage()); + } + } + + /** + * 配置摄像头参数(快捷方法) + */ + @PutMapping("/{id}/config/camera") + public ApiResponse configureCameraParams(@PathVariable Long id, @RequestBody Map request) { + String ipAddress = (String) request.get("ipAddress"); + String resolution = (String) request.get("resolution"); + Integer framerate = request.get("framerate") != null ? Integer.valueOf(request.get("framerate").toString()) : null; + String protocol = (String) request.get("protocol"); + String username = (String) request.get("username"); + String password = (String) request.get("password"); + + log.info("配置摄像头参数, deviceId: {}, ipAddress: {}, resolution: {}", id, ipAddress, resolution); + try { + deviceConfigIntegrationService.configureCameraParams(id, ipAddress, resolution, framerate, protocol, username, password); + return ApiResponse.success("摄像头参数配置成功"); + } catch (Exception e) { + log.error("配置摄像头参数失败, deviceId: {}", id, e); + return ApiResponse.fail("配置摄像头参数失败: " + e.getMessage()); + } + } + + /** + * 删除设备配置 + */ + @DeleteMapping("/{id}/config/{configId}") + public ApiResponse deleteDeviceConfig(@PathVariable Long id, @PathVariable Long configId) { + log.info("删除设备配置, deviceId: {}, configId: {}", id, configId); + try { + deviceConfigIntegrationService.deleteDeviceConfig(id, configId); + return ApiResponse.success("设备配置删除成功"); + } catch (Exception e) { + log.error("删除设备配置失败, deviceId: {}, configId: {}", id, configId, e); + return ApiResponse.fail("删除设备配置失败: " + e.getMessage()); + } + } + + // ========== 景区设备管理操作 ========== + + /** + * 获取景区IPC设备列表 + */ + @GetMapping("/scenic/{scenicId}/ipc") + public ApiResponse getScenicIpcDevices(@PathVariable Long scenicId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize) { + log.info("获取景区IPC设备列表, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize); + try { + DeviceV2ListResponse response = deviceIntegrationService.getScenicIpcDevices(scenicId, page, pageSize); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("获取景区IPC设备列表失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区IPC设备列表失败: " + e.getMessage()); + } + } + + /** + * 获取景区激活设备列表 + */ + @GetMapping("/scenic/{scenicId}/active") + public ApiResponse getScenicActiveDevices(@PathVariable Long scenicId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize) { + log.info("获取景区激活设备列表, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize); + try { + DeviceV2ListResponse response = deviceIntegrationService.getScenicActiveDevices(scenicId, page, pageSize); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("获取景区激活设备列表失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区激活设备列表失败: " + e.getMessage()); + } + } + + /** + * 获取景区所有设备列表 + */ + @GetMapping("/scenic/{scenicId}/all") + public ApiResponse getScenicAllDevices(@PathVariable Long scenicId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize) { + log.info("获取景区所有设备列表, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize); + try { + DeviceV2ListResponse response = deviceIntegrationService.listDevices(page, pageSize, null, null, null, null, scenicId); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("获取景区所有设备列表失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区所有设备列表失败: " + e.getMessage()); + } + } +} \ No newline at end of file From dac3b8d847daefa894df02676fe2151ba218e067 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 11:07:07 +0800 Subject: [PATCH 07/18] =?UTF-8?q?refactor(integration):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=AE=BE=E5=A4=87=E9=9B=86=E6=88=90=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=B1=BB=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 @ConditionalOnProperty 注解 - 添加 @ConfigurationProperties 注解 --- .../integration/device/config/DeviceIntegrationConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java b/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java index 6b4c6de..79dfcf1 100644 --- a/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java +++ b/src/main/java/com/ycwl/basic/integration/device/config/DeviceIntegrationConfig.java @@ -1,12 +1,12 @@ package com.ycwl.basic.integration.device.config; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration -@ConditionalOnProperty(prefix = "integration.device") +@ConfigurationProperties(prefix = "integration.device") public class DeviceIntegrationConfig { public DeviceIntegrationConfig() { From d35a1facbdbdfea370e6150a2bb4950243cb540b Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 11:22:52 +0800 Subject: [PATCH 08/18] =?UTF-8?q?refactor(device):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 DeviceConfigIntegrationService 和 DeviceIntegrationService 添加 fallback 服务 - 为设备配置和信息获取方法添加缓存逻辑 - 移除冗余的设备配置设置方法 - 优化设备信息和配置的获取流程 --- .../example/DeviceIntegrationExample.java | 10 +- .../DeviceIntegrationFallbackExample.java | 112 ++++++++++++++ .../DeviceConfigIntegrationService.java | 143 +++--------------- .../DeviceIntegrationFallbackService.java | 128 ++++++++++++++++ .../service/DeviceIntegrationService.java | 59 ++++++-- 5 files changed, 306 insertions(+), 146 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java create mode 100644 src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java index 84d2231..388c93d 100644 --- a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java @@ -100,12 +100,7 @@ public class DeviceIntegrationExample { log.info("=== 设备配置管理 ==="); Long deviceId = 1L; - - // 配置摄像头基本参数 - deviceConfigService.configureCameraParams( - deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password123"); - log.info("摄像头基本参数已配置"); - + // 获取设备所有配置 List configs = deviceConfigService.getDeviceConfigs(deviceId); log.info("设备配置数量: {}", configs.size()); @@ -116,9 +111,6 @@ public class DeviceIntegrationExample { // 使用批量配置API BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() - .addVideoConfig("1920x1080", 30, "H264") - .addNetworkConfig("192.168.1.100", 554, "RTSP") - .addAuthConfig("admin", "password123") .build(); BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfig(deviceId, builderRequest); diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java new file mode 100644 index 0000000..df2b0c0 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java @@ -0,0 +1,112 @@ +package com.ycwl.basic.integration.device.example; + +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; +import com.ycwl.basic.integration.device.service.DeviceIntegrationFallbackService; +import com.ycwl.basic.integration.device.dto.device.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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 DeviceIntegrationFallbackService fallbackService; + + /** + * 演示设备信息获取的降级机制 + */ + 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 deviceOperationFallbackExample() { + 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(deviceCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(configCacheKey); + + log.info("设备降级缓存存在: {}", hasDeviceCache); + log.info("配置降级缓存存在: {}", hasConfigCache); + + // 清理特定的降级缓存 + if (hasDeviceCache) { + fallbackService.clearFallbackCache(deviceCacheKey); + log.info("已清理设备降级缓存"); + } + } + + /** + * 运行所有降级示例 + */ + public void runAllFallbackExamples() { + log.info("开始运行设备集成降级示例..."); + + deviceInfoFallbackExample(); + deviceOperationFallbackExample(); + fallbackCacheManagementExample(); + + log.info("设备集成降级示例运行完成"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java index 4be322d..2b7b7cb 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -18,6 +18,7 @@ import java.util.Map; public class DeviceConfigIntegrationService { private final DeviceConfigV2Client deviceConfigV2Client; + private final DeviceIntegrationFallbackService fallbackService; public List getDeviceConfigs(Long deviceId) { log.info("获取设备配置列表, deviceId: {}", deviceId); @@ -39,14 +40,26 @@ public class DeviceConfigIntegrationService { public Map getDeviceFlatConfig(Long deviceId) { log.info("获取设备扁平化配置, deviceId: {}", deviceId); - CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); - return handleResponse(response, "获取设备扁平化配置失败"); + return fallbackService.executeWithFallback( + "device:flat:config:" + deviceId, + () -> { + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); + return handleResponse(response, "获取设备扁平化配置失败"); + }, + Map.class + ); } public Map getDeviceFlatConfigByNo(String deviceNo) { log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo); - CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); - return handleResponse(response, "根据设备编号获取扁平化配置失败"); + return fallbackService.executeWithFallback( + "device:flat:config:no:" + deviceNo, + () -> { + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); + return handleResponse(response, "根据设备编号获取扁平化配置失败"); + }, + Map.class + ); } public DeviceConfigV2DTO createDeviceConfig(Long deviceId, CreateDeviceConfigRequest request) { @@ -76,73 +89,6 @@ public class DeviceConfigIntegrationService { return handleResponse(response, "批量更新设备配置失败"); } - public void batchFlatUpdateDeviceConfig(Long deviceId, Map configs) { - log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", deviceId, configs.size()); - CommonResponse response = deviceConfigV2Client.batchFlatUpdateDeviceConfig(deviceId, configs); - handleResponse(response, "扁平化批量更新设备配置失败"); - } - - /** - * 设置设备IP地址 - */ - public void setDeviceIpAddress(Long deviceId, String ipAddress) { - Map config = new HashMap<>(); - config.put("ip_address", ipAddress); - batchFlatUpdateDeviceConfig(deviceId, config); - } - - /** - * 设置设备分辨率 - */ - public void setDeviceResolution(Long deviceId, String resolution) { - Map config = new HashMap<>(); - config.put("resolution", resolution); - batchFlatUpdateDeviceConfig(deviceId, config); - } - - /** - * 设置设备帧率 - */ - public void setDeviceFramerate(Long deviceId, Integer framerate) { - Map config = new HashMap<>(); - config.put("framerate", framerate.toString()); - batchFlatUpdateDeviceConfig(deviceId, config); - } - - /** - * 设置设备协议 - */ - public void setDeviceProtocol(Long deviceId, String protocol) { - Map config = new HashMap<>(); - config.put("protocol", protocol); - batchFlatUpdateDeviceConfig(deviceId, config); - } - - /** - * 设置设备认证信息 - */ - public void setDeviceAuth(Long deviceId, String username, String password) { - Map config = new HashMap<>(); - config.put("username", username); - config.put("password", password); - batchFlatUpdateDeviceConfig(deviceId, config); - } - - /** - * 配置摄像头参数 - */ - public void configureCameraParams(Long deviceId, String ipAddress, String resolution, - Integer framerate, String protocol, String username, String password) { - Map configs = new HashMap<>(); - configs.put("ip_address", ipAddress); - configs.put("resolution", resolution); - configs.put("framerate", framerate.toString()); - configs.put("protocol", protocol); - if (username != null) configs.put("username", username); - if (password != null) configs.put("password", password); - batchFlatUpdateDeviceConfig(deviceId, configs); - } - /** * 获取设备特定配置值 */ @@ -151,20 +97,6 @@ public class DeviceConfigIntegrationService { return config != null ? config.getConfigValue() : null; } - /** - * 获取设备IP地址 - */ - public String getDeviceIpAddress(Long deviceId) { - return getDeviceConfigValue(deviceId, "ip_address"); - } - - /** - * 获取设备分辨率 - */ - public String getDeviceResolution(Long deviceId) { - return getDeviceConfigValue(deviceId, "resolution"); - } - /** * 创建批量配置请求构建器 */ @@ -206,46 +138,7 @@ public class DeviceConfigIntegrationService { request.getConfigs().add(item); return this; } - - /** - * 添加常用视频配置 - */ - public BatchDeviceConfigRequestBuilder addVideoConfig(String resolution, Integer framerate, String codec) { - addConfig("resolution", resolution); - addConfig("framerate", framerate.toString()); - if (codec != null) { - addConfig("codec", codec); - } - return this; - } - - /** - * 添加网络配置 - */ - public BatchDeviceConfigRequestBuilder addNetworkConfig(String ipAddress, Integer port, String protocol) { - addConfig("ip_address", ipAddress); - if (port != null) { - addConfig("port", port.toString()); - } - if (protocol != null) { - addConfig("protocol", protocol); - } - return this; - } - - /** - * 添加认证配置 - */ - public BatchDeviceConfigRequestBuilder addAuthConfig(String username, String password) { - if (username != null) { - addConfig("username", username); - } - if (password != null) { - addConfig("password", password); - } - return this; - } - + /** * 构建请求对象 */ diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java new file mode 100644 index 0000000..59ee0be --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java @@ -0,0 +1,128 @@ +package com.ycwl.basic.integration.device.service; + +import com.ycwl.basic.integration.device.dto.device.*; +import com.ycwl.basic.utils.JacksonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 设备集成服务失败降级处理 + * 提供失败时的降级策略,不使用常规缓存 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceIntegrationFallbackService { + + private final RedisTemplate redisTemplate; + + // 降级缓存键前缀 + private static final String FALLBACK_CACHE_PREFIX = "device:fallback:"; + private static final long FALLBACK_CACHE_TTL = 7; // 7天 + + /** + * 执行操作,失败时降级到缓存结果 + * + * @param cacheKey 缓存键 + * @param operation 主要操作 + * @param resultClass 结果类型 + * @param 结果类型 + * @return 操作结果或缓存的结果 + */ + public T executeWithFallback(String cacheKey, Supplier operation, Class resultClass) { + try { + T result = operation.get(); + if (result != null) { + // 操作成功,保存结果用于将来的降级 + storeFallbackCache(cacheKey, result); + } + return result; + } catch (Exception e) { + log.warn("操作失败,尝试降级到缓存结果, cacheKey: {}", cacheKey, e); + return getFallbackFromCache(cacheKey, resultClass); + } + } + + /** + * 执行操作,失败时降级到缓存结果,无返回值版本 + * + * @param cacheKey 缓存键 + * @param operation 主要操作 + */ + public void executeWithFallback(String cacheKey, Runnable operation) { + try { + operation.run(); + // 操作成功,记录成功状态 + storeFallbackCache(cacheKey + ":success", "true"); + } catch (Exception e) { + log.warn("操作失败,检查是否有历史成功记录, cacheKey: {}", cacheKey, e); + String successRecord = getFallbackFromCache(cacheKey + ":success", String.class); + if (successRecord == null) { + log.error("操作失败且无历史成功记录, cacheKey: {}", cacheKey); + throw e; + } + log.info("操作失败但有历史成功记录,忽略此次失败, cacheKey: {}", cacheKey); + } + } + + /** + * 存储降级缓存 + */ + private void storeFallbackCache(String cacheKey, Object value) { + try { + String key = FALLBACK_CACHE_PREFIX + cacheKey; + String jsonValue = JacksonUtil.toJSONString(value); + redisTemplate.opsForValue().set(key, jsonValue, FALLBACK_CACHE_TTL, TimeUnit.DAYS); + log.debug("存储降级缓存成功, key: {}", key); + } catch (Exception e) { + log.warn("存储降级缓存失败, cacheKey: {}", cacheKey, e); + } + } + + /** + * 从降级缓存获取结果 + */ + private T getFallbackFromCache(String cacheKey, Class resultClass) { + try { + String key = FALLBACK_CACHE_PREFIX + cacheKey; + String cachedValue = redisTemplate.opsForValue().get(key); + if (cachedValue != null) { + log.info("从降级缓存获取结果, key: {}", key); + if (resultClass == String.class) { + return resultClass.cast(cachedValue); + } + return JacksonUtil.parseObject(cachedValue, resultClass); + } + } catch (Exception e) { + log.warn("从降级缓存获取结果失败, cacheKey: {}", cacheKey, e); + } + return null; + } + + /** + * 清除降级缓存 + * + * @param cacheKey 缓存键 + */ + public void clearFallbackCache(String cacheKey) { + String key = FALLBACK_CACHE_PREFIX + cacheKey; + redisTemplate.delete(key); + log.debug("清除降级缓存, key: {}", key); + } + + /** + * 检查是否有降级缓存 + * + * @param cacheKey 缓存键 + * @return 是否存在降级缓存 + */ + public boolean hasFallbackCache(String cacheKey) { + String key = FALLBACK_CACHE_PREFIX + cacheKey; + return Boolean.TRUE.equals(redisTemplate.hasKey(key)); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java index bef9d9f..4e8dcda 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -14,29 +14,54 @@ import org.springframework.stereotype.Service; public class DeviceIntegrationService { private final DeviceV2Client deviceV2Client; + private final DeviceIntegrationFallbackService fallbackService; public DeviceV2DTO getDevice(Long deviceId) { log.info("获取设备信息, deviceId: {}", deviceId); - CommonResponse response = deviceV2Client.getDevice(deviceId); - return handleResponse(response, "获取设备信息失败"); + return fallbackService.executeWithFallback( + "device:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.getDevice(deviceId); + return handleResponse(response, "获取设备信息失败"); + }, + DeviceV2DTO.class + ); } public DeviceV2DTO getDeviceByNo(String deviceNo) { log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo); - CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); - return handleResponse(response, "根据设备编号获取设备信息失败"); + return fallbackService.executeWithFallback( + "device:no:" + deviceNo, + () -> { + CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); + return handleResponse(response, "根据设备编号获取设备信息失败"); + }, + DeviceV2DTO.class + ); } public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) { log.info("获取设备配置信息, deviceId: {}", deviceId); - CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); - return handleResponse(response, "获取设备配置信息失败"); + return fallbackService.executeWithFallback( + "device:config:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); + return handleResponse(response, "获取设备配置信息失败"); + }, + DeviceV2WithConfigDTO.class + ); } public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) { log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo); - CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); - return handleResponse(response, "根据设备编号获取设备配置信息失败"); + return fallbackService.executeWithFallback( + "device:config:no:" + deviceNo, + () -> { + CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); + return handleResponse(response, "根据设备编号获取设备配置信息失败"); + }, + DeviceV2WithConfigDTO.class + ); } public DeviceV2DTO createDevice(CreateDeviceRequest request) { @@ -47,14 +72,24 @@ public class DeviceIntegrationService { public void updateDevice(Long deviceId, UpdateDeviceRequest request) { log.info("更新设备信息, deviceId: {}", deviceId); - CommonResponse response = deviceV2Client.updateDevice(deviceId, request); - handleResponse(response, "更新设备信息失败"); + fallbackService.executeWithFallback( + "device:update:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.updateDevice(deviceId, request); + handleResponse(response, "更新设备信息失败"); + } + ); } public void deleteDevice(Long deviceId) { log.info("删除设备, deviceId: {}", deviceId); - CommonResponse response = deviceV2Client.deleteDevice(deviceId); - handleResponse(response, "删除设备失败"); + fallbackService.executeWithFallback( + "device:delete:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.deleteDevice(deviceId); + handleResponse(response, "删除设备失败"); + } + ); } public DeviceV2ListResponse listDevices(Integer page, Integer pageSize, String name, String no, From 8c8a6baa5e67abe89bc6828b81fdae70e91c3a23 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 12:24:55 +0800 Subject: [PATCH 09/18] =?UTF-8?q?refactor(integration):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E9=9B=86=E6=88=90=E6=9C=8D=E5=8A=A1=E7=9A=84=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -移除各服务自定义的降级服务类,统一降级逻辑 - 新增 IntegrationFallbackService作为通用降级服务 - 更新设备和景区服务的降级处理方式 - 优化降级缓存管理,增加统计信息和批量清理功能 - 调整 API 接口,移除扁平化批量更新等相关方法 --- .../controller/pc/DeviceV2Controller.java | 36 --- .../java/com/ycwl/basic/integration/CLAUDE.md | 281 +++++++++++++++--- .../common/config/IntegrationProperties.java | 47 +++ .../service/IntegrationFallbackService.java | 257 ++++++++++++++++ .../device/client/DeviceConfigV2Client.java | 6 - .../DeviceIntegrationFallbackExample.java | 48 +-- .../DeviceConfigIntegrationService.java | 9 +- .../DeviceIntegrationFallbackService.java | 128 -------- .../service/DeviceIntegrationService.java | 27 +- .../dto/config/BatchUpdateResponse.java | 8 + .../example/ScenicIntegrationExample.java | 118 +++++++- .../ScenicConfigIntegrationService.java | 15 +- .../service/ScenicIntegrationService.java | 37 ++- 13 files changed, 757 insertions(+), 260 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java delete mode 100644 src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java index 48df318..cb6de9f 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java @@ -399,21 +399,6 @@ public class DeviceV2Controller { } } - /** - * 扁平化批量更新设备配置 - */ - @PostMapping("/{id}/config/flat-batch") - public ApiResponse batchFlatUpdateDeviceConfig(@PathVariable Long id, - @RequestBody Map configs) { - log.info("扁平化批量更新设备配置, deviceId: {}, configs count: {}", id, configs.size()); - try { - deviceConfigIntegrationService.batchFlatUpdateDeviceConfig(id, configs); - return ApiResponse.success("设备配置批量更新成功"); - } catch (Exception e) { - log.error("扁平化批量更新设备配置失败, deviceId: {}", id, e); - return ApiResponse.fail("扁平化批量更新设备配置失败: " + e.getMessage()); - } - } /** * 更新设备配置 @@ -431,27 +416,6 @@ public class DeviceV2Controller { } } - /** - * 配置摄像头参数(快捷方法) - */ - @PutMapping("/{id}/config/camera") - public ApiResponse configureCameraParams(@PathVariable Long id, @RequestBody Map request) { - String ipAddress = (String) request.get("ipAddress"); - String resolution = (String) request.get("resolution"); - Integer framerate = request.get("framerate") != null ? Integer.valueOf(request.get("framerate").toString()) : null; - String protocol = (String) request.get("protocol"); - String username = (String) request.get("username"); - String password = (String) request.get("password"); - - log.info("配置摄像头参数, deviceId: {}, ipAddress: {}, resolution: {}", id, ipAddress, resolution); - try { - deviceConfigIntegrationService.configureCameraParams(id, ipAddress, resolution, framerate, protocol, username, password); - return ApiResponse.success("摄像头参数配置成功"); - } catch (Exception e) { - log.error("配置摄像头参数失败, deviceId: {}", id, e); - return ApiResponse.fail("配置摄像头参数失败: " + e.getMessage()); - } - } /** * 删除设备配置 diff --git a/src/main/java/com/ycwl/basic/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md index 173da25..2eb1a1e 100644 --- a/src/main/java/com/ycwl/basic/integration/CLAUDE.md +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -17,6 +17,7 @@ The integration package (`com.ycwl.basic.integration`) is responsible for extern - **IntegrationException**: Standardized exception for integration failures - **CommonResponse/PageResponse**: Standard response wrappers for external service calls - **ConfigValueUtil**: Utility for handling configuration values +- **IntegrationFallbackService**: Universal fallback service for handling integration failures #### Service-Specific Integrations Currently implemented: @@ -35,6 +36,91 @@ service/ └── example/ # Usage examples ``` +## Integration Fallback Mechanism + +### Overview +The integration package includes a comprehensive fallback mechanism that provides automatic degradation when external microservices are unavailable or return errors. This mechanism ensures system resilience and maintains service availability even when dependencies fail. + +### Core Components + +#### IntegrationFallbackService +Universal fallback service that handles failure degradation for all microservice integrations: + +**Key Features:** +- **Automatic Fallback**: Transparently handles service failures and returns cached results +- **Configurable TTL**: Service-specific cache TTL configuration (default: 7 days) +- **Cache Management**: Comprehensive cache statistics, cleanup, and monitoring +- **Service Isolation**: Per-service cache namespacing and configuration +- **Operation Types**: Supports both query operations (return cached data) and mutation operations (ignore failures with history) + +**Core Methods:** +```java +// Query operations with fallback + T executeWithFallback(String serviceName, String cacheKey, Supplier operation, Class resultClass) + +// Mutation operations with fallback +void executeWithFallback(String serviceName, String cacheKey, Runnable operation) + +// Cache management +boolean hasFallbackCache(String serviceName, String cacheKey) +void clearFallbackCache(String serviceName, String cacheKey) +void clearAllFallbackCache(String serviceName) +FallbackCacheStats getFallbackCacheStats(String serviceName) +``` + +### Fallback Strategy + +#### Query Operations (GET methods) - WITH FALLBACK +1. **Normal Execution**: Execute the primary operation +2. **Success Handling**: Store successful result in fallback cache for future degradation +3. **Failure Handling**: On failure, attempt to retrieve cached result from previous success +4. **Final Fallback**: If no cached result exists, propagate the original exception + +#### Mutation Operations (PUT/POST/DELETE methods) - NO FALLBACK +1. **Direct Execution**: Execute the primary operation directly without fallback +2. **Success Handling**: Operation completes successfully +3. **Failure Handling**: Immediately propagate the original exception to caller +4. **Rationale**: Users need to know if their create/update/delete operations truly succeeded or failed + +### Configuration + +#### Properties Structure +```yaml +integration: + fallback: + enabled: true # Global fallback switch + cachePrefix: "integration:fallback:" # Global cache prefix + defaultTtlDays: 7 # Default cache TTL in days + + # Service-specific configurations + scenic: + enabled: true # Service-specific fallback switch + ttlDays: 10 # Custom TTL for scenic service + cachePrefix: "scenic:fallback:" # Custom cache prefix (optional) + + device: + enabled: true + ttlDays: 5 # Custom TTL for device service +``` + +#### Configuration Priority +1. **Service-specific settings**: Override global defaults when specified +2. **Global defaults**: Used when service-specific settings are not provided +3. **Hardcoded defaults**: Final fallback when no configuration is available + +### Cache Key Strategy + +Cache keys follow a standardized naming convention: +``` +{cachePrefix}{serviceName}:{operationType}:{resourceId} +``` + +**Examples:** +- `integration:fallback:zt-device:device:1001` - Device info cache +- `integration:fallback:zt-device:device:config:1001` - Device config cache +- `integration:fallback:zt-scenic:scenic:2001` - Scenic info cache +- `integration:fallback:zt-scenic:scenic:flat:config:2001` - Scenic flat config cache + ## Scenic Integration (ZT-Scenic Microservice) ### Key Components @@ -45,8 +131,8 @@ service/ - **DefaultConfigClient**: Default configuration operations #### Services -- **ScenicIntegrationService**: High-level scenic operations -- **ScenicConfigIntegrationService**: Configuration management +- **ScenicIntegrationService**: High-level scenic operations (with automatic fallback) +- **ScenicConfigIntegrationService**: Configuration management (with automatic fallback) - **DefaultConfigIntegrationService**: Default configuration handling #### Configuration @@ -63,37 +149,74 @@ integration: ### Usage Examples -#### Basic Scenic Operations +#### Basic Scenic Operations (with Automatic Fallback) ```java @Autowired private ScenicIntegrationService scenicService; -// Get scenic with configuration +// Get scenic with configuration (automatically falls back to cache on failure) ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId); -// Create new scenic +// Get scenic basic info (automatically falls back to cache on failure) +ScenicV2DTO scenicInfo = scenicService.getScenic(scenicId); + +// Get flat configuration (automatically falls back to cache on failure) +Map config = scenicService.getScenicFlatConfig(scenicId); + +// Create new scenic (direct operation, fails immediately on error) CreateScenicRequest request = new CreateScenicRequest(); request.setName("Test Scenic"); ScenicV2DTO result = scenicService.createScenic(request); -// Filter scenics +// Update scenic (direct operation, fails immediately on error) +UpdateScenicRequest updateRequest = new UpdateScenicRequest(); +updateRequest.setName("Updated Scenic"); +ScenicV2DTO updated = scenicService.updateScenic(scenicId, updateRequest); + +// Filter scenics (no fallback for complex queries) ScenicFilterRequest filterRequest = new ScenicFilterRequest(); // configure filters... ScenicFilterPageResponse response = scenicService.filterScenics(filterRequest); ``` -#### Configuration Management +#### Configuration Management (with Automatic Fallback) ```java @Autowired private ScenicConfigIntegrationService configService; -// Get flat configuration -Map config = scenicService.getScenicFlatConfig(scenicId); +// Get flat configuration (automatically falls back to cache on failure) +Map config = configService.getFlatConfigs(scenicId); -// Batch update configurations +// Batch update configurations (direct operation, fails immediately on error) BatchConfigRequest batchRequest = new BatchConfigRequest(); // configure batch updates... -configService.batchUpdateConfigs(scenicId, batchRequest); +BatchUpdateResponse result = configService.batchUpdateConfigs(scenicId, batchRequest); + +// Batch flat update configurations (direct operation, fails immediately on error) +Map flatUpdates = new HashMap<>(); +flatUpdates.put("max_visitors", "5000"); +flatUpdates.put("opening_hours", "08:00-18:00"); +BatchUpdateResponse flatResult = configService.batchFlatUpdateConfigs(scenicId, flatUpdates); +``` + +#### Fallback Cache Management for Scenics +```java +@Autowired +private IntegrationFallbackService fallbackService; + +// Check fallback cache status +boolean hasScenicCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:2001"); +boolean hasConfigCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:flat:configs:2001"); + +// Get cache statistics +IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-scenic"); +log.info("Scenic fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + +// Clear specific cache +fallbackService.clearFallbackCache("zt-scenic", "scenic:2001"); + +// Clear all scenic caches +fallbackService.clearAllFallbackCache("zt-scenic"); ``` ## Device Integration (ZT-Device Microservice) @@ -105,8 +228,8 @@ configService.batchUpdateConfigs(scenicId, batchRequest); - **DeviceConfigV2Client**: Device configuration management #### Services -- **DeviceIntegrationService**: High-level device operations -- **DeviceConfigIntegrationService**: Device configuration management +- **DeviceIntegrationService**: High-level device operations (with automatic fallback) +- **DeviceConfigIntegrationService**: Device configuration management (with automatic fallback) #### Configuration ```yaml @@ -122,52 +245,56 @@ integration: ### Usage Examples -#### Basic Device Operations +#### Basic Device Operations (with Automatic Fallback) ```java @Autowired private DeviceIntegrationService deviceService; -// Create IPC camera device +// Create IPC camera device (operation tracked for future fallback) DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId); -// Get device with configuration +// Get device with configuration (automatically falls back to cache on failure) DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId); -// Get device by number +// Get device by number (automatically falls back to cache on failure) DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); -// List scenic devices +// Get basic device info (automatically falls back to cache on failure) +DeviceV2DTO basicDevice = deviceService.getDevice(deviceId); + +// List scenic devices (no fallback for list operations) DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10); -// Enable/disable device +// Enable/disable device (direct operation, fails immediately on error) deviceService.enableDevice(deviceId); deviceService.disableDevice(deviceId); + +// Update device (direct operation, fails immediately on error) +UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); +updateRequest.setName("更新后的摄像头"); +deviceService.updateDevice(deviceId, updateRequest); ``` -#### Device Configuration Management +#### Device Configuration Management (with Automatic Fallback) ```java @Autowired private DeviceConfigIntegrationService configService; -// Configure camera basic parameters -configService.configureCameraBasicParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP"); +// Get device configurations (with fallback) +List configs = configService.getDeviceConfigs(deviceId); -// Configure camera with authentication -configService.configureCameraFullParams(deviceId, "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); - -// Set specific configuration -configService.setDeviceIpAddress(deviceId, "192.168.1.101"); -configService.setDeviceResolution(deviceId, "2560x1440"); -configService.setDeviceFramerate(deviceId, 60); - -// Get flat configuration +// Get flat configuration (automatically falls back to cache on failure) Map config = configService.getDeviceFlatConfig(deviceId); -// Batch update configurations (simple way) -Map batchConfigs = new HashMap<>(); -batchConfigs.put("brightness", "50"); -batchConfigs.put("contrast", "80"); -configService.batchFlatUpdateDeviceConfig(deviceId, batchConfigs); +// Get flat configuration by device number (with fallback) +Map configByNo = configService.getDeviceFlatConfigByNo("CAM001"); + +// Batch update configurations (direct operation, fails immediately on error) +BatchDeviceConfigRequest batchRequest = configService.createBatchConfigBuilder() + .addConfig("brightness", "50") + .addConfig("contrast", "80") + .build(); +configService.batchUpdateDeviceConfig(deviceId, batchRequest); // New batch configuration API with detailed results BatchDeviceConfigRequest request = configService.createBatchConfigBuilder() @@ -189,20 +316,32 @@ if (result.getFailed() > 0) { #### Device Management Patterns ```java -// Create and configure camera in one operation +// Create and configure camera in one operation (both operations direct, no fallback) DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId); -configService.configureCameraFullParams(camera.getId(), "192.168.1.100", "1920x1080", 30, "RTSP", "admin", "password"); +// Use batch configuration to set camera parameters (direct operation) +BatchDeviceConfigRequest cameraConfig = configService.createBatchConfigBuilder() + .addConfig("ip_address", "192.168.1.100") + .addConfig("resolution", "1920x1080") + .addConfig("framerate", "30") + .addConfig("protocol", "RTSP") + .addConfig("username", "admin") + .addConfig("password", "password") + .build(); +configService.batchUpdateDeviceConfig(camera.getId(), cameraConfig); // Get scenic camera status DeviceV2WithConfigListResponse camerasWithConfig = deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); -// Batch update camera resolution +// Batch update camera resolution (direct operations, no fallback) for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) { - configService.setDeviceResolution(device.getId(), "2560x1440"); + BatchDeviceConfigRequest resolutionUpdate = configService.createBatchConfigBuilder() + .addConfig("resolution", "2560x1440") + .build(); + configService.batchUpdateDeviceConfig(device.getId(), resolutionUpdate); } -// Monitor device configuration completeness +// Monitor device configuration completeness (with automatic fallback) for (DeviceV2DTO device : activeDevices.getList()) { Map config = configService.getDeviceFlatConfig(device.getId()); boolean hasIpConfig = config.containsKey("ip_address"); @@ -210,6 +349,26 @@ for (DeviceV2DTO device : activeDevices.getList()) { } ``` +#### Fallback Cache Management for Devices +```java +@Autowired +private IntegrationFallbackService fallbackService; + +// Check fallback cache status +boolean hasDeviceCache = fallbackService.hasFallbackCache("zt-device", "device:1001"); +boolean hasConfigCache = fallbackService.hasFallbackCache("zt-device", "device:flat:config:1001"); + +// Get cache statistics +IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-device"); +log.info("Device fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + +// Clear specific cache +fallbackService.clearFallbackCache("zt-device", "device:1001"); + +// Clear all device caches +fallbackService.clearAllFallbackCache("zt-device"); +``` + ### Enhanced Batch Configuration API Device Integration now supports an enhanced batch configuration API that provides detailed processing results and supports default configuration rules. @@ -359,6 +518,24 @@ Automatically converts Feign errors to IntegrationException: ### Properties Structure ```yaml integration: + # Fallback configuration + fallback: + enabled: true # Global fallback switch + cachePrefix: "integration:fallback:" # Global cache prefix + defaultTtlDays: 7 # Default cache TTL in days + + # Service-specific fallback configurations + scenic: + enabled: true # Enable fallback for scenic service + ttlDays: 10 # Custom TTL for scenic (longer due to stable data) + # cachePrefix: "scenic:fallback:" # Optional custom prefix + + device: + enabled: true # Enable fallback for device service + ttlDays: 5 # Custom TTL for device (shorter due to dynamic data) + # cachePrefix: "device:fallback:" # Optional custom prefix + + # Service configurations scenic: enabled: true serviceName: zt-scenic @@ -449,10 +626,23 @@ logging: - Provide meaningful error messages for business context - Include service name in IntegrationException for debugging +### Fallback Strategy Best Practices +- **Query operations only**: Only apply fallback to GET operations (data retrieval) +- **No fallback for mutations**: CREATE/UPDATE/DELETE operations should fail immediately to provide clear feedback +- **Cache key design**: Use clear, hierarchical cache keys for easy identification and cleanup +- **TTL management**: Set appropriate TTL based on data freshness requirements +- **Monitoring**: Regularly check fallback cache statistics and cleanup stale entries +- **Service isolation**: Configure service-specific fallback settings based on reliability needs +- **Error transparency**: Let users know exactly when their modification operations fail + ### Configuration - Use environment-specific configuration profiles - Set appropriate timeouts based on service characteristics - Enable retries only when safe (idempotent operations) +- Configure fallback TTL based on business requirements: + - **Critical data**: Longer TTL (7-14 days) for essential operations + - **Volatile data**: Shorter TTL (1-3 days) for frequently changing data + - **Configuration data**: Medium TTL (5-7 days) for semi-static settings ### DTOs and Mapping - Keep DTOs simple and focused on data transfer @@ -462,4 +652,11 @@ logging: ### Service Layer Design - Keep integration services focused on external service calls - Handle response transformation and error conversion -- Avoid business logic in integration services \ No newline at end of file +- Avoid business logic in integration services +- Use fallback service for resilience without changing business logic + +### Cache Management Strategies +- **Proactive cleanup**: Monitor cache size and clean up periodically +- **Service-specific management**: Separate cache management per service +- **Debugging support**: Use cache statistics for troubleshooting +- **Configuration validation**: Ensure fallback configuration matches service requirements \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java index c4620f8..a25541e 100644 --- a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java +++ b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java @@ -11,6 +11,11 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "integration") public class IntegrationProperties { + /** + * 降级服务配置 + */ + private FallbackConfig fallback = new FallbackConfig(); + /** * 景区服务配置 */ @@ -70,4 +75,46 @@ public class IntegrationProperties { private boolean retryEnabled = false; private int maxRetries = 3; } + + @Data + public static class FallbackConfig { + /** + * 是否启用降级功能 + */ + private boolean enabled = true; + + /** + * 降级缓存前缀 + */ + private String cachePrefix = "integration:fallback:"; + + /** + * 默认降级缓存TTL(天) + */ + private long defaultTtlDays = 1; + + /** + * 服务特定的降级配置 + */ + private ServiceFallbackConfig scenic = new ServiceFallbackConfig(); + private ServiceFallbackConfig device = new ServiceFallbackConfig(); + } + + @Data + public static class ServiceFallbackConfig { + /** + * 是否启用此服务的降级功能 + */ + private boolean enabled = true; + + /** + * 服务特定的缓存TTL(天),如果为0则使用默认TTL + */ + private long ttlDays = 0; + + /** + * 服务特定的缓存前缀,如果为空则使用默认前缀 + */ + private String cachePrefix; + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java b/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java new file mode 100644 index 0000000..ca3a9cd --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/service/IntegrationFallbackService.java @@ -0,0 +1,257 @@ +package com.ycwl.basic.integration.common.service; + +import com.ycwl.basic.integration.common.config.IntegrationProperties; +import com.ycwl.basic.utils.JacksonUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 集成服务通用失败降级处理 + * 提供统一的降级策略,支持所有微服务集成 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class IntegrationFallbackService { + + private final RedisTemplate redisTemplate; + private final IntegrationProperties integrationProperties; + + // 默认降级缓存配置 + private static final String DEFAULT_FALLBACK_PREFIX = "integration:fallback:"; + private static final long DEFAULT_FALLBACK_TTL = 7; // 7天 + + /** + * 执行操作,失败时降级到缓存结果 + * + * @param serviceName 服务名称 (如: zt-device, zt-scenic) + * @param cacheKey 缓存键 + * @param operation 主要操作 + * @param resultClass 结果类型 + * @param 结果类型 + * @return 操作结果或缓存的结果 + */ + public T executeWithFallback(String serviceName, String cacheKey, Supplier operation, Class resultClass) { + try { + T result = operation.get(); + if (result != null) { + // 操作成功,保存结果用于将来的降级 + storeFallbackCache(serviceName, cacheKey, result); + } + return result; + } catch (Exception e) { + log.warn("[{}] 操作失败,尝试降级到缓存结果, cacheKey: {}", serviceName, cacheKey, e); + T fallbackResult = getFallbackFromCache(serviceName, cacheKey, resultClass); + if (fallbackResult == null) { + log.error("[{}] 操作失败且无缓存数据, cacheKey: {}", serviceName, cacheKey); + throw e; + } + log.info("[{}] 成功从降级缓存获取结果, cacheKey: {}", serviceName, cacheKey); + return fallbackResult; + } + } + + /** + * 执行操作,失败时降级到缓存结果,无返回值版本 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + * @param operation 主要操作 + */ + public void executeWithFallback(String serviceName, String cacheKey, Runnable operation) { + try { + operation.run(); + // 操作成功,记录成功状态 + storeFallbackCache(serviceName, cacheKey + ":success", "true"); + log.debug("[{}] 操作成功,已记录成功状态, cacheKey: {}", serviceName, cacheKey); + } catch (Exception e) { + log.warn("[{}] 操作失败,检查是否有历史成功记录, cacheKey: {}", serviceName, cacheKey, e); + String successRecord = getFallbackFromCache(serviceName, cacheKey + ":success", String.class); + if (successRecord == null) { + log.error("[{}] 操作失败且无历史成功记录, cacheKey: {}", serviceName, cacheKey); + throw e; + } + log.info("[{}] 操作失败但有历史成功记录,忽略此次失败, cacheKey: {}", serviceName, cacheKey); + } + } + + /** + * 存储降级缓存 + */ + private void storeFallbackCache(String serviceName, String cacheKey, Object value) { + try { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + String jsonValue = JacksonUtil.toJSONString(value); + long ttl = getFallbackTtl(serviceName); + redisTemplate.opsForValue().set(fullKey, jsonValue, ttl, TimeUnit.DAYS); + log.debug("[{}] 存储降级缓存成功, key: {}", serviceName, fullKey); + } catch (Exception e) { + log.warn("[{}] 存储降级缓存失败, cacheKey: {}", serviceName, cacheKey, e); + } + } + + /** + * 从降级缓存获取结果 + */ + private T getFallbackFromCache(String serviceName, String cacheKey, Class resultClass) { + try { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + String cachedValue = redisTemplate.opsForValue().get(fullKey); + if (cachedValue != null) { + log.debug("[{}] 从降级缓存获取结果, key: {}", serviceName, fullKey); + if (resultClass == String.class) { + return resultClass.cast(cachedValue); + } + return JacksonUtil.parseObject(cachedValue, resultClass); + } + } catch (Exception e) { + log.warn("[{}] 从降级缓存获取结果失败, cacheKey: {}", serviceName, cacheKey, e); + } + return null; + } + + /** + * 清除降级缓存 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + */ + public void clearFallbackCache(String serviceName, String cacheKey) { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + redisTemplate.delete(fullKey); + log.debug("[{}] 清除降级缓存, key: {}", serviceName, fullKey); + } + + /** + * 批量清除服务的所有降级缓存 + * + * @param serviceName 服务名称 + */ + public void clearAllFallbackCache(String serviceName) { + String pattern = buildFullCacheKey(serviceName, "*"); + Set keys = redisTemplate.keys(pattern); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + log.info("[{}] 批量清除降级缓存,共删除 {} 个缓存项", serviceName, keys.size()); + } + } + + /** + * 检查是否有降级缓存 + * + * @param serviceName 服务名称 + * @param cacheKey 缓存键 + * @return 是否存在降级缓存 + */ + public boolean hasFallbackCache(String serviceName, String cacheKey) { + String fullKey = buildFullCacheKey(serviceName, cacheKey); + return Boolean.TRUE.equals(redisTemplate.hasKey(fullKey)); + } + + /** + * 获取服务的降级缓存统计信息 + * + * @param serviceName 服务名称 + * @return 缓存统计信息 + */ + public FallbackCacheStats getFallbackCacheStats(String serviceName) { + String pattern = buildFullCacheKey(serviceName, "*"); + Set keys = redisTemplate.keys(pattern); + int totalCount = keys != null ? keys.size() : 0; + + return FallbackCacheStats.builder() + .serviceName(serviceName) + .totalCacheCount(totalCount) + .cacheKeyPattern(pattern) + .fallbackTtlDays(getFallbackTtl(serviceName)) + .build(); + } + + /** + * 构建完整的缓存键 + */ + private String buildFullCacheKey(String serviceName, String cacheKey) { + String prefix = getFallbackPrefix(serviceName); + return prefix + serviceName + ":" + cacheKey; + } + + /** + * 获取服务的降级缓存前缀 + */ + private String getFallbackPrefix(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return DEFAULT_FALLBACK_PREFIX; + } + + // 获取服务特定的缓存前缀 + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + if (serviceConfig != null && serviceConfig.getCachePrefix() != null) { + return serviceConfig.getCachePrefix(); + } + + // 使用全局配置的前缀 + return integrationProperties.getFallback().getCachePrefix(); + } + + /** + * 获取服务的降级缓存TTL + */ + private long getFallbackTtl(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return DEFAULT_FALLBACK_TTL; + } + + // 获取服务特定的TTL + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + if (serviceConfig != null && serviceConfig.getTtlDays() > 0) { + return serviceConfig.getTtlDays(); + } + + // 使用全局配置的TTL + return integrationProperties.getFallback().getDefaultTtlDays(); + } + + /** + * 获取服务特定的降级配置 + */ + private IntegrationProperties.ServiceFallbackConfig getServiceFallbackConfig(String serviceName) { + switch (serviceName.toLowerCase()) { + case "zt-scenic": + return integrationProperties.getFallback().getScenic(); + case "zt-device": + return integrationProperties.getFallback().getDevice(); + default: + return null; + } + } + + /** + * 检查服务是否启用降级功能 + */ + public boolean isFallbackEnabled(String serviceName) { + if (!integrationProperties.getFallback().isEnabled()) { + return false; + } + + IntegrationProperties.ServiceFallbackConfig serviceConfig = getServiceFallbackConfig(serviceName); + return serviceConfig == null || serviceConfig.isEnabled(); + } + + /** + * 降级缓存统计信息 + */ + @lombok.Builder + @lombok.Data + public static class FallbackCacheStats { + private String serviceName; + private int totalCacheCount; + private String cacheKeyPattern; + private long fallbackTtlDays; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java index c94ca1e..86afa90 100644 --- a/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java @@ -71,10 +71,4 @@ public interface DeviceConfigV2Client { CommonResponse batchUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, @RequestBody BatchDeviceConfigRequest request); - /** - * 扁平化批量更新设备配置 - */ - @PostMapping("/{deviceId}/batch-flat") - CommonResponse batchFlatUpdateDeviceConfig(@PathVariable("deviceId") Long deviceId, - @RequestBody Map configs); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java index df2b0c0..4429f12 100644 --- a/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java @@ -1,8 +1,8 @@ package com.ycwl.basic.integration.device.example; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; -import com.ycwl.basic.integration.device.service.DeviceIntegrationFallbackService; import com.ycwl.basic.integration.device.dto.device.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -11,8 +11,8 @@ import org.springframework.stereotype.Component; import java.util.Map; /** - * 设备集成降级机制示例 - * 演示失败降级策略的使用 + * 设备集成示例(包含降级机制) + * 演示设备集成和失败降级策略的使用 */ @Slf4j @Component @@ -21,7 +21,9 @@ public class DeviceIntegrationFallbackExample { private final DeviceIntegrationService deviceService; private final DeviceConfigIntegrationService configService; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; /** * 演示设备信息获取的降级机制 @@ -51,26 +53,26 @@ public class DeviceIntegrationFallbackExample { } /** - * 演示设备操作的降级机制 + * 演示设备操作(无降级机制) */ - public void deviceOperationFallbackExample() { - log.info("=== 设备操作降级示例 ==="); + public void deviceOperationExample() { + log.info("=== 设备操作示例 ==="); Long deviceId = 1001L; try { - // 设备更新操作 - 自动降级 + // 设备更新操作 - 直接操作,失败时抛出异常 UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); updateRequest.setName("更新后的摄像头"); deviceService.updateDevice(deviceId, updateRequest); log.info("设备更新操作完成"); - // 设备排序更新 - 自动降级 + // 设备排序更新 - 直接操作,失败时抛出异常 deviceService.updateDeviceSort(deviceId, 5); log.info("设备排序更新完成"); } catch (Exception e) { - log.error("设备操作降级失败", e); + log.error("设备操作失败", e); } } @@ -84,29 +86,39 @@ public class DeviceIntegrationFallbackExample { String configCacheKey = "device:flat:config:1001"; // 检查降级缓存状态 - boolean hasDeviceCache = fallbackService.hasFallbackCache(deviceCacheKey); - boolean hasConfigCache = fallbackService.hasFallbackCache(configCacheKey); + boolean hasDeviceCache = fallbackService.hasFallbackCache(SERVICE_NAME, deviceCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey); log.info("设备降级缓存存在: {}", hasDeviceCache); log.info("配置降级缓存存在: {}", hasConfigCache); // 清理特定的降级缓存 if (hasDeviceCache) { - fallbackService.clearFallbackCache(deviceCacheKey); + fallbackService.clearFallbackCache(SERVICE_NAME, deviceCacheKey); log.info("已清理设备降级缓存"); } + + // 获取降级缓存统计信息 + IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME); + log.info("设备服务降级缓存统计: {}", stats); + + // 批量清理所有设备降级缓存 + if (stats.getTotalCacheCount() > 10) { + fallbackService.clearAllFallbackCache(SERVICE_NAME); + log.info("已批量清理所有设备降级缓存"); + } } /** - * 运行所有降级示例 + * 运行所有示例 */ - public void runAllFallbackExamples() { - log.info("开始运行设备集成降级示例..."); + public void runAllExamples() { + log.info("开始运行设备集成示例..."); deviceInfoFallbackExample(); - deviceOperationFallbackExample(); + deviceOperationExample(); fallbackCacheManagementExample(); - log.info("设备集成降级示例运行完成"); + log.info("设备集成示例运行完成"); } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java index 2b7b7cb..850c35b 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.device.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.client.DeviceConfigV2Client; import com.ycwl.basic.integration.device.dto.config.*; import lombok.RequiredArgsConstructor; @@ -18,7 +19,9 @@ import java.util.Map; public class DeviceConfigIntegrationService { private final DeviceConfigV2Client deviceConfigV2Client; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; public List getDeviceConfigs(Long deviceId) { log.info("获取设备配置列表, deviceId: {}", deviceId); @@ -41,6 +44,7 @@ public class DeviceConfigIntegrationService { public Map getDeviceFlatConfig(Long deviceId) { log.info("获取设备扁平化配置, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:flat:config:" + deviceId, () -> { CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); @@ -53,6 +57,7 @@ public class DeviceConfigIntegrationService { public Map getDeviceFlatConfigByNo(String deviceNo) { log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:flat:config:no:" + deviceNo, () -> { CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); @@ -89,6 +94,8 @@ public class DeviceConfigIntegrationService { return handleResponse(response, "批量更新设备配置失败"); } + + /** * 获取设备特定配置值 */ diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java deleted file mode 100644 index 59ee0be..0000000 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationFallbackService.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.ycwl.basic.integration.device.service; - -import com.ycwl.basic.integration.device.dto.device.*; -import com.ycwl.basic.utils.JacksonUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -/** - * 设备集成服务失败降级处理 - * 提供失败时的降级策略,不使用常规缓存 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class DeviceIntegrationFallbackService { - - private final RedisTemplate redisTemplate; - - // 降级缓存键前缀 - private static final String FALLBACK_CACHE_PREFIX = "device:fallback:"; - private static final long FALLBACK_CACHE_TTL = 7; // 7天 - - /** - * 执行操作,失败时降级到缓存结果 - * - * @param cacheKey 缓存键 - * @param operation 主要操作 - * @param resultClass 结果类型 - * @param 结果类型 - * @return 操作结果或缓存的结果 - */ - public T executeWithFallback(String cacheKey, Supplier operation, Class resultClass) { - try { - T result = operation.get(); - if (result != null) { - // 操作成功,保存结果用于将来的降级 - storeFallbackCache(cacheKey, result); - } - return result; - } catch (Exception e) { - log.warn("操作失败,尝试降级到缓存结果, cacheKey: {}", cacheKey, e); - return getFallbackFromCache(cacheKey, resultClass); - } - } - - /** - * 执行操作,失败时降级到缓存结果,无返回值版本 - * - * @param cacheKey 缓存键 - * @param operation 主要操作 - */ - public void executeWithFallback(String cacheKey, Runnable operation) { - try { - operation.run(); - // 操作成功,记录成功状态 - storeFallbackCache(cacheKey + ":success", "true"); - } catch (Exception e) { - log.warn("操作失败,检查是否有历史成功记录, cacheKey: {}", cacheKey, e); - String successRecord = getFallbackFromCache(cacheKey + ":success", String.class); - if (successRecord == null) { - log.error("操作失败且无历史成功记录, cacheKey: {}", cacheKey); - throw e; - } - log.info("操作失败但有历史成功记录,忽略此次失败, cacheKey: {}", cacheKey); - } - } - - /** - * 存储降级缓存 - */ - private void storeFallbackCache(String cacheKey, Object value) { - try { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - String jsonValue = JacksonUtil.toJSONString(value); - redisTemplate.opsForValue().set(key, jsonValue, FALLBACK_CACHE_TTL, TimeUnit.DAYS); - log.debug("存储降级缓存成功, key: {}", key); - } catch (Exception e) { - log.warn("存储降级缓存失败, cacheKey: {}", cacheKey, e); - } - } - - /** - * 从降级缓存获取结果 - */ - private T getFallbackFromCache(String cacheKey, Class resultClass) { - try { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - String cachedValue = redisTemplate.opsForValue().get(key); - if (cachedValue != null) { - log.info("从降级缓存获取结果, key: {}", key); - if (resultClass == String.class) { - return resultClass.cast(cachedValue); - } - return JacksonUtil.parseObject(cachedValue, resultClass); - } - } catch (Exception e) { - log.warn("从降级缓存获取结果失败, cacheKey: {}", cacheKey, e); - } - return null; - } - - /** - * 清除降级缓存 - * - * @param cacheKey 缓存键 - */ - public void clearFallbackCache(String cacheKey) { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - redisTemplate.delete(key); - log.debug("清除降级缓存, key: {}", key); - } - - /** - * 检查是否有降级缓存 - * - * @param cacheKey 缓存键 - * @return 是否存在降级缓存 - */ - public boolean hasFallbackCache(String cacheKey) { - String key = FALLBACK_CACHE_PREFIX + cacheKey; - return Boolean.TRUE.equals(redisTemplate.hasKey(key)); - } -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java index 4e8dcda..3dfe39e 100644 --- a/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.device.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.device.client.DeviceV2Client; import com.ycwl.basic.integration.device.dto.device.*; import lombok.RequiredArgsConstructor; @@ -14,11 +15,14 @@ import org.springframework.stereotype.Service; public class DeviceIntegrationService { private final DeviceV2Client deviceV2Client; - private final DeviceIntegrationFallbackService fallbackService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; public DeviceV2DTO getDevice(Long deviceId) { log.info("获取设备信息, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:" + deviceId, () -> { CommonResponse response = deviceV2Client.getDevice(deviceId); @@ -31,6 +35,7 @@ public class DeviceIntegrationService { public DeviceV2DTO getDeviceByNo(String deviceNo) { log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:no:" + deviceNo, () -> { CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); @@ -43,6 +48,7 @@ public class DeviceIntegrationService { public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) { log.info("获取设备配置信息, deviceId: {}", deviceId); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:config:" + deviceId, () -> { CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); @@ -55,6 +61,7 @@ public class DeviceIntegrationService { public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) { log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo); return fallbackService.executeWithFallback( + SERVICE_NAME, "device:config:no:" + deviceNo, () -> { CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); @@ -72,24 +79,14 @@ public class DeviceIntegrationService { public void updateDevice(Long deviceId, UpdateDeviceRequest request) { log.info("更新设备信息, deviceId: {}", deviceId); - fallbackService.executeWithFallback( - "device:update:" + deviceId, - () -> { - CommonResponse response = deviceV2Client.updateDevice(deviceId, request); - handleResponse(response, "更新设备信息失败"); - } - ); + CommonResponse response = deviceV2Client.updateDevice(deviceId, request); + handleResponse(response, "更新设备信息失败"); } public void deleteDevice(Long deviceId) { log.info("删除设备, deviceId: {}", deviceId); - fallbackService.executeWithFallback( - "device:delete:" + deviceId, - () -> { - CommonResponse response = deviceV2Client.deleteDevice(deviceId); - handleResponse(response, "删除设备失败"); - } - ); + CommonResponse response = deviceV2Client.deleteDevice(deviceId); + handleResponse(response, "删除设备失败"); } public DeviceV2ListResponse listDevices(Integer page, Integer pageSize, String name, String no, diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java index d3f35b6..ca154ed 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java @@ -13,4 +13,12 @@ public class BatchUpdateResponse { @JsonProperty("message") private String message; + + public Integer getSuccess() { + return (updatedCount != null ? updatedCount : 0) + (createdCount != null ? createdCount : 0); + } + + public Integer getFailed() { + return 0; // 当前响应格式不包含失败计数,返回0作为默认值 + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java index f1f50e3..fe5c875 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java @@ -1,9 +1,9 @@ package com.ycwl.basic.integration.scenic.example; -import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest; -import com.ycwl.basic.integration.scenic.dto.filter.FilterCondition; -import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; -import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.scenic.dto.config.*; +import com.ycwl.basic.integration.scenic.dto.filter.*; +import com.ycwl.basic.integration.scenic.dto.scenic.*; import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; import lombok.RequiredArgsConstructor; @@ -11,10 +11,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** - * ZT-Scenic集成服务使用示例 - * 仅供参考,实际使用时根据业务需要调用相应的服务方法 + * 景区集成示例(包含降级机制) + * 演示景区集成和失败降级策略的使用 */ @Slf4j @Component @@ -23,6 +25,9 @@ public class ScenicIntegrationExample { private final ScenicIntegrationService scenicIntegrationService; private final ScenicConfigIntegrationService scenicConfigIntegrationService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; /** * 示例:创建景区并设置配置 @@ -75,4 +80,105 @@ public class ScenicIntegrationExample { log.error("筛选景区失败", e); } } + + /** + * 演示基础景区操作的降级机制 + */ + public void basicScenicOperationsExample() { + log.info("=== 基础景区操作示例(含降级机制) ==="); + + Long scenicId = 2001L; + + try { + // 获取景区信息 - 自动降级 + ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId); + log.info("获取景区成功: {}", scenic.getName()); + + // 获取景区配置信息 - 自动降级 + ScenicV2WithConfigDTO scenicWithConfig = scenicIntegrationService.getScenicWithConfig(scenicId); + log.info("获取景区配置成功,配置数量: {}", scenicWithConfig.getConfig().size()); + + // 获取扁平化配置 - 自动降级 + Map flatConfig = scenicIntegrationService.getScenicFlatConfig(scenicId); + log.info("获取扁平化配置成功,配置项数量: {}", flatConfig.size()); + + } catch (Exception e) { + log.error("景区操作降级失败", e); + } + } + + /** + * 演示景区配置管理的降级机制 + */ + public void scenicConfigManagementFallbackExample() { + log.info("=== 景区配置管理示例(含降级机制) ==="); + + Long scenicId = 2001L; + + try { + // 获取扁平化配置 - 自动降级 + Map flatConfigs = scenicConfigIntegrationService.getFlatConfigs(scenicId); + log.info("获取扁平化配置成功,配置项数量: {}", flatConfigs.size()); + + // 批量更新配置 - 直接操作,失败时抛出异常 + Map updates = new HashMap<>(); + updates.put("max_visitors", "5000"); + updates.put("opening_hours", "08:00-18:00"); + + BatchUpdateResponse result = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, updates); + log.info("批量更新配置完成: 成功 {}, 失败 {}", result.getSuccess(), result.getFailed()); + + } catch (Exception e) { + log.error("景区配置管理操作失败", e); + } + } + + /** + * 演示降级缓存管理 + */ + public void fallbackCacheManagementExample() { + log.info("=== 景区降级缓存管理示例 ==="); + + String scenicCacheKey = "scenic:2001"; + String configCacheKey = "scenic:flat:configs:2001"; + + // 检查降级缓存状态 + boolean hasScenicCache = fallbackService.hasFallbackCache(SERVICE_NAME, scenicCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey); + + log.info("景区降级缓存存在: {}", hasScenicCache); + log.info("配置降级缓存存在: {}", hasConfigCache); + + // 获取降级缓存统计信息 + IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME); + log.info("景区服务降级缓存统计: 缓存数量={}, TTL={}天", + stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + + // 清理特定的降级缓存 + if (hasScenicCache) { + fallbackService.clearFallbackCache(SERVICE_NAME, scenicCacheKey); + log.info("已清理景区降级缓存"); + } + + // 如果缓存过多,批量清理 + if (stats.getTotalCacheCount() > 50) { + fallbackService.clearAllFallbackCache(SERVICE_NAME); + log.info("已批量清理所有景区降级缓存"); + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + log.info("开始运行景区集成示例(包含降级机制)..."); + + createScenicWithConfig(); + filterScenics(); + basicScenicOperationsExample(); + scenicConfigManagementFallbackExample(); + fallbackCacheManagementExample(); + + log.info("景区集成示例运行完成"); + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java index 39151fd..abb3914 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; import com.ycwl.basic.integration.scenic.dto.config.*; import lombok.RequiredArgsConstructor; @@ -17,6 +18,9 @@ import java.util.Map; public class ScenicConfigIntegrationService { private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public List listConfigs(Long scenicId) { log.info("获取景区配置列表, scenicId: {}", scenicId); @@ -32,8 +36,15 @@ public class ScenicConfigIntegrationService { public Map getFlatConfigs(Long scenicId) { log.info("获取景区扁平化配置, scenicId: {}", scenicId); - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:flat:configs:" + scenicId, + () -> { + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + }, + Map.class + ); } public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) { diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java index e101398..fba59c2 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; import com.ycwl.basic.integration.scenic.client.ScenicV2Client; import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; @@ -25,23 +26,47 @@ public class ScenicIntegrationService { private final ScenicV2Client scenicV2Client; private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public ScenicV2DTO getScenic(Long scenicId) { log.info("获取景区信息, scenicId: {}", scenicId); - CommonResponse response = scenicV2Client.getScenic(scenicId); - return handleResponse(response, "获取景区信息失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenic(scenicId); + return handleResponse(response, "获取景区信息失败"); + }, + ScenicV2DTO.class + ); } public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) { log.info("获取景区配置信息, scenicId: {}", scenicId); - CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); - return handleResponse(response, "获取景区配置信息失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:config:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); + return handleResponse(response, "获取景区配置信息失败"); + }, + ScenicV2WithConfigDTO.class + ); } public Map getScenicFlatConfig(Long scenicId) { log.info("获取景区扁平化配置, scenicId: {}", scenicId); - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:flat:config:" + scenicId, + () -> { + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + }, + Map.class + ); } public ScenicV2DTO createScenic(CreateScenicRequest request) { From 2dee78247e87a84fd8dad76336a0c7cf39bb24ed Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 12:27:51 +0800 Subject: [PATCH 10/18] =?UTF-8?q?refactor(repository):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E8=AE=BE=E5=A4=87=E5=92=8C=E6=99=AF=E7=82=B9=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E7=BC=93=E5=AD=98=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除了 DeviceRepository 和 ScenicRepository 中的缓存相关代码 - 移除了成功结果缓存和错误降级逻辑 -简化了设备和景点信息获取方法,直接调用服务接口返回结果 --- .../basic/repository/DeviceRepository.java | 54 ++---------- .../basic/repository/ScenicRepository.java | 83 +++---------------- 2 files changed, 17 insertions(+), 120 deletions(-) diff --git a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java index 77d9121..46fe66f 100644 --- a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java +++ b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java @@ -31,8 +31,6 @@ public class DeviceRepository { public static final String DEVICE_ONLINE_CACHE_KEY = "device:online_status:%s"; public static final String DEVICE_CACHE_KEY = "device:%s"; public static final String DEVICE_CONFIG_CACHE_KEY = "device:%s:config"; - public static final String DEVICE_CACHE_SUCCESS_KEY = "device:success:%s"; - public static final String DEVICE_NO_CACHE_SUCCESS_KEY = "device:success:no:%s"; /** * 将DeviceV2DTO转换为DeviceEntity @@ -60,52 +58,16 @@ public class DeviceRepository { public DeviceEntity getDevice(Long deviceId) { log.debug("获取设备信息, deviceId: {}", deviceId); - try { - DeviceV2DTO deviceDto = deviceIntegrationService.getDevice(deviceId); - DeviceEntity device = convertToEntity(deviceDto); - if (device != null) { - // 存储到常规缓存 - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - // 存储到成功结果缓存,用于失败时降级 - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); - } - return device; - } catch (Exception e) { - log.warn("调用zt-device服务失败, deviceId: {}, 尝试返回缓存结果", deviceId, e); - String cachedDevice = redisTemplate.opsForValue().get(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId)); - if (cachedDevice != null) { - log.info("返回缓存的设备信息, deviceId: {}", deviceId); - return JacksonUtil.parseObject(cachedDevice, DeviceEntity.class); - } - log.error("无法获取设备信息且无缓存数据, deviceId: {}", deviceId); - throw e; - } + DeviceV2DTO deviceDto = deviceIntegrationService.getDevice(deviceId); + DeviceEntity device = convertToEntity(deviceDto); + return device; } public DeviceEntity getDeviceByDeviceNo(String deviceNo) { log.debug("根据设备编号获取设备信息, deviceNo: {}", deviceNo); - try { - DeviceV2DTO deviceDto = deviceIntegrationService.getDeviceByNo(deviceNo); - DeviceEntity device = convertToEntity(deviceDto); - if (device != null) { - // 存储到常规缓存 - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceNo), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getId()), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - // 存储到成功结果缓存,用于失败时降级 - redisTemplate.opsForValue().set(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_SUCCESS_KEY, device.getId()), JacksonUtil.toJSONString(device), 7, TimeUnit.DAYS); - } - return device; - } catch (Exception e) { - log.warn("调用zt-device服务失败, deviceNo: {}, 尝试返回缓存结果", deviceNo, e); - String cachedDevice = redisTemplate.opsForValue().get(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo)); - if (cachedDevice != null) { - log.info("返回缓存的设备信息, deviceNo: {}", deviceNo); - return JacksonUtil.parseObject(cachedDevice, DeviceEntity.class); - } - log.error("无法获取设备信息且无缓存数据, deviceNo: {}", deviceNo); - throw e; - } + DeviceV2DTO deviceDto = deviceIntegrationService.getDeviceByNo(deviceNo); + DeviceEntity device = convertToEntity(deviceDto); + return device; } public DeviceConfigEntity getDeviceConfig(Long deviceId) { @@ -137,8 +99,6 @@ public class DeviceRepository { } } redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceNo)); - // 清理成功结果缓存 - redisTemplate.delete(String.format(DEVICE_NO_CACHE_SUCCESS_KEY, deviceNo)); return true; } public boolean clearDeviceCache(Long deviceId) { @@ -149,8 +109,6 @@ public class DeviceRepository { } redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceId)); redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)); - // 清理成功结果缓存 - redisTemplate.delete(String.format(DEVICE_CACHE_SUCCESS_KEY, deviceId)); return true; } diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 1075949..4d88f41 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -49,51 +49,15 @@ public class ScenicRepository { public ScenicV2DTO getScenicBasic(Long id) { String key = String.format(SCENIC_BASIC_CACHE_KEY, id); - try { - ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); - - // 请求成功,写入缓存 - if (scenicDTO != null) { - redisTemplate.opsForValue().set( - key, - JacksonUtil.toJSONString(scenicDTO) - ); - } - return scenicDTO; - } catch (Exception e) { - // 请求失败,尝试从缓存获取历史成功数据 - String cacheKey = key; - if (redisTemplate.hasKey(cacheKey)) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicV2DTO.class); - } - // 缓存也没有,返回null - return null; - } + ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); + return scenicDTO; } public ScenicEntity getScenic(Long id) { String key = String.format(SCENIC_CACHE_KEY, id); - try { - ScenicV2WithConfigDTO scenicDTO = scenicIntegrationService.getScenicWithConfig(id); - ScenicEntity scenicEntity = convertToScenicEntity(scenicDTO); - - // 请求成功,写入缓存 - if (scenicEntity != null) { - redisTemplate.opsForValue().set( - key, - JacksonUtil.toJSONString(scenicEntity) - ); - } - return scenicEntity; - } catch (Exception e) { - // 请求失败,尝试从缓存获取历史成功数据 - String cacheKey = key; - if (redisTemplate.hasKey(cacheKey)) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicEntity.class); - } - // 缓存也没有,返回null - return null; - } + ScenicV2WithConfigDTO scenicDTO = scenicIntegrationService.getScenicWithConfig(id); + ScenicEntity scenicEntity = convertToScenicEntity(scenicDTO); + return scenicEntity; } public ScenicConfigEntity getScenicConfig(Long scenicId) { @@ -384,38 +348,13 @@ public class ScenicRepository { public ScenicConfigManager getScenicConfigManagerWithCache(Long scenicId) { String key = String.format(SCENIC_CONFIG_CACHE_KEY + ":manager", scenicId); - try { - List configList = - scenicConfigIntegrationService.listConfigs(scenicId); - if (configList != null) { - ScenicConfigManager manager = new ScenicConfigManager(configList); - - // 请求成功,写入缓存(将配置列表序列化存储) - redisTemplate.opsForValue().set( - key, - JacksonUtil.toJSONString(configList) - ); - - return manager; - } - return null; - } catch (Exception e) { - // 请求失败,尝试从缓存获取历史成功数据 - if (redisTemplate.hasKey(key)) { - try { - String cachedConfigJson = redisTemplate.opsForValue().get(key); - @SuppressWarnings("unchecked") - List cachedConfigList = - JacksonUtil.parseArray(cachedConfigJson, ScenicConfigV2DTO.class); - return new ScenicConfigManager(cachedConfigList); - } catch (Exception cacheException) { - // 缓存解析失败,返回null - return null; - } - } - // 缓存也没有,返回null - return null; + List configList = + scenicConfigIntegrationService.listConfigs(scenicId); + if (configList != null) { + ScenicConfigManager manager = new ScenicConfigManager(configList); + return manager; } + return null; } } From 8e770a5b9742088811a8da84141cad165b393729 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 15:30:54 +0800 Subject: [PATCH 11/18] =?UTF-8?q?refactor(integration):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增通用 ConfigManager 类,实现配置管理的通用功能 - 新增 DeviceConfigManager 和 ScenicConfigManager 类,分别实现设备和景区的配置管理- 更新相关控制器和服务,使用新的配置管理器类 -调整设备和景区的配置数据结构,以适应新的管理方式 --- .../controller/mobile/AppClaimController.java | 2 +- .../common/manager/ConfigManager.java | 241 ++++++++++++++++++ .../common/manager/DeviceConfigManager.java | 122 +++++++++ .../common/manager}/ScenicConfigManager.java | 19 +- .../service/impl/VoucherPrintServiceImpl.java | 2 +- .../basic/repository/DeviceRepository.java | 51 +++- .../basic/repository/ScenicRepository.java | 3 +- .../service/pc/impl/ScenicServiceImpl.java | 4 +- 8 files changed, 424 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java rename src/main/java/com/ycwl/basic/{util => integration/common/manager}/ScenicConfigManager.java (95%) diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppClaimController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppClaimController.java index f574a54..46947e0 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppClaimController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppClaimController.java @@ -11,7 +11,7 @@ import com.ycwl.basic.pricing.service.ICouponService; import com.ycwl.basic.pricing.service.VoucherPrintService; import com.ycwl.basic.repository.FaceRepository; import com.ycwl.basic.repository.ScenicRepository; -import com.ycwl.basic.util.ScenicConfigManager; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import com.ycwl.basic.utils.ApiResponse; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java new file mode 100644 index 0000000..9570d9d --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java @@ -0,0 +1,241 @@ +package com.ycwl.basic.integration.common.manager; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * 通用配置管理器基类 + * 提供配置查找、类型转换等通用功能 + * + * @param 配置DTO类型 + */ +public abstract class ConfigManager { + + protected List configs; + + public ConfigManager(List 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 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 getEnum(String key, Class enumClass) { + T config = findConfigByKey(key); + if (config == null) { + return null; + } + Object value = getConfigValue(config); + return parseEnum(value, enumClass); + } + + /** + * 解析枚举值 + */ + private > E parseEnum(Object value, Class 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 getEnum(String key, Class 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 getAllConfigs() { + return new ArrayList<>(configs); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java new file mode 100644 index 0000000..cda3818 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java @@ -0,0 +1,122 @@ +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 { + + public DeviceConfigManager(List 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; + } + + // 设备配置的常用快捷方法 + + /** + * 获取设备IP地址 + */ + public String getIpAddress() { + return getString("ip_address"); + } + + /** + * 获取设备分辨率 + */ + public String getResolution() { + return getString("resolution"); + } + + /** + * 获取设备帧率 + */ + public Integer getFramerate() { + return getInteger("framerate"); + } + + /** + * 获取设备协议 + */ + public String getProtocol() { + return getString("protocol"); + } + + /** + * 获取设备用户名 + */ + public String getUsername() { + return getString("username"); + } + + /** + * 获取设备密码 + */ + public String getPassword() { + return getString("password"); + } + + /** + * 获取设备亮度 + */ + public Integer getBrightness() { + return getInteger("brightness"); + } + + /** + * 获取设备对比度 + */ + public Integer getContrast() { + return getInteger("contrast"); + } + + /** + * 获取设备质量 + */ + public String getQuality() { + return getString("quality"); + } + + /** + * 检查设备是否启用录制 + */ + public Boolean isRecordingEnabled() { + return getBoolean("recording_enabled"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/util/ScenicConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java similarity index 95% rename from src/main/java/com/ycwl/basic/util/ScenicConfigManager.java rename to src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java index 11892b6..7376c6b 100644 --- a/src/main/java/com/ycwl/basic/util/ScenicConfigManager.java +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java @@ -1,4 +1,4 @@ -package com.ycwl.basic.util; +package com.ycwl.basic.integration.common.manager; import com.ycwl.basic.integration.common.util.ConfigValueUtil; import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO; @@ -9,11 +9,12 @@ import java.util.stream.Collectors; /** * 景区配置管理器 + * 基于通用ConfigManager实现景区配置的管理功能 * * 提供类型安全的配置值获取功能,支持多种数据类型的自动转换, * 当类型不兼容时返回null而不是抛出异常。 */ -public class ScenicConfigManager { +public class ScenicConfigManager extends ConfigManager { private final Map configMap; @@ -23,6 +24,7 @@ public class ScenicConfigManager { * @param configList 配置项列表 */ public ScenicConfigManager(List configList) { + super(configList); this.configMap = new HashMap<>(); if (configList != null) { for (ScenicConfigV2DTO config : configList) { @@ -39,8 +41,19 @@ public class ScenicConfigManager { * @param configMap 配置Map */ public ScenicConfigManager(Map 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; + } /** * 获取字符串值 @@ -325,7 +338,7 @@ public class ScenicConfigManager { * * @return 配置Map的拷贝 */ - public Map getAllConfigs() { + public Map getAllConfigsAsMap() { return new HashMap<>(configMap); } diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java index 41b6dcf..4effcc6 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java @@ -16,7 +16,7 @@ import com.ycwl.basic.pricing.service.VoucherPrintService; import com.ycwl.basic.printer.ticket.FeiETicketPrinter; import com.ycwl.basic.repository.FaceRepository; import com.ycwl.basic.repository.ScenicRepository; -import com.ycwl.basic.util.ScenicConfigManager; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import com.ycwl.basic.utils.WxMpUtil; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; diff --git a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java index 46fe66f..a9d0a0e 100644 --- a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java +++ b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java @@ -1,5 +1,7 @@ package com.ycwl.basic.repository; +import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; +import com.ycwl.basic.integration.device.dto.config.DeviceConfigV2DTO; import com.ycwl.basic.utils.JacksonUtil; import com.ycwl.basic.mapper.DeviceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; @@ -7,6 +9,7 @@ import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.utils.SnowFlakeUtil; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -31,6 +34,9 @@ public class DeviceRepository { public static final String DEVICE_ONLINE_CACHE_KEY = "device:online_status:%s"; public static final String DEVICE_CACHE_KEY = "device:%s"; public static final String DEVICE_CONFIG_CACHE_KEY = "device:%s:config"; + public static final String DEVICE_CONFIG_MANAGER_CACHE_KEY = "device:%s:config:manager"; + @Autowired + private DeviceConfigIntegrationService deviceConfigIntegrationService; /** * 将DeviceV2DTO转换为DeviceEntity @@ -71,18 +77,41 @@ public class DeviceRepository { } public DeviceConfigEntity getDeviceConfig(Long deviceId) { - if (redisTemplate.hasKey(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId))) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)), DeviceConfigEntity.class); + List configList = deviceConfigIntegrationService.getDeviceConfigs(deviceId); + if (configList != null && !configList.isEmpty()) { + return convertToDeviceConfigEntity(configList, deviceId); } - DeviceConfigEntity deviceConfig = deviceMapper.getConfigByDeviceId(deviceId); - if (null == deviceConfig) { - deviceConfig = new DeviceConfigEntity(); - deviceConfig.setId(SnowFlakeUtil.getLongId()); - deviceConfig.setDeviceId(deviceId); - deviceMapper.addConfig(deviceConfig); + return null; + } + + /** + * 获取设备配置管理器 + * + * @param deviceId 设备ID + * @return DeviceConfigManager实例,如果获取失败返回null + */ + public DeviceConfigManager getDeviceConfigManager(Long deviceId) { + try { + List configList = deviceConfigIntegrationService.getDeviceConfigs(deviceId); + return new DeviceConfigManager(configList); + } catch (Exception e) { + log.warn("获取设备配置管理器失败, deviceId: {}", deviceId, e); + return null; } - redisTemplate.opsForValue().set(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId), JacksonUtil.toJSONString(deviceConfig), 3, TimeUnit.DAYS); - return deviceConfig; + } + + /** + * 将DeviceConfigV2DTO列表转换为DeviceConfigEntity(为了兼容性) + */ + private DeviceConfigEntity convertToDeviceConfigEntity(List configList, Long deviceId) { + DeviceConfigEntity entity = new DeviceConfigEntity(); + entity.setId(SnowFlakeUtil.getLongId()); + entity.setDeviceId(deviceId); + + // 由于DeviceConfigEntity没有通用的configJson字段,这里只设置基本信息 + // 具体的配置项需要通过DeviceConfigManager来访问 + + return entity; } public boolean clearDeviceCache(String deviceNo) { @@ -106,9 +135,11 @@ public class DeviceRepository { DeviceEntity device = getDevice(deviceId); redisTemplate.delete(String.format(DEVICE_CACHE_KEY, device.getNo())); redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, device.getNo())); + redisTemplate.delete(String.format(DEVICE_CONFIG_MANAGER_CACHE_KEY, device.getNo())); } redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceId)); redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)); + redisTemplate.delete(String.format(DEVICE_CONFIG_MANAGER_CACHE_KEY, deviceId)); return true; } diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 4d88f41..5488f6e 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -19,13 +19,12 @@ import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.pay.enums.PayAdapterType; import com.ycwl.basic.storage.enums.StorageType; import com.ycwl.basic.utils.JacksonUtil; -import com.ycwl.basic.util.ScenicConfigManager; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.List; -import java.util.Map; @Component public class ScenicRepository { diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index 232299e..ab3109a 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -3,7 +3,6 @@ package com.ycwl.basic.service.pc.impl; import com.ycwl.basic.facebody.FaceBodyFactory; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.pay.PayFactory; import com.ycwl.basic.pay.adapter.IPayAdapter; @@ -12,10 +11,9 @@ import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.exceptions.StorageUnsupportedException; -import com.ycwl.basic.util.ScenicConfigManager; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import com.ycwl.basic.util.TtlCacheMap; import com.ycwl.basic.utils.ApiResponse; -import com.ycwl.basic.utils.JacksonUtil; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; From 25b912c7afcc60a318f7213658dcbe8c3baeb854 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 16:13:43 +0800 Subject: [PATCH 12/18] =?UTF-8?q?refactor(DeviceConfigManager):=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E9=85=8D=E7=BD=AE=E5=BF=AB=E6=8D=B7=E6=96=B9=E6=B3=95?= =?UTF-8?q?-=20=E5=88=A0=E9=99=A4=E4=BA=86=E5=A4=9A=E4=B8=AA=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=96=B9=E6=B3=95=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=20IP=20=E5=9C=B0=E5=9D=80=E3=80=81=E5=88=86=E8=BE=A8=E7=8E=87?= =?UTF-8?q?=E3=80=81=E5=B8=A7=E7=8E=87=E7=AD=89=20-=20=E7=AE=80=E5=8C=96?= =?UTF-8?q?=E4=BA=86=20DeviceConfigManager=20=E7=B1=BB=E7=9A=84=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=8F=90=E9=AB=98=E4=BA=86=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=9A=84=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/manager/DeviceConfigManager.java | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java index cda3818..30f9661 100644 --- a/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java +++ b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java @@ -48,75 +48,4 @@ public class DeviceConfigManager extends ConfigManager { return config != null ? config.getId() : null; } - // 设备配置的常用快捷方法 - - /** - * 获取设备IP地址 - */ - public String getIpAddress() { - return getString("ip_address"); - } - - /** - * 获取设备分辨率 - */ - public String getResolution() { - return getString("resolution"); - } - - /** - * 获取设备帧率 - */ - public Integer getFramerate() { - return getInteger("framerate"); - } - - /** - * 获取设备协议 - */ - public String getProtocol() { - return getString("protocol"); - } - - /** - * 获取设备用户名 - */ - public String getUsername() { - return getString("username"); - } - - /** - * 获取设备密码 - */ - public String getPassword() { - return getString("password"); - } - - /** - * 获取设备亮度 - */ - public Integer getBrightness() { - return getInteger("brightness"); - } - - /** - * 获取设备对比度 - */ - public Integer getContrast() { - return getInteger("contrast"); - } - - /** - * 获取设备质量 - */ - public String getQuality() { - return getString("quality"); - } - - /** - * 检查设备是否启用录制 - */ - public Boolean isRecordingEnabled() { - return getBoolean("recording_enabled"); - } } \ No newline at end of file From 35b2e7c6558a0402b81259d085326fbeb3a018a4 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 16:14:01 +0800 Subject: [PATCH 13/18] =?UTF-8?q?feat(ConfigManager):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=95=BF=E6=95=B4=E5=9E=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=80=BC=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getLong 方法,用于获取长整型配置值 - 增加 getLong 方法的重载版本,支持返回默认值 -支持处理不同类型的配置值,包括 Long、Number 和 String --- .../common/manager/ConfigManager.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java index 9570d9d..fb902e6 100644 --- a/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java @@ -94,6 +94,36 @@ public abstract class ConfigManager { 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; + } + /** * 获取布尔型配置值 */ From 3efad90750e045d2b3a0cdef9e9e89eb64de2104 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 16:15:35 +0800 Subject: [PATCH 14/18] =?UTF-8?q?refactor(device-config):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 DeviceConfigEntity 替换为 DeviceConfigManager - 优化设备配置的获取逻辑,使用 getInteger 和 getLong 方法 - 移除未使用的代码块,提高代码可读性 - 统一设备配置的处理方式,提高代码维护性 --- .../basic/controller/viid/ViidController.java | 83 +++++++++---------- .../service/pc/impl/FaceServiceImpl.java | 7 +- .../task/impl/TaskFaceServiceImpl.java | 7 +- .../com/ycwl/basic/task/VideoPieceGetter.java | 8 +- 4 files changed, 52 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java index 60f48ec..72b069e 100644 --- a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java +++ b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.core.util.ObjectUtil; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.utils.JacksonUtil; import com.ycwl.basic.annotation.IgnoreLogReq; import com.ycwl.basic.annotation.IgnoreToken; @@ -33,7 +34,6 @@ import com.ycwl.basic.model.viid.req.UnRegisterReq; import com.ycwl.basic.model.viid.resp.SystemTimeResp; import com.ycwl.basic.model.viid.resp.VIIDBaseResp; import com.ycwl.basic.repository.DeviceRepository; -import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.service.task.TaskFaceService; import com.ycwl.basic.storage.StorageFactory; @@ -87,8 +87,6 @@ public class ViidController { @Autowired private DeviceRepository deviceRepository; @Autowired - private ScenicRepository scenicRepository; - @Autowired private TaskFaceService taskFaceService; private final Map executors = new ConcurrentHashMap<>(); @Autowired @@ -245,17 +243,14 @@ public class ViidController { if (device == null) { continue; } - DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(device.getId()); + DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(device.getId()); if (deviceConfig == null) { log.warn("设备配置不存在:" + deviceID); return new VIIDBaseResp( new ResponseStatusObject(faceId, "/VIID/Faces", "0", "OK", sdfTime.format(new Date())) ); } - int viidMode = 0; - if (deviceConfig.getViidType() != null) { - viidMode = deviceConfig.getViidType(); - } + Integer viidMode = deviceConfig.getInteger("viid_mode", 0); Date shotTime = null; if (StringUtils.isNotBlank(face.getShotTime())) { try { @@ -333,7 +328,7 @@ public class ViidController { faceSampleMapper.updateScore(faceSample.getId(), addFaceResp.getScore()); } } - if (Integer.valueOf(1).equals(deviceConfig.getEnablePreBook())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("enable_pre_book"))) { DynamicTaskGenerator.addTask(faceSample.getId()); } }); @@ -352,40 +347,40 @@ public class ViidController { MultipartFile _file = ImageUtils.base64ToMultipartFile(_subImage.getData()); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { - List cropConfigs = deviceConfig._getCropConfig(); - for (DeviceCropConfig cropConfig : cropConfigs) { - source.setId(SnowFlakeUtil.getLongId()); - String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); - MultipartFile _finalFile = _file; - if (cropConfig.getCropType() == 1) { - // 按固定位置截图 - try { - _finalFile = ImageUtils.cropImage(_file, cropConfig.getTargetX(), cropConfig.getTargetY(), cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); - } catch (IOException e) { - log.error("裁切图片失败!", e); - } catch (RasterFormatException e) { - log.error("裁切图片出错!", e); - } - } else if (cropConfig.getCropType() == 2) { - // 按人脸位置 - try { - int targetX = facePosition.getLtX() - (cropConfig.getTargetWidth() - facePosition.getWidth())/2; - int targetY = facePosition.getLtY() - (cropConfig.getTargetHeight() - facePosition.getHeight())/2; - _finalFile = ImageUtils.cropImage(_file, targetX, targetY, cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); - } catch (IOException e) { - log.error("裁切图片失败!", e); - } catch (RasterFormatException e) { - log.error("裁切图片出错!", e); - } - facePosition.setImgHeight(cropConfig.getTargetHeight()); - facePosition.setImgWidth(cropConfig.getTargetWidth()); - } - String _sourceUrl = scenicStorageAdapter.uploadFile(_finalFile, filename); - scenicStorageAdapter.setAcl(StorageAcl.PUBLIC_READ, filename); - source.setUrl(_sourceUrl); - source.setPosJson(JacksonUtil.toJSONString(facePosition)); - sourceMapper.add(source); - } +// List cropConfigs = deviceConfig._getCropConfig(); +// for (DeviceCropConfig cropConfig : cropConfigs) { +// source.setId(SnowFlakeUtil.getLongId()); +// String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); +// MultipartFile _finalFile = _file; +// if (cropConfig.getCropType() == 1) { +// // 按固定位置截图 +// try { +// _finalFile = ImageUtils.cropImage(_file, cropConfig.getTargetX(), cropConfig.getTargetY(), cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); +// } catch (IOException e) { +// log.error("裁切图片失败!", e); +// } catch (RasterFormatException e) { +// log.error("裁切图片出错!", e); +// } +// } else if (cropConfig.getCropType() == 2) { +// // 按人脸位置 +// try { +// int targetX = facePosition.getLtX() - (cropConfig.getTargetWidth() - facePosition.getWidth())/2; +// int targetY = facePosition.getLtY() - (cropConfig.getTargetHeight() - facePosition.getHeight())/2; +// _finalFile = ImageUtils.cropImage(_file, targetX, targetY, cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); +// } catch (IOException e) { +// log.error("裁切图片失败!", e); +// } catch (RasterFormatException e) { +// log.error("裁切图片出错!", e); +// } +// facePosition.setImgHeight(cropConfig.getTargetHeight()); +// facePosition.setImgWidth(cropConfig.getTargetWidth()); +// } +// String _sourceUrl = scenicStorageAdapter.uploadFile(_finalFile, filename); +// scenicStorageAdapter.setAcl(StorageAcl.PUBLIC_READ, filename); +// source.setUrl(_sourceUrl); +// source.setPosJson(JacksonUtil.toJSONString(facePosition)); +// sourceMapper.add(source); +// } }); } log.info("人脸信息及原图{}张入库成功!设备ID:{}", type14ImageList.size(), deviceID); @@ -425,7 +420,7 @@ public class ViidController { faceSampleMapper.updateScore(faceSample.getId(), addFaceResp.getScore()); } } - if (Integer.valueOf(1).equals(deviceConfig.getEnablePreBook())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("enable_pre_book"))) { DynamicTaskGenerator.addTask(faceSample.getId()); } }); diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java index b8f5664..cc32add 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java @@ -9,6 +9,7 @@ import com.ycwl.basic.constant.BaseContextHandler; import com.ycwl.basic.enums.StatisticEnum; import com.ycwl.basic.exception.BaseException; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.mapper.FaceMapper; @@ -290,7 +291,7 @@ public class FaceServiceImpl implements FaceService { if (sampleListIds != null && !sampleListIds.isEmpty()) {// 匹配原片:照片 List sourceEntities = sourceMapper.listBySampleIds(sampleListIds); List memberSourceEntityList = sourceEntities.stream().map(sourceEntity -> { - DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(sourceEntity.getDeviceId()); + DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(sourceEntity.getDeviceId()); MemberSourceEntity memberSourceEntity = new MemberSourceEntity(); memberSourceEntity.setScenicId(face.getScenicId()); memberSourceEntity.setFaceId(face.getId()); @@ -300,11 +301,11 @@ public class FaceServiceImpl implements FaceService { memberSourceEntity.setIsFree(0); if (deviceConfig != null) { if (sourceEntity.getType() == 1) { - if (Integer.valueOf(1).equals(deviceConfig.getVideoFree())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("video_free"))) { memberSourceEntity.setIsFree(1); } } else if (sourceEntity.getType() == 2) { - if (Integer.valueOf(1).equals(deviceConfig.getImageFree())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("image_free"))) { memberSourceEntity.setIsFree(1); } } diff --git a/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java index 343ee0b..7f11b91 100644 --- a/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java @@ -1,6 +1,7 @@ package com.ycwl.basic.service.task.impl; import cn.hutool.core.date.DateUtil; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.utils.JacksonUtil; import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest; import com.ycwl.basic.biz.OrderBiz; @@ -115,7 +116,7 @@ public class TaskFaceServiceImpl implements TaskFaceService { if (sampleListIds != null && !sampleListIds.isEmpty()) {// 匹配原片:照片 List sourceEntities = sourceMapper.listBySampleIds(sampleListIds); List memberSourceEntityList = sourceEntities.stream().map(sourceEntity -> { - DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(sourceEntity.getDeviceId()); + DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(sourceEntity.getDeviceId()); MemberSourceEntity memberSourceEntity = new MemberSourceEntity(); memberSourceEntity.setScenicId(scenicId); memberSourceEntity.setFaceId(faceEntity.getId()); @@ -125,11 +126,11 @@ public class TaskFaceServiceImpl implements TaskFaceService { memberSourceEntity.setIsFree(0); if (deviceConfig != null) { if (sourceEntity.getType() == 1) { - if (Integer.valueOf(1).equals(deviceConfig.getVideoFree())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("video_free"))) { memberSourceEntity.setIsFree(1); } } else if (sourceEntity.getType() == 2) { - if (Integer.valueOf(1).equals(deviceConfig.getImageFree())) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("image_free"))) { memberSourceEntity.setIsFree(1); } } diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java index 97bfc56..8fd42a8 100644 --- a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java +++ b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java @@ -7,6 +7,7 @@ import com.ycwl.basic.constant.StorageConstant; import com.ycwl.basic.device.DeviceFactory; import com.ycwl.basic.device.entity.common.FileObject; import com.ycwl.basic.device.operator.IDeviceStorageOperator; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; @@ -147,9 +148,10 @@ public class VideoPieceGetter { List allDeviceByScenicId = deviceRepository.getAllDeviceByScenicId(scenicId); allDeviceByScenicId.forEach(device -> { Long deviceId = device.getId(); - DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(deviceId); - if (deviceConfig != null && deviceConfig.getPairDevice() != null) { - pairDeviceMap.putIfAbsent(deviceId, deviceConfig.getPairDevice()); + DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(deviceId); + Long pairDevice = deviceConfig.getLong("pair_device"); + if (pairDevice != null) { + pairDeviceMap.putIfAbsent(deviceId, pairDevice); } }); } From 982e9180f14b8208821f71fbef82e58ddef90d19 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 2 Sep 2025 23:49:15 +0800 Subject: [PATCH 15/18] =?UTF-8?q?feat(ConfigManager):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getObject、getMap、getList 等方法,增强配置值获取和转换能力 - 支持 JSON 字符串自动反序列化为指定类型对象 - 优化配置值处理逻辑,提高代码复用性和可维护性 - 移除 ScenicConfigManager 中的冗余方法 --- .../common/manager/ConfigManager.java | 135 ++++++++++++++++++ .../common/manager/ScenicConfigManager.java | 120 +--------------- 2 files changed, 136 insertions(+), 119 deletions(-) diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java index fb902e6..c4bee39 100644 --- a/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java @@ -1,8 +1,10 @@ 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; /** * 通用配置管理器基类 @@ -268,4 +270,137 @@ public abstract class ConfigManager { public List 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 getObject(String key, Class 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 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) 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 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) value; + } + + if (value instanceof String) { + try { + return JacksonUtil.parseObject((String) value, List.class); + } catch (Exception e) { + return null; + } + } + + return null; + } + + /** + * 获取指定元素类型的List配置值 + */ + public List getList(String key, Class 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; + } + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java index 7376c6b..5722220 100644 --- a/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ScenicConfigManager.java @@ -55,47 +55,6 @@ public class ScenicConfigManager extends ConfigManager { return config != null ? config.getConfigValue() : null; } - /** - * 获取字符串值 - * - * @param key 配置键 - * @return 字符串值,如果键不存在或转换失败返回null - */ - public String getString(String key) { - return ConfigValueUtil.getStringValue(configMap, key); - } - - /** - * 获取字符串值,如果为null则返回默认值 - * - * @param key 配置键 - * @param defaultValue 默认值 - * @return 字符串值或默认值 - */ - public String getString(String key, String defaultValue) { - return ConfigValueUtil.getStringValue(configMap, key, defaultValue); - } - - /** - * 获取整数值 - * - * @param key 配置键 - * @return Integer值,如果键不存在或转换失败返回null - */ - public Integer getInteger(String key) { - return ConfigValueUtil.getIntValue(configMap, key); - } - - /** - * 获取整数值,如果为null则返回默认值 - * - * @param key 配置键 - * @param defaultValue 默认值 - * @return Integer值或默认值 - */ - public Integer getInteger(String key, Integer defaultValue) { - return ConfigValueUtil.getIntValue(configMap, key, defaultValue); - } /** * 获取长整数值 @@ -206,85 +165,7 @@ public class ScenicConfigManager extends ConfigManager { return ConfigValueUtil.getBooleanValue(configMap, key, defaultValue); } - /** - * 获取枚举值 - * - * @param key 配置键 - * @param enumClass 枚举类型 - * @param 枚举类型泛型 - * @return 枚举值,如果键不存在或转换失败返回null - */ - public > T getEnum(String key, Class enumClass) { - return ConfigValueUtil.getEnumValue(configMap, key, enumClass); - } - /** - * 获取枚举值,如果为null则返回默认值 - * - * @param key 配置键 - * @param enumClass 枚举类型 - * @param defaultValue 默认值 - * @param 枚举类型泛型 - * @return 枚举值或默认值 - */ - public > T getEnum(String key, Class enumClass, T defaultValue) { - T value = ConfigValueUtil.getEnumValue(configMap, key, enumClass); - return value != null ? value : defaultValue; - } - - /** - * 获取原始对象值 - * - * @param key 配置键 - * @return 原始Object值 - */ - public Object getObject(String key) { - return ConfigValueUtil.getObjectValue(configMap, key); - } - - /** - * 获取并转换为指定类型的对象 - * - * @param key 配置键 - * @param clazz 目标类型 - * @param 目标类型泛型 - * @return 转换后的对象,如果转换失败返回null - */ - public T getObject(String key, Class clazz) { - return ConfigValueUtil.getObjectValue(configMap, key, clazz); - } - - /** - * 获取Map类型的值 - * - * @param key 配置键 - * @return Map值,如果转换失败返回null - */ - public Map getMap(String key) { - return ConfigValueUtil.getMapValue(configMap, key); - } - - /** - * 获取List类型的值 - * - * @param key 配置键 - * @return List值,如果转换失败返回null - */ - public List getList(String key) { - return ConfigValueUtil.getListValue(configMap, key); - } - - /** - * 获取指定元素类型的List值 - * - * @param key 配置键 - * @param elementClass List元素类型 - * @param List元素类型泛型 - * @return 指定类型的List,如果转换失败返回null - */ - public List getList(String key, Class elementClass) { - return ConfigValueUtil.getListValue(configMap, key, elementClass); - } /** * 检查配置键是否存在 @@ -320,6 +201,7 @@ public class ScenicConfigManager extends ConfigManager { * * @return 配置项数量 */ + @Override public int size() { return configMap.size(); } From 9a086fc86dac1fe51cba8f6503723fe93bdf840b Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 3 Sep 2025 17:32:14 +0800 Subject: [PATCH 16/18] =?UTF-8?q?refactor(device):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E9=85=8D=E7=BD=AE=E8=8E=B7=E5=8F=96=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 ViidController 中添加获取 DeviceConfigEntity 的逻辑 - 在 VideoPieceGetter 中使用 DeviceConfigManager 替代 DeviceConfigEntity - 优化设备配置参数的获取方式,使用 getBigDecimal 和 getString 方法 - 移除未使用的代码片段,提高代码可读性 --- .../basic/controller/viid/ViidController.java | 69 ++++++++++--------- .../com/ycwl/basic/task/VideoPieceGetter.java | 26 +++---- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java index 72b069e..cb81a47 100644 --- a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java +++ b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java @@ -244,6 +244,7 @@ public class ViidController { continue; } DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(device.getId()); + DeviceConfigEntity deviceConfigEntity = deviceRepository.getDeviceConfig(device.getId()); if (deviceConfig == null) { log.warn("设备配置不存在:" + deviceID); return new VIIDBaseResp( @@ -347,40 +348,40 @@ public class ViidController { MultipartFile _file = ImageUtils.base64ToMultipartFile(_subImage.getData()); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { -// List cropConfigs = deviceConfig._getCropConfig(); -// for (DeviceCropConfig cropConfig : cropConfigs) { -// source.setId(SnowFlakeUtil.getLongId()); -// String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); -// MultipartFile _finalFile = _file; -// if (cropConfig.getCropType() == 1) { -// // 按固定位置截图 -// try { -// _finalFile = ImageUtils.cropImage(_file, cropConfig.getTargetX(), cropConfig.getTargetY(), cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); -// } catch (IOException e) { -// log.error("裁切图片失败!", e); -// } catch (RasterFormatException e) { -// log.error("裁切图片出错!", e); -// } -// } else if (cropConfig.getCropType() == 2) { -// // 按人脸位置 -// try { -// int targetX = facePosition.getLtX() - (cropConfig.getTargetWidth() - facePosition.getWidth())/2; -// int targetY = facePosition.getLtY() - (cropConfig.getTargetHeight() - facePosition.getHeight())/2; -// _finalFile = ImageUtils.cropImage(_file, targetX, targetY, cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); -// } catch (IOException e) { -// log.error("裁切图片失败!", e); -// } catch (RasterFormatException e) { -// log.error("裁切图片出错!", e); -// } -// facePosition.setImgHeight(cropConfig.getTargetHeight()); -// facePosition.setImgWidth(cropConfig.getTargetWidth()); -// } -// String _sourceUrl = scenicStorageAdapter.uploadFile(_finalFile, filename); -// scenicStorageAdapter.setAcl(StorageAcl.PUBLIC_READ, filename); -// source.setUrl(_sourceUrl); -// source.setPosJson(JacksonUtil.toJSONString(facePosition)); -// sourceMapper.add(source); -// } + List cropConfigs = deviceConfigEntity._getCropConfig(); + for (DeviceCropConfig cropConfig : cropConfigs) { + source.setId(SnowFlakeUtil.getLongId()); + String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); + MultipartFile _finalFile = _file; + if (cropConfig.getCropType() == 1) { + // 按固定位置截图 + try { + _finalFile = ImageUtils.cropImage(_file, cropConfig.getTargetX(), cropConfig.getTargetY(), cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); + } catch (IOException e) { + log.error("裁切图片失败!", e); + } catch (RasterFormatException e) { + log.error("裁切图片出错!", e); + } + } else if (cropConfig.getCropType() == 2) { + // 按人脸位置 + try { + int targetX = facePosition.getLtX() - (cropConfig.getTargetWidth() - facePosition.getWidth())/2; + int targetY = facePosition.getLtY() - (cropConfig.getTargetHeight() - facePosition.getHeight())/2; + _finalFile = ImageUtils.cropImage(_file, targetX, targetY, cropConfig.getTargetWidth(), cropConfig.getTargetHeight()); + } catch (IOException e) { + log.error("裁切图片失败!", e); + } catch (RasterFormatException e) { + log.error("裁切图片出错!", e); + } + facePosition.setImgHeight(cropConfig.getTargetHeight()); + facePosition.setImgWidth(cropConfig.getTargetWidth()); + } + String _sourceUrl = scenicStorageAdapter.uploadFile(_finalFile, filename); + scenicStorageAdapter.setAcl(StorageAcl.PUBLIC_READ, filename); + source.setUrl(_sourceUrl); + source.setPosJson(JacksonUtil.toJSONString(facePosition)); + sourceMapper.add(source); + } }); } log.info("人脸信息及原图{}张入库成功!设备ID:{}", type14ImageList.size(), deviceID); diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java index 8fd42a8..45476d2 100644 --- a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java +++ b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java @@ -257,23 +257,15 @@ public class VideoPieceGetter { private boolean doCut(Long deviceId, Long faceSampleId, Date baseTime, Task task) { DeviceEntity device = deviceRepository.getDevice(deviceId); - DeviceConfigEntity config = deviceRepository.getDeviceConfig(deviceId); + DeviceConfigManager config = deviceRepository.getDeviceConfigManager(deviceId); + DeviceConfigEntity dConfig = deviceRepository.getDeviceConfig(deviceId); SourceEntity source = sourceMapper.querySameVideo(faceSampleId, device.getId()); if (source == null || task.force) { - BigDecimal cutPre = BigDecimal.valueOf(5L); - BigDecimal cutPost = BigDecimal.valueOf(4L); - if (config != null) { - // 有配置 - if (config.getCutPre() != null) { - cutPre = config.getCutPre(); - } - if (config.getCutPost() != null) { - cutPost = config.getCutPost(); - } - } - IDeviceStorageOperator pieceGetter = DeviceFactory.getDeviceStorageOperator(device, config); + BigDecimal cutPre = config.getBigDecimal("cut_pre", BigDecimal.valueOf(5L)); + BigDecimal cutPost = config.getBigDecimal("cut_post", BigDecimal.valueOf(5L)); + IDeviceStorageOperator pieceGetter = DeviceFactory.getDeviceStorageOperator(device, dConfig); if (pieceGetter == null) { return false; } @@ -319,8 +311,8 @@ public class VideoPieceGetter { sourceEntity.setUrl(imgSource.getUrl()); sourceEntity.setPosJson(imgSource.getPosJson()); } - if (StringUtils.isNotBlank(config.getVideoCrop())) { - sourceEntity.setPosJson(config.getVideoCrop()); + if (StringUtils.isNotBlank(config.getString("video_crop"))) { + sourceEntity.setPosJson(config.getString("video_crop")); } sourceEntity.setVideoUrl(url); sourceEntity.setFaceSampleId(faceSampleId); @@ -348,8 +340,8 @@ public class VideoPieceGetter { videoReUploader.addTask(sourceEntity.getId()); } else { source.setVideoUrl(url); - if (StringUtils.isNotBlank(config.getVideoCrop())) { - source.setPosJson(config.getVideoCrop()); + if (StringUtils.isNotBlank(config.getString("video_crop"))) { + source.setPosJson(config.getString("video_crop")); } sourceMapper.update(source); videoReUploader.addTask(source.getId()); From 7779b84c810b8e82634632fec5364151a0c1f93a Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 4 Sep 2025 10:03:00 +0800 Subject: [PATCH 17/18] =?UTF-8?q?feat(device):=20=E9=9B=86=E6=88=90=20zt-d?= =?UTF-8?q?evice=20=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 DeviceController、DeviceService 相关代码 - 更新 ViidController、WvpController 使用 DeviceIntegrationService - 修改 DeviceFactory 创建 DeviceEntity 的方式 - 更新 DeviceRepository 使用 DeviceV2DTO -调整 CustomUploadTaskService、AppScenicServiceImpl 中的设备相关逻辑 - 移除 DeviceServiceImpl 类 - 更新 VideoPieceCleaner、VideoPieceGetter 任务类,使用 DeviceIntegrationService 获取设备信息 --- .../basic/controller/pc/DeviceController.java | 79 -------- .../basic/controller/viid/ViidController.java | 45 +++-- .../basic/controller/wvp/WvpController.java | 4 - .../com/ycwl/basic/device/DeviceFactory.java | 30 ++- .../basic/repository/DeviceRepository.java | 48 +---- .../custom/CustomUploadTaskService.java | 28 ++- .../mobile/impl/AppScenicServiceImpl.java | 41 +++- .../ycwl/basic/service/pc/DeviceService.java | 35 ---- .../service/pc/impl/DeviceServiceImpl.java | 175 ------------------ .../ycwl/basic/task/VideoPieceCleaner.java | 37 ++-- .../com/ycwl/basic/task/VideoPieceGetter.java | 27 ++- 11 files changed, 159 insertions(+), 390 deletions(-) delete mode 100644 src/main/java/com/ycwl/basic/controller/pc/DeviceController.java delete mode 100644 src/main/java/com/ycwl/basic/service/pc/DeviceService.java delete mode 100644 src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceController.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceController.java deleted file mode 100644 index abd2e1c..0000000 --- a/src/main/java/com/ycwl/basic/controller/pc/DeviceController.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.ycwl.basic.controller.pc; - -import com.github.pagehelper.PageInfo; -import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; -import com.ycwl.basic.model.pc.device.req.DeviceAddOrUpdateReq; -import com.ycwl.basic.model.pc.device.req.DeviceBatchSortRequest; -import com.ycwl.basic.model.pc.device.req.DeviceReqQuery; -import com.ycwl.basic.model.pc.device.req.DeviceSortRequest; -import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; -import com.ycwl.basic.model.pc.template.req.TemplateSortRequest; -import com.ycwl.basic.service.pc.DeviceService; -import com.ycwl.basic.utils.ApiResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -/** - * @Author:longbinbin - * @Date:2024/12/2 16:13 - */ -@RestController -@RequestMapping("/api/device/v1") -// 设备管理 -public class DeviceController { - @Autowired - private DeviceService deviceService; - - // 设备分页查询 - @PostMapping("/page") - public ApiResponse> pageQuery(@RequestBody DeviceReqQuery deviceReqQuery) { - return deviceService.pageQuery(deviceReqQuery); - } - // 设备列表查询 - @PostMapping("/list") - public ApiResponse list(@RequestBody DeviceReqQuery deviceReqQuery) { - return deviceService.list(deviceReqQuery); - } - // 设备详情查询 - @GetMapping("/getDetails/{id}") - public ApiResponse getDetails(@PathVariable("id") Long id) { - return deviceService.getById(id); - } - // 新增或修改设备 - @PostMapping("/addOrUpdate") - public ApiResponse addOrUpdate(@RequestBody DeviceAddOrUpdateReq deviceReqQuery) { - return deviceService.addOrUpdate(deviceReqQuery); - } - // 删除设备 - @DeleteMapping("/delete/{id}") - public ApiResponse delete(@PathVariable("id") Long id) { - return deviceService.deleteById(id); - } - // 修改设备状态 - @PutMapping("/updateStatus/{id}") - public ApiResponse updateStatus(@PathVariable("id") Long id) { - return deviceService.updateStatus(id); - } - - // 排序设备 - @PostMapping("/sort") - public ApiResponse sortDevice(@RequestBody DeviceSortRequest request) { - return deviceService.sortDevice(request.getDeviceId(), request.getAfterDeviceId()); - } - - @PostMapping("/scenic/{scenicId}/sortBatch") - public ApiResponse sortDeviceBatch(@PathVariable("scenicId") Long scenicId, @RequestBody DeviceBatchSortRequest request) { - return deviceService.batchSort(scenicId, request); - } - - @GetMapping("/config/{id}") - public ApiResponse getConfig(@PathVariable("id") Long id) { - return ApiResponse.success(deviceService.getConfig(id)); - } - - @PostMapping("/saveConfig/{configId}") - public ApiResponse saveConfig(@PathVariable("configId") Long configId, @RequestBody DeviceConfigEntity deviceConfigEntity) { - deviceService.saveConfig(configId, deviceConfigEntity); - return ApiResponse.success(null); - } -} diff --git a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java index cb81a47..1050e8e 100644 --- a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java +++ b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java @@ -10,7 +10,10 @@ import com.ycwl.basic.annotation.IgnoreLogReq; import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; import com.ycwl.basic.facebody.entity.AddFaceResp; -import com.ycwl.basic.mapper.DeviceMapper; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.CreateDeviceRequest; +import com.ycwl.basic.integration.device.dto.device.UpdateDeviceRequest; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.mapper.FaceSampleMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; @@ -80,7 +83,7 @@ import static com.ycwl.basic.constant.StorageConstant.VIID_FACE; @Slf4j public class ViidController { @Autowired - private DeviceMapper deviceMapper; + private DeviceIntegrationService deviceIntegrationService; private static final String serverId = "00000000000000000001"; @Autowired private SourceMapper sourceMapper; @@ -127,12 +130,21 @@ public class ViidController { } device.setKeepaliveAt(new Date()); device.setIpAddr(IpUtils.getIpAddr(request)); - if (device.getId() != null) { - deviceMapper.updateEntity(device); - } else { - device.setId(SnowFlakeUtil.getLongId()); - deviceMapper.addEntity(device); - deviceRepository.clearDeviceCache(deviceId); + if (device.getId() == null) { + // 通过zt-device服务创建新设备 + CreateDeviceRequest createRequest = new CreateDeviceRequest(); + createRequest.setName(device.getName()); + createRequest.setNo(device.getNo()); + createRequest.setType("IPC"); // 默认类型为IPC + createRequest.setIsActive(0); + createRequest.setScenicId(0L); + createRequest.setSort(0); + try { + DeviceV2DTO createdDevice = deviceIntegrationService.createDevice(createRequest); + device.setId(createdDevice.getId()); + } catch (Exception e) { + log.warn("创建设备失败,设备编号: {}, 错误: {}", deviceId, e.getMessage()); + } } return new VIIDBaseResp( new ResponseStatusObject(serverId, "/VIID/System/Register", "0", "注册成功", sdfTime.format(new Date())) @@ -164,9 +176,20 @@ public class ViidController { device.setOnline(1); device.setKeepaliveAt(new Date()); device.setIpAddr(IpUtils.getIpAddr(request)); - device.setId(SnowFlakeUtil.getLongId()); - deviceMapper.addEntity(device); - deviceRepository.clearDeviceCache(deviceId); + // 通过zt-device服务创建新设备 + CreateDeviceRequest createRequest = new CreateDeviceRequest(); + createRequest.setName(device.getName()); + createRequest.setNo(device.getNo()); + createRequest.setType("IPC"); // 默认类型为IPC + createRequest.setIsActive(0); + createRequest.setScenicId(0L); + createRequest.setSort(0); + try { + DeviceV2DTO createdDevice = deviceIntegrationService.createDevice(createRequest); + device.setId(createdDevice.getId()); + } catch (Exception e) { + log.warn("创建设备失败,设备编号: {}, 错误: {}", deviceId, e.getMessage()); + } } else { deviceRepository.updateOnlineStatus(device.getId(), IpUtils.getIpAddr(request), 1, new Date()); } diff --git a/src/main/java/com/ycwl/basic/controller/wvp/WvpController.java b/src/main/java/com/ycwl/basic/controller/wvp/WvpController.java index 6d772f2..24ad068 100644 --- a/src/main/java/com/ycwl/basic/controller/wvp/WvpController.java +++ b/src/main/java/com/ycwl/basic/controller/wvp/WvpController.java @@ -6,7 +6,6 @@ import com.ycwl.basic.constant.StorageConstant; import com.ycwl.basic.device.entity.common.FileObject; import com.ycwl.basic.device.operator.WvpPassiveStorageOperator; import com.ycwl.basic.model.wvp.WvpSyncReqVo; -import com.ycwl.basic.service.pc.DeviceService; import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.enums.StorageAcl; @@ -30,15 +29,12 @@ import java.util.List; @RequestMapping("/wvp/v1/") public class WvpController { - @Autowired - private DeviceService deviceService; @Autowired private ScenicService scenicService; @IgnoreLogReq @PostMapping("/scenic/{scenicId}/sync") public ApiResponse> sync(@PathVariable("scenicId") Long scenicId, @RequestBody WvpSyncReqVo reqVo) { - deviceService.updateDevices(scenicId, reqVo); return ApiResponse.success(WvpPassiveStorageOperator.getTaskListByScenicId(scenicId)); } diff --git a/src/main/java/com/ycwl/basic/device/DeviceFactory.java b/src/main/java/com/ycwl/basic/device/DeviceFactory.java index 8f106f4..125739f 100644 --- a/src/main/java/com/ycwl/basic/device/DeviceFactory.java +++ b/src/main/java/com/ycwl/basic/device/DeviceFactory.java @@ -11,10 +11,13 @@ import com.ycwl.basic.device.operator.VptPassiveStorageOperator; import com.ycwl.basic.device.operator.WvpActiveStorageOperator; import com.ycwl.basic.device.operator.WvpPassiveStorageOperator; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; +import java.time.ZoneId; +import java.util.Date; public class DeviceFactory { - public static IDeviceStorageOperator getDeviceStorageOperator(DeviceEntity device, DeviceConfigEntity config) { + public static IDeviceStorageOperator getDeviceStorageOperator(DeviceV2DTO device, DeviceConfigEntity config) { IDeviceStorageOperator operator = null; if (config == null) { return null; @@ -35,11 +38,34 @@ public class DeviceFactory { if (operator == null) { return null; } - operator.setDevice(device); + operator.setDevice(convertToEntity(device)); operator.setDeviceConfig(config); return operator; } + /** + * 将DeviceV2DTO转换为DeviceEntity + */ + private static DeviceEntity convertToEntity(DeviceV2DTO dto) { + if (dto == null) { + return null; + } + DeviceEntity entity = new DeviceEntity(); + entity.setId(dto.getId()); + entity.setName(dto.getName()); + entity.setNo(dto.getNo()); + entity.setScenicId(dto.getScenicId()); + entity.setStatus(dto.getIsActive()); + // 转换时间格式:LocalDateTime -> Date + if (dto.getCreateTime() != null) { + entity.setCreateAt(Date.from(dto.getCreateTime().atZone(ZoneId.systemDefault()).toInstant())); + } + if (dto.getUpdateTime() != null) { + entity.setUpdateAt(Date.from(dto.getUpdateTime().atZone(ZoneId.systemDefault()).toInstant())); + } + return entity; + } + public static IDeviceStatusChecker getDeviceStatusChecker(DeviceEntity device, DeviceConfigEntity config) { IDeviceStatusChecker checker = null; if (config.getOnlineCheck() <= 0) { diff --git a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java index a9d0a0e..dc1803c 100644 --- a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java +++ b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java @@ -3,7 +3,6 @@ package com.ycwl.basic.repository; import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; import com.ycwl.basic.integration.device.dto.config.DeviceConfigV2DTO; import com.ycwl.basic.utils.JacksonUtil; -import com.ycwl.basic.mapper.DeviceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.utils.SnowFlakeUtil; @@ -19,13 +18,10 @@ import java.time.ZoneId; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; @Slf4j @Component public class DeviceRepository { - @Autowired - private DeviceMapper deviceMapper; @Autowired private RedisTemplate redisTemplate; @Autowired @@ -33,8 +29,6 @@ public class DeviceRepository { public static final String DEVICE_ONLINE_CACHE_KEY = "device:online_status:%s"; public static final String DEVICE_CACHE_KEY = "device:%s"; - public static final String DEVICE_CONFIG_CACHE_KEY = "device:%s:config"; - public static final String DEVICE_CONFIG_MANAGER_CACHE_KEY = "device:%s:config:manager"; @Autowired private DeviceConfigIntegrationService deviceConfigIntegrationService; @@ -114,35 +108,6 @@ public class DeviceRepository { return entity; } - public boolean clearDeviceCache(String deviceNo) { - if (deviceNo == null) { - return true; - } - if (redisTemplate.hasKey(String.format(DEVICE_CACHE_KEY, deviceNo))) { - DeviceEntity device = getDeviceByDeviceNo(deviceNo); - if (device != null) { - redisTemplate.delete(String.format(DEVICE_CACHE_KEY, device.getNo())); - clearDeviceCache(device.getId()); - } else { - redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceNo)); - } - } - redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceNo)); - return true; - } - public boolean clearDeviceCache(Long deviceId) { - if (redisTemplate.hasKey(String.format(DEVICE_CACHE_KEY, deviceId))) { - DeviceEntity device = getDevice(deviceId); - redisTemplate.delete(String.format(DEVICE_CACHE_KEY, device.getNo())); - redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, device.getNo())); - redisTemplate.delete(String.format(DEVICE_CONFIG_MANAGER_CACHE_KEY, device.getNo())); - } - redisTemplate.delete(String.format(DEVICE_CACHE_KEY, deviceId)); - redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)); - redisTemplate.delete(String.format(DEVICE_CONFIG_MANAGER_CACHE_KEY, deviceId)); - return true; - } - public void updateOnlineStatus(String deviceNo, int online, Date keepaliveAt) { DeviceEntity device = getDeviceByDeviceNo(deviceNo); if (null == device) { @@ -177,8 +142,15 @@ public class DeviceRepository { redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, device.getNo()), JacksonUtil.toJSONString(device)); } - public List getAllDeviceByScenicId(Long scenicId) { - List deviceIdList = deviceMapper.listAllByScenicId(scenicId); - return deviceIdList.stream().map(this::getDevice).collect(Collectors.toList()); + public List getAllDeviceByScenicId(Long scenicId) { + try { + var deviceListResponse = deviceIntegrationService.getScenicActiveDevices(scenicId, 1, 1000); + if (deviceListResponse != null && deviceListResponse.getList() != null) { + return deviceListResponse.getList(); + } + } catch (Exception e) { + log.warn("获取景区设备列表失败,景区ID: {}, 错误: {}", scenicId, e.getMessage()); + } + return List.of(); } } diff --git a/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java index 44ef69a..0fc719b 100644 --- a/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java +++ b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java @@ -1,7 +1,5 @@ package com.ycwl.basic.service.custom; -import com.aliyun.credentials.Client; -import com.aliyun.credentials.models.Config; import com.aliyun.mts20140618.models.QuerySmarttagJobRequest; import com.aliyun.mts20140618.models.QuerySmarttagJobResponse; import com.aliyun.mts20140618.models.QuerySmarttagJobResponseBody; @@ -10,11 +8,11 @@ import com.aliyun.mts20140618.models.SubmitSmarttagJobResponse; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; import com.ycwl.basic.facebody.entity.AddFaceResp; -import com.ycwl.basic.mapper.DeviceMapper; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.mapper.CustomUploadTaskMapper; import com.ycwl.basic.mapper.FaceSampleMapper; import com.ycwl.basic.mapper.SourceMapper; -import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.custom.entity.CustomUploadTaskEntity; import com.ycwl.basic.model.custom.entity.FaceData; import com.ycwl.basic.model.custom.req.CreateUploadTaskReq; @@ -30,20 +28,15 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static com.ycwl.basic.constant.StorageConstant.VIID_FACE; - @Slf4j @Service public class CustomUploadTaskService { @@ -52,7 +45,7 @@ public class CustomUploadTaskService { private CustomUploadTaskMapper customUploadTaskMapper; @Autowired - private DeviceMapper deviceMapper; + private DeviceIntegrationService deviceIntegrationService; @Autowired private ScenicService scenicService; @@ -80,7 +73,7 @@ public class CustomUploadTaskService { } // 验证设备访问权限 - DeviceEntity device = validateDeviceAccess(req.getAccessKey(), req.getType()); + DeviceV2DTO device = validateDeviceAccess(req.getAccessKey(), req.getType()); long taskId = SnowFlakeUtil.getLongId(); String savePath = generateSavePath(device.getScenicId(), req.getFileName()); @@ -108,7 +101,7 @@ public class CustomUploadTaskService { public void completeUpload(String accessKey, Long taskId) { // 验证设备访问权限 - DeviceEntity device = validateDeviceAccess(accessKey, null); + DeviceV2DTO device = validateDeviceAccess(accessKey, null); CustomUploadTaskEntity task = customUploadTaskMapper.selectById(taskId); if (task == null) { @@ -146,7 +139,7 @@ public class CustomUploadTaskService { public void markTaskFailed(String accessKey, Long taskId, String errorMsg) { // 验证设备访问权限 - DeviceEntity device = validateDeviceAccess(accessKey, null); + DeviceV2DTO device = validateDeviceAccess(accessKey, null); CustomUploadTaskEntity task = customUploadTaskMapper.selectById(taskId); if (task == null) { @@ -214,17 +207,18 @@ public class CustomUploadTaskService { } } - private DeviceEntity validateDeviceAccess(String accessKey, String type) { + private DeviceV2DTO validateDeviceAccess(String accessKey, String type) { if (StringUtils.isBlank(accessKey)) { throw new RuntimeException("设备访问密钥不能为空"); } - DeviceEntity device = deviceMapper.getByDeviceNo(accessKey); - if (device == null || device.getStatus() != 1) { + // 通过zt-device服务获取设备信息 + DeviceV2DTO deviceV2DTO = deviceIntegrationService.getDeviceByNo(accessKey); + if (deviceV2DTO == null || deviceV2DTO.getIsActive() != 1) { throw new RuntimeException("无效的设备访问密钥或设备已被禁用"); } - return device; + return deviceV2DTO; } private String createAliyunMtsTask(String inputPath) { diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java index c9bd91f..9ff5358 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java @@ -2,8 +2,10 @@ package com.ycwl.basic.service.mobile.impl; import cn.hutool.core.bean.BeanUtil; import com.github.pagehelper.PageInfo; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; -import com.ycwl.basic.mapper.DeviceMapper; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.DeviceV2ListResponse; import com.ycwl.basic.mapper.ExtraDeviceMapper; import com.ycwl.basic.mapper.ScenicAccountMapper; import com.ycwl.basic.model.jwt.JwtInfo; @@ -34,8 +36,10 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT; @@ -48,7 +52,7 @@ import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT; public class AppScenicServiceImpl implements AppScenicService { @Autowired - private DeviceMapper deviceMapper; + private DeviceIntegrationService deviceIntegrationService; @Autowired private ScenicAccountMapper scenicAccountMapper; @Autowired @@ -80,7 +84,14 @@ public class AppScenicServiceImpl implements AppScenicService { @Override public ApiResponse deviceCountByScenicId(Long scenicId) { JwtInfo worker = JwtTokenUtil.getWorker(); - ScenicDeviceCountVO scenicDeviceCountVO = deviceMapper.deviceCountByScenicId(scenicId); + // 通过zt-device服务获取设备统计 + DeviceV2ListResponse deviceListResponse = deviceIntegrationService.getScenicActiveDevices(scenicId, 1, 1000); + ScenicDeviceCountVO scenicDeviceCountVO = new ScenicDeviceCountVO(); + if (deviceListResponse != null && deviceListResponse.getList() != null) { + scenicDeviceCountVO.setTotalDeviceCount(deviceListResponse.getList().size()); + } else { + scenicDeviceCountVO.setTotalDeviceCount(0); + } return ApiResponse.success(scenicDeviceCountVO); } @@ -107,7 +118,14 @@ public class AppScenicServiceImpl implements AppScenicService { scenicRespVO.setKfCodeUrl(scenic.getKfCodeUrl()); } - ScenicDeviceCountVO scenicDeviceCountVO = deviceMapper.deviceCountByScenicId(id); + // 通过zt-device服务获取设备统计 + DeviceV2ListResponse deviceListResponse = deviceIntegrationService.getScenicActiveDevices(id, 1, 1000); + ScenicDeviceCountVO scenicDeviceCountVO = new ScenicDeviceCountVO(); + if (deviceListResponse != null && deviceListResponse.getList() != null) { + scenicDeviceCountVO.setTotalDeviceCount(deviceListResponse.getList().size()); + } else { + scenicDeviceCountVO.setTotalDeviceCount(0); + } scenicRespVO.setLensNum(scenicDeviceCountVO.getTotalDeviceCount()); return ApiResponse.success(scenicRespVO); } @@ -257,7 +275,18 @@ public class AppScenicServiceImpl implements AppScenicService { @Override public ApiResponse> getDevices(Long scenicId) { - List deviceRespVOList = deviceMapper.listByScenicIdWithWVP(scenicId); + DeviceV2ListResponse deviceV2ListResponse = deviceIntegrationService.listDevices(1, 1000, null, null, null, 1, scenicId); + List deviceRespVOList = deviceV2ListResponse.getList().stream().map(device -> { + DeviceRespVO deviceRespVO = new DeviceRespVO(); + deviceRespVO.setId(device.getId()); + deviceRespVO.setName(device.getName()); + deviceRespVO.setNo(device.getNo()); + + DeviceConfigManager config = deviceRepository.getDeviceConfigManager(device.getId()); + deviceRespVO.setDeviceNo(device.getNo()); + deviceRespVO.setChannelNo(config.getString("channel_no")); + return deviceRespVO; + }).collect(Collectors.toList()); for (DeviceRespVO deviceRespVO : deviceRespVOList) { DeviceEntity onlineStatus = deviceRepository.getOnlineStatus(deviceRespVO.getId()); if (onlineStatus != null) { @@ -282,7 +311,7 @@ public class AppScenicServiceImpl implements AppScenicService { deviceRespVO.setOnline(0); continue; } - Long ts = Long.parseLong(onlineTs); + long ts = Long.parseLong(onlineTs); Date keepaliveAt = new Date(ts*1000); deviceRespVO.setUpdateAt(keepaliveAt); deviceRespVO.setKeepaliveAt(keepaliveAt); diff --git a/src/main/java/com/ycwl/basic/service/pc/DeviceService.java b/src/main/java/com/ycwl/basic/service/pc/DeviceService.java deleted file mode 100644 index e25afd4..0000000 --- a/src/main/java/com/ycwl/basic/service/pc/DeviceService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.ycwl.basic.service.pc; - -import com.github.pagehelper.PageInfo; -import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; -import com.ycwl.basic.model.pc.device.req.DeviceAddOrUpdateReq; -import com.ycwl.basic.model.pc.device.req.DeviceBatchSortRequest; -import com.ycwl.basic.model.pc.device.req.DeviceReqQuery; -import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; -import com.ycwl.basic.model.wvp.WvpSyncReqVo; -import com.ycwl.basic.utils.ApiResponse; - -import java.util.List; - -/** - * @Author:longbinbin - * @Date:2024/12/2 16:14 - * 设备管理 - */ -public interface DeviceService { - ApiResponse> pageQuery(DeviceReqQuery deviceReqQuery); - ApiResponse> list(DeviceReqQuery deviceReqQuery); - ApiResponse getById(Long id); - ApiResponse addOrUpdate(DeviceAddOrUpdateReq deviceReqQuery); - ApiResponse deleteById(Long id); - ApiResponse updateStatus(Long id); - - DeviceConfigEntity getConfig(Long id); - void saveConfig(Long configId, DeviceConfigEntity config); - - void updateDevices(Long scenicId, WvpSyncReqVo reqVo); - - ApiResponse sortDevice(Long deviceId, Long afterDeviceId); - - ApiResponse batchSort(Long scenicId, DeviceBatchSortRequest request); -} diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java deleted file mode 100644 index 9235cea..0000000 --- a/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.ycwl.basic.service.pc.impl; - -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; -import com.ycwl.basic.model.pc.device.entity.DeviceEntity; -import com.ycwl.basic.model.pc.device.req.DeviceBatchSortRequest; -import com.ycwl.basic.model.wvp.WvpSyncReqVo; -import com.ycwl.basic.repository.DeviceRepository; -import com.ycwl.basic.mapper.DeviceMapper; -import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; -import com.ycwl.basic.model.pc.device.req.DeviceAddOrUpdateReq; -import com.ycwl.basic.model.pc.device.req.DeviceReqQuery; -import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; -import com.ycwl.basic.service.pc.DeviceService; -import com.ycwl.basic.utils.ApiResponse; -import com.ycwl.basic.utils.SnowFlakeUtil; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @Author:longbinbin - * @Date:2024/12/2 16:18 - */ -@Service -public class DeviceServiceImpl implements DeviceService { - @Autowired - private DeviceMapper deviceMapper; - @Autowired - private DeviceRepository deviceRepository; - @Override - public ApiResponse> pageQuery(DeviceReqQuery deviceReqQuery) { - PageHelper.startPage(deviceReqQuery.getPageNum(), deviceReqQuery.getPageSize()); - List list = deviceMapper.list(deviceReqQuery); - for (DeviceRespVO deviceRespVO : list) { - DeviceEntity onlineStatus = deviceRepository.getOnlineStatus(deviceRespVO.getId()); - if (onlineStatus != null) { - deviceRespVO.setKeepaliveAt(onlineStatus.getKeepaliveAt()); - if (new Date().getTime() - onlineStatus.getKeepaliveAt().getTime() > 300000) { - deviceRespVO.setOnline(0); - } else { - deviceRespVO.setOnline(onlineStatus.getOnline()); - } - } else { - deviceRespVO.setOnline(0); - deviceRespVO.setKeepaliveAt(null); - } - } - PageInfo pageInfo = new PageInfo<>(list); - return ApiResponse.success(pageInfo); - } - - @Override - public ApiResponse> list(DeviceReqQuery deviceReqQuery) { - return ApiResponse.success(deviceMapper.list(deviceReqQuery)); - } - - @Override - public ApiResponse getById(Long id) { - return ApiResponse.success(deviceMapper.getById(id)); - } - - @Override - public ApiResponse addOrUpdate(DeviceAddOrUpdateReq deviceReqQuery) { - Long id = deviceReqQuery.getId(); - if (id == null) { - deviceReqQuery.setId(SnowFlakeUtil.getLongId()); - if (StringUtils.isBlank(deviceReqQuery.getNo())) { - deviceReqQuery.setNo(deviceReqQuery.getId().toString()); - } - deviceReqQuery.setStatus(0); - return ApiResponse.success(deviceMapper.add(deviceReqQuery)); - } else { - deviceRepository.clearDeviceCache(deviceReqQuery.getId()); - deviceMapper.update(deviceReqQuery); - deviceRepository.clearDeviceCache(deviceReqQuery.getId()); - return ApiResponse.success(0); - } - } - - @Override - public ApiResponse deleteById(Long id) { - return ApiResponse.success(deviceMapper.deleteById(id)); - } - - @Override - public ApiResponse updateStatus(Long id) { - deviceRepository.clearDeviceCache(id); - deviceMapper.updateStatus(id); - deviceRepository.clearDeviceCache(id); - return ApiResponse.success(1); - } - - @Override - public DeviceConfigEntity getConfig(Long id) { - DeviceConfigEntity config = deviceMapper.getConfigByDeviceId(id); - if (config == null) { - config = new DeviceConfigEntity(); - config.setId(SnowFlakeUtil.getLongId()); - config.setDeviceId(id); - deviceMapper.addConfig(config); - } - return config; - } - - @Override - public void saveConfig(Long configId, DeviceConfigEntity config) { - config.setId(configId); - deviceMapper.updateConfig(config); - deviceRepository.clearDeviceCache(config.getDeviceId()); - } - - @Override - public void updateDevices(Long scenicId, WvpSyncReqVo reqVo) { - if (reqVo == null) { - return; - } - if (reqVo.getDevices() != null && !reqVo.getDevices().isEmpty()) { - for (WvpSyncReqVo.DeviceItem deviceItem : reqVo.getDevices()) { - DeviceEntity device = deviceRepository.getDeviceByDeviceNo(deviceItem.getDeviceNo()); - if (device != null) { - device.setOnline(deviceItem.getOnline()); - device.setKeepaliveAt(deviceItem.getKeepaliveAt()); - deviceRepository.updateOnlineStatus(device.getId(), deviceItem.getIp(), 1, deviceItem.getKeepaliveAt()); - } - } - } - } - - @Override - public ApiResponse sortDevice(Long deviceId, Long afterDeviceId) { - DeviceEntity device = deviceRepository.getDevice(deviceId); - if (device == null) { - return ApiResponse.fail("设备不存在"); - } - List scenicDeviceList = deviceRepository.getAllDeviceByScenicId(device.getScenicId()); - AtomicInteger sortNum = new AtomicInteger(0); - for (DeviceEntity item : scenicDeviceList) { - item.setSort(sortNum.addAndGet(1)); - } - Optional templateOptional = scenicDeviceList.stream().filter(item -> item.getId().equals(deviceId)).findAny(); - if (templateOptional.isEmpty()) { - return ApiResponse.fail("设备不存在"); - } - Optional afterTemplateOptional = scenicDeviceList.stream().filter(item -> item.getId().equals(afterDeviceId)).findAny(); - if (afterTemplateOptional.isPresent()) { - DeviceEntity afterTemplate = afterTemplateOptional.get(); - Integer newSort = afterTemplate.getSort(); - DeviceEntity oldTemplate = templateOptional.get(); - Integer oldSort = oldTemplate.getSort(); - afterTemplate.setSort(oldSort); - oldTemplate.setSort(newSort); - } - scenicDeviceList.forEach(item -> { - deviceMapper.updateSort(item.getId(), item.getSort()); - deviceRepository.clearDeviceCache(item.getId()); - deviceRepository.clearDeviceCache(item.getNo()); - }); - return ApiResponse.success(true); - } - - @Override - public ApiResponse batchSort(Long scenicId, DeviceBatchSortRequest request) { - for (DeviceBatchSortRequest.SortItem item : request.getList()) { - deviceMapper.updateSort(item.getId(), item.getSort()); - deviceRepository.clearDeviceCache(item.getId()); - } - return ApiResponse.success(true); - } -} diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceCleaner.java b/src/main/java/com/ycwl/basic/task/VideoPieceCleaner.java index 5b249b8..243d5be 100644 --- a/src/main/java/com/ycwl/basic/task/VideoPieceCleaner.java +++ b/src/main/java/com/ycwl/basic/task/VideoPieceCleaner.java @@ -3,11 +3,16 @@ package com.ycwl.basic.task; import com.ycwl.basic.device.DeviceFactory; import com.ycwl.basic.device.operator.IDeviceStorageOperator; -import com.ycwl.basic.mapper.DeviceMapper; +import com.ycwl.basic.integration.common.manager.DeviceConfigManager; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.DeviceV2ListResponse; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; +import java.util.stream.Collectors; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.pc.device.req.DeviceReqQuery; import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; +import com.ycwl.basic.repository.DeviceRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; @@ -24,29 +29,33 @@ import java.util.List; @Profile("prod") public class VideoPieceCleaner { @Autowired - private DeviceMapper deviceMapper; + private DeviceIntegrationService deviceIntegrationService; + @Autowired + private DeviceRepository deviceRepository; @Scheduled(cron = "0 0 0 * * ?") public void clean() { log.info("开始删除视频文件"); - List deviceList = deviceMapper.listAll(); - for (DeviceEntity device : deviceList) { - DeviceConfigEntity config = deviceMapper.getConfigByDeviceId(device.getId()); - if (config == null) { + // 通过zt-device服务获取所有激活设备 + DeviceV2ListResponse deviceListResponse = deviceIntegrationService.listDevices(1, 10000, null, null, null, 1, null); + List deviceList; + if (deviceListResponse == null) { + return; + } + for (DeviceV2DTO device : deviceListResponse.getList()) { + DeviceConfigManager config = deviceRepository.getDeviceConfigManager(device.getId()); + DeviceConfigEntity dConfig = deviceRepository.getDeviceConfig(device.getId()); + Integer storeExpireDay = config.getInteger("store_expire_day"); + if (storeExpireDay == null || storeExpireDay <= 0) { continue; } - if (config.getStoreExpireDay() == null) { - continue; - } - if (config.getStoreExpireDay() <= 0) { - continue; - } - IDeviceStorageOperator storageOperator = DeviceFactory.getDeviceStorageOperator(device, config); + IDeviceStorageOperator storageOperator = DeviceFactory.getDeviceStorageOperator(device, dConfig); if (storageOperator == null) { continue; } - storageOperator.removeFilesBeforeDate(new Date(System.currentTimeMillis() - config.getStoreExpireDay() * 24 * 60 * 60 * 1000)); + storageOperator.removeFilesBeforeDate(new Date(System.currentTimeMillis() - storeExpireDay * 24 * 60 * 60 * 1000)); log.info("删除视频文件完成"); } } + } diff --git a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java index 45476d2..6259ff9 100644 --- a/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java +++ b/src/main/java/com/ycwl/basic/task/VideoPieceGetter.java @@ -16,6 +16,8 @@ import com.ycwl.basic.mapper.FaceSampleMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.model.mobile.order.IsBuyRespVO; import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity; import com.ycwl.basic.model.pc.source.entity.SourceEntity; @@ -77,6 +79,8 @@ public class VideoPieceGetter { private VideoReUploader videoReUploader; @Autowired private ScenicRepository scenicRepository; + @Autowired + private DeviceIntegrationService deviceIntegrationService; @Data public static class Task { @@ -145,7 +149,7 @@ public class VideoPieceGetter { Map pairDeviceMap = new ConcurrentHashMap<>(); if (!list.isEmpty()) { Long scenicId = list.getFirst().getScenicId(); - List allDeviceByScenicId = deviceRepository.getAllDeviceByScenicId(scenicId); + List allDeviceByScenicId = deviceRepository.getAllDeviceByScenicId(scenicId); allDeviceByScenicId.forEach(device -> { Long deviceId = device.getId(); DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(deviceId); @@ -256,16 +260,21 @@ public class VideoPieceGetter { } private boolean doCut(Long deviceId, Long faceSampleId, Date baseTime, Task task) { - DeviceEntity device = deviceRepository.getDevice(deviceId); + // 通过zt-device服务获取设备信息 + DeviceV2DTO deviceV2 = deviceIntegrationService.getDevice(deviceId); + if (deviceV2 == null) { + log.warn("设备不存在,设备ID: {}", deviceId); + return false; + } DeviceConfigManager config = deviceRepository.getDeviceConfigManager(deviceId); DeviceConfigEntity dConfig = deviceRepository.getDeviceConfig(deviceId); - SourceEntity source = sourceMapper.querySameVideo(faceSampleId, device.getId()); + SourceEntity source = sourceMapper.querySameVideo(faceSampleId, deviceV2.getId()); if (source == null || task.force) { BigDecimal cutPre = config.getBigDecimal("cut_pre", BigDecimal.valueOf(5L)); BigDecimal cutPost = config.getBigDecimal("cut_post", BigDecimal.valueOf(5L)); - IDeviceStorageOperator pieceGetter = DeviceFactory.getDeviceStorageOperator(device, dConfig); + IDeviceStorageOperator pieceGetter = DeviceFactory.getDeviceStorageOperator(deviceV2, dConfig); if (pieceGetter == null) { return false; } @@ -316,7 +325,7 @@ public class VideoPieceGetter { } sourceEntity.setVideoUrl(url); sourceEntity.setFaceSampleId(faceSampleId); - sourceEntity.setScenicId(device.getScenicId()); + sourceEntity.setScenicId(deviceV2.getScenicId()); sourceEntity.setDeviceId(deviceId); sourceEntity.setType(1); if (task.memberId != null && task.faceId != null) { @@ -324,9 +333,9 @@ public class VideoPieceGetter { videoSource.setMemberId(task.getMemberId()); videoSource.setType(1); videoSource.setFaceId(task.getFaceId()); - videoSource.setScenicId(device.getScenicId()); + videoSource.setScenicId(deviceV2.getScenicId()); videoSource.setSourceId(sourceEntity.getId()); - IsBuyRespVO isBuy = orderBiz.isBuy(task.getMemberId(), device.getScenicId(), 1, task.getFaceId()); + IsBuyRespVO isBuy = orderBiz.isBuy(task.getMemberId(), deviceV2.getScenicId(), 1, task.getFaceId()); if (isBuy.isBuy()) { // 如果用户买过 videoSource.setIsBuy(1); } else if (isBuy.isFree()) { // 全免费逻辑 @@ -352,10 +361,10 @@ public class VideoPieceGetter { int count = sourceMapper.hasRelationTo(task.getMemberId(), source.getId(), 1); if (count <= 0) { // 没有关联 - IsBuyRespVO isBuy = orderBiz.isBuy(task.getMemberId(), device.getScenicId(), 1, task.getFaceId()); + IsBuyRespVO isBuy = orderBiz.isBuy(task.getMemberId(), deviceV2.getScenicId(), 1, task.getFaceId()); MemberSourceEntity videoSource = new MemberSourceEntity(); videoSource.setId(SnowFlakeUtil.getLongId()); - videoSource.setScenicId(device.getScenicId()); + videoSource.setScenicId(deviceV2.getScenicId()); videoSource.setFaceId(task.getFaceId()); videoSource.setMemberId(task.getMemberId()); videoSource.setType(1); From bbfc61d75a7e9665ed78d199a65dc7cef12918c4 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 4 Sep 2025 10:03:45 +0800 Subject: [PATCH 18/18] =?UTF-8?q?refactor:=20=E5=88=A0=E9=99=A4=20DeviceMa?= =?UTF-8?q?pper=20=E6=8E=A5=E5=8F=A3=E5=8F=8A=E5=85=B6=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E7=9A=84=20XML=20=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除了 DeviceMapper.java 文件,包括所有与设备管理相关的接口方法 - 删除了 DeviceMapper.xml 文件,包括所有与设备管理相关的 SQL 语句 - 此次重构移除了设备管理功能模块的持久层代码 --- .../com/ycwl/basic/mapper/DeviceMapper.java | 52 ------ src/main/resources/mapper/DeviceMapper.xml | 153 ------------------ 2 files changed, 205 deletions(-) delete mode 100644 src/main/java/com/ycwl/basic/mapper/DeviceMapper.java delete mode 100644 src/main/resources/mapper/DeviceMapper.xml diff --git a/src/main/java/com/ycwl/basic/mapper/DeviceMapper.java b/src/main/java/com/ycwl/basic/mapper/DeviceMapper.java deleted file mode 100644 index 79bde93..0000000 --- a/src/main/java/com/ycwl/basic/mapper/DeviceMapper.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.ycwl.basic.mapper; - -import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO; -import com.ycwl.basic.model.pc.device.entity.DeviceConfigEntity; -import com.ycwl.basic.model.pc.device.entity.DeviceEntity; -import com.ycwl.basic.model.pc.device.req.DeviceAddOrUpdateReq; -import com.ycwl.basic.model.pc.device.req.DeviceReqQuery; -import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import java.util.Date; -import java.util.List; - -/** - * @Author:longbinbin - * @Date:2024/11/29 14:48 - * device(设备管理) - */ -@Mapper -public interface DeviceMapper { - List list(DeviceReqQuery deviceReqQuery); - List listAll(); - DeviceRespVO getById(Long id); - int add(DeviceAddOrUpdateReq deviceReqQuery); - int deleteById(Long id); - int update(DeviceAddOrUpdateReq deviceReqQuery); - int updateStatus(Long id); - - DeviceEntity getByDeviceId(Long deviceId); - List listByScenicIdWithWVP(Long scenicId); - - ScenicDeviceCountVO deviceCountByScenicId(@Param("scenicId") Long scenicId); - - DeviceConfigEntity getConfigByDeviceId(Long deviceId); - int addConfig(DeviceConfigEntity deviceConfigEntity); - int updateConfig(DeviceConfigEntity deviceConfigEntity); - - DeviceEntity getByDeviceNo(String deviceNo); - - int updateEntity(DeviceEntity device); - - int addEntity(DeviceEntity device); - - int updateOnlineStatus(Long id, String ipAddr, int online, Date keepaliveAt); - - DeviceEntity getByDeviceNo2(String deviceNo); - - List listAllByScenicId(Long scenicId); - - int updateSort(Long id, Integer sort); -} diff --git a/src/main/resources/mapper/DeviceMapper.xml b/src/main/resources/mapper/DeviceMapper.xml deleted file mode 100644 index 86602ca..0000000 --- a/src/main/resources/mapper/DeviceMapper.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - - insert into device(id, scenic_id, name, no, latitude, longitude) values (#{id}, #{scenicId}, #{name}, #{no}, #{latitude}, #{longitude}) - - - insert into device_config(id, device_id, create_time) - values (#{id}, #{deviceId}, now()) - - - insert into device(id, name, no, status, online, ip_addr, keepalive_at, create_at, update_at) - values (#{id}, #{name}, #{no}, 0, #{online}, #{ipAddr}, #{keepaliveAt}, now(), now()) - - - update device set scenic_id = #{scenicId}, name = #{name}, no = #{no}, longitude = #{longitude}, latitude = #{latitude}, update_at = now() where id = #{id} - - - update device - set status = (CASE - status - WHEN 1 THEN - 0 - WHEN 0 THEN - 1 - ELSE null - END) - where id = #{id} - - - update device_config - set viid_type = #{viidType}, - store_type = #{storeType}, - store_config_json = #{storeConfigJson}, - store_expire_day = #{storeExpireDay}, - online_check = #{onlineCheck}, - online_max_interval = #{onlineMaxInterval}, - cut_pre = #{cutPre}, - cut_post = #{cutPost}, - enable_pre_book = #{enablePreBook}, - image_free = #{imageFree}, - video_free = #{videoFree}, - pair_device = #{pairDevice}, - video_crop = #{videoCrop} - where id = #{id} - - - update device - set name = #{name}, - no = #{no}, - online = #{online}, - ip_addr = #{ipAddr}, - keepalive_at = #{keepaliveAt}, - update_at = now() - where id = #{id} - - - update device - set online = #{online}, - ip_addr = #{ipAddr}, - keepalive_at = #{keepaliveAt}, - update_at = now() - where id = #{id} - - - update device - set sort = #{sort} - where id = #{id} - - - delete from device where id = #{id} - - - - - - - - - - - - \ No newline at end of file