From 4ca4444ad33a2a1a51f5c06e78ee9365dfb1eed6 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期四, 08 十月 2026 17:11:11 +0800
Subject: [PATCH] feat(mes): 种子二维码标签国标四要素合规改造

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java |  202 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 187 insertions(+), 15 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
index 822e040..65b9938 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
@@ -2,22 +2,123 @@
 
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.regex.Pattern;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_ITEM_NAME_MISSING;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_OPERATOR_MISSING;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_UNIT_CODE_INVALID;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_QR_LABEL_UNIT_CODE_MISSING;
 
 /**
- * MES 浜岀淮鐮佹爣绛炬瀯寤哄櫒锛氱粺涓�"浜岀淮鐮佺紪鐮佺函鐮佸�� + 鍥剧墖鍙犲姞鍙鏂囨湰"鐨勬枃鏈粍瑁呰鍒欙紝
- * 淇濊瘉鎵规銆佹墭鐩樸�佽鐮佷笁绉嶆爣绛剧殑鍏抽敭淇℃伅淇濈暀涓�鑷�
+ * MES 浜岀淮鐮佹爣绛炬瀯寤哄櫒锛氱粺涓�"浜岀淮鐮佺紪鐮佸唴瀹� + 鍥剧墖鍙犲姞鍙鏂囨湰"鐨勬枃鏈粍瑁呰鍒欍��
+ * <p>
+ * 绉嶅瓙鏍囩鍙椼�婂啘浣滅墿绉嶅瓙鏍囩浜岀淮鐮佺紪鐮佽鍒欍�嬩笌銆婂啘浣滅墿绉嶅瓙鏍囩閫氬垯銆嬶紙GB 20464-2024锛夌害鏉燂紝
+ * 鏈�灏忛攢鍞崟鍏冪殑鏍囩蹇呴』鍖呭惈鍥涢」淇℃伅涓旀寜鍥哄畾椤哄簭鎺掑垪锛�
+ * <ol>
+ *     <li>鍝佺鍚嶇О</li>
+ *     <li>鐢熶骇缁忚惀鑰呭悕绉帮紙杩涘彛绉嶅瓙涓鸿繘鍙e晢鍚嶇О锛�</li>
+ *     <li>鍗曞厓璇嗗埆浠g爜锛堜笉瓒呰繃 20 涓瓧绗︼紝鏁板瓧鎴栧瓧姣嶆暟瀛楃粍鍚堬級</li>
+ *     <li>杩芥函缃戝潃</li>
+ * </ol>
+ * 鍥涜绱犵己澶卞嵆鏋勬垚涓嶅悎瑙勬爣绛撅紝鍥犳杩欓噷閲囧彇銆岀己澶卞嵆鎶涘紓甯搞�佹嫆缁濆鍑恒�嶇殑绛栫暐锛岀粷涓嶉潤榛樿烦杩団�斺��
+ * 闈欓粯璺宠繃姝f槸鏍囩缂洪」娴佸叆鍗板埛鐜妭鐨勬牴鍥犮��
+ * <p>
+ * 浜岀淮鐮佸唴瀹瑰湪閰嶇疆浜� {@code mes.trace-base-url} 鏃朵负甯� code 鍙傛暟鐨勬函婧� URL锛屽惁鍒欐寜
+ * {@code mes.allow-plain-qr-code} 鍐冲畾鏄惁闄嶇骇涓虹函鐮佸�硷紙浠呴檺鏈湴鍏煎鍘嗗彶鐮侊級銆�
  */
 public final class MesProQrLabelBuilder {
 
     private MesProQrLabelBuilder() {
+    }
+
+    /**
+     * 鍗曞厓璇嗗埆浠g爜鏍煎紡锛�1~20 浣嶆暟瀛楁垨瀛楁瘝鏁板瓧缁勫悎锛堝浗鏍囪姹傦紝涓嶅惈杩炲瓧绗︾瓑绗﹀彿锛�
+     */
+    private static final Pattern UNIT_CODE_PATTERN = Pattern.compile("^[0-9A-Za-z]{1,20}$");
+
+    /** H5 婧簮椤甸潰鍩虹鍦板潃锛岀敱 {@code MesQrCodeConfig} 鍚姩鏃舵敞鍏ワ紱涓虹┖鏃朵簩缁寸爜缂栫爜绾爜鍊� */
+    private static volatile String traceBaseUrl = "";
+    /** 鐢熶骇缁忚惀鑰呭悕绉帮紝鐢� {@code MesQrCodeConfig} 鍚姩鏃舵敞鍏� */
+    private static volatile String operatorName = "";
+    /** 杩涘彛鍟嗗悕绉帮紝杩涘彛绉嶅瓙鍦烘櫙浣跨敤 */
+    private static volatile String importerName = "";
+    /** 鏄惁杩涘彛绉嶅瓙鍦烘櫙 */
+    private static volatile boolean imported = false;
+    /** 鏄惁鍏佽浜岀淮鐮侀檷绾х紪鐮佺函鐮佸�硷紙浠呴檺鏈湴鍏煎鍘嗗彶鐮侊級 */
+    private static volatile boolean allowPlainQrCode = false;
+
+    public static void setTraceBaseUrl(String traceBaseUrl) {
+        MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, "");
+    }
+
+    public static void setOperatorName(String operatorName) {
+        MesProQrLabelBuilder.operatorName = StrUtil.blankToDefault(operatorName, "");
+    }
+
+    public static void setImporterName(String importerName) {
+        MesProQrLabelBuilder.importerName = StrUtil.blankToDefault(importerName, "");
+    }
+
+    public static void setImported(boolean imported) {
+        MesProQrLabelBuilder.imported = imported;
+    }
+
+    public static void setAllowPlainQrCode(boolean allowPlainQrCode) {
+        MesProQrLabelBuilder.allowPlainQrCode = allowPlainQrCode;
+    }
+
+    /**
+     * 褰撳墠搴旀爣娉ㄧ殑鐢熶骇缁忚惀鑰呭悕绉帮細杩涘彛绉嶅瓙鍦烘櫙浼樺厛鍙栬繘鍙e晢鍚嶇О锛屽惁鍒欏彇鐢熶骇缁忚惀鑰呭悕绉�
+     *
+     * @return 鍚嶇О锛屾湭閰嶇疆鏃惰繑鍥炵┖涓�
+     */
+    public static String resolveOperatorName() {
+        if (imported && StrUtil.isNotBlank(importerName)) {
+            return importerName;
+        }
+        return operatorName;
+    }
+
+    /**
+     * 杩芥函缃戝潃锛堝澶栧睍绀虹敤锛屼笉鍚� code 鍙傛暟锛�
+     */
+    public static String resolveTraceUrl() {
+        return traceBaseUrl;
+    }
+
+    /**
+     * 鏄惁杩涘彛绉嶅瓙鍦烘櫙锛氫负 true 鏃舵爣绛句笌婧簮椤靛簲灞曠ず銆岃繘鍙e晢鍚嶇О銆�
+     */
+    public static boolean isImported() {
+        return imported;
+    }
+
+    /**
+     * 缁勮浜岀淮鐮佸唴瀹癸細閰嶇疆浜嗘函婧愬湴鍧�鍒欐嫾 URL锛屽惁鍒欐寜寮�鍏冲喅瀹氭槸鍚﹂檷绾т负绾爜鍊�
+     * <p>
+     * 鏈厤缃函婧愬湴鍧�涓旀湭寮�鍚檷绾у紑鍏虫椂鐩存帴鎶ラ敊锛岄伩鍏嶅嵃鍑轰笉鍚拷婧綉鍧�鐨勬爣绛俱��
+     */
+    private static String buildQrCodeContent(String code) {
+        if (StrUtil.isEmpty(traceBaseUrl)) {
+            if (allowPlainQrCode) {
+                return code;
+            }
+            throw exception(PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
+        }
+        return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
     }
 
     /**
@@ -51,7 +152,7 @@
         if (user != null) {
             keyValues.put("璐d换浜�", user.getNickname());
         }
-        return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build();
+        return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build();
     }
 
     /**
@@ -76,44 +177,115 @@
             keyValues.put("宸茶琚嬫暟", String.valueOf(pallet.getBagCount()));
         }
         if (batch != null) {
+            // 鍝佺鍗曠嫭鍙栨壒娆″搧绉嶏細fillBatchKeyValues 宸蹭笉鍐嶅啓鍏ュ搧绉嶏紝閬垮厤涓庤鐮佸搧绉嶄簰鐩歌鐩�
+            MesMdmItemDO batchItem = itemMap.get(batch.getItemId());
+            if (batchItem != null && StrUtil.isNotEmpty(batchItem.getName())) {
+                keyValues.put("鍝佺", batchItem.getName());
+            }
             fillBatchKeyValues(keyValues, batch, itemMap, userMap);
         }
-        return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build();
+        return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build();
     }
 
     /**
-     * 琚嬬爜浜岀淮鐮佹爣绛�
+     * 琚嬬爜浜岀淮鐮佹爣绛撅紙鏈�灏忛攢鍞崟鍏冿級
+     * <p>
+     * 鎸夊浗鏍囪姹傦紝鍥涜绱犵疆浜庢爣绛炬渶鍓嶅苟鎸夊浐瀹氶『搴忔帓鍒楋細鍝佺鍚嶇О 鈫� 鐢熶骇缁忚惀鑰� 鈫� 鍗曞厓璇嗗埆浠g爜 鈫� 杩芥函缃戝潃锛�
+     * 琚嬪簭鍙枫�佹壒娆″彿銆佷骇绾裤�佽矗浠讳汉绛夌敓浜т俊鎭綔涓鸿緟鍔╁唴瀹规帓鍒楀湪鍏跺悗銆傚洓瑕佺礌浠讳竴缂哄け鍗虫姏寮傚父锛屾嫆缁濆鍑恒��
      */
     public static QrLabelInfo buildBagCodeLabel(MesProBagCodeDO bagCode,
                                                 MesProBatchDO batch,
                                                 Map<Long, MesMdmItemDO> itemMap,
                                                 Map<Long, AdminUserRespDTO> userMap) {
         LinkedHashMap<String, String> keyValues = new LinkedHashMap<>();
-        keyValues.put("琚嬬爜", bagCode.getCode());
-        keyValues.put("琚嬪簭鍙�", bagCode.getBagNo() == null ? "" : String.valueOf(bagCode.getBagNo()));
-        keyValues.put("鎵规鍙�", bagCode.getBatchCode());
+
+        // ===== 鍥涜绱狅紙鍥芥爣瑕佹眰锛岄『搴忓浐瀹氾紝缂哄け鍗抽樆鏂鍑猴級=====
+        putRequired(keyValues, "鍝佺鍚嶇О", resolveItemName(bagCode, batch, itemMap),
+                PRO_QR_LABEL_ITEM_NAME_MISSING);
+        putRequired(keyValues, imported ? "杩涘彛鍟嗗悕绉�" : "鐢熶骇缁忚惀鑰呭悕绉�", resolveOperatorName(),
+                PRO_QR_LABEL_OPERATOR_MISSING);
+        putRequired(keyValues, "鍗曞厓璇嗗埆浠g爜", resolveUnitCode(bagCode.getCode()),
+                PRO_QR_LABEL_UNIT_CODE_MISSING);
+        putRequired(keyValues, "杩芥函缃戝潃", resolveTraceUrl(), PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
+
+        // ===== 浠ヤ笅涓鸿緟鍔╃敓浜т俊鎭� =====
+        if (bagCode.getBagNo() != null) {
+            keyValues.put("琚嬪簭鍙�", String.valueOf(bagCode.getBagNo()));
+        }
+        if (StrUtil.isNotEmpty(bagCode.getBatchCode())) {
+            keyValues.put("鎵规鍙�", bagCode.getBatchCode());
+        }
         if (StrUtil.isNotEmpty(bagCode.getPalletCode())) {
             keyValues.put("缁戝畾鎵樼洏", bagCode.getPalletCode());
         }
         if (batch != null) {
             fillBatchKeyValues(keyValues, batch, itemMap, userMap);
         }
-        return QrLabelInfo.builder().fileName(bagCode.getCode()).code(bagCode.getCode()).keyValues(keyValues).build();
+        return QrLabelInfo.builder()
+                .fileName(bagCode.getCode())
+                .code(buildQrCodeContent(bagCode.getCode()))
+                .keyValues(keyValues)
+                .build();
     }
 
     /**
-     * 濉厖鎵规鍏叡鍙鏂囨湰锛堝搧绉嶃�佽鏍笺�佷骇绾裤�佺敓浜ф棩鏈熴�佽矗浠讳汉锛�
+     * 鍐欏叆蹇呭~瀛楁锛氬�间负绌烘椂鎶涘嚭瀵瑰簲寮傚父锛岀粷涓嶉潤榛樿烦杩�
+     */
+    private static void putRequired(LinkedHashMap<String, String> keyValues, String label,
+                                    String value, ErrorCode errorCode) {
+        if (StrUtil.isBlank(value)) {
+            throw exception(errorCode);
+        }
+        keyValues.put(label, value.trim());
+    }
+
+    /**
+     * 瑙f瀽鍝佺鍚嶇О锛氫紭鍏堝彇琚嬬爜鑷韩缁戝畾鐨勫搧绉嶏紙鏁版嵁鍖呯敓鎴愭椂鍐欏叆锛屾湭褰曟壒娆′篃鍙嵃鍒凤級锛�
+     * 琚嬬爜鏈粦瀹氬搧绉嶆椂鍥為��鎵规鍝佺銆�
+     */
+    private static String resolveItemName(MesProBagCodeDO bagCode, MesProBatchDO batch,
+                                          Map<Long, MesMdmItemDO> itemMap) {
+        if (StrUtil.isNotBlank(bagCode.getItemName())) {
+            return bagCode.getItemName();
+        }
+        if (batch == null) {
+            return null;
+        }
+        MesMdmItemDO item = itemMap.get(batch.getItemId());
+        return item == null ? null : item.getName();
+    }
+
+    /**
+     * 鏍¢獙骞惰繑鍥炲崟鍏冭瘑鍒唬鐮侊紙鍥芥爣锛氫笉瓒呰繃 20 涓瓧绗︼紝鏁板瓧鎴栧瓧姣嶆暟瀛楃粍鍚堬級
+     */
+    private static String resolveUnitCode(String code) {
+        if (StrUtil.isBlank(code)) {
+            return null;
+        }
+        if (!UNIT_CODE_PATTERN.matcher(code).matches()) {
+            throw exception(PRO_QR_LABEL_UNIT_CODE_INVALID);
+        }
+        return code;
+    }
+
+    /**
+     * 濉厖鎵规鍏叡鍙鏂囨湰锛堣鏍笺�佷骇绾裤�佺敓浜ф棩鏈熴�佽矗浠讳汉锛�
+     * <p>
+     * 娉ㄦ剰锛氭澶勪笉鍐嶅啓鍏ャ�屽搧绉嶃�嶏紝鍝佺鍚嶇О缁熶竴鐢� {@link #resolveItemName} 瑙f瀽锛�
+     * 閬垮厤鎵规鍝佺瑕嗙洊琚嬬爜鍝佺瀵艰嚧鏍囩涓庢函婧愰〉灞曠ず涓嶄竴鑷淬��
      */
     private static void fillBatchKeyValues(LinkedHashMap<String, String> keyValues, MesProBatchDO batch,
                                            Map<Long, MesMdmItemDO> itemMap, Map<Long, AdminUserRespDTO> userMap) {
         MesMdmItemDO item = itemMap.get(batch.getItemId());
-        if (item != null) {
-            keyValues.put("鍝佺", item.getName());
+        if (item != null && StrUtil.isNotEmpty(item.getSpecification())) {
             keyValues.put("瑙勬牸", item.getSpecification());
         }
-        keyValues.put("浜х嚎", StrUtil.join(" / ", batch.getLineName(), batch.getLineCode()));
-        keyValues.put("鐢熶骇鏃ユ湡", batch.getProduceDate() == null ? ""
-                : LocalDateTimeUtil.format(batch.getProduceDate(), "yyyy-MM-dd HH:mm"));
+        if (StrUtil.isNotEmpty(batch.getLineName()) || StrUtil.isNotEmpty(batch.getLineCode())) {
+            keyValues.put("浜х嚎", StrUtil.join(" / ", batch.getLineName(), batch.getLineCode()));
+        }
+        if (batch.getProduceDate() != null) {
+            keyValues.put("鐢熶骇鏃ユ湡", LocalDateTimeUtil.format(batch.getProduceDate(), "yyyy-MM-dd HH:mm"));
+        }
         AdminUserRespDTO user = userMap.get(batch.getOwnerUserId());
         if (user != null) {
             keyValues.put("璐d换浜�", user.getNickname());

--
Gitblit v1.9.3