| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599 |
- 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<Line> LINES = Collections.unmodifiableList(Arrays.asList(
- new Line("P1"),
- new Line("P2"),
- new Line("P3"),
- new Line("P4")
- ));
- // 内置产品号列表(顺序即 UI 展示顺序)
- private static final List<Product> 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<String, String> LEGACY_PROJECT_TO_PRODUCT;
- static {
- Map<String, String> 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<String, Boolean> 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<Listener> 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<Line> 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<Product> 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<String, Boolean> getFeaturesOverride() {
- return new LinkedHashMap<>(featuresOverride);
- }
- /**
- * 批量设置功能开关覆盖(UI一次提交多项时用)
- * newValues 为 null 或空则清空覆盖(恢复 yaml 默认)
- */
- public void setFeaturesOverride(Map<String, Boolean> newValues) {
- featuresOverride.clear();
- if (newValues != null) {
- for (Map.Entry<String, Boolean> 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); }
- }
- }
- }
|