package cn.iocoder.yudao.module.mes.framework.config;
|
|
import cn.hutool.core.util.StrUtil;
|
|
import java.net.URI;
|
import java.util.Optional;
|
|
/**
|
* 追溯网址校验器:判断一个追溯网址是否能印在种子标签上让消费者扫得开。
|
* <p>
|
* 追溯网址有两个来源——配置文件 {@code mes.trace-base-url} 与字典 {@code mes_trace_config}。
|
* 两者必须走**同一套**校验规则,否则从字典进来的值就能绕过标签合规检查,
|
* 印出消费者扫不开的标签。因此把规则收敛到这里,供启动校验与字典解析共同复用。
|
* <p>
|
* 注意:本类只做「地址本身是否可用」的静态判断,不含任何配置读取与异常抛出,便于单测。
|
*/
|
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<String> 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);
|
}
|
|
}
|