| | |
| | | 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; |
| | |
| | | */ |
| | | 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, ""); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 取当前生效的配置快照 |
| | | * |
| | | * @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()); |
| | | } |
| | | |
| | | 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 operatorName; |
| | | 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; |
| | | } |
| | | |
| | |
| | | * 是否进口种子场景:为 true 时标签与溯源页应展示「进口商名称」 |
| | | */ |
| | | public static boolean isImported() { |
| | | return imported; |
| | | return isImported(currentSnapshot()); |
| | | } |
| | | |
| | | private static boolean isImported(MesTraceConfigSnapshot snapshot) { |
| | | return snapshot != null ? snapshot.imported() : imported; |
| | | } |
| | | |
| | | /** |
| | |
| | | * 未配置溯源地址且未开启降级开关时直接报错,避免印出不含追溯网址的标签。 |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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) { |
| | |
| | | } |
| | | return QrLabelInfo.builder() |
| | | .fileName(bagCode.getCode()) |
| | | .code(buildQrCodeContent(bagCode.getCode())) |
| | | .code(buildQrCodeContent(bagCode.getCode(), traceUrl)) |
| | | .keyValues(keyValues) |
| | | .build(); |
| | | } |