|
|
@@ -50,22 +50,22 @@ public class ProjectConfigManager {
|
|
|
|
|
|
// ========== 产品号定义 ==========
|
|
|
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; // 底护板码 前缀
|
|
|
+ private final String id; // P02-1/AY7-610/AY7-520/P04-1/650
|
|
|
+ private final String[] productPrefixes; // 工件码(框架码) 前缀白名单
|
|
|
+ private final String[] coldPlatePrefixes; // 冷板码 前缀白名单
|
|
|
+ private final String[] bottomPlatePrefixes; // 底护板码 前缀白名单
|
|
|
|
|
|
- public Product(String id, String productPrefix, String coldPlatePrefix, String bottomPlatePrefix) {
|
|
|
+ public Product(String id, String[] productPrefixes, String[] coldPlatePrefixes, String[] bottomPlatePrefixes) {
|
|
|
this.id = id;
|
|
|
- this.productPrefix = productPrefix;
|
|
|
- this.coldPlatePrefix = coldPlatePrefix;
|
|
|
- this.bottomPlatePrefix = bottomPlatePrefix;
|
|
|
+ this.productPrefixes = productPrefixes;
|
|
|
+ this.coldPlatePrefixes = coldPlatePrefixes;
|
|
|
+ this.bottomPlatePrefixes = bottomPlatePrefixes;
|
|
|
}
|
|
|
|
|
|
public String getId() { return id; }
|
|
|
- public String getProductPrefix() { return productPrefix; }
|
|
|
- public String getColdPlatePrefix() { return coldPlatePrefix; }
|
|
|
- public String getBottomPlatePrefix() { return bottomPlatePrefix; }
|
|
|
+ public String[] getProductPrefixes() { return productPrefixes; }
|
|
|
+ public String[] getColdPlatePrefixes() { return coldPlatePrefixes; }
|
|
|
+ public String[] getBottomPlatePrefixes() { return bottomPlatePrefixes; }
|
|
|
|
|
|
/** 供UI下拉显示 */
|
|
|
public String getDisplay() { return id; }
|
|
|
@@ -81,11 +81,11 @@ public class ProjectConfigManager {
|
|
|
|
|
|
// 内置产品号列表(顺序即 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")
|
|
|
+ new Product("P02-1", new String[]{"+KA99", "+KC14", "+KB98"}, new String[]{"+KB004", "2510"}, new String[]{"+KB77"}),
|
|
|
+ new Product("AY7-610", new String[]{"+KB24"}, new String[]{"+KA94"}, new String[]{"+KB77"}),
|
|
|
+ new Product("AY7-520", new String[]{"+KB23"}, new String[]{"+KB004"}, new String[]{"+KB77"}),
|
|
|
+ new Product("P04-1", new String[]{"+KA93"}, new String[]{"+KA94"}, new String[]{"+KB77"}),
|
|
|
+ new Product("650", new String[]{"+KA64IS"}, new String[]{"+KA64IG"}, new String[]{"+KB78"})
|
|
|
));
|
|
|
|
|
|
// 默认值
|
|
|
@@ -138,6 +138,10 @@ public class ProjectConfigManager {
|
|
|
private Boolean showMaterialInputOverride;
|
|
|
// 拉铆过程参数上传类型 (A/B/C/D),默认 A
|
|
|
private String prodParamsType = "A";
|
|
|
+ // 前缀白名单运行时覆盖(优先级高于产品定义)
|
|
|
+ private String[] productPrefixOverride;
|
|
|
+ private String[] coldPlatePrefixOverride;
|
|
|
+ private String[] bottomPlatePrefixOverride;
|
|
|
private final List<Listener> listeners = new ArrayList<>();
|
|
|
|
|
|
private ProjectConfigManager() {
|
|
|
@@ -225,8 +229,34 @@ public class ProjectConfigManager {
|
|
|
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);
|
|
|
+ // 前缀白名单覆盖
|
|
|
+ JSONObject prefixObj = json.getJSONObject("prefixOverride");
|
|
|
+ if (prefixObj != null) {
|
|
|
+ String productPrefixStr = trimToNull(prefixObj.getString("product"));
|
|
|
+ if (productPrefixStr != null) {
|
|
|
+ productPrefixOverride = productPrefixStr.split(",");
|
|
|
+ for (int i = 0; i < productPrefixOverride.length; i++) {
|
|
|
+ productPrefixOverride[i] = productPrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String coldPlatePrefixStr = trimToNull(prefixObj.getString("coldPlate"));
|
|
|
+ if (coldPlatePrefixStr != null) {
|
|
|
+ coldPlatePrefixOverride = coldPlatePrefixStr.split(",");
|
|
|
+ for (int i = 0; i < coldPlatePrefixOverride.length; i++) {
|
|
|
+ coldPlatePrefixOverride[i] = coldPlatePrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String bottomPlatePrefixStr = trimToNull(prefixObj.getString("bottomPlate"));
|
|
|
+ if (bottomPlatePrefixStr != null) {
|
|
|
+ bottomPlatePrefixOverride = bottomPlatePrefixStr.split(",");
|
|
|
+ for (int i = 0; i < bottomPlatePrefixOverride.length; i++) {
|
|
|
+ bottomPlatePrefixOverride[i] = bottomPlatePrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("[ProjectConfig] 加载成功: line={}, product={}, ip={}, station={}, lineSn={}, features={}, prefixOverride={}",
|
|
|
+ currentLineId, currentProductId, deviceIp, stationCodeOverride, lineSnOverride, featuresOverride,
|
|
|
+ (productPrefixOverride != null || coldPlatePrefixOverride != null || bottomPlatePrefixOverride != null));
|
|
|
} catch (Exception e) {
|
|
|
log.error("[ProjectConfig] 加载失败: {}", e.getMessage(), e);
|
|
|
}
|
|
|
@@ -256,6 +286,20 @@ public class ProjectConfigManager {
|
|
|
if (!featuresOverride.isEmpty()) {
|
|
|
root.put("featuresOverride", featuresOverride);
|
|
|
}
|
|
|
+ // 前缀白名单覆盖
|
|
|
+ if (productPrefixOverride != null || coldPlatePrefixOverride != null || bottomPlatePrefixOverride != null) {
|
|
|
+ JSONObject prefixObj = new JSONObject();
|
|
|
+ if (productPrefixOverride != null) {
|
|
|
+ prefixObj.put("product", String.join(",", productPrefixOverride));
|
|
|
+ }
|
|
|
+ if (coldPlatePrefixOverride != null) {
|
|
|
+ prefixObj.put("coldPlate", String.join(",", coldPlatePrefixOverride));
|
|
|
+ }
|
|
|
+ if (bottomPlatePrefixOverride != null) {
|
|
|
+ prefixObj.put("bottomPlate", String.join(",", bottomPlatePrefixOverride));
|
|
|
+ }
|
|
|
+ root.put("prefixOverride", prefixObj);
|
|
|
+ }
|
|
|
root.put("lastModified", System.currentTimeMillis());
|
|
|
String content = JSON.toJSONString(root, JSONWriter.Feature.PrettyFormat);
|
|
|
Files.write(configPath, content.getBytes(StandardCharsets.UTF_8));
|
|
|
@@ -313,33 +357,147 @@ public class ProjectConfigManager {
|
|
|
|
|
|
// ========== 前缀快捷方法 ==========
|
|
|
|
|
|
- /** 工件码(框架码)前缀,若当前产品找不到则返回null */
|
|
|
- public String getProductPrefix() {
|
|
|
+ /** 工件码(框架码)前缀数组,优先返回运行时覆盖值,其次返回产品定义 */
|
|
|
+ public String[] getProductPrefixes() {
|
|
|
+ if (productPrefixOverride != null) return productPrefixOverride;
|
|
|
Product p = getCurrentProduct();
|
|
|
- return p != null ? p.getProductPrefix() : null;
|
|
|
+ return p != null ? p.getProductPrefixes() : null;
|
|
|
}
|
|
|
|
|
|
- /** 冷板码前缀 */
|
|
|
- public String getColdPlatePrefix() {
|
|
|
+ /** 冷板码前缀数组,优先返回运行时覆盖值 */
|
|
|
+ public String[] getColdPlatePrefixes() {
|
|
|
+ if (coldPlatePrefixOverride != null) return coldPlatePrefixOverride;
|
|
|
Product p = getCurrentProduct();
|
|
|
- return p != null ? p.getColdPlatePrefix() : null;
|
|
|
+ return p != null ? p.getColdPlatePrefixes() : null;
|
|
|
}
|
|
|
|
|
|
- /** 底护板码前缀 */
|
|
|
- public String getBottomPlatePrefix() {
|
|
|
+ /** 底护板码前缀数组,优先返回运行时覆盖值 */
|
|
|
+ public String[] getBottomPlatePrefixes() {
|
|
|
+ if (bottomPlatePrefixOverride != null) return bottomPlatePrefixOverride;
|
|
|
Product p = getCurrentProduct();
|
|
|
- return p != null ? p.getBottomPlatePrefix() : null;
|
|
|
+ return p != null ? p.getBottomPlatePrefixes() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验工件码是否匹配任一允许的前缀
|
|
|
+ */
|
|
|
+ public boolean matchProductPrefix(String barcode) {
|
|
|
+ if (barcode == null || barcode.isEmpty()) return false;
|
|
|
+ String[] prefixes = getProductPrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return true;
|
|
|
+ for (String prefix : prefixes) {
|
|
|
+ if (prefix != null && barcode.startsWith(prefix.trim())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拼接所有允许的工件码前缀,用于错误提示
|
|
|
+ */
|
|
|
+ public String getProductPrefixHint() {
|
|
|
+ String[] prefixes = getProductPrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return "";
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < prefixes.length; i++) {
|
|
|
+ if (i > 0) sb.append("/");
|
|
|
+ sb.append(prefixes[i].trim());
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验冷板码是否匹配任一允许的前缀
|
|
|
+ */
|
|
|
+ public boolean matchColdPlatePrefix(String barcode) {
|
|
|
+ if (barcode == null || barcode.isEmpty()) return false;
|
|
|
+ String[] prefixes = getColdPlatePrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return true;
|
|
|
+ for (String prefix : prefixes) {
|
|
|
+ if (prefix != null && barcode.startsWith(prefix.trim())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拼接所有允许的冷板码前缀,用于错误提示
|
|
|
+ */
|
|
|
+ public String getColdPlatePrefixHint() {
|
|
|
+ String[] prefixes = getColdPlatePrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return "";
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < prefixes.length; i++) {
|
|
|
+ if (i > 0) sb.append("/");
|
|
|
+ sb.append(prefixes[i].trim());
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验底护板码是否匹配任一允许的前缀
|
|
|
+ */
|
|
|
+ public boolean matchBottomPlatePrefix(String barcode) {
|
|
|
+ if (barcode == null || barcode.isEmpty()) return false;
|
|
|
+ String[] prefixes = getBottomPlatePrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return true;
|
|
|
+ for (String prefix : prefixes) {
|
|
|
+ if (prefix != null && barcode.startsWith(prefix.trim())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拼接所有允许的底护板码前缀,用于错误提示
|
|
|
+ */
|
|
|
+ public String getBottomPlatePrefixHint() {
|
|
|
+ String[] prefixes = getBottomPlatePrefixes();
|
|
|
+ if (prefixes == null || prefixes.length == 0) return "";
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < prefixes.length; i++) {
|
|
|
+ if (i > 0) sb.append("/");
|
|
|
+ sb.append(prefixes[i].trim());
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据物料标签获取前缀(供 ScanMaterialStep 区分)
|
|
|
* 支持"冷板码""底护板码"两种标签
|
|
|
+ * @deprecated 使用 matchMaterialPrefix 和 getMaterialPrefixHint 替代
|
|
|
*/
|
|
|
+ @Deprecated
|
|
|
public String getMaterialPrefixByLabel(String label) {
|
|
|
if (label == null) return null;
|
|
|
- if (label.contains("冷板")) return getColdPlatePrefix();
|
|
|
- if (label.contains("底护板")) return getBottomPlatePrefix();
|
|
|
- return null;
|
|
|
+ String[] prefixes = null;
|
|
|
+ if (label.contains("冷板")) prefixes = getColdPlatePrefixes();
|
|
|
+ else if (label.contains("底护板")) prefixes = getBottomPlatePrefixes();
|
|
|
+ // 返回第一个前缀(兼容旧逻辑)
|
|
|
+ return (prefixes != null && prefixes.length > 0) ? prefixes[0] : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据物料标签校验前缀(白名单匹配)
|
|
|
+ */
|
|
|
+ public boolean matchMaterialPrefix(String barcode, String label) {
|
|
|
+ if (label == null) return true;
|
|
|
+ if (label.contains("冷板")) return matchColdPlatePrefix(barcode);
|
|
|
+ if (label.contains("底护板")) return matchBottomPlatePrefix(barcode);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据物料标签获取前缀提示
|
|
|
+ */
|
|
|
+ public String getMaterialPrefixHint(String label) {
|
|
|
+ if (label == null) return "";
|
|
|
+ if (label.contains("冷板")) return getColdPlatePrefixHint();
|
|
|
+ if (label.contains("底护板")) return getBottomPlatePrefixHint();
|
|
|
+ return "";
|
|
|
}
|
|
|
|
|
|
// ========== 切换产线位置 ==========
|
|
|
@@ -428,6 +586,96 @@ public class ProjectConfigManager {
|
|
|
public String getLineSnOverride() { return lineSnOverride; }
|
|
|
public String getServerIpOverride() { return serverIpOverride; }
|
|
|
|
|
|
+ // ========== 前缀白名单覆盖 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置工件码前缀白名单覆盖
|
|
|
+ * @param prefixes 逗号分隔的前缀字符串,如 "+KA99,+KC14,+KB98";null 或空字符串表示清除覆盖
|
|
|
+ */
|
|
|
+ public void setProductPrefixOverride(String prefixes) {
|
|
|
+ if (prefixes == null || prefixes.trim().isEmpty()) {
|
|
|
+ productPrefixOverride = null;
|
|
|
+ } else {
|
|
|
+ productPrefixOverride = prefixes.split(",");
|
|
|
+ for (int i = 0; i < productPrefixOverride.length; i++) {
|
|
|
+ productPrefixOverride[i] = productPrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ save();
|
|
|
+ log.info("[ProjectConfig] 工件码前缀覆盖: {}", prefixes);
|
|
|
+ firePrefixChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置冷板码前缀白名单覆盖
|
|
|
+ */
|
|
|
+ public void setColdPlatePrefixOverride(String prefixes) {
|
|
|
+ if (prefixes == null || prefixes.trim().isEmpty()) {
|
|
|
+ coldPlatePrefixOverride = null;
|
|
|
+ } else {
|
|
|
+ coldPlatePrefixOverride = prefixes.split(",");
|
|
|
+ for (int i = 0; i < coldPlatePrefixOverride.length; i++) {
|
|
|
+ coldPlatePrefixOverride[i] = coldPlatePrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ save();
|
|
|
+ log.info("[ProjectConfig] 冷板码前缀覆盖: {}", prefixes);
|
|
|
+ firePrefixChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置底护板码前缀白名单覆盖
|
|
|
+ */
|
|
|
+ public void setBottomPlatePrefixOverride(String prefixes) {
|
|
|
+ if (prefixes == null || prefixes.trim().isEmpty()) {
|
|
|
+ bottomPlatePrefixOverride = null;
|
|
|
+ } else {
|
|
|
+ bottomPlatePrefixOverride = prefixes.split(",");
|
|
|
+ for (int i = 0; i < bottomPlatePrefixOverride.length; i++) {
|
|
|
+ bottomPlatePrefixOverride[i] = bottomPlatePrefixOverride[i].trim();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ save();
|
|
|
+ log.info("[ProjectConfig] 底护板码前缀覆盖: {}", prefixes);
|
|
|
+ firePrefixChanged();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取工件码前缀覆盖的字符串表示(用于UI显示)
|
|
|
+ */
|
|
|
+ public String getProductPrefixOverrideString() {
|
|
|
+ if (productPrefixOverride == null) return null;
|
|
|
+ return String.join(",", productPrefixOverride);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取冷板码前缀覆盖的字符串表示
|
|
|
+ */
|
|
|
+ public String getColdPlatePrefixOverrideString() {
|
|
|
+ if (coldPlatePrefixOverride == null) return null;
|
|
|
+ return String.join(",", coldPlatePrefixOverride);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取底护板码前缀覆盖的字符串表示
|
|
|
+ */
|
|
|
+ public String getBottomPlatePrefixOverrideString() {
|
|
|
+ if (bottomPlatePrefixOverride == null) return null;
|
|
|
+ return String.join(",", bottomPlatePrefixOverride);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除所有前缀覆盖,恢复使用产品定义
|
|
|
+ */
|
|
|
+ public void clearPrefixOverrides() {
|
|
|
+ productPrefixOverride = null;
|
|
|
+ coldPlatePrefixOverride = null;
|
|
|
+ bottomPlatePrefixOverride = null;
|
|
|
+ save();
|
|
|
+ log.info("[ProjectConfig] 已清除所有前缀覆盖");
|
|
|
+ firePrefixChanged();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 设置服务器(MES)IP 覆盖
|
|
|
*/
|
|
|
@@ -545,6 +793,7 @@ public class ProjectConfigManager {
|
|
|
default void onFeaturesChanged() {}
|
|
|
default void onStationChanged() {}
|
|
|
default void onUiChanged() {}
|
|
|
+ default void onPrefixChanged() {}
|
|
|
}
|
|
|
|
|
|
public void addListener(Listener l) {
|
|
|
@@ -596,4 +845,10 @@ public class ProjectConfigManager {
|
|
|
try { l.onUiChanged(); } catch (Exception e) { log.warn("listener error", e); }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private void firePrefixChanged() {
|
|
|
+ for (Listener l : new ArrayList<>(listeners)) {
|
|
|
+ try { l.onPrefixChanged(); } catch (Exception e) { log.warn("listener error", e); }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|