From bf6b4cda94c61fbe993036e752ef0b13f186d2af Mon Sep 17 00:00:00 2001 From: 16337 <1633794139@qq.com> Date: Fri, 10 Apr 2026 11:28:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=BA=A7=E7=AE=97=E6=B3=95=E5=8F=82=E6=95=B0=EF=BC=88device=5F?= =?UTF-8?q?algo=5Fparams=20=E8=A1=A8=20+=20API=20+=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=8C=89=E8=AE=BE=E5=A4=87=E5=90=88=E5=B9=B6?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aiot/bean/AiAlgorithmDeviceParams.java | 27 ++++++++++++ .../controller/AiAlgorithmController.java | 34 ++++++++++++++- .../dao/AiAlgorithmDeviceParamsMapper.java | 24 +++++++++++ .../vmp/aiot/service/IAiAlgorithmService.java | 5 +++ .../service/impl/AiAlgorithmServiceImpl.java | 35 +++++++++++++++ .../impl/AiRedisConfigServiceImpl.java | 43 +++++++++++++++---- 数据库/aiot/迁移-添加device_algo_params表.sql | 12 ++++++ 7 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/genersoft/iot/vmp/aiot/bean/AiAlgorithmDeviceParams.java create mode 100644 src/main/java/com/genersoft/iot/vmp/aiot/dao/AiAlgorithmDeviceParamsMapper.java create mode 100644 数据库/aiot/迁移-添加device_algo_params表.sql diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/bean/AiAlgorithmDeviceParams.java b/src/main/java/com/genersoft/iot/vmp/aiot/bean/AiAlgorithmDeviceParams.java new file mode 100644 index 000000000..0229ddf85 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/aiot/bean/AiAlgorithmDeviceParams.java @@ -0,0 +1,27 @@ +package com.genersoft.iot.vmp.aiot.bean; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "设备级算法参数") +public class AiAlgorithmDeviceParams { + + @Schema(description = "数据库自增ID") + private Integer id; + + @Schema(description = "边缘设备ID") + private String deviceId; + + @Schema(description = "算法编码") + private String algoCode; + + @Schema(description = "设备级参数JSON") + private String params; + + @Schema(description = "创建时间") + private String createTime; + + @Schema(description = "更新时间") + private String updateTime; +} diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiAlgorithmController.java b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiAlgorithmController.java index d982728bd..f8ef6c592 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiAlgorithmController.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/controller/AiAlgorithmController.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.aiot.controller; import com.genersoft.iot.vmp.aiot.bean.AiAlgorithm; +import com.genersoft.iot.vmp.aiot.bean.AiAlgorithmDeviceParams; import com.genersoft.iot.vmp.aiot.service.IAiAlgorithmService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -22,8 +23,23 @@ public class AiAlgorithmController { @Operation(summary = "查询算法列表") @GetMapping("/list") - public List queryList() { - return algorithmService.queryAll(); + public List queryList(@RequestParam(required = false) String deviceId) { + List algorithms = algorithmService.queryAll(); + if (deviceId != null && !deviceId.isEmpty()) { + // 查设备级参数,合并到每个算法的 globalParams 字段返回 + List deviceParams = algorithmService.queryDeviceParams(deviceId); + Map deviceParamsMap = new java.util.HashMap<>(); + for (AiAlgorithmDeviceParams dp : deviceParams) { + deviceParamsMap.put(dp.getAlgoCode(), dp.getParams()); + } + for (AiAlgorithm algo : algorithms) { + String dp = deviceParamsMap.get(algo.getAlgoCode()); + if (dp != null && !dp.isEmpty()) { + algo.setGlobalParams(dp); + } + } + } + return algorithms; } @Operation(summary = "启用/禁用算法") @@ -44,4 +60,18 @@ public class AiAlgorithmController { String globalParams = body.get("globalParams"); algorithmService.saveGlobalParams(algoCode, globalParams); } + + @Operation(summary = "查询设备级算法参数") + @GetMapping("/device-params/{deviceId}") + public List queryDeviceParams(@PathVariable String deviceId) { + return algorithmService.queryDeviceParams(deviceId); + } + + @Operation(summary = "保存设备级算法参数") + @PostMapping("/device-params/{deviceId}/{algoCode}") + public void saveDeviceParams(@PathVariable String deviceId, @PathVariable String algoCode, + @RequestBody Map body) { + String params = body.get("params"); + algorithmService.saveDeviceParams(deviceId, algoCode, params); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/dao/AiAlgorithmDeviceParamsMapper.java b/src/main/java/com/genersoft/iot/vmp/aiot/dao/AiAlgorithmDeviceParamsMapper.java new file mode 100644 index 000000000..a92dedbaa --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/aiot/dao/AiAlgorithmDeviceParamsMapper.java @@ -0,0 +1,24 @@ +package com.genersoft.iot.vmp.aiot.dao; + +import com.genersoft.iot.vmp.aiot.bean.AiAlgorithmDeviceParams; +import org.apache.ibatis.annotations.*; + +import java.util.List; + +@Mapper +public interface AiAlgorithmDeviceParamsMapper { + + @Select("SELECT * FROM wvp_ai_algorithm_device_params WHERE device_id=#{deviceId} ORDER BY id") + List queryByDeviceId(@Param("deviceId") String deviceId); + + @Select("SELECT * FROM wvp_ai_algorithm_device_params WHERE device_id=#{deviceId} AND algo_code=#{algoCode}") + AiAlgorithmDeviceParams queryByDeviceAndAlgo(@Param("deviceId") String deviceId, @Param("algoCode") String algoCode); + + @Insert("INSERT INTO wvp_ai_algorithm_device_params (device_id, algo_code, params, create_time, update_time) " + + "VALUES (#{deviceId}, #{algoCode}, #{params}, #{createTime}, #{updateTime}) " + + "ON DUPLICATE KEY UPDATE params=#{params}, update_time=#{updateTime}") + int upsert(AiAlgorithmDeviceParams record); + + @Delete("DELETE FROM wvp_ai_algorithm_device_params WHERE device_id=#{deviceId} AND algo_code=#{algoCode}") + int deleteByDeviceAndAlgo(@Param("deviceId") String deviceId, @Param("algoCode") String algoCode); +} diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/service/IAiAlgorithmService.java b/src/main/java/com/genersoft/iot/vmp/aiot/service/IAiAlgorithmService.java index f4890e17f..9e11e94e7 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/service/IAiAlgorithmService.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/service/IAiAlgorithmService.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.aiot.service; import com.genersoft.iot.vmp.aiot.bean.AiAlgorithm; +import com.genersoft.iot.vmp.aiot.bean.AiAlgorithmDeviceParams; import java.util.List; @@ -15,4 +16,8 @@ public interface IAiAlgorithmService { void syncFromEdge(); void saveGlobalParams(String algoCode, String globalParams); + + List queryDeviceParams(String deviceId); + + void saveDeviceParams(String deviceId, String algoCode, String params); } diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiAlgorithmServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiAlgorithmServiceImpl.java index 5eca031bb..224c33fb4 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiAlgorithmServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiAlgorithmServiceImpl.java @@ -1,11 +1,14 @@ package com.genersoft.iot.vmp.aiot.service.impl; import com.genersoft.iot.vmp.aiot.bean.AiAlgorithm; +import com.genersoft.iot.vmp.aiot.bean.AiAlgorithmDeviceParams; import com.genersoft.iot.vmp.aiot.config.AiServiceConfig; import com.genersoft.iot.vmp.aiot.dao.AiAlgorithmMapper; +import com.genersoft.iot.vmp.aiot.dao.AiAlgorithmDeviceParamsMapper; import com.genersoft.iot.vmp.aiot.service.IAiAlgorithmService; import com.genersoft.iot.vmp.aiot.service.IAiConfigLogService; import com.genersoft.iot.vmp.aiot.service.IAiConfigService; +import com.genersoft.iot.vmp.aiot.service.IAiRedisConfigService; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +28,9 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService { @Autowired private AiAlgorithmMapper algorithmMapper; + @Autowired + private AiAlgorithmDeviceParamsMapper deviceParamsMapper; + @Autowired private AiServiceConfig aiServiceConfig; @@ -34,6 +40,9 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService { @Autowired private IAiConfigService configService; + @Autowired + private IAiRedisConfigService redisConfigService; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** @@ -176,4 +185,30 @@ public class AiAlgorithmServiceImpl implements IAiAlgorithmService { log.warn("[AI算法] 全局参数推送失败(参数已保存): {}", e.getMessage()); } } + + @Override + public List queryDeviceParams(String deviceId) { + return deviceParamsMapper.queryByDeviceId(deviceId); + } + + @Override + public void saveDeviceParams(String deviceId, String algoCode, String params) { + String now = LocalDateTime.now().format(FORMATTER); + AiAlgorithmDeviceParams record = new AiAlgorithmDeviceParams(); + record.setDeviceId(deviceId); + record.setAlgoCode(algoCode); + record.setParams(params); + record.setCreateTime(now); + record.setUpdateTime(now); + deviceParamsMapper.upsert(record); + log.info("[AI算法] 保存设备级参数: deviceId={}, algoCode={}, params={}", deviceId, algoCode, params); + + // 只推送该设备的配置 + try { + redisConfigService.writeDeviceAggregatedConfig(deviceId, "UPDATE"); + log.info("[AI算法] 设备级参数变更已推送到设备: {}", deviceId); + } catch (Exception e) { + log.warn("[AI算法] 设备级参数推送失败(参数已保存): {}", e.getMessage()); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiRedisConfigServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiRedisConfigServiceImpl.java index 3ee5d3e8a..67d7e0756 100644 --- a/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiRedisConfigServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/aiot/service/impl/AiRedisConfigServiceImpl.java @@ -4,9 +4,11 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.genersoft.iot.vmp.aiot.bean.AiAlgorithm; import com.genersoft.iot.vmp.aiot.bean.AiAlgoTemplate; +import com.genersoft.iot.vmp.aiot.bean.AiAlgorithmDeviceParams; import com.genersoft.iot.vmp.aiot.bean.AiRoi; import com.genersoft.iot.vmp.aiot.bean.AiRoiAlgoBind; import com.genersoft.iot.vmp.aiot.dao.AiAlgorithmMapper; +import com.genersoft.iot.vmp.aiot.dao.AiAlgorithmDeviceParamsMapper; import com.genersoft.iot.vmp.aiot.dao.AiAlgoTemplateMapper; import com.genersoft.iot.vmp.aiot.dao.AiRoiAlgoBindMapper; import com.genersoft.iot.vmp.aiot.dao.AiRoiMapper; @@ -46,6 +48,9 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { @Autowired private AiAlgorithmMapper algorithmMapper; + @Autowired + private AiAlgorithmDeviceParamsMapper deviceParamsMapper; + @Autowired private AiAlgoTemplateMapper templateMapper; @@ -492,6 +497,13 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { algoMap.put(algo.getAlgoCode(), algo); } + // 查询该设备的设备级参数,构建 algo_code -> params 索引 + List deviceParamsList = deviceParamsMapper.queryByDeviceId(deviceId); + Map deviceParamsMap = new LinkedHashMap<>(); + for (AiAlgorithmDeviceParams dp : deviceParamsList) { + deviceParamsMap.put(dp.getAlgoCode(), dp.getParams()); + } + for (String cameraId : cameraIds) { // 摄像头信息 Map cameraMap = new LinkedHashMap<>(); @@ -607,12 +619,13 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { bindMap.put("enabled", bind.getEnabled() != null && bind.getEnabled() == 1); bindMap.put("priority", bind.getPriority() != null ? bind.getPriority() : 0); - // params: 三级合并 param_schema.default < global_params < bind.params + // params: 四级合并 param_schema.default < global_params < device_params < bind.params AiAlgorithm algo = algoMap.get(bind.getAlgoCode()); String algoParamSchema = algo != null ? algo.getParamSchema() : null; String algoGlobalParams = algo != null ? algo.getGlobalParams() : null; + String algoDeviceParams = deviceParamsMap.get(bind.getAlgoCode()); String effectiveParams = resolveEffectiveParams(bind); - String mergedParams = mergeParams(algoParamSchema, algoGlobalParams, effectiveParams); + String mergedParams = mergeParams(algoParamSchema, algoGlobalParams, algoDeviceParams, effectiveParams); try { bindMap.put("params", objectMapper.readValue(mergedParams, Object.class)); } catch (Exception e) { @@ -629,13 +642,16 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { flatConfig.put("binds", binds); // 顶层新增 global_params: {algo_code: {param_key: value, ...}, ...} + // 优先使用设备级参数,没有则使用全局参数 Map globalParamsMap = new LinkedHashMap<>(); for (AiAlgorithm algo : allAlgorithms) { - if (algo.getGlobalParams() != null && !algo.getGlobalParams().isEmpty()) { + String deviceParams = deviceParamsMap.get(algo.getAlgoCode()); + String sourceParams = deviceParams != null && !deviceParams.isEmpty() ? deviceParams : algo.getGlobalParams(); + if (sourceParams != null && !sourceParams.isEmpty()) { try { - globalParamsMap.put(algo.getAlgoCode(), objectMapper.readValue(algo.getGlobalParams(), Object.class)); + globalParamsMap.put(algo.getAlgoCode(), objectMapper.readValue(sourceParams, Object.class)); } catch (Exception e) { - log.warn("[AiRedis] 解析算法 {} 的 globalParams 失败: {}", algo.getAlgoCode(), e.getMessage()); + log.warn("[AiRedis] 解析算法 {} 的参数失败: {}", algo.getAlgoCode(), e.getMessage()); } } } @@ -647,14 +663,15 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { } /** - * 三级参数合并:param_schema 的 default 值 < global_params < bind 的 params + * 四级参数合并:param_schema 的 default 值 < global_params < device_params < bind 的 params * @param paramSchema 算法参数模板JSON(含 default 字段) * @param globalParams 用户自定义的全局默认参数JSON + * @param deviceParams 设备级别的参数JSON * @param bindParams 绑定级别的参数JSON(最高优先级) * @return 合并后的参数JSON字符串 */ @SuppressWarnings("unchecked") - private String mergeParams(String paramSchema, String globalParams, String bindParams) { + private String mergeParams(String paramSchema, String globalParams, String deviceParams, String bindParams) { Map merged = new LinkedHashMap<>(); // 1. 从 paramSchema 提取 default 值(最低优先级) @@ -684,7 +701,17 @@ public class AiRedisConfigServiceImpl implements IAiRedisConfigService { } } - // 3. 用 bindParams 覆盖(最高优先级) + // 3. 用 deviceParams 覆盖 + if (deviceParams != null && !deviceParams.isEmpty()) { + try { + Map device = JSON.parseObject(deviceParams, LinkedHashMap.class); + merged.putAll(device); + } catch (Exception e) { + log.debug("[AiRedis] 解析 deviceParams 失败: {}", e.getMessage()); + } + } + + // 4. 用 bindParams 覆盖(最高优先级) if (bindParams != null && !bindParams.isEmpty()) { try { Map bind = JSON.parseObject(bindParams, LinkedHashMap.class); diff --git a/数据库/aiot/迁移-添加device_algo_params表.sql b/数据库/aiot/迁移-添加device_algo_params表.sql new file mode 100644 index 000000000..d6b527921 --- /dev/null +++ b/数据库/aiot/迁移-添加device_algo_params表.sql @@ -0,0 +1,12 @@ +-- 设备级算法参数表 +-- 允许为每个边缘设备单独配置算法参数,覆盖全局默认值 +CREATE TABLE IF NOT EXISTS wvp_ai_algorithm_device_params ( + id INT AUTO_INCREMENT PRIMARY KEY, + device_id VARCHAR(50) NOT NULL COMMENT '边缘设备ID', + algo_code VARCHAR(100) NOT NULL COMMENT '算法编码', + params TEXT NULL COMMENT '设备级参数JSON', + create_time VARCHAR(50) NULL, + update_time VARCHAR(50) NULL, + UNIQUE KEY uk_device_algo (device_id, algo_code), + INDEX idx_device_id (device_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备级算法参数';