package cn.iocoder.yudao.module.mes.framework.config;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder;
|
import cn.iocoder.yudao.module.mes.framework.qrcode.QrCodeImageUtils;
|
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.Resource;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.context.annotation.Configuration;
|
import org.springframework.core.env.Environment;
|
|
import java.util.Arrays;
|
|
/**
|
* MES 二维码相关配置:启动时校验并把配置注入二维码标签构建器,避免侵入 Controller 调用点。
|
* <p>
|
* 种子标签属于受国标约束的合规物料,一旦印出即无法召回,因此这里采取「启动即校验、不合法即拒绝启动」
|
* 的策略,而不是等到导出时才失败或静默降级:
|
* <ul>
|
* <li>追溯网址必须为公众可访问地址,禁止内网 IP / 本机地址 / 未替换的占位符;</li>
|
* <li>生产经营者名称必填(标签四要素之一,缺失即为不合规标签);</li>
|
* <li>当前环境必须存在可渲染中文的字体(否则标签中文会变成方框)。</li>
|
* </ul>
|
* 追溯网址与生产经营者名称的**运行时**取值优先来自字典 {@code mes_trace_config},
|
* 可在管理后台「字典管理」中修改而无需重启;配置文件的值退居为兜底默认值。
|
* <p>
|
* 但启动校验**只针对配置文件**,原因有二:一是这道防线必须在数据库可用之前就能拦住不合规配置;
|
* 二是 @PostConstruct 阶段不应访问数据库。字典值的合规校验由 {@link MesTraceConfigResolver}
|
* 在读取时完成(非法则记 ERROR 并回退配置文件,见该类注释)。
|
*/
|
@Slf4j
|
@Configuration(proxyBeanMethods = false)
|
public class MesQrCodeConfig {
|
|
/**
|
* 本地环境 profile 名:该 profile 下放宽校验,便于本地开发调试
|
*/
|
private static final String PROFILE_LOCAL = "local";
|
|
@Resource
|
private MesProperties mesProperties;
|
|
@Resource
|
private Environment environment;
|
|
@Resource
|
private MesTraceConfigResolver mesTraceConfigResolver;
|
|
@PostConstruct
|
public void init() {
|
// 1. 注入配置文件的值(字体校验独立于业务配置,本地环境同样必须通过,否则标签中文不可读)
|
MesProQrLabelBuilder.setTraceBaseUrl(mesProperties.getTraceBaseUrl());
|
MesProQrLabelBuilder.setOperatorName(mesProperties.getOperatorName());
|
MesProQrLabelBuilder.setImporterName(mesProperties.getImporterName());
|
MesProQrLabelBuilder.setImported(mesProperties.isImported());
|
MesProQrLabelBuilder.setAllowPlainQrCode(mesProperties.isAllowPlainQrCode());
|
QrCodeImageUtils.assertCjkFontAvailable();
|
|
// 2. 校验配置文件。下面的校验与日志都必须读配置文件的值,因此字典来源不能在此之前装配
|
if (isLocalProfile()) {
|
log.info("[init] 当前为本地环境({}),跳过标签合规配置的严格校验;配置文件 traceBaseUrl={}",
|
PROFILE_LOCAL, mesProperties.getTraceBaseUrl());
|
} else {
|
validateTraceBaseUrl();
|
validateOperatorName();
|
log.info("[init] 二维码标签配置校验通过;配置文件 traceBaseUrl={},生产经营者={}",
|
mesProperties.getTraceBaseUrl(), MesProQrLabelBuilder.resolveOperatorName());
|
}
|
|
// 3. 最后装配字典来源:此后 resolveTraceUrl()/resolveOperatorName() 优先走字典
|
// (字典未配置则自动回退配置文件)。此处只装配方法引用、绝不调用——
|
// 解析字典需要访问数据库,不能在 @PostConstruct 阶段触发。
|
MesProQrLabelBuilder.setTraceConfigProvider(mesTraceConfigResolver::resolveSnapshot);
|
}
|
|
private boolean isLocalProfile() {
|
return Arrays.asList(environment.getActiveProfiles()).contains(PROFILE_LOCAL);
|
}
|
|
/**
|
* 校验配置文件中的追溯网址:必须为公众扫码可访问的地址
|
* <p>
|
* 与字典值共用 {@link MesTraceUrlValidator},保证两个来源的准入门槛完全一致,
|
* 不会出现「配置文件拦得住、字典绕得过」的缺口。
|
*/
|
private void validateTraceBaseUrl() {
|
String url = StrUtil.trim(mesProperties.getTraceBaseUrl());
|
MesTraceUrlValidator.invalidReason(url).ifPresent(reason -> {
|
throw new IllegalStateException("mes.trace-base-url " + reason
|
+ "(该项是字典 " + MesTraceConfigResolver.DICT_TYPE + " 未配置时的兜底值)");
|
});
|
}
|
|
/**
|
* 校验生产经营者名称:标签四要素之一,缺失即为不合规标签
|
* <p>
|
* 此处仅告警不阻断启动:生产经营者名称按主体区分,部分环境可能尚未配置,
|
* 强制启动失败会影响无关功能。真正的拦截点在标签构建处(缺失时拒绝导出),
|
* 因此不会出现"配置缺失却印出不合规标签"的情况。
|
*/
|
private void validateOperatorName() {
|
if (StrUtil.isBlank(MesProQrLabelBuilder.resolveOperatorName())) {
|
log.warn("[init] 生产经营者名称未配置(配置文件 mes.operator-name 与字典 {} 均为空)。"
|
+ "该字段为种子标签四要素之一,缺失时将无法导出二维码标签,请尽快补充。",
|
MesTraceConfigResolver.DICT_TYPE);
|
}
|
}
|
|
}
|