| | |
| | | package com.mes.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType; |
| | | import com.github.xingshuangs.iot.protocol.s7.service.S7PLC; |
| | | import com.mes.config.PlcAddressMappingConfig; |
| | | import com.mes.entity.PlcAddressMapping; |
| | | import com.mes.mapper.PlcAddressMappingMapper; |
| | | import com.mes.s7.enhanced.EnhancedS7Serializer; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.catalina.core.ApplicationContext; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import com.mes.entity.PlcAddress; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * PLC地址映射服务 |
| | | * 管理PLC地址映射配置,支持从数据库和配置文件加载 |
| | | * |
| | | * @author zhoush |
| | | * @date 2025/10/29 |
| | | * PLC地址映射服务接口 |
| | | * 定义PLC地址映射配置管理的核心方法 |
| | | * |
| | | * @author huang |
| | | * @date 2025/10/30 |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class PlcAddressMappingService { |
| | | |
| | | @Autowired |
| | | private PlcAddressMappingMapper plcAddressMappingMapper; |
| | | |
| | | @Autowired |
| | | private PlcAddressMappingConfig plcAddressMappingConfig; |
| | | |
| | | // JSON解析器 |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | public interface PlcAddressService { |
| | | |
| | | /** |
| | | * 获取包含地址映射的项目配置 |
| | | * 合并数据库和配置文件中的映射信息 |
| | | * 获取包含地址映射的项目配置(合并数据库与配置文件) |
| | | * @param projectId 项目ID |
| | | * @return 合并后的项目配置 |
| | | */ |
| | | public PlcAddressMappingConfig.ProjectPlcConfig getProjectConfigWithMapping(String projectId) { |
| | | try { |
| | | // 1. 获取数据库中的配置 |
| | | PlcAddressMapping mapping = getMappingByProjectId(projectId); |
| | | PlcAddressMappingConfig.ProjectPlcConfig projectConfig = null; |
| | | |
| | | if (mapping != null) { |
| | | // 从数据库实体转换为项目配置对象 |
| | | projectConfig = convertToProjectConfig(mapping); |
| | | } else { |
| | | // 数据库中没有配置,尝试从配置文件获取 |
| | | if (fileConfig != null && fileConfig.getProjects().containsKey(projectId)) { |
| | | projectConfig = fileConfig.getProjects().get(projectId); |
| | | } else { |
| | | // 创建默认配置 |
| | | projectConfig = new PlcAddressMappingConfig.ProjectPlcConfig(); |
| | | projectConfig.setDbArea(fileConfig != null ? fileConfig.getDefaultDbArea() : "DB1"); |
| | | projectConfig.setBeginIndex(fileConfig != null ? fileConfig.getDefaultBeginIndex() : 0); |
| | | projectConfig.setAddressMapping(new HashMap<>()); |
| | | } |
| | | } |
| | | |
| | | // 2. 如果配置文件中有额外的映射信息,进行合并 |
| | | if (fileConfig != null && fileConfig.getProjects() != null && fileConfig.getProjects().containsKey(projectId)) { |
| | | PlcAddressMappingConfig.ProjectPlcConfig fileProjectConfig = fileConfig.getProjects().get(projectId); |
| | | |
| | | // 合并地址映射 |
| | | if (fileProjectConfig.getAddressMapping() != null) { |
| | | if (projectConfig.getAddressMapping() == null) { |
| | | projectConfig.setAddressMapping(new HashMap<>()); |
| | | } |
| | | // 文件配置作为基础,数据库配置作为覆盖(优先级更高) |
| | | Map<String, Integer> mergedMapping = new HashMap<>(fileProjectConfig.getAddressMapping()); |
| | | if (projectConfig.getAddressMapping() != null) { |
| | | mergedMapping.putAll(projectConfig.getAddressMapping()); |
| | | } |
| | | projectConfig.setAddressMapping(mergedMapping); |
| | | } |
| | | |
| | | // 如果项目配置中缺少某些属性,从文件配置中补充 |
| | | if (projectConfig.getDbArea() == null || projectConfig.getDbArea().trim().isEmpty()) { |
| | | projectConfig.setDbArea(fileProjectConfig.getDbArea()); |
| | | } |
| | | if (projectConfig.getPlcIp() == null || projectConfig.getPlcIp().trim().isEmpty()) { |
| | | projectConfig.setPlcIp(fileProjectConfig.getPlcIp()); |
| | | } |
| | | if (projectConfig.getPlcType() == null || projectConfig.getPlcType().trim().isEmpty()) { |
| | | projectConfig.setPlcType(fileProjectConfig.getPlcType()); |
| | | } |
| | | } |
| | | |
| | | return projectConfig; |
| | | } catch (Exception e) { |
| | | log.error("获取项目配置失败,项目ID: {}", projectId, e); |
| | | // 返回默认配置 |
| | | PlcAddressMappingConfig.ProjectPlcConfig defaultConfig = new PlcAddressMappingConfig.ProjectPlcConfig(); |
| | | defaultConfig.setDbArea(fileConfig != null ? fileConfig.getDefaultDbArea() : "DB1"); |
| | | defaultConfig.setBeginIndex(fileConfig != null ? fileConfig.getDefaultBeginIndex() : 0); |
| | | defaultConfig.setAddressMapping(new HashMap<>()); |
| | | return defaultConfig; |
| | | } |
| | | } |
| | | PlcAddress getProjectConfigWithMapping(String projectId); |
| | | |
| | | /** |
| | | * 从数据库获取项目配置 |
| | | * @param projectId 项目ID |
| | | * @return 项目配置(PlcAddress实体),不存在则返回null |
| | | */ |
| | | public PlcAddressMappingConfig.ProjectPlcConfig getProjectConfig(String projectId) { |
| | | try { |
| | | // 优先从数据库获取配置 |
| | | PlcAddressMapping mapping = plcAddressMappingMapper.selectOne( |
| | | new LambdaQueryWrapper<PlcAddressMapping>() |
| | | .eq(PlcAddressMapping::getProjectId, projectId) |
| | | ); |
| | | |
| | | if (mapping != null) { |
| | | return convertToProjectConfig(mapping); |
| | | } |
| | | |
| | | // 如果数据库中没有,则从配置文件获取 |
| | | if (fileConfig != null && fileConfig.getProjects() != null && fileConfig.getProjects().containsKey(projectId)) { |
| | | return fileConfig.getProjects().get(projectId); |
| | | } |
| | | |
| | | // 如果都没有,返回null |
| | | return null; |
| | | } catch (Exception e) { |
| | | log.error("获取项目配置失败,项目ID: {}", projectId, e); |
| | | return null; |
| | | } |
| | | } |
| | | PlcAddress getProjectConfig(String projectId); |
| | | |
| | | /** |
| | | * 获取所有映射配置 |
| | | * 合并数据库和配置文件中的映射信息 |
| | | * 获取所有映射配置(合并后的addressMapping会写回实体的JSON字段) |
| | | * @return 所有映射配置列表 |
| | | */ |
| | | public List<PlcAddressMapping> getAllMappings() { |
| | | try { |
| | | List<PlcAddressMapping> mappings = plcAddressMappingMapper.selectList(null); |
| | | |
| | | // 处理每个映射配置 |
| | | for (PlcAddressMapping mapping : mappings) { |
| | | // 获取项目标识 |
| | | String projectId = mapping.getProjectId(); |
| | | |
| | | // 获取合并后的项目配置(包含数据库和配置文件的映射信息) |
| | | PlcAddressMappingConfig.ProjectPlcConfig projectConfig = getProjectConfigWithMapping(projectId); |
| | | |
| | | // 将合并后的映射信息转换为JSON字符串 |
| | | if (projectConfig.getAddressMapping() != null && !projectConfig.getAddressMapping().isEmpty()) { |
| | | try { |
| | | String mergedMappingJson = objectMapper.writeValueAsString(projectConfig.getAddressMapping()); |
| | | mapping.setAddressMapping(mergedMappingJson); |
| | | // addressMapping属性已经映射到数据库的address_mapping_json字段 |
| | | } catch (Exception e) { |
| | | log.warn("转换合并后的映射信息失败,项目ID: {}", projectId, e); |
| | | // 保持原有的addressMapping值 |
| | | } |
| | | } |
| | | } |
| | | |
| | | return mappings; |
| | | } catch (Exception e) { |
| | | log.error("获取所有PLC地址映射配置失败", e); |
| | | throw new RuntimeException("获取所有PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | List<PlcAddress> getAllMappings(); |
| | | |
| | | /** |
| | | * 分页获取PLC地址映射配置 |
| | | * @param page 页码 |
| | | * @param size 每页大小 |
| | | * @param projectId 项目ID(可选) |
| | | * @param plcIp PLC IP(可选) |
| | | * @return 分页结果 |
| | | */ |
| | | public IPage<PlcAddressMapping> getMappingsByPage(int page, int size, String projectId, String plcIp) { |
| | | try { |
| | | Page<PlcAddressMapping> pageParam = new Page<>(page, size); |
| | | LambdaQueryWrapper<PlcAddressMapping> queryWrapper = new LambdaQueryWrapper<>(); |
| | | |
| | | if (projectId != null && !projectId.trim().isEmpty()) { |
| | | queryWrapper.like(PlcAddressMapping::getProjectId, projectId); |
| | | } |
| | | |
| | | if (plcIp != null && !plcIp.trim().isEmpty()) { |
| | | queryWrapper.like(PlcAddressMapping::getPlcIp, plcIp); |
| | | } |
| | | |
| | | return plcAddressMappingMapper.selectPage(pageParam, queryWrapper); |
| | | } catch (Exception e) { |
| | | log.error("分页获取PLC地址映射配置失败", e); |
| | | throw new RuntimeException("分页获取PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | IPage<PlcAddress> getMappingsByPage(int page, int size, String projectId, String plcIp); |
| | | |
| | | /** |
| | | * 根据ID获取PLC地址映射配置 |
| | | * @param id 配置ID |
| | | * @return PLC地址映射配置 |
| | | */ |
| | | public PlcAddressMapping getMappingById(Long id) { |
| | | try { |
| | | return plcAddressMappingMapper.selectById(id); |
| | | } catch (Exception e) { |
| | | log.error("根据ID获取PLC地址映射配置失败,ID: {}", id, e); |
| | | throw new RuntimeException("根据ID获取PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | PlcAddress getMappingById(Long id); |
| | | |
| | | /** |
| | | * 根据项目标识获取PLC地址映射配置 |
| | | * 根据项目ID获取PLC地址映射配置 |
| | | * @param projectId 项目ID |
| | | * @return PLC地址映射配置 |
| | | */ |
| | | public PlcAddressMapping getMappingByProjectId(String projectId) { |
| | | try { |
| | | return plcAddressMappingMapper.selectOne( |
| | | new LambdaQueryWrapper<PlcAddressMapping>() |
| | | .eq(PlcAddressMapping::getProjectId, projectId) |
| | | ); |
| | | } catch (Exception e) { |
| | | log.error("根据项目标识获取PLC地址映射配置失败,项目ID: {}", projectId, e); |
| | | throw new RuntimeException("根据项目标识获取PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | PlcAddress getMappingByProjectId(String projectId); |
| | | |
| | | /** |
| | | * 保存PLC地址映射配置 |
| | | * 创建PLC地址映射配置 |
| | | * @param mapping 映射配置 |
| | | * @return 创建后的配置 |
| | | */ |
| | | public PlcAddressMapping saveMapping(PlcAddressMapping mapping) { |
| | | try { |
| | | // 检查项目ID是否已存在 |
| | | PlcAddressMapping existing = plcAddressMappingMapper.selectOne( |
| | | new LambdaQueryWrapper<PlcAddressMapping>() |
| | | .eq(PlcAddressMapping::getProjectId, mapping.getProjectId()) |
| | | ); |
| | | |
| | | if (existing != null) { |
| | | throw new RuntimeException("项目标识 " + mapping.getProjectId() + " 已存在"); |
| | | } |
| | | |
| | | plcAddressMappingMapper.insert(mapping); |
| | | return mapping; |
| | | } catch (Exception e) { |
| | | log.error("保存PLC地址映射配置失败", e); |
| | | throw new RuntimeException("保存PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | PlcAddress saveMapping(PlcAddress mapping); |
| | | |
| | | /** |
| | | * 更新PLC地址映射配置 |
| | | * @param mapping 映射配置 |
| | | * @return 更新后的配置,未找到返回null |
| | | */ |
| | | public PlcAddressMapping updateMapping(PlcAddressMapping mapping) { |
| | | try { |
| | | // 检查ID是否存在 |
| | | PlcAddressMapping existing = plcAddressMappingMapper.selectById(mapping.getId()); |
| | | if (existing == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 如果项目ID有变化,检查新项目ID是否已存在 |
| | | if (!existing.getProjectId().equals(mapping.getProjectId())) { |
| | | PlcAddressMapping duplicate = plcAddressMappingMapper.selectOne( |
| | | new LambdaQueryWrapper<PlcAddressMapping>() |
| | | .eq(PlcAddressMapping::getProjectId, mapping.getProjectId()) |
| | | ); |
| | | |
| | | if (duplicate != null) { |
| | | throw new RuntimeException("项目标识 " + mapping.getProjectId() + " 已存在"); |
| | | } |
| | | } |
| | | |
| | | plcAddressMappingMapper.updateById(mapping); |
| | | return mapping; |
| | | } catch (Exception e) { |
| | | log.error("更新PLC地址映射配置失败", e); |
| | | throw new RuntimeException("更新PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | PlcAddress updateMapping(PlcAddress mapping); |
| | | |
| | | /** |
| | | * 删除PLC地址映射配置 |
| | | * @param id 配置ID |
| | | * @return 是否成功 |
| | | */ |
| | | public boolean deleteMapping(Long id) { |
| | | try { |
| | | int result = plcAddressMappingMapper.deleteById(id); |
| | | return result > 0; |
| | | } catch (Exception e) { |
| | | log.error("删除PLC地址映射配置失败,ID: {}", id, e); |
| | | throw new RuntimeException("删除PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | boolean deleteMapping(Long id); |
| | | |
| | | /** |
| | | * 批量删除PLC地址映射配置 |
| | | * @param ids 配置ID列表 |
| | | * @return 删除数量 |
| | | */ |
| | | public int deleteMappings(List<Long> ids) { |
| | | try { |
| | | if (ids == null || ids.isEmpty()) { |
| | | return 0; |
| | | } |
| | | return plcAddressMappingMapper.deleteBatchIds(ids); |
| | | } catch (Exception e) { |
| | | log.error("批量删除PLC地址映射配置失败,ID列表: {}", ids, e); |
| | | throw new RuntimeException("批量删除PLC地址映射配置失败", e); |
| | | } |
| | | } |
| | | int deleteMappings(List<Long> ids); |
| | | |
| | | /** |
| | | * 测试PLC连接 |
| | | * @param mapping 映射配置 |
| | | * @return 是否成功 |
| | | */ |
| | | public boolean testConnection(PlcAddressMapping mapping) { |
| | | try { |
| | | // 解析PLC类型 |
| | | EPlcType plcType = EPlcType.S1200; // 默认值 |
| | | if (mapping.getPlcType() != null && !mapping.getPlcType().trim().isEmpty()) { |
| | | try { |
| | | plcType = EPlcType.valueOf(mapping.getPlcType()); |
| | | } catch (IllegalArgumentException e) { |
| | | log.warn("未知的PLC类型: {}, 使用默认类型 S1200", mapping.getPlcType()); |
| | | } |
| | | } |
| | | |
| | | // 创建S7PLC实例 |
| | | String plcIp = mapping.getPlcIp() != null ? mapping.getPlcIp() : "192.168.10.21"; |
| | | S7PLC s7Plc = new S7PLC(plcType, plcIp); |
| | | |
| | | // 创建EnhancedS7Serializer实例 |
| | | EnhancedS7Serializer serializer = EnhancedS7Serializer.newInstance(s7Plc); |
| | | |
| | | // 尝试读取一个简单的数据来测试连接 |
| | | String dbArea = mapping.getDbArea() != null ? mapping.getDbArea() : "DB1"; |
| | | int beginIndex = mapping.getBeginIndex(); |
| | | |
| | | // 这里我们只测试连接,不读取实际数据 |
| | | // 实际项目中可能需要根据具体需求调整 |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("PLC连接测试失败", e); |
| | | return false; |
| | | } |
| | | } |
| | | boolean testConnection(PlcAddress mapping); |
| | | |
| | | /** |
| | | * 重新加载配置文件中的映射 |
| | | * 重新加载配置文件中的映射配置 |
| | | */ |
| | | public void reloadConfigMappings() { |
| | | try { |
| | | // 关键修改3:通过Spring上下文刷新配置(适用于Spring Boot 2.x) |
| | | ApplicationContext applicationContext = SpringContextHolder.getApplicationContext(); |
| | | if (applicationContext != null) { |
| | | // 获取配置绑定的BeanDefinition |
| | | ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext; |
| | | ConfigurableEnvironment environment = configurableContext.getEnvironment(); |
| | | |
| | | // 刷新配置源(重新读取配置文件) |
| | | for (PropertySource<?> source : environment.getPropertySources()) { |
| | | if (source instanceof ResourcePropertySource) { |
| | | ResourcePropertySource resourceSource = (ResourcePropertySource) source; |
| | | // 重新加载配置文件资源 |
| | | resourceSource.refresh(); |
| | | } |
| | | } |
| | | |
| | | // 重新绑定配置到PlcAddressMappingConfig |
| | | Binder.get(environment) |
| | | .bind("plc.address.mapping", PlcAddressMappingConfig.class) |
| | | .ifBound(config -> { |
| | | // 更新注入的配置实例(因@ConfigurationProperties默认是单例,需手动更新属性) |
| | | this.plcAddressMappingConfig.setDefaultDbArea(config.getDefaultDbArea()); |
| | | this.plcAddressMappingConfig.setDefaultBeginIndex(config.getDefaultBeginIndex()); |
| | | this.plcAddressMappingConfig.setProjects(config.getProjects()); |
| | | }); |
| | | |
| | | log.info("成功重新加载配置文件中的PLC地址映射"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("重新加载配置文件中的PLC地址映射失败", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将数据库实体转换为项目配置对象 |
| | | */ |
| | | private PlcAddressMappingConfig.ProjectPlcConfig convertToProjectConfig(PlcAddressMapping mapping) { |
| | | PlcAddressMappingConfig.ProjectPlcConfig config = new PlcAddressMappingConfig.ProjectPlcConfig(); |
| | | |
| | | config.setDbArea(mapping.getDbArea()); |
| | | config.setBeginIndex(mapping.getBeginIndex()); |
| | | config.setPlcIp(mapping.getPlcIp()); |
| | | config.setPlcType(mapping.getPlcType()); |
| | | |
| | | // 解析地址映射JSON |
| | | if (mapping.getAddressMapping() != null && !mapping.getAddressMapping().trim().isEmpty()) { |
| | | try { |
| | | // 使用Jackson解析JSON字符串为Map<String, Integer> |
| | | Map<String, Integer> addressMap = objectMapper.readValue( |
| | | mapping.getAddressMapping(), |
| | | new TypeReference<Map<String, Integer>>() {} |
| | | ); |
| | | config.setAddressMapping(addressMap); |
| | | } catch (Exception e) { |
| | | log.warn("解析地址映射JSON失败: {}", mapping.getAddressMapping(), e); |
| | | // 如果解析失败,创建一个空的映射 |
| | | config.setAddressMapping(new HashMap<>()); |
| | | } |
| | | } else { |
| | | // 如果没有地址映射,创建一个空的映射 |
| | | config.setAddressMapping(new HashMap<>()); |
| | | } |
| | | |
| | | return config; |
| | | } |
| | | void reloadConfigMappings(); |
| | | } |