liu
11 小时以前 da93fafc417b4f590b6ab639ccbbe67c14c8753c
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
@@ -7,6 +7,7 @@
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.mes.framework.config.MesTraceConfigSnapshot;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import java.net.URLEncoder;
@@ -69,16 +70,30 @@
     */
    private static final Pattern UNIT_CODE_PATTERN = Pattern.compile("^[0-9A-Za-z]{1,20}$");
    /** H5 溯源页面基础地址,由 {@code MesQrCodeConfig} 启动时注入;为空时二维码编码纯码值 */
    /**
     * 配置来源:由 {@code MesQrCodeConfig} 启动时装配,指向字典解析器。
     * <p>
     * 为 null 时(单测、非 Spring 上下文)回退下面几个静态字段,也就是配置文件的值。
     */
    private static volatile MesTraceConfigProvider configProvider;
    /** H5 溯源页面基础地址,配置文件 {@code mes.trace-base-url} 的值;字典未配置时的兜底 */
    private static volatile String traceBaseUrl = "";
    /** 生产经营者名称,由 {@code MesQrCodeConfig} 启动时注入 */
    /** 生产经营者名称,配置文件 {@code mes.operator-name} 的值;字典未配置时的兜底 */
    private static volatile String operatorName = "";
    /** 进口商名称,进口种子场景使用 */
    /** 进口商名称,配置文件 {@code mes.importer-name} 的值;字典未配置时的兜底 */
    private static volatile String importerName = "";
    /** 是否进口种子场景 */
    /** 是否进口种子场景,配置文件 {@code mes.imported} 的值;字典未配置时的兜底 */
    private static volatile boolean imported = false;
    /** 是否允许二维码降级编码纯码值(仅限本地兼容历史码) */
    private static volatile boolean allowPlainQrCode = false;
    /**
     * 装配配置来源。传 null 可恢复为「只读配置文件静态值」,单测借此避免静态状态串味。
     */
    public static void setTraceConfigProvider(MesTraceConfigProvider provider) {
        MesProQrLabelBuilder.configProvider = provider;
    }
    public static void setTraceBaseUrl(String traceBaseUrl) {
        MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, "");
@@ -101,21 +116,46 @@
    }
    /**
     * 取当前生效的配置快照
     *
     * @return 快照;未装配配置来源时返回 null,调用方回退静态字段(配置文件的值)
     */
    private static MesTraceConfigSnapshot currentSnapshot() {
        MesTraceConfigProvider provider = configProvider;
        return provider != null ? provider.get() : null;
    }
    /**
     * 当前应标注的生产经营者名称:进口种子场景优先取进口商名称,否则取生产经营者名称
     *
     * @return 名称,未配置时返回空串
     */
    public static String resolveOperatorName() {
        if (imported && StrUtil.isNotBlank(importerName)) {
            return importerName;
        return resolveOperatorName(currentSnapshot());
        }
        return operatorName;
    private static String resolveOperatorName(MesTraceConfigSnapshot snapshot) {
        boolean importedScene = snapshot != null ? snapshot.imported() : imported;
        String currentImporterName = snapshot != null ? snapshot.importerName() : importerName;
        String currentOperatorName = snapshot != null ? snapshot.operatorName() : operatorName;
        if (importedScene && StrUtil.isNotBlank(currentImporterName)) {
            return currentImporterName;
        }
        return currentOperatorName;
    }
    /**
     * 追溯网址(对外展示用,不含 code 参数)
     */
    public static String resolveTraceUrl() {
        return resolveTraceUrl(currentSnapshot());
    }
    private static String resolveTraceUrl(MesTraceConfigSnapshot snapshot) {
        // 快照为空值时不覆盖配置文件的值:配置来源只应「补充」而不应把兜底值抹成空
        if (snapshot != null && StrUtil.isNotBlank(snapshot.traceUrl())) {
            return snapshot.traceUrl().trim();
        }
        return traceBaseUrl;
    }
@@ -123,7 +163,11 @@
     * 是否进口种子场景:为 true 时标签与溯源页应展示「进口商名称」
     */
    public static boolean isImported() {
        return imported;
        return isImported(currentSnapshot());
    }
    private static boolean isImported(MesTraceConfigSnapshot snapshot) {
        return snapshot != null ? snapshot.imported() : imported;
    }
    /**
@@ -132,13 +176,26 @@
     * 未配置溯源地址且未开启降级开关时直接报错,避免印出不含追溯网址的标签。
     */
    private static String buildQrCodeContent(String code) {
        if (StrUtil.isEmpty(traceBaseUrl)) {
        return buildQrCodeContent(code, resolveTraceUrl());
    }
    /**
     * 组装二维码内容(使用调用方给定的追溯网址)
     * <p>
     * 标签上既有二维码内容、又有可读的「追溯网址」文本时,两处必须传同一个网址:
     * 分别取值可能跨越缓存刷新,导致同一张标签上文字与二维码指向不同地址。
     *
     * @param code     码值
     * @param traceUrl 本次生效的追溯网址,为空表示未配置
     */
    private static String buildQrCodeContent(String code, String traceUrl) {
        if (StrUtil.isEmpty(traceUrl)) {
            if (allowPlainQrCode) {
                return code;
            }
            throw exception(PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
        }
        return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
        return traceUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
    }
    /**
@@ -217,16 +274,21 @@
                                                MesProBatchDO batch,
                                                Map<Long, MesMdmItemDO> itemMap,
                                                Map<Long, AdminUserRespDTO> userMap) {
        // 整张标签只取一次配置快照:可读文本与二维码内容共用同一个追溯网址。
        // 若两处各自取值,缓存可能恰好在其间刷新,同一张标签上的文字与二维码就会指向不同地址。
        MesTraceConfigSnapshot snapshot = currentSnapshot();
        String traceUrl = resolveTraceUrl(snapshot);
        LinkedHashMap<String, String> keyValues = new LinkedHashMap<>();
        // ===== 四要素(国标要求,顺序固定,缺失即阻断导出)=====
        putRequired(keyValues, "品种名称", resolveItemName(bagCode, batch, itemMap),
                PRO_QR_LABEL_ITEM_NAME_MISSING);
        putRequired(keyValues, imported ? "进口商名称" : "生产经营者名称", resolveOperatorName(),
                PRO_QR_LABEL_OPERATOR_MISSING);
        putRequired(keyValues, isImported(snapshot) ? "进口商名称" : "生产经营者名称",
                resolveOperatorName(snapshot), PRO_QR_LABEL_OPERATOR_MISSING);
        putRequired(keyValues, "单元识别代码", resolveUnitCode(bagCode.getCode()),
                PRO_QR_LABEL_UNIT_CODE_MISSING);
        putRequired(keyValues, "追溯网址", resolveTraceUrl(), PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
        putRequired(keyValues, "追溯网址", traceUrl, PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
        // ===== 以下为辅助生产信息 =====
        if (bagCode.getBagNo() != null) {
@@ -243,7 +305,7 @@
        }
        return QrLabelInfo.builder()
                .fileName(bagCode.getCode())
                .code(buildQrCodeContent(bagCode.getCode()))
                .code(buildQrCodeContent(bagCode.getCode(), traceUrl))
                .keyValues(keyValues)
                .build();
    }