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/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/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/pc/DeviceV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java new file mode 100644 index 0000000..cb6de9f --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java @@ -0,0 +1,487 @@ +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()); + } + } + + + /** + * 更新设备配置 + */ + @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()); + } + } + + + /** + * 删除设备配置 + */ + @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 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..1050e8e 100644 --- a/src/main/java/com/ycwl/basic/controller/viid/ViidController.java +++ b/src/main/java/com/ycwl/basic/controller/viid/ViidController.java @@ -4,12 +4,16 @@ 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; 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; @@ -33,7 +37,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; @@ -80,15 +83,13 @@ 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; @Autowired private DeviceRepository deviceRepository; @Autowired - private ScenicRepository scenicRepository; - @Autowired private TaskFaceService taskFaceService; private final Map executors = new ConcurrentHashMap<>(); @Autowired @@ -129,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())) @@ -166,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()); } @@ -245,17 +266,15 @@ public class ViidController { if (device == null) { continue; } - DeviceConfigEntity deviceConfig = deviceRepository.getDeviceConfig(device.getId()); + DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(device.getId()); + DeviceConfigEntity deviceConfigEntity = deviceRepository.getDeviceConfig(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 +352,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,7 +371,7 @@ public class ViidController { MultipartFile _file = ImageUtils.base64ToMultipartFile(_subImage.getData()); ThreadPoolExecutor executor = getExecutor(scenicId); executor.execute(() -> { - List cropConfigs = deviceConfig._getCropConfig(); + List cropConfigs = deviceConfigEntity._getCropConfig(); for (DeviceCropConfig cropConfig : cropConfigs) { source.setId(SnowFlakeUtil.getLongId()); String filename = StorageUtil.joinPath(PHOTO_PATH, UUID.randomUUID() + "." + ext); @@ -425,7 +444,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/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/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/integration/CLAUDE.md b/src/main/java/com/ycwl/basic/integration/CLAUDE.md new file mode 100644 index 0000000..2eb1a1e --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/CLAUDE.md @@ -0,0 +1,662 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Integration Package Overview + +The integration package (`com.ycwl.basic.integration`) is responsible for external microservice integrations using Spring Cloud OpenFeign and Nacos service discovery. It provides a standardized approach for calling external services with proper error handling, configuration management, and response processing. + +## Architecture + +### Core Components + +#### Common Infrastructure (`com.ycwl.basic.integration.common`) +- **IntegrationProperties**: Centralized configuration properties for all integrations +- **FeignConfig**: Global Feign configuration with error decoder and request interceptors +- **FeignErrorDecoder**: Custom error decoder that converts Feign errors to IntegrationException +- **IntegrationException**: Standardized exception for integration failures +- **CommonResponse/PageResponse**: Standard response wrappers for external service calls +- **ConfigValueUtil**: Utility for handling configuration values +- **IntegrationFallbackService**: Universal fallback service for handling integration failures + +#### Service-Specific Integrations +Currently implemented: +- **Scenic Integration** (`com.ycwl.basic.integration.scenic`): ZT-Scenic microservice integration +- **Device Integration** (`com.ycwl.basic.integration.device`): ZT-Device microservice integration + +### Integration Pattern + +Each external service integration follows this structure: +``` +service/ +├── client/ # Feign clients for HTTP calls +├── config/ # Service-specific configuration +├── dto/ # Data transfer objects +├── service/ # Service layer with business logic +└── example/ # Usage examples +``` + +## Integration Fallback Mechanism + +### Overview +The integration package includes a comprehensive fallback mechanism that provides automatic degradation when external microservices are unavailable or return errors. This mechanism ensures system resilience and maintains service availability even when dependencies fail. + +### Core Components + +#### IntegrationFallbackService +Universal fallback service that handles failure degradation for all microservice integrations: + +**Key Features:** +- **Automatic Fallback**: Transparently handles service failures and returns cached results +- **Configurable TTL**: Service-specific cache TTL configuration (default: 7 days) +- **Cache Management**: Comprehensive cache statistics, cleanup, and monitoring +- **Service Isolation**: Per-service cache namespacing and configuration +- **Operation Types**: Supports both query operations (return cached data) and mutation operations (ignore failures with history) + +**Core Methods:** +```java +// Query operations with fallback + T executeWithFallback(String serviceName, String cacheKey, Supplier operation, Class resultClass) + +// Mutation operations with fallback +void executeWithFallback(String serviceName, String cacheKey, Runnable operation) + +// Cache management +boolean hasFallbackCache(String serviceName, String cacheKey) +void clearFallbackCache(String serviceName, String cacheKey) +void clearAllFallbackCache(String serviceName) +FallbackCacheStats getFallbackCacheStats(String serviceName) +``` + +### Fallback Strategy + +#### Query Operations (GET methods) - WITH FALLBACK +1. **Normal Execution**: Execute the primary operation +2. **Success Handling**: Store successful result in fallback cache for future degradation +3. **Failure Handling**: On failure, attempt to retrieve cached result from previous success +4. **Final Fallback**: If no cached result exists, propagate the original exception + +#### Mutation Operations (PUT/POST/DELETE methods) - NO FALLBACK +1. **Direct Execution**: Execute the primary operation directly without fallback +2. **Success Handling**: Operation completes successfully +3. **Failure Handling**: Immediately propagate the original exception to caller +4. **Rationale**: Users need to know if their create/update/delete operations truly succeeded or failed + +### Configuration + +#### Properties Structure +```yaml +integration: + fallback: + enabled: true # Global fallback switch + cachePrefix: "integration:fallback:" # Global cache prefix + defaultTtlDays: 7 # Default cache TTL in days + + # Service-specific configurations + scenic: + enabled: true # Service-specific fallback switch + ttlDays: 10 # Custom TTL for scenic service + cachePrefix: "scenic:fallback:" # Custom cache prefix (optional) + + device: + enabled: true + ttlDays: 5 # Custom TTL for device service +``` + +#### Configuration Priority +1. **Service-specific settings**: Override global defaults when specified +2. **Global defaults**: Used when service-specific settings are not provided +3. **Hardcoded defaults**: Final fallback when no configuration is available + +### Cache Key Strategy + +Cache keys follow a standardized naming convention: +``` +{cachePrefix}{serviceName}:{operationType}:{resourceId} +``` + +**Examples:** +- `integration:fallback:zt-device:device:1001` - Device info cache +- `integration:fallback:zt-device:device:config:1001` - Device config cache +- `integration:fallback:zt-scenic:scenic:2001` - Scenic info cache +- `integration:fallback:zt-scenic:scenic:flat:config:2001` - Scenic flat config cache + +## Scenic Integration (ZT-Scenic Microservice) + +### Key Components + +#### Feign Clients +- **ScenicV2Client**: Main scenic operations (CRUD, filtering, listing) +- **ScenicConfigV2Client**: Scenic configuration management +- **DefaultConfigClient**: Default configuration operations + +#### Services +- **ScenicIntegrationService**: High-level scenic operations (with automatic fallback) +- **ScenicConfigIntegrationService**: Configuration management (with automatic fallback) +- **DefaultConfigIntegrationService**: Default configuration handling + +#### Configuration +```yaml +integration: + scenic: + enabled: true + serviceName: zt-scenic + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 +``` + +### Usage Examples + +#### Basic Scenic Operations (with Automatic Fallback) +```java +@Autowired +private ScenicIntegrationService scenicService; + +// Get scenic with configuration (automatically falls back to cache on failure) +ScenicV2WithConfigDTO scenic = scenicService.getScenicWithConfig(scenicId); + +// Get scenic basic info (automatically falls back to cache on failure) +ScenicV2DTO scenicInfo = scenicService.getScenic(scenicId); + +// Get flat configuration (automatically falls back to cache on failure) +Map config = scenicService.getScenicFlatConfig(scenicId); + +// Create new scenic (direct operation, fails immediately on error) +CreateScenicRequest request = new CreateScenicRequest(); +request.setName("Test Scenic"); +ScenicV2DTO result = scenicService.createScenic(request); + +// Update scenic (direct operation, fails immediately on error) +UpdateScenicRequest updateRequest = new UpdateScenicRequest(); +updateRequest.setName("Updated Scenic"); +ScenicV2DTO updated = scenicService.updateScenic(scenicId, updateRequest); + +// Filter scenics (no fallback for complex queries) +ScenicFilterRequest filterRequest = new ScenicFilterRequest(); +// configure filters... +ScenicFilterPageResponse response = scenicService.filterScenics(filterRequest); +``` + +#### Configuration Management (with Automatic Fallback) +```java +@Autowired +private ScenicConfigIntegrationService configService; + +// Get flat configuration (automatically falls back to cache on failure) +Map config = configService.getFlatConfigs(scenicId); + +// Batch update configurations (direct operation, fails immediately on error) +BatchConfigRequest batchRequest = new BatchConfigRequest(); +// configure batch updates... +BatchUpdateResponse result = configService.batchUpdateConfigs(scenicId, batchRequest); + +// Batch flat update configurations (direct operation, fails immediately on error) +Map flatUpdates = new HashMap<>(); +flatUpdates.put("max_visitors", "5000"); +flatUpdates.put("opening_hours", "08:00-18:00"); +BatchUpdateResponse flatResult = configService.batchFlatUpdateConfigs(scenicId, flatUpdates); +``` + +#### Fallback Cache Management for Scenics +```java +@Autowired +private IntegrationFallbackService fallbackService; + +// Check fallback cache status +boolean hasScenicCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:2001"); +boolean hasConfigCache = fallbackService.hasFallbackCache("zt-scenic", "scenic:flat:configs:2001"); + +// Get cache statistics +IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-scenic"); +log.info("Scenic fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + +// Clear specific cache +fallbackService.clearFallbackCache("zt-scenic", "scenic:2001"); + +// Clear all scenic caches +fallbackService.clearAllFallbackCache("zt-scenic"); +``` + +## Device Integration (ZT-Device Microservice) + +### Key Components + +#### Feign Clients +- **DeviceV2Client**: Main device operations (CRUD, filtering, listing) +- **DeviceConfigV2Client**: Device configuration management + +#### Services +- **DeviceIntegrationService**: High-level device operations (with automatic fallback) +- **DeviceConfigIntegrationService**: Device configuration management (with automatic fallback) + +#### Configuration +```yaml +integration: + device: + enabled: true + serviceName: zt-device + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 +``` + +### Usage Examples + +#### Basic Device Operations (with Automatic Fallback) +```java +@Autowired +private DeviceIntegrationService deviceService; + +// Create IPC camera device (operation tracked for future fallback) +DeviceV2DTO ipcDevice = deviceService.createIpcDevice("前门摄像头", "CAM001", scenicId); + +// Get device with configuration (automatically falls back to cache on failure) +DeviceV2WithConfigDTO device = deviceService.getDeviceWithConfig(deviceId); + +// Get device by number (automatically falls back to cache on failure) +DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); + +// Get basic device info (automatically falls back to cache on failure) +DeviceV2DTO basicDevice = deviceService.getDevice(deviceId); + +// List scenic devices (no fallback for list operations) +DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(scenicId, 1, 10); + +// Enable/disable device (direct operation, fails immediately on error) +deviceService.enableDevice(deviceId); +deviceService.disableDevice(deviceId); + +// Update device (direct operation, fails immediately on error) +UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); +updateRequest.setName("更新后的摄像头"); +deviceService.updateDevice(deviceId, updateRequest); +``` + +#### Device Configuration Management (with Automatic Fallback) +```java +@Autowired +private DeviceConfigIntegrationService configService; + +// Get device configurations (with fallback) +List configs = configService.getDeviceConfigs(deviceId); + +// Get flat configuration (automatically falls back to cache on failure) +Map config = configService.getDeviceFlatConfig(deviceId); + +// Get flat configuration by device number (with fallback) +Map configByNo = configService.getDeviceFlatConfigByNo("CAM001"); + +// Batch update configurations (direct operation, fails immediately on error) +BatchDeviceConfigRequest batchRequest = configService.createBatchConfigBuilder() + .addConfig("brightness", "50") + .addConfig("contrast", "80") + .build(); +configService.batchUpdateDeviceConfig(deviceId, batchRequest); + +// New batch configuration API with detailed results +BatchDeviceConfigRequest request = configService.createBatchConfigBuilder() + .addVideoConfig("4K", 30, "H265") // Add video configuration + .addNetworkConfig("192.168.1.100", 554, "RTSP") // Add network configuration + .addAuthConfig("admin", "password") // Add authentication configuration + .addConfig("recording_enabled", "true") // Use default configuration + .addConfig("custom_setting", "value", "string", "Custom setting") // Custom configuration + .build(); + +BatchUpdateResponse result = configService.batchUpdateDeviceConfigWithResult(deviceId, request); +if (result.getFailed() > 0) { + // Handle partial failure + result.getProcessedItems().stream() + .filter(item -> "failed".equals(item.getStatus())) + .forEach(item -> log.warn("Config {} failed: {}", item.getConfigKey(), item.getMessage())); +} +``` + +#### Device Management Patterns +```java +// Create and configure camera in one operation (both operations direct, no fallback) +DeviceV2DTO camera = deviceService.createIpcDevice("摄像头1", "CAM001", scenicId); +// Use batch configuration to set camera parameters (direct operation) +BatchDeviceConfigRequest cameraConfig = configService.createBatchConfigBuilder() + .addConfig("ip_address", "192.168.1.100") + .addConfig("resolution", "1920x1080") + .addConfig("framerate", "30") + .addConfig("protocol", "RTSP") + .addConfig("username", "admin") + .addConfig("password", "password") + .build(); +configService.batchUpdateDeviceConfig(camera.getId(), cameraConfig); + +// Get scenic camera status +DeviceV2WithConfigListResponse camerasWithConfig = + deviceService.listDevicesWithConfig(1, 100, null, null, "IPC", 1, scenicId); + +// Batch update camera resolution (direct operations, no fallback) +for (DeviceV2WithConfigDTO device : camerasWithConfig.getList()) { + BatchDeviceConfigRequest resolutionUpdate = configService.createBatchConfigBuilder() + .addConfig("resolution", "2560x1440") + .build(); + configService.batchUpdateDeviceConfig(device.getId(), resolutionUpdate); +} + +// Monitor device configuration completeness (with automatic fallback) +for (DeviceV2DTO device : activeDevices.getList()) { + Map config = configService.getDeviceFlatConfig(device.getId()); + boolean hasIpConfig = config.containsKey("ip_address"); + // log configuration status... +} +``` + +#### Fallback Cache Management for Devices +```java +@Autowired +private IntegrationFallbackService fallbackService; + +// Check fallback cache status +boolean hasDeviceCache = fallbackService.hasFallbackCache("zt-device", "device:1001"); +boolean hasConfigCache = fallbackService.hasFallbackCache("zt-device", "device:flat:config:1001"); + +// Get cache statistics +IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats("zt-device"); +log.info("Device fallback cache: {} items, TTL: {} days", stats.getTotalCacheCount(), stats.getFallbackTtlDays()); + +// Clear specific cache +fallbackService.clearFallbackCache("zt-device", "device:1001"); + +// Clear all device caches +fallbackService.clearAllFallbackCache("zt-device"); +``` + +### Enhanced Batch Configuration API + +Device Integration now supports an enhanced batch configuration API that provides detailed processing results and supports default configuration rules. + +#### Default Configuration Rules +The system uses `device_id = 0` configurations as default templates: +- **Configurations with defaults**: System enforces default `config_type` and `description`, only `configValue` is updated +- **Configurations without defaults**: Allows custom `config_type` and `description` + +#### Usage Examples +```java +// 1. Using Builder Pattern +BatchDeviceConfigRequest request = configService.createBatchConfigBuilder() + .addVideoConfig("1920x1080", 30, "H264") // Video settings + .addNetworkConfig("192.168.1.100", 554, "RTSP") // Network settings + .addAuthConfig("admin", "password123") // Authentication + .addConfig("recording_enabled", "true") // Use default config rule + .addConfig("custom_path", "/data", "string", "Storage path") // Custom config + .build(); + +BatchUpdateResponse result = configService.batchUpdateDeviceConfigWithResult(deviceId, request); + +// 2. Processing Results +log.info("Batch update: {} success, {} failed", result.getSuccess(), result.getFailed()); + +for (ProcessedConfigItem item : result.getProcessedItems()) { + if ("success".equals(item.getStatus())) { + log.info("✅ {} updated (action: {}, hasDefault: {})", + item.getConfigKey(), item.getAction(), item.getHasDefault()); + } else { + log.warn("❌ {} failed: {}", item.getConfigKey(), item.getMessage()); + } +} + +// 3. Error Handling +if (result.getFailed() > 0) { + result.getErrors().forEach(error -> log.warn("Error: {}", error)); +} +``` + +#### Response Format +- **200 OK**: All configurations updated successfully +- **202 Accepted**: Partial success (some configurations failed) +- **400 Bad Request**: All configurations failed + +Each processed item includes: +- `status`: "success" or "failed" +- `action`: "create" or "update" +- `hasDefault`: Whether default configuration rules were applied +- `finalType`/`finalDescription`: Actually used type and description + +### Device Types +- **IPC**: IP Camera devices for video monitoring +- **CUSTOM**: Custom device types for sensors, controllers, etc. + +### Common Configuration Keys +- `ip_address`: Device IP address +- `resolution`: Video resolution (e.g., "1920x1080", "3840x2160") +- `framerate`: Video frame rate (integer) +- `protocol`: Communication protocol (e.g., "RTSP", "HTTP") +- `username`: Authentication username +- `password`: Authentication password +- `brightness`: Display brightness (0-100) +- `contrast`: Display contrast (0-100) +- `quality`: Video quality ("low", "medium", "high") + +## Adding New Service Integrations + +### 1. Create Package Structure +``` +com.ycwl.basic.integration.{service-name}/ +├── client/ +├── config/ +├── dto/ +├── service/ +└── example/ +``` + +### 2. Add Configuration Properties +Update `IntegrationProperties` to include new service configuration: +```java +@Data +public static class NewServiceConfig { + private boolean enabled = true; + private String serviceName = "service-name"; + private int connectTimeout = 5000; + private int readTimeout = 10000; + // other configs... +} +``` + +### 3. Create Feign Client +```java +@FeignClient(name = "service-name", contextId = "service-context", path = "/api/path") +public interface NewServiceClient { + @GetMapping("/endpoint") + CommonResponse getData(@PathVariable Long id); + // other endpoints... +} +``` + +### 4. Implement Service Layer +```java +@Service +@RequiredArgsConstructor +public class NewServiceIntegrationService { + private final NewServiceClient client; + + public ResponseDTO getData(Long id) { + CommonResponse response = client.getData(id); + return handleResponse(response, "Failed to get data"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "service-name"); + } + return response.getData(); + } +} +``` + +## Error Handling + +### IntegrationException +All integration failures are wrapped in `IntegrationException`: +```java +public class IntegrationException extends RuntimeException { + private final Integer code; + private final String serviceName; + // constructors and getters... +} +``` + +### FeignErrorDecoder +Automatically converts Feign errors to IntegrationException: +- Parses CommonResponse error format +- Extracts service name from method key +- Provides meaningful error messages + +## Configuration Management + +### Properties Structure +```yaml +integration: + # Fallback configuration + fallback: + enabled: true # Global fallback switch + cachePrefix: "integration:fallback:" # Global cache prefix + defaultTtlDays: 7 # Default cache TTL in days + + # Service-specific fallback configurations + scenic: + enabled: true # Enable fallback for scenic service + ttlDays: 10 # Custom TTL for scenic (longer due to stable data) + # cachePrefix: "scenic:fallback:" # Optional custom prefix + + device: + enabled: true # Enable fallback for device service + ttlDays: 5 # Custom TTL for device (shorter due to dynamic data) + # cachePrefix: "device:fallback:" # Optional custom prefix + + # Service configurations + scenic: + enabled: true + serviceName: zt-scenic + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 + device: + enabled: true + serviceName: zt-device + connectTimeout: 5000 + readTimeout: 10000 + retryEnabled: false + maxRetries: 3 +``` + +### Configuration Refresh +Uses `@RefreshScope` to support dynamic configuration updates without restart. + +## Testing Integration Services + +### Unit Testing +Test service layers by mocking Feign clients: +```java +@ExtendWith(MockitoExtension.class) +class ScenicIntegrationServiceTest { + @Mock + private ScenicV2Client scenicV2Client; + + @InjectMocks + private ScenicIntegrationService scenicService; + + @Test + void testGetScenic() { + // Mock response + CommonResponse response = new CommonResponse<>(); + response.setSuccess(true); + response.setData(new ScenicV2DTO()); + + when(scenicV2Client.getScenic(1L)).thenReturn(response); + + // Test + ScenicV2DTO result = scenicService.getScenic(1L); + assertNotNull(result); + } +} +``` + +### Integration Testing +Use `@SpringBootTest` with test profiles and mock external services. + +## Common Development Tasks + +### Running Integration Tests +```bash +# Run specific integration test class +mvn test -Dtest=ScenicIntegrationServiceTest + +# Run all integration tests +mvn test -Dtest="com.ycwl.basic.integration.*Test" + +# Run device integration tests +mvn test -Dtest=DeviceIntegrationServiceTest +mvn test -Dtest="com.ycwl.basic.integration.device.*Test" +``` + +### Adding New DTOs +1. Create DTO classes in the appropriate `dto` package +2. Follow existing patterns with proper Jackson annotations +3. Use `@JsonProperty` for field mapping when needed + +### Debugging Integration Issues +1. Enable Feign client logging in `application-dev.yml`: +```yaml +logging: + level: + com.ycwl.basic.integration: DEBUG +``` + +2. Check Nacos service discovery in development +3. Verify service configurations and timeouts +4. Review FeignErrorDecoder logs for detailed error information + +## Best Practices + +### Response Handling +- Always use the `handleResponse` pattern for consistent error handling +- Provide meaningful error messages for business context +- Include service name in IntegrationException for debugging + +### Fallback Strategy Best Practices +- **Query operations only**: Only apply fallback to GET operations (data retrieval) +- **No fallback for mutations**: CREATE/UPDATE/DELETE operations should fail immediately to provide clear feedback +- **Cache key design**: Use clear, hierarchical cache keys for easy identification and cleanup +- **TTL management**: Set appropriate TTL based on data freshness requirements +- **Monitoring**: Regularly check fallback cache statistics and cleanup stale entries +- **Service isolation**: Configure service-specific fallback settings based on reliability needs +- **Error transparency**: Let users know exactly when their modification operations fail + +### Configuration +- Use environment-specific configuration profiles +- Set appropriate timeouts based on service characteristics +- Enable retries only when safe (idempotent operations) +- Configure fallback TTL based on business requirements: + - **Critical data**: Longer TTL (7-14 days) for essential operations + - **Volatile data**: Shorter TTL (1-3 days) for frequently changing data + - **Configuration data**: Medium TTL (5-7 days) for semi-static settings + +### DTOs and Mapping +- Keep DTOs simple and focused on data transfer +- Use proper Jackson annotations for field mapping +- Separate request/response DTOs for clarity + +### Service Layer Design +- Keep integration services focused on external service calls +- Handle response transformation and error conversion +- Avoid business logic in integration services +- Use fallback service for resilience without changing business logic + +### Cache Management Strategies +- **Proactive cleanup**: Monitor cache size and clean up periodically +- **Service-specific management**: Separate cache management per service +- **Debugging support**: Use cache statistics for troubleshooting +- **Configuration validation**: Ensure fallback configuration matches service requirements \ 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..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,11 +11,21 @@ import org.springframework.stereotype.Component; @ConfigurationProperties(prefix = "integration") public class IntegrationProperties { + /** + * 降级服务配置 + */ + private FallbackConfig fallback = new FallbackConfig(); + /** * 景区服务配置 */ private ScenicConfig scenic = new ScenicConfig(); + /** + * 设备服务配置 + */ + private DeviceConfig device = new DeviceConfig(); + @Data public static class ScenicConfig { /** @@ -40,4 +50,71 @@ public class IntegrationProperties { private boolean retryEnabled = false; private int maxRetries = 3; } + + @Data + public static class DeviceConfig { + /** + * 是否启用设备服务集成 + */ + private boolean enabled = true; + + /** + * 服务名称 + */ + private String serviceName = "zt-device"; + + /** + * 超时配置(毫秒) + */ + private int connectTimeout = 5000; + private int readTimeout = 10000; + + /** + * 重试配置 + */ + private boolean retryEnabled = false; + private int maxRetries = 3; + } + + @Data + public static class FallbackConfig { + /** + * 是否启用降级功能 + */ + private boolean enabled = true; + + /** + * 降级缓存前缀 + */ + private String cachePrefix = "integration:fallback:"; + + /** + * 默认降级缓存TTL(天) + */ + private long defaultTtlDays = 1; + + /** + * 服务特定的降级配置 + */ + private ServiceFallbackConfig scenic = new ServiceFallbackConfig(); + private ServiceFallbackConfig device = new ServiceFallbackConfig(); + } + + @Data + public static class ServiceFallbackConfig { + /** + * 是否启用此服务的降级功能 + */ + private boolean enabled = true; + + /** + * 服务特定的缓存TTL(天),如果为0则使用默认TTL + */ + private long ttlDays = 0; + + /** + * 服务特定的缓存前缀,如果为空则使用默认前缀 + */ + private String cachePrefix; + } } \ No newline at end of file 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..c4bee39 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/manager/ConfigManager.java @@ -0,0 +1,406 @@ +package com.ycwl.basic.integration.common.manager; + +import com.ycwl.basic.utils.JacksonUtil; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 通用配置管理器基类 + * 提供配置查找、类型转换等通用功能 + * + * @param 配置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 Long getLong(String key) { + T config = findConfigByKey(key); + if (config == null) { + return null; + } + Object value = getConfigValue(config); + if (value == null) return null; + if (value instanceof Long) return (Long) value; + if (value instanceof Number) return ((Number) value).longValue(); + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 获取长整型配置值,如果不存在则返回默认值 + */ + public Long getLong(String key, Long defaultValue) { + Long value = getLong(key); + return value != null ? value : defaultValue; + } + + /** + * 获取布尔型配置值 + */ + public Boolean getBoolean(String key) { + T config = findConfigByKey(key); + if (config == null) { + return null; + } + Object value = getConfigValue(config); + if (value == null) return null; + if (value instanceof Boolean) return (Boolean) value; + if (value instanceof String) { + String str = ((String) value).toLowerCase().trim(); + return "true".equals(str) || "1".equals(str) || "yes".equals(str); + } + if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } + return null; + } + + /** + * 获取布尔型配置值,如果不存在则返回默认值 + */ + public Boolean getBoolean(String key, Boolean defaultValue) { + Boolean value = getBoolean(key); + return value != null ? value : defaultValue; + } + + /** + * 获取浮点型配置值 + */ + public Float getFloat(String key) { + T config = findConfigByKey(key); + if (config == null) { + return null; + } + Object value = getConfigValue(config); + if (value == null) return null; + if (value instanceof Float) return (Float) value; + if (value instanceof Number) return ((Number) value).floatValue(); + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 获取浮点型配置值,如果不存在则返回默认值 + */ + public Float getFloat(String key, Float defaultValue) { + Float value = getFloat(key); + return value != null ? value : defaultValue; + } + + /** + * 获取BigDecimal配置值 + */ + public BigDecimal getBigDecimal(String key) { + T config = findConfigByKey(key); + if (config == null) { + return null; + } + Object value = getConfigValue(config); + if (value == null) return null; + if (value instanceof BigDecimal) return (BigDecimal) value; + if (value instanceof Number) return new BigDecimal(value.toString()); + if (value instanceof String) { + try { + return new BigDecimal((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 获取BigDecimal配置值,如果不存在则返回默认值 + */ + public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) { + BigDecimal value = getBigDecimal(key); + return value != null ? value : defaultValue; + } + + /** + * 获取枚举配置值 + */ + public > E 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); + } + + /** + * 获取原始对象配置值 + */ + 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/DeviceConfigManager.java b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java new file mode 100644 index 0000000..30f9661 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/manager/DeviceConfigManager.java @@ -0,0 +1,51 @@ +package com.ycwl.basic.integration.common.manager; + +import com.ycwl.basic.integration.device.dto.config.DeviceConfigV2DTO; + +import java.util.List; + +/** + * 设备配置管理器 + * 基于通用ConfigManager实现设备配置的管理功能 + */ +public class DeviceConfigManager extends ConfigManager { + + 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; + } + +} \ 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 70% 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..5722220 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,50 +41,20 @@ public class ScenicConfigManager { * @param configMap 配置Map */ public ScenicConfigManager(Map configMap) { + super(null); // 使用Map构造时,父类configs为null this.configMap = configMap != null ? new HashMap<>(configMap) : new HashMap<>(); } - - /** - * 获取字符串值 - * - * @param key 配置键 - * @return 字符串值,如果键不存在或转换失败返回null - */ - public String getString(String key) { - return ConfigValueUtil.getStringValue(configMap, key); + + @Override + protected String getConfigKey(ScenicConfigV2DTO config) { + return config != null ? config.getConfigKey() : null; + } + + @Override + protected Object getConfigValue(ScenicConfigV2DTO config) { + return config != null ? config.getConfigValue() : null; } - /** - * 获取字符串值,如果为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); - } /** * 获取长整数值 @@ -193,85 +165,7 @@ public class ScenicConfigManager { 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); - } /** * 检查配置键是否存在 @@ -307,6 +201,7 @@ public class ScenicConfigManager { * * @return 配置项数量 */ + @Override public int size() { return configMap.size(); } @@ -325,7 +220,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/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 new file mode 100644 index 0000000..86afa90 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/client/DeviceConfigV2Client.java @@ -0,0 +1,74 @@ +package com.ycwl.basic.integration.device.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.device.dto.config.*; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@FeignClient(name = "zt-device", contextId = "device-config-v2", path = "/api/device/config/v2") +public interface DeviceConfigV2Client { + + /** + * 获取设备所有配置 + */ + @GetMapping("/{deviceId}") + CommonResponse> 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); + +} \ 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..79dfcf1 --- /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.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@ConfigurationProperties(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/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/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/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/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..26425da --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/CreateDeviceRequest.java @@ -0,0 +1,13 @@ +package com.ycwl.basic.integration.device.dto.device; + +import lombok.Data; + +@Data +public class CreateDeviceRequest { + private String name; + private String no; + private String type; + private Integer isActive; + private Long scenicId; + private Integer sort; +} \ 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..9ac6b8c --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/DeviceV2DTO.java @@ -0,0 +1,39 @@ +package com.ycwl.basic.integration.device.dto.device; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class DeviceV2DTO { + @JsonProperty("id") + private Long id; + + @JsonProperty("name") + private String name; + + @JsonProperty("no") + private String no; + + @JsonProperty("type") + private String type; // IPC, CUSTOM + + @JsonProperty("isActive") + private Integer isActive; + + @JsonProperty("scenicId") + private Long scenicId; + + @JsonProperty("sort") + private Integer sort; + + @JsonProperty("createTime") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @JsonProperty("updateTime") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; +} \ 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..a3a39b5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/dto/device/UpdateDeviceRequest.java @@ -0,0 +1,13 @@ +package com.ycwl.basic.integration.device.dto.device; + +import lombok.Data; + +@Data +public class UpdateDeviceRequest { + private String name; + private String no; + private String type; + private Integer isActive; + private Long scenicId; + private Integer sort; +} \ 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..388c93d --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationExample.java @@ -0,0 +1,184 @@ +package com.ycwl.basic.integration.device.example; + +import com.ycwl.basic.integration.device.dto.device.*; +import com.ycwl.basic.integration.device.dto.config.*; +import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * Device Integration 使用示例 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DeviceIntegrationExample { + + private final DeviceIntegrationService deviceService; + private final DeviceConfigIntegrationService deviceConfigService; + + /** + * 基本设备操作 + */ + public void basicDeviceOperations() { + log.info("=== 基本设备操作 ==="); + + // 创建IPC摄像头设备(默认排序) + DeviceV2DTO ipcDevice = deviceService.createIpcDevice( + "前门摄像头", "CAM001", 1001L); + log.info("创建IPC设备: {}, 排序值: {}", ipcDevice.getName(), ipcDevice.getSort()); + + // 根据ID获取设备信息 + DeviceV2DTO device = deviceService.getDevice(ipcDevice.getId()); + log.info("获取设备信息: {}, 排序值: {}", device.getName(), device.getSort()); + + // 根据设备编号获取设备信息 + DeviceV2DTO deviceByNo = deviceService.getDeviceByNo("CAM001"); + log.info("根据编号获取设备: {}", deviceByNo.getName()); + + // 获取设备详细信息(含配置) + DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(ipcDevice.getId()); + log.info("获取设备配置: {}", deviceWithConfig.getName()); + + // 分页查询景区设备列表 + DeviceV2ListResponse deviceList = deviceService.getScenicIpcDevices(1001L, 1, 10); + log.info("景区设备列表: 总数={}", deviceList.getTotal()); + + // 启用设备 + deviceService.enableDevice(ipcDevice.getId()); + log.info("设备已启用"); + } + + /** + * 设备排序功能演示 + */ + public void deviceSortingOperations() { + log.info("=== 设备排序功能演示 ==="); + + Long scenicId = 1001L; + + // 创建带排序的设备 + DeviceV2DTO camera1 = deviceService.createIpcDeviceWithSort( + "大门摄像头", "CAM_GATE", scenicId, 10); + log.info("创建摄像头1: {}, 排序: {}", camera1.getName(), camera1.getSort()); + + DeviceV2DTO camera2 = deviceService.createIpcDeviceWithSort( + "后门摄像头", "CAM_BACK", scenicId, 20); + log.info("创建摄像头2: {}, 排序: {}", camera2.getName(), camera2.getSort()); + + DeviceV2DTO sensor1 = deviceService.createCustomDeviceWithSort( + "温度传感器", "TEMP_01", scenicId, 5); + log.info("创建传感器: {}, 排序: {}", sensor1.getName(), sensor1.getSort()); + + // 更新设备排序 + deviceService.updateDeviceSort(camera1.getId(), 1); + log.info("更新摄像头1排序为1(置顶)"); + + // 获取排序后的设备列表 + DeviceV2ListResponse sortedList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId); + log.info("排序后的设备列表:"); + for (DeviceV2DTO device : sortedList.getList()) { + log.info(" - {}: 排序={}, 类型={}", device.getName(), device.getSort(), device.getType()); + } + + // 批量调整排序演示 + log.info("--- 批量调整排序演示 ---"); + deviceService.updateDeviceSort(sensor1.getId(), 15); // 传感器排到中间 + deviceService.updateDeviceSort(camera2.getId(), 30); // 后门摄像头排到最后 + log.info("批量排序调整完成"); + } + + /** + * 设备配置管理 + */ + public void deviceConfigurationOperations() { + log.info("=== 设备配置管理 ==="); + + Long deviceId = 1L; + + // 获取设备所有配置 + List configs = deviceConfigService.getDeviceConfigs(deviceId); + log.info("设备配置数量: {}", configs.size()); + + // 获取扁平化配置 + Map flatConfig = deviceConfigService.getDeviceFlatConfig(deviceId); + log.info("扁平化配置项数: {}", flatConfig.size()); + + // 使用批量配置API + BatchDeviceConfigRequest builderRequest = deviceConfigService.createBatchConfigBuilder() + .build(); + + BatchUpdateResponse result = deviceConfigService.batchUpdateDeviceConfig(deviceId, builderRequest); + log.info("批量配置更新结果: 成功={}, 失败={}", result.getSuccess(), result.getFailed()); + } + + /** + * 排序最佳实践演示 + */ + public void sortingBestPractices() { + log.info("=== 排序最佳实践演示 ==="); + + Long scenicId = 1001L; + + // 推荐使用10的倍数作为排序值 + DeviceV2DTO device1 = deviceService.createIpcDeviceWithSort( + "重要摄像头", "CAM_IMPORTANT", scenicId, 10); + + DeviceV2DTO device2 = deviceService.createIpcDeviceWithSort( + "普通摄像头", "CAM_NORMAL", scenicId, 20); + + DeviceV2DTO device3 = deviceService.createIpcDeviceWithSort( + "备用摄像头", "CAM_BACKUP", scenicId, 30); + + log.info("使用10的倍数创建设备排序: 10, 20, 30"); + + // 在中间插入新设备 + DeviceV2DTO insertDevice = deviceService.createIpcDeviceWithSort( + "中间摄像头", "CAM_MIDDLE", scenicId, 25); + log.info("在20和30之间插入设备,排序值: 25"); + + // 置顶操作 + deviceService.updateDeviceSort(device2.getId(), 1); + log.info("将普通摄像头置顶(排序值: 1)"); + + // 查看最终排序结果 + DeviceV2ListResponse finalList = deviceService.listDevices(1, 10, null, null, null, 1, scenicId); + log.info("最终排序结果:"); + for (DeviceV2DTO device : finalList.getList()) { + log.info(" - {}: 排序={}", device.getName(), device.getSort()); + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + try { + basicDeviceOperations(); + deviceSortingOperations(); + sortingBestPractices(); + deviceConfigurationOperations(); + log.info("=== 所有示例执行完成 ==="); + } catch (Exception e) { + log.error("示例执行过程中发生错误", e); + } + } + + /** + * 运行基础示例(简化版) + */ + public void runBasicExamples() { + try { + basicDeviceOperations(); + deviceConfigurationOperations(); + log.info("=== 基础示例执行完成 ==="); + } catch (Exception e) { + log.error("示例执行过程中发生错误", e); + } + } +} \ 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 new file mode 100644 index 0000000..4429f12 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/example/DeviceIntegrationFallbackExample.java @@ -0,0 +1,124 @@ +package com.ycwl.basic.integration.device.example; + +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.device.service.DeviceIntegrationService; +import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; +import com.ycwl.basic.integration.device.dto.device.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 设备集成示例(包含降级机制) + * 演示设备集成和失败降级策略的使用 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DeviceIntegrationFallbackExample { + + private final DeviceIntegrationService deviceService; + private final DeviceConfigIntegrationService configService; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; + + /** + * 演示设备信息获取的降级机制 + */ + public void deviceInfoFallbackExample() { + log.info("=== 设备信息获取降级示例 ==="); + + Long deviceId = 1001L; + String deviceNo = "CAM001"; + + try { + // 获取设备信息 - 自动降级 + DeviceV2DTO device = deviceService.getDevice(deviceId); + log.info("获取设备成功: {}", device.getName()); + + // 根据设备号获取设备 - 自动降级 + DeviceV2DTO deviceByNo = deviceService.getDeviceByNo(deviceNo); + log.info("根据设备号获取设备成功: {}", deviceByNo.getName()); + + // 获取设备配置 - 自动降级 + DeviceV2WithConfigDTO deviceWithConfig = deviceService.getDeviceWithConfig(deviceId); + log.info("获取设备配置成功,配置数量: {}", deviceWithConfig.getConfig().size()); + + } catch (Exception e) { + log.error("所有降级策略失败", e); + } + } + + /** + * 演示设备操作(无降级机制) + */ + public void deviceOperationExample() { + log.info("=== 设备操作示例 ==="); + + Long deviceId = 1001L; + + try { + // 设备更新操作 - 直接操作,失败时抛出异常 + UpdateDeviceRequest updateRequest = new UpdateDeviceRequest(); + updateRequest.setName("更新后的摄像头"); + deviceService.updateDevice(deviceId, updateRequest); + log.info("设备更新操作完成"); + + // 设备排序更新 - 直接操作,失败时抛出异常 + deviceService.updateDeviceSort(deviceId, 5); + log.info("设备排序更新完成"); + + } catch (Exception e) { + log.error("设备操作失败", e); + } + } + + /** + * 演示降级缓存管理 + */ + public void fallbackCacheManagementExample() { + log.info("=== 降级缓存管理示例 ==="); + + String deviceCacheKey = "device:1001"; + String configCacheKey = "device:flat:config:1001"; + + // 检查降级缓存状态 + boolean hasDeviceCache = fallbackService.hasFallbackCache(SERVICE_NAME, deviceCacheKey); + boolean hasConfigCache = fallbackService.hasFallbackCache(SERVICE_NAME, configCacheKey); + + log.info("设备降级缓存存在: {}", hasDeviceCache); + log.info("配置降级缓存存在: {}", hasConfigCache); + + // 清理特定的降级缓存 + if (hasDeviceCache) { + fallbackService.clearFallbackCache(SERVICE_NAME, deviceCacheKey); + log.info("已清理设备降级缓存"); + } + + // 获取降级缓存统计信息 + IntegrationFallbackService.FallbackCacheStats stats = fallbackService.getFallbackCacheStats(SERVICE_NAME); + log.info("设备服务降级缓存统计: {}", stats); + + // 批量清理所有设备降级缓存 + if (stats.getTotalCacheCount() > 10) { + fallbackService.clearAllFallbackCache(SERVICE_NAME); + log.info("已批量清理所有设备降级缓存"); + } + } + + /** + * 运行所有示例 + */ + public void runAllExamples() { + log.info("开始运行设备集成示例..."); + + deviceInfoFallbackExample(); + deviceOperationExample(); + fallbackCacheManagementExample(); + + log.info("设备集成示例运行完成"); + } +} \ 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..850c35b --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceConfigIntegrationService.java @@ -0,0 +1,167 @@ +package com.ycwl.basic.integration.device.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.device.client.DeviceConfigV2Client; +import com.ycwl.basic.integration.device.dto.config.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceConfigIntegrationService { + + private final DeviceConfigV2Client deviceConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; + + public List 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); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:flat:config:" + deviceId, + () -> { + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfig(deviceId); + return handleResponse(response, "获取设备扁平化配置失败"); + }, + Map.class + ); + } + + public Map getDeviceFlatConfigByNo(String deviceNo) { + log.info("根据设备编号获取扁平化配置, deviceNo: {}", deviceNo); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:flat:config:no:" + deviceNo, + () -> { + CommonResponse> response = deviceConfigV2Client.getDeviceFlatConfigByNo(deviceNo); + return handleResponse(response, "根据设备编号获取扁平化配置失败"); + }, + Map.class + ); + } + + 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 BatchUpdateResponse batchUpdateDeviceConfig(Long deviceId, BatchDeviceConfigRequest request) { + log.info("批量更新设备配置, deviceId: {}, configs count: {}", deviceId, request.getConfigs().size()); + CommonResponse response = deviceConfigV2Client.batchUpdateDeviceConfig(deviceId, request); + return handleResponse(response, "批量更新设备配置失败"); + } + + + + /** + * 获取设备特定配置值 + */ + public String getDeviceConfigValue(Long deviceId, String configKey) { + DeviceConfigV2DTO config = getDeviceConfigByKey(deviceId, configKey); + return config != null ? config.getConfigValue() : null; + } + + /** + * 创建批量配置请求构建器 + */ + public BatchDeviceConfigRequestBuilder createBatchConfigBuilder() { + return new BatchDeviceConfigRequestBuilder(); + } + + /** + * 批量配置请求构建器 + */ + public static class BatchDeviceConfigRequestBuilder { + private final BatchDeviceConfigRequest request = new BatchDeviceConfigRequest(); + + public BatchDeviceConfigRequestBuilder() { + request.setConfigs(new java.util.ArrayList<>()); + } + + /** + * 添加配置项(有默认配置的情况,只需提供key和value) + */ + public BatchDeviceConfigRequestBuilder addConfig(String configKey, String configValue) { + BatchDeviceConfigRequest.BatchDeviceConfigItem item = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item.setConfigKey(configKey); + item.setConfigValue(configValue); + request.getConfigs().add(item); + return this; + } + + /** + * 添加配置项(完整参数,用于自定义配置) + */ + public BatchDeviceConfigRequestBuilder addConfig(String configKey, String configValue, + String configType, String description) { + BatchDeviceConfigRequest.BatchDeviceConfigItem item = new BatchDeviceConfigRequest.BatchDeviceConfigItem(); + item.setConfigKey(configKey); + item.setConfigValue(configValue); + item.setConfigType(configType); + item.setDescription(description); + request.getConfigs().add(item); + return this; + } + + /** + * 构建请求对象 + */ + public BatchDeviceConfigRequest build() { + return request; + } + } + + private T 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..3dfe39e --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/device/service/DeviceIntegrationService.java @@ -0,0 +1,218 @@ +package com.ycwl.basic.integration.device.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.common.service.IntegrationFallbackService; +import com.ycwl.basic.integration.device.client.DeviceV2Client; +import com.ycwl.basic.integration.device.dto.device.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DeviceIntegrationService { + + private final DeviceV2Client deviceV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-device"; + + public DeviceV2DTO getDevice(Long deviceId) { + log.info("获取设备信息, deviceId: {}", deviceId); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.getDevice(deviceId); + return handleResponse(response, "获取设备信息失败"); + }, + DeviceV2DTO.class + ); + } + + public DeviceV2DTO getDeviceByNo(String deviceNo) { + log.info("根据设备编号获取设备信息, deviceNo: {}", deviceNo); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:no:" + deviceNo, + () -> { + CommonResponse response = deviceV2Client.getDeviceByNo(deviceNo); + return handleResponse(response, "根据设备编号获取设备信息失败"); + }, + DeviceV2DTO.class + ); + } + + public DeviceV2WithConfigDTO getDeviceWithConfig(Long deviceId) { + log.info("获取设备配置信息, deviceId: {}", deviceId); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:config:" + deviceId, + () -> { + CommonResponse response = deviceV2Client.getDeviceWithConfig(deviceId); + return handleResponse(response, "获取设备配置信息失败"); + }, + DeviceV2WithConfigDTO.class + ); + } + + public DeviceV2WithConfigDTO getDeviceWithConfigByNo(String deviceNo) { + log.info("根据设备编号获取设备配置信息, deviceNo: {}", deviceNo); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "device:config:no:" + deviceNo, + () -> { + CommonResponse response = deviceV2Client.getDeviceByNoWithConfig(deviceNo); + return handleResponse(response, "根据设备编号获取设备配置信息失败"); + }, + DeviceV2WithConfigDTO.class + ); + } + + public DeviceV2DTO createDevice(CreateDeviceRequest request) { + log.info("创建设备, name: {}, no: {}, type: {}", request.getName(), request.getNo(), request.getType()); + CommonResponse 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); + request.setSort(0); + return createDevice(request); + } + + /** + * 创建IPC摄像头设备(带排序) + */ + public DeviceV2DTO createIpcDeviceWithSort(String name, String deviceNo, Long scenicId, Integer sort) { + CreateDeviceRequest request = new CreateDeviceRequest(); + request.setName(name); + request.setNo(deviceNo); + request.setType("IPC"); + request.setIsActive(1); + request.setScenicId(scenicId); + request.setSort(sort != null ? sort : 0); + return createDevice(request); + } + + /** + * 创建自定义设备 + */ + public DeviceV2DTO createCustomDevice(String name, String deviceNo, Long scenicId) { + CreateDeviceRequest request = new CreateDeviceRequest(); + request.setName(name); + request.setNo(deviceNo); + request.setType("CUSTOM"); + request.setIsActive(1); + request.setScenicId(scenicId); + request.setSort(0); + return createDevice(request); + } + + /** + * 创建自定义设备(带排序) + */ + public DeviceV2DTO createCustomDeviceWithSort(String name, String deviceNo, Long scenicId, Integer sort) { + CreateDeviceRequest request = new CreateDeviceRequest(); + request.setName(name); + request.setNo(deviceNo); + request.setType("CUSTOM"); + request.setIsActive(1); + request.setScenicId(scenicId); + request.setSort(sort != null ? sort : 0); + return createDevice(request); + } + + /** + * 启用设备 + */ + public void enableDevice(Long deviceId) { + UpdateDeviceRequest request = new UpdateDeviceRequest(); + request.setIsActive(1); + updateDevice(deviceId, request); + } + + /** + * 禁用设备 + */ + public void disableDevice(Long deviceId) { + UpdateDeviceRequest request = new UpdateDeviceRequest(); + request.setIsActive(0); + updateDevice(deviceId, request); + } + + /** + * 更新设备排序 + */ + public void updateDeviceSort(Long deviceId, Integer sort) { + log.info("更新设备排序, deviceId: {}, sort: {}", deviceId, sort); + UpdateDeviceRequest request = new UpdateDeviceRequest(); + request.setSort(sort); + updateDevice(deviceId, request); + } + + /** + * 获取景区的IPC设备列表 + */ + public DeviceV2ListResponse getScenicIpcDevices(Long scenicId, Integer page, Integer pageSize) { + return listDevices(page, pageSize, null, null, "IPC", 1, scenicId); + } + + /** + * 获取景区的所有激活设备 + */ + public DeviceV2ListResponse getScenicActiveDevices(Long scenicId, Integer page, Integer pageSize) { + return listDevices(page, pageSize, null, null, null, 1, scenicId); + } + + private T 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/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 3017e24..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,43 +18,61 @@ import java.util.Map; public class ScenicConfigIntegrationService { private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public List listConfigs(Long scenicId) { + log.info("获取景区配置列表, scenicId: {}", scenicId); CommonResponse> response = scenicConfigV2Client.listConfigs(scenicId); return handleResponse(response, "获取景区配置列表失败"); } public ScenicConfigV2DTO getConfigByKey(Long scenicId, String configKey) { + log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey); CommonResponse response = scenicConfigV2Client.getConfigByKey(scenicId, configKey); return handleResponse(response, "根据键获取景区配置失败"); } public Map getFlatConfigs(Long scenicId) { - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + log.info("获取景区扁平化配置, scenicId: {}", scenicId); + 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) { + log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey()); CommonResponse response = scenicConfigV2Client.createConfig(scenicId, request); return handleResponse(response, "创建景区配置失败"); } public ScenicConfigV2DTO updateConfig(Long scenicId, String id, UpdateConfigRequest request) { + log.info("更新景区配置, scenicId: {}, id: {}", scenicId, id); CommonResponse response = scenicConfigV2Client.updateConfig(scenicId, id, request); return handleResponse(response, "更新景区配置失败"); } public void deleteConfig(Long scenicId, String id) { + log.info("删除景区配置, scenicId: {}, id: {}", scenicId, id); CommonResponse response = scenicConfigV2Client.deleteConfig(scenicId, id); handleResponse(response, "删除景区配置失败"); } public BatchUpdateResponse batchUpdateConfigs(Long scenicId, BatchConfigRequest request) { + log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size()); CommonResponse response = scenicConfigV2Client.batchUpdateConfigs(scenicId, request); return handleResponse(response, "批量更新景区配置失败"); } public BatchUpdateResponse batchFlatUpdateConfigs(Long scenicId, Map configs) { + log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size()); CommonResponse response = scenicConfigV2Client.batchFlatUpdateConfigs(scenicId, configs); return handleResponse(response, "扁平化批量更新景区配置失败"); } 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 e17bda2..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,48 +26,81 @@ public class ScenicIntegrationService { private final ScenicV2Client scenicV2Client; private final ScenicConfigV2Client scenicConfigV2Client; + private final IntegrationFallbackService fallbackService; + + private static final String SERVICE_NAME = "zt-scenic"; public ScenicV2DTO getScenic(Long scenicId) { - CommonResponse response = scenicV2Client.getScenic(scenicId); - return handleResponse(response, "获取景区信息失败"); + log.info("获取景区信息, scenicId: {}", scenicId); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenic(scenicId); + return handleResponse(response, "获取景区信息失败"); + }, + ScenicV2DTO.class + ); } public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) { - CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); - return handleResponse(response, "获取景区配置信息失败"); + log.info("获取景区配置信息, scenicId: {}", scenicId); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:config:" + scenicId, + () -> { + CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); + return handleResponse(response, "获取景区配置信息失败"); + }, + ScenicV2WithConfigDTO.class + ); } public Map getScenicFlatConfig(Long scenicId) { - CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); - return handleResponse(response, "获取景区扁平化配置失败"); + log.info("获取景区扁平化配置, scenicId: {}", scenicId); + return fallbackService.executeWithFallback( + SERVICE_NAME, + "scenic:flat:config:" + scenicId, + () -> { + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + }, + Map.class + ); } public ScenicV2DTO createScenic(CreateScenicRequest request) { + log.info("创建景区, name: {}", request.getName()); CommonResponse response = scenicV2Client.createScenic(request); return handleResponse(response, "创建景区失败"); } public ScenicV2DTO updateScenic(Long scenicId, UpdateScenicRequest request) { + log.info("更新景区信息, scenicId: {}", scenicId); CommonResponse response = scenicV2Client.updateScenic(scenicId, request); return handleResponse(response, "更新景区信息失败"); } public void deleteScenic(Long scenicId) { + log.info("删除景区, scenicId: {}", scenicId); CommonResponse response = scenicV2Client.deleteScenic(scenicId); handleResponse(response, "删除景区失败"); } public ScenicFilterPageResponse filterScenics(ScenicFilterRequest request) { + log.info("筛选景区, filters: {}", request.getFilters().size()); CommonResponse response = scenicV2Client.filterScenics(request); return handleResponse(response, "筛选景区失败"); } public ScenicV2ListResponse listScenics(Integer page, Integer pageSize, Integer status, String name) { + log.info("分页查询景区列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); CommonResponse response = scenicV2Client.listScenics(page, pageSize, status, name); return handleResponse(response, "分页查询景区列表失败"); } public ScenicV2WithConfigListResponse listScenicsWithConfig(Integer page, Integer pageSize, Integer status, String name) { + log.info("分页查询景区带配置列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); CommonResponse response = scenicV2Client.listScenicsWithConfig(page, pageSize, status, name); return handleResponse(response, "分页查询景区带配置列表失败"); } 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/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/pricing/service/impl/VoucherPrintServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java index 72ad396..17f1f54 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 210af67..dc1803c 100644 --- a/src/main/java/com/ycwl/basic/repository/DeviceRepository.java +++ b/src/main/java/com/ycwl/basic/repository/DeviceRepository.java @@ -1,98 +1,111 @@ 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; +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; +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"; + @Autowired + private DeviceConfigIntegrationService deviceConfigIntegrationService; + + /** + * 将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); - } - DeviceEntity device = deviceMapper.getByDeviceId(deviceId); - if (null != device) { - redisTemplate.opsForValue().set(String.format(DEVICE_CACHE_KEY, deviceId), JacksonUtil.toJSONString(device), 3, TimeUnit.DAYS); - } + log.debug("获取设备信息, deviceId: {}", deviceId); + DeviceV2DTO deviceDto = deviceIntegrationService.getDevice(deviceId); + DeviceEntity device = convertToEntity(deviceDto); 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); - } - 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); - } + log.debug("根据设备编号获取设备信息, deviceNo: {}", deviceNo); + DeviceV2DTO deviceDto = deviceIntegrationService.getDeviceByNo(deviceNo); + DeviceEntity device = convertToEntity(deviceDto); return device; } 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); - } - redisTemplate.opsForValue().set(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId), JacksonUtil.toJSONString(deviceConfig), 3, TimeUnit.DAYS); - return deviceConfig; + return null; } - public boolean clearDeviceCache(String deviceNo) { - if (deviceNo == null) { - return true; + /** + * 获取设备配置管理器 + * + * @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; } - 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_CACHE_KEY, deviceId)); - redisTemplate.delete(String.format(DEVICE_CONFIG_CACHE_KEY, deviceId)); - return true; + + /** + * 将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 void updateOnlineStatus(String deviceNo, int online, Date keepaliveAt) { @@ -129,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/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 1075949..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 { @@ -49,51 +48,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 +347,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; } } 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 1a42ea2..0000000 --- a/src/main/java/com/ycwl/basic/service/pc/impl/DeviceServiceImpl.java +++ /dev/null @@ -1,176 +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()); - deviceRepository.clearDeviceCache(item.getNo2()); - }); - 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/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/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; 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/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 97bfc56..6259ff9 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; @@ -15,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; @@ -76,6 +79,8 @@ public class VideoPieceGetter { private VideoReUploader videoReUploader; @Autowired private ScenicRepository scenicRepository; + @Autowired + private DeviceIntegrationService deviceIntegrationService; @Data public static class Task { @@ -144,12 +149,13 @@ 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(); - 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); } }); } @@ -254,24 +260,21 @@ public class VideoPieceGetter { } private boolean doCut(Long deviceId, Long faceSampleId, Date baseTime, Task task) { - DeviceEntity device = deviceRepository.getDevice(deviceId); - DeviceConfigEntity config = deviceRepository.getDeviceConfig(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 = 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(deviceV2, dConfig); if (pieceGetter == null) { return false; } @@ -317,12 +320,12 @@ 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); - sourceEntity.setScenicId(device.getScenicId()); + sourceEntity.setScenicId(deviceV2.getScenicId()); sourceEntity.setDeviceId(deviceId); sourceEntity.setType(1); if (task.memberId != null && task.faceId != null) { @@ -330,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()) { // 全免费逻辑 @@ -346,8 +349,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()); @@ -358,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); 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