package com.mes.core; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 项目配置管理器 * 负责切换"产线位置"和"产品号"(两者互相独立、可自由组合) * 以及拉铆设备IP的运行时持久化 * * 产线位置(Line):决定工位号→工序名称的映射表(配合 OprnoRegistry 使用) * 产品号(Product):决定工件码/冷板码/底护板码前缀 * * 两张表内置,避免误改;运行时可变的仅"当前产线位置id"、"当前产品号id"和"设备IP" */ public class ProjectConfigManager { private static final Logger log = LoggerFactory.getLogger(ProjectConfigManager.class); private static final String CONFIG_DIR = "config"; private static final String CONFIG_FILE = "project_config.json"; // ========== 产线位置定义 ========== public static final class Line { private final String id; // P1/P2/P3/P4 public Line(String id) { this.id = id; } public String getId() { return id; } /** 供UI下拉显示 */ public String getDisplay() { return id; } } // ========== 产品号定义 ========== public static final class Product { private final String id; // P02-1/AY7-610/AY7-520/P04-1/650 private final String productPrefix; // 工件码(框架码) 前缀 private final String coldPlatePrefix; // 冷板码 前缀 private final String bottomPlatePrefix; // 底护板码 前缀 public Product(String id, String productPrefix, String coldPlatePrefix, String bottomPlatePrefix) { this.id = id; this.productPrefix = productPrefix; this.coldPlatePrefix = coldPlatePrefix; this.bottomPlatePrefix = bottomPlatePrefix; } public String getId() { return id; } public String getProductPrefix() { return productPrefix; } public String getColdPlatePrefix() { return coldPlatePrefix; } public String getBottomPlatePrefix() { return bottomPlatePrefix; } /** 供UI下拉显示 */ public String getDisplay() { return id; } } // 内置产线位置列表(顺序即 UI 展示顺序) private static final List LINES = Collections.unmodifiableList(Arrays.asList( new Line("P1"), new Line("P2"), new Line("P3"), new Line("P4") )); // 内置产品号列表(顺序即 UI 展示顺序) private static final List PRODUCTS = Collections.unmodifiableList(Arrays.asList( new Product("P02-1", "+KA99", "+KB004", "+KB77"), new Product("AY7-610", "+KB24", "+KA94", "+KB77"), new Product("AY7-520", "+KB23", "+KB004", "+KB77"), new Product("P04-1", "+KA93", "+KA94", "+KB77"), new Product("650", "+KA64IS", "+KA64IG", "+KB78") )); // 默认值 private static final String DEFAULT_LINE_ID = "P2"; private static final String DEFAULT_PRODUCT_ID = "AY7-610"; private static final String DEFAULT_DEVICE_IP = "192.168.0.6"; // 旧配置迁移:老版本 currentProjectId(P1~P4) → 产品号 的对应关系 private static final Map LEGACY_PROJECT_TO_PRODUCT; static { Map m = new LinkedHashMap<>(); m.put("P1", "P02-1"); m.put("P2", "AY7-610"); m.put("P3", "AY7-520"); m.put("P4", "P04-1"); LEGACY_PROJECT_TO_PRODUCT = Collections.unmodifiableMap(m); } // ========== 单例 ========== private static volatile ProjectConfigManager instance; public static ProjectConfigManager getInstance() { if (instance == null) { synchronized (ProjectConfigManager.class) { if (instance == null) { instance = new ProjectConfigManager(); } } } return instance; } // ========== 状态 ========== private final Path configPath; private String currentLineId; private String currentProductId; private String deviceIp; private String stationCodeOverride; // 覆盖 yaml 的 stations[0].code private String lineSnOverride; // 覆盖 yaml 的 line_sn private String serverIpOverride; // 覆盖 yaml 的 server.ip private final Map featuresOverride = new LinkedHashMap<>(); // 设备连接覆盖(null=不覆盖,走yaml默认) private Boolean deviceEnabledOverride; // 设备信息行覆盖:是否启用 + label(寄存器地址不在UI改) private Boolean deviceRow1EnabledOverride; private String deviceRow1LabelOverride; private Boolean deviceRow2EnabledOverride; private String deviceRow2LabelOverride; // 物料码输入框显隐覆盖 private Boolean showMaterialInputOverride; // 拉铆过程参数上传类型 (A/B/C/D),默认 A private String prodParamsType = "A"; private final List listeners = new ArrayList<>(); private ProjectConfigManager() { this.configPath = Paths.get(CONFIG_DIR, CONFIG_FILE); this.currentLineId = DEFAULT_LINE_ID; this.currentProductId = DEFAULT_PRODUCT_ID; this.deviceIp = DEFAULT_DEVICE_IP; load(); } // ========== 加载/保存 ========== private void load() { if (!Files.exists(configPath)) { log.info("[ProjectConfig] 配置文件不存在,使用默认: line={}, product={}, ip={}", currentLineId, currentProductId, deviceIp); return; } try { String content = new String(Files.readAllBytes(configPath), StandardCharsets.UTF_8); JSONObject json = JSON.parseObject(content); if (json == null) return; // 新字段:产线位置 + 产品号 String lineId = json.getString("currentLineId"); if (lineId != null && findLine(lineId) != null) { currentLineId = lineId; } String productId = json.getString("currentProductId"); if (productId != null && findProduct(productId) != null) { currentProductId = productId; } // 兼容旧配置:老版本只有 currentProjectId(如"P4",旧版项目id同时代表产线位置+产品号) // 若新字段缺失,则按旧的 id→产品号 对应关系拆分迁移 if (lineId == null || productId == null) { String oldPid = json.getString("currentProjectId"); if (oldPid != null) { if (lineId == null && findLine(oldPid) != null) { currentLineId = oldPid; } if (productId == null) { String legacyProduct = LEGACY_PROJECT_TO_PRODUCT.get(oldPid.toUpperCase()); if (legacyProduct != null) { currentProductId = legacyProduct; } } } } String ip = json.getString("deviceIp"); if (ip != null && !ip.trim().isEmpty()) { deviceIp = ip.trim(); } // 加载功能开关覆盖 JSONObject featuresObj = json.getJSONObject("featuresOverride"); if (featuresObj != null) { featuresOverride.clear(); for (String key : featuresObj.keySet()) { Boolean v = featuresObj.getBoolean(key); if (v != null) featuresOverride.put(key, v); } } String sc = json.getString("stationCodeOverride"); if (sc != null && !sc.trim().isEmpty()) { stationCodeOverride = sc.trim(); } String ls = json.getString("lineSnOverride"); if (ls != null && !ls.trim().isEmpty()) { lineSnOverride = ls.trim(); } String sIp = json.getString("serverIpOverride"); if (sIp != null && !sIp.trim().isEmpty()) { serverIpOverride = sIp.trim(); } // 设备连接覆盖 if (json.containsKey("deviceEnabledOverride")) { deviceEnabledOverride = json.getBoolean("deviceEnabledOverride"); } // 设备信息行 JSONObject uiObj = json.getJSONObject("uiOverride"); if (uiObj != null) { deviceRow1EnabledOverride = uiObj.getBoolean("deviceRow1Enabled"); deviceRow1LabelOverride = trimToNull(uiObj.getString("deviceRow1Label")); deviceRow2EnabledOverride = uiObj.getBoolean("deviceRow2Enabled"); deviceRow2LabelOverride = trimToNull(uiObj.getString("deviceRow2Label")); showMaterialInputOverride = uiObj.getBoolean("showMaterialInput"); String pt = trimToNull(uiObj.getString("prodParamsType")); if (pt != null) prodParamsType = pt.toUpperCase(); } log.info("[ProjectConfig] 加载成功: line={}, product={}, ip={}, station={}, lineSn={}, features={}", currentLineId, currentProductId, deviceIp, stationCodeOverride, lineSnOverride, featuresOverride); } catch (Exception e) { log.error("[ProjectConfig] 加载失败: {}", e.getMessage(), e); } } private void save() { try { if (configPath.getParent() != null) { Files.createDirectories(configPath.getParent()); } JSONObject root = new JSONObject(); root.put("currentLineId", currentLineId); root.put("currentProductId", currentProductId); root.put("deviceIp", deviceIp); if (stationCodeOverride != null) root.put("stationCodeOverride", stationCodeOverride); if (lineSnOverride != null) root.put("lineSnOverride", lineSnOverride); if (serverIpOverride != null) root.put("serverIpOverride", serverIpOverride); if (deviceEnabledOverride != null) root.put("deviceEnabledOverride", deviceEnabledOverride); JSONObject uiObj = new JSONObject(); if (deviceRow1EnabledOverride != null) uiObj.put("deviceRow1Enabled", deviceRow1EnabledOverride); if (deviceRow1LabelOverride != null) uiObj.put("deviceRow1Label", deviceRow1LabelOverride); if (deviceRow2EnabledOverride != null) uiObj.put("deviceRow2Enabled", deviceRow2EnabledOverride); if (deviceRow2LabelOverride != null) uiObj.put("deviceRow2Label", deviceRow2LabelOverride); if (showMaterialInputOverride != null) uiObj.put("showMaterialInput", showMaterialInputOverride); uiObj.put("prodParamsType", prodParamsType); if (!uiObj.isEmpty()) root.put("uiOverride", uiObj); if (!featuresOverride.isEmpty()) { root.put("featuresOverride", featuresOverride); } root.put("lastModified", System.currentTimeMillis()); String content = JSON.toJSONString(root, JSONWriter.Feature.PrettyFormat); Files.write(configPath, content.getBytes(StandardCharsets.UTF_8)); log.info("[ProjectConfig] 保存成功: {}", configPath); } catch (IOException e) { log.error("[ProjectConfig] 保存失败: {}", e.getMessage(), e); } } // ========== 产线位置查询 ========== public List getLines() { return LINES; } public Line findLine(String id) { if (id == null) return null; for (Line l : LINES) { if (l.getId().equalsIgnoreCase(id)) return l; } return null; } public Line getCurrentLine() { Line l = findLine(currentLineId); return l != null ? l : LINES.get(0); } public String getCurrentLineId() { return currentLineId; } // ========== 产品号查询 ========== public List getProducts() { return PRODUCTS; } public Product findProduct(String id) { if (id == null) return null; for (Product p : PRODUCTS) { if (p.getId().equalsIgnoreCase(id)) return p; } return null; } public Product getCurrentProduct() { Product p = findProduct(currentProductId); return p != null ? p : PRODUCTS.get(0); } public String getCurrentProductId() { return currentProductId; } // ========== 前缀快捷方法 ========== /** 工件码(框架码)前缀,若当前产品找不到则返回null */ public String getProductPrefix() { Product p = getCurrentProduct(); return p != null ? p.getProductPrefix() : null; } /** 冷板码前缀 */ public String getColdPlatePrefix() { Product p = getCurrentProduct(); return p != null ? p.getColdPlatePrefix() : null; } /** 底护板码前缀 */ public String getBottomPlatePrefix() { Product p = getCurrentProduct(); return p != null ? p.getBottomPlatePrefix() : null; } /** * 根据物料标签获取前缀(供 ScanMaterialStep 区分) * 支持"冷板码""底护板码"两种标签 */ public String getMaterialPrefixByLabel(String label) { if (label == null) return null; if (label.contains("冷板")) return getColdPlatePrefix(); if (label.contains("底护板")) return getBottomPlatePrefix(); return null; } // ========== 切换产线位置 ========== public void switchLine(String lineId) { Line l = findLine(lineId); if (l == null) { log.warn("[ProjectConfig] 无效的产线位置id: {}", lineId); return; } if (l.getId().equalsIgnoreCase(currentLineId)) return; String old = currentLineId; currentLineId = l.getId(); save(); log.info("[ProjectConfig] 切换产线位置: {} -> {}", old, currentLineId); fireLineChanged(l); } // ========== 切换产品号 ========== public void switchProduct(String productId) { Product p = findProduct(productId); if (p == null) { log.warn("[ProjectConfig] 无效的产品号id: {}", productId); return; } if (p.getId().equalsIgnoreCase(currentProductId)) return; String old = currentProductId; currentProductId = p.getId(); save(); log.info("[ProjectConfig] 切换产品号: {} -> {}", old, currentProductId); fireProductChanged(p); } // ========== 设备IP ========== public String getDeviceIp() { return deviceIp; } public void setDeviceIp(String ip) { if (ip == null || ip.trim().isEmpty()) return; ip = ip.trim(); if (ip.equals(this.deviceIp)) return; String old = this.deviceIp; this.deviceIp = ip; save(); log.info("[ProjectConfig] 修改拉铆设备IP: {} -> {}", old, ip); fireDeviceIpChanged(ip); } // ========== 功能开关覆盖 ========== /** * 获取功能开关覆盖值,null 表示未覆盖(走 yaml 默认) */ public Boolean getFeatureOverride(String feature) { return featuresOverride.get(feature); } public Map getFeaturesOverride() { return new LinkedHashMap<>(featuresOverride); } /** * 批量设置功能开关覆盖(UI一次提交多项时用) * newValues 为 null 或空则清空覆盖(恢复 yaml 默认) */ public void setFeaturesOverride(Map newValues) { featuresOverride.clear(); if (newValues != null) { for (Map.Entry e : newValues.entrySet()) { if (e.getKey() != null && e.getValue() != null) { featuresOverride.put(e.getKey(), e.getValue()); } } } save(); log.info("[ProjectConfig] 更新功能开关: {}", featuresOverride); fireFeaturesChanged(); } // ========== 工位号/线体覆盖 ========== public String getStationCodeOverride() { return stationCodeOverride; } public String getLineSnOverride() { return lineSnOverride; } public String getServerIpOverride() { return serverIpOverride; } /** * 设置服务器(MES)IP 覆盖 */ public void setServerIpOverride(String ip) { if (ip == null) return; ip = ip.trim(); if (ip.isEmpty()) return; if (ip.equalsIgnoreCase(this.serverIpOverride)) return; this.serverIpOverride = ip; save(); log.info("[ProjectConfig] 修改服务器IP: {}", ip); fireServerIpChanged(ip); } public Boolean getDeviceRow1EnabledOverride() { return deviceRow1EnabledOverride; } /** * 拉铆过程参数上传类型 (A/B/C/D),默认 A */ public String getProdParamsType() { return prodParamsType != null ? prodParamsType : "A"; } /** * 设置拉铆过程参数上传类型 * @param type 有效值 A/B/C/D(大小写不敏感,其它值忽略) */ public void setProdParamsType(String type) { if (type == null) return; String t = type.trim().toUpperCase(); if (t.isEmpty()) return; if (!"A".equals(t) && !"B".equals(t) && !"C".equals(t) && !"D".equals(t)) { log.warn("[ProjectConfig] 无效的拉铆参数类型: {}, 忽略", type); return; } if (t.equals(this.prodParamsType)) return; String old = this.prodParamsType; this.prodParamsType = t; save(); log.info("[ProjectConfig] 修改拉铆参数类型: {} -> {}", old, t); } // ========== 设备连接覆盖 ========== public Boolean getDeviceEnabledOverride() { return deviceEnabledOverride; } public void setDeviceEnabledOverride(Boolean enabled) { this.deviceEnabledOverride = enabled; save(); log.info("[ProjectConfig] 设备连接覆盖: {}", enabled); } public String getDeviceRow1LabelOverride() { return deviceRow1LabelOverride; } public Boolean getDeviceRow2EnabledOverride() { return deviceRow2EnabledOverride; } public String getDeviceRow2LabelOverride() { return deviceRow2LabelOverride; } public Boolean getShowMaterialInputOverride() { return showMaterialInputOverride; } /** * 更新设备信息行UI覆盖(启用/label),null 表示不覆盖 */ public void setDeviceInfoRowsOverride(Boolean row1Enabled, String row1Label, Boolean row2Enabled, String row2Label, Boolean showMaterialInput) { this.deviceRow1EnabledOverride = row1Enabled; this.deviceRow1LabelOverride = trimToNull(row1Label); this.deviceRow2EnabledOverride = row2Enabled; this.deviceRow2LabelOverride = trimToNull(row2Label); this.showMaterialInputOverride = showMaterialInput; save(); log.info("[ProjectConfig] UI覆盖: row1=({},{}), row2=({},{}), showMaterial={}", row1Enabled, deviceRow1LabelOverride, row2Enabled, deviceRow2LabelOverride, showMaterialInput); fireUiChanged(); } private static String trimToNull(String s) { if (s == null) return null; String t = s.trim(); return t.isEmpty() ? null : t; } /** * 设置工位号与线体覆盖(任一可为null表示不覆盖该项) */ public void setStationOverride(String stationCode, String lineSn) { boolean changed = false; if (stationCode != null) { stationCode = stationCode.trim(); if (!stationCode.isEmpty() && !stationCode.equalsIgnoreCase(this.stationCodeOverride)) { this.stationCodeOverride = stationCode; changed = true; } } if (lineSn != null) { lineSn = lineSn.trim(); if (!lineSn.isEmpty() && !lineSn.equalsIgnoreCase(this.lineSnOverride)) { this.lineSnOverride = lineSn; changed = true; } } if (changed) { save(); log.info("[ProjectConfig] 修改工位: code={}, lineSn={}", stationCodeOverride, lineSnOverride); fireStationChanged(); } } // ========== 事件监听(UI订阅) ========== public interface Listener { default void onLineChanged(Line line) {} default void onProductChanged(Product product) {} default void onDeviceIpChanged(String newIp) {} default void onServerIpChanged(String newIp) {} default void onFeaturesChanged() {} default void onStationChanged() {} default void onUiChanged() {} } public void addListener(Listener l) { if (l != null && !listeners.contains(l)) listeners.add(l); } public void removeListener(Listener l) { listeners.remove(l); } private void fireLineChanged(Line l) { for (Listener listener : new ArrayList<>(listeners)) { try { listener.onLineChanged(l); } catch (Exception e) { log.warn("listener error", e); } } } private void fireProductChanged(Product p) { for (Listener listener : new ArrayList<>(listeners)) { try { listener.onProductChanged(p); } catch (Exception e) { log.warn("listener error", e); } } } private void fireDeviceIpChanged(String ip) { for (Listener l : new ArrayList<>(listeners)) { try { l.onDeviceIpChanged(ip); } catch (Exception e) { log.warn("listener error", e); } } } private void fireServerIpChanged(String ip) { for (Listener l : new ArrayList<>(listeners)) { try { l.onServerIpChanged(ip); } catch (Exception e) { log.warn("listener error", e); } } } private void fireFeaturesChanged() { for (Listener l : new ArrayList<>(listeners)) { try { l.onFeaturesChanged(); } catch (Exception e) { log.warn("listener error", e); } } } private void fireStationChanged() { for (Listener l : new ArrayList<>(listeners)) { try { l.onStationChanged(); } catch (Exception e) { log.warn("listener error", e); } } } private void fireUiChanged() { for (Listener l : new ArrayList<>(listeners)) { try { l.onUiChanged(); } catch (Exception e) { log.warn("listener error", e); } } } }