package cn.iocoder.yudao.module.mes.framework.config;
import cn.hutool.core.util.StrUtil;
import java.net.URI;
import java.util.Optional;
/**
* 追溯网址校验器:判断一个追溯网址是否能印在种子标签上让消费者扫得开。
*
* 追溯网址有两个来源——配置文件 {@code mes.trace-base-url} 与字典 {@code mes_trace_config}。
* 两者必须走**同一套**校验规则,否则从字典进来的值就能绕过标签合规检查,
* 印出消费者扫不开的标签。因此把规则收敛到这里,供启动校验与字典解析共同复用。
*
* 注意:本类只做「地址本身是否可用」的静态判断,不含任何配置读取与异常抛出,便于单测。
*/
public final class MesTraceUrlValidator {
private MesTraceUrlValidator() {
}
/**
* 该地址是否可用于标签:空、占位符、本机地址、内网地址、无法解析的地址均视为不可用。
*
* @param url 追溯网址
* @return true 表示可印在标签上
*/
public static boolean isValid(String url) {
return invalidReason(url).isEmpty();
}
/**
* 判断追溯网址是否可用,不可用时给出人类可读的原因。
*
* @param url 追溯网址
* @return 空表示可用;有值即为不可用的原因
*/
public static Optional invalidReason(String url) {
String trimmed = StrUtil.trim(url);
if (StrUtil.isEmpty(trimmed)) {
return Optional.of("未配置。二维码标签必须包含追溯网址,"
+ "请填写公众扫码可访问的 H5 溯源页地址。");
}
// 占位符与本机地址:多为从示例配置直接拷来、忘记替换,印出去消费者必然扫不开
if (StrUtil.containsAnyIgnoreCase(trimmed, "your-domain", "example.com", "localhost")) {
return Optional.of("仍为占位符或本机地址:" + trimmed
+ ",印出的标签消费者将无法访问,请填写真实的外网地址。");
}
String host = extractHost(trimmed);
if (StrUtil.isEmpty(host)) {
return Optional.of("格式非法,无法解析主机名:" + trimmed);
}
if (isPrivateHost(host)) {
return Optional.of("指向内网地址(" + host + "):" + trimmed
+ ",公众扫码无法访问,请填写外网可访问的地址。");
}
return Optional.empty();
}
/**
* 从 URL 中提取主机名,无法解析时返回 null
*/
private static String extractHost(String url) {
try {
return URI.create(url).getHost();
} catch (IllegalArgumentException ex) {
return null;
}
}
/**
* 判断是否为本机 / 内网 / 链路本地地址
*/
private static boolean isPrivateHost(String host) {
if ("localhost".equalsIgnoreCase(host) || host.endsWith(".local")) {
return true;
}
String[] parts = host.split("\\.");
if (parts.length != 4) {
return false; // 域名形式,交由 DNS 解析,不在此处拦截
}
int a, b;
try {
a = Integer.parseInt(parts[0]);
b = Integer.parseInt(parts[1]);
} catch (NumberFormatException ex) {
return false;
}
// 127.0.0.0/8 本机、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 私网、169.254.0.0/16 链路本地
return a == 127
|| a == 10
|| (a == 172 && b >= 16 && b <= 31)
|| (a == 192 && b == 168)
|| (a == 169 && b == 254);
}
}