Snail-Job 的客户端 IP 获取机制可以改造为类似 Spring Cloud 的 cloud.inetutils.preferred-networks 配置方式,实现网络段优先配置并自动替换 snail-job.host 参数。
实现思路
我们需要创建几个核心组件:
- 配置类:读取 snailjob.network 配置项
- 网络工具类:根据配置选择最佳 IP
- 自动配置类:将选定的 IP 注入到 snail-job.host 参数中
代码实现
- 首先,创建配置类来读取网络优先配置:
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@ConfigurationProperties(prefix = "snailjob.network")
public class SnailJobNetworkProperties {
/**
* 优先使用的网络段列表,支持CIDR表示法,如:192.168.1.0/24
*/
private List<String> preferredNetworks;
/**
* 是否优先使用IPv4地址
*/
private boolean preferIpv4 = true;
/**
* 是否优先使用站点本地地址(如私有网络地址)
*/
private boolean preferSiteLocalAddress = true;
// getter和setter方法
public List<String> getPreferredNetworks() {
return preferredNetworks;
}
public void setPreferredNetworks(List<String> preferredNetworks) {
this.preferredNetworks = preferredNetworks;
}
public boolean isPreferIpv4() {
return preferIpv4;
}
public void setPreferIpv4(boolean preferIpv4) {
this.preferIpv4 = preferIpv4;
}
public boolean isPreferSiteLocalAddress() {
return preferSiteLocalAddress;
}
public void setPreferSiteLocalAddress(boolean preferSiteLocalAddress) {
this.preferSiteLocalAddress = preferSiteLocalAddress;
}
}
- 接下来,创建网络工具类来实现 IP 选择逻辑:
import org.springframework.util.StringUtils;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;
public class SnailJobNetworkUtils {
private static final Pattern IPV4_PATTERN = Pattern.compile(
"^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
private final SnailJobNetworkProperties properties;
public SnailJobNetworkUtils(SnailJobNetworkProperties properties) {
this.properties = properties;
}
/**
* 获取符合条件的首选IP地址
*/
public String findPreferredHostAddress() {
List<InetAddress> validAddresses = findAllValidAddresses();
return selectPreferredAddress(validAddresses);
}
private List<InetAddress> findAllValidAddresses() {
List<InetAddress> result = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (ni.isUp()) {
Enumeration<InetAddress> addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if (isValidAddress(address)) {
result.add(address);
}
}
}
}
} catch (SocketException e) {
throw new RuntimeException("Failed to retrieve network interfaces", e);
}
return result;
}
private boolean isValidAddress(InetAddress address) {
if (address.isLoopbackAddress()) {
return false;
}
// 如果配置了优先IPv4,则排除IPv6地址
if (properties.isPreferIpv4() && address instanceof Inet6Address) {
return false;
}
// 如果配置了优先本地地址,则检查是否为本地地址
if (properties.isPreferSiteLocalAddress() && !address.isSiteLocalAddress()) {
return false;
}
return true;
}
private String selectPreferredAddress(List<InetAddress> addresses) {
// 首先尝试按优先网络段匹配
if (properties.getPreferredNetworks() != null && !properties.getPreferredNetworks().isEmpty()) {
for (String network : properties.getPreferredNetworks()) {
String[] parts = network.split("/");
String baseIp = parts[0];
int prefixLength = Integer.parseInt(parts[1]);
for (InetAddress address : addresses) {
if (matchesNetwork(address, baseIp, prefixLength)) {
return address.getHostAddress();
}
}
}
}
// 如果没有匹配的网络段,返回第一个有效的地址
if (!addresses.isEmpty()) {
return addresses.get(0).getHostAddress();
}
// 最后尝试使用回环地址
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
return "127.0.0.1";
}
}
private boolean matchesNetwork(InetAddress address, String baseIp, int prefixLength) {
if (address instanceof Inet6Address && !IPV4_PATTERN.matcher(baseIp).matches()) {
// IPv6匹配逻辑
byte[] addrBytes = address.getAddress();
byte[] baseBytes = textToNumericFormatV6(baseIp);
if (baseBytes == null || addrBytes.length != baseBytes.length) {
return false;
}
int bitsToCompare = Math.min(prefixLength, addrBytes.length * 8);
for (int i = 0; i < bitsToCompare / 8; i++) {
if (addrBytes[i] != baseBytes[i]) {
return false;
}
}
int remainingBits = bitsToCompare % 8;
if (remainingBits != 0) {
int mask = 0xFF << (8 - remainingBits);
if ((addrBytes[bitsToCompare / 8] & mask) != (baseBytes[bitsToCompare / 8] & mask)) {
return false;
}
}
return true;
} else if (address instanceof Inet4Address && IPV4_PATTERN.matcher(baseIp).matches()) {
// IPv4匹配逻辑
byte[] addrBytes = address.getAddress();
String[] baseParts = baseIp.split("\\.");
int[] baseOctets = new int[4];
for (int i = 0; i < 4; i++) {
baseOctets[i] = Integer.parseInt(baseParts[i]);
}
int maskBits = prefixLength;
int mask = -1 << (32 - maskBits);
int addrInt = ((addrBytes[0] & 0xFF) << 24) |
((addrBytes[1] & 0xFF) << 16) |
((addrBytes[2] & 0xFF) << 8) |
(addrBytes[3] & 0xFF);
int baseInt = (baseOctets[0] << 24) |
(baseOctets[1] << 16) |
(baseOctets[2] << 8) |
baseOctets[3];
return (addrInt & mask) == (baseInt & mask);
}
return false;
}
// IPv6文本格式转字节数组
private byte[] textToNumericFormatV6(String ipAddress) {
// 简化实现,实际应使用更完善的IPv6解析
try {
return Inet6Address.getByName(ipAddress).getAddress();
} catch (UnknownHostException e) {
return null;
}
}
}
- 最后,创建自动配置类,将获取到的 IP 注入到 snail-job.host 参数中:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableConfigurationProperties(SnailJobNetworkProperties.class)
public class SnailJobAutoConfiguration {
@Autowired
private SnailJobNetworkProperties networkProperties;
@Autowired
private ConfigurableEnvironment environment;
@Bean
public SnailJobNetworkUtils snailJobNetworkUtils() {
return new SnailJobNetworkUtils(networkProperties);
}
@Bean
public Object configureSnailJobHost(SnailJobNetworkUtils networkUtils) {
// 获取首选IP地址
String clientIp = networkUtils.findPreferredHostAddress();
// 创建一个高优先级的属性源,将获取的IP注入到snail-job.host
Map<String, Object> source = new HashMap<>();
source.put("snail-job.host", clientIp);
environment.getPropertySources().addFirst(
new MapPropertySource("snailJobHost", source)
);
System.out.println("自动配置 Snail-Job 客户端IP为: " + clientIp);
return null; // 不需要实际的bean实例,只是触发配置
}
}
使用方法
在你的 application.yml 或 application.properties 中添加以下配置:
snailjob:
network:
preferred-networks:
- 192.168.1.0/24 # 优先使用192.168.1.x网段的IP
- 10.0.0.0/8 # 其次使用10.x.x.x网段的IP
prefer-ipv4: true # 优先使用IPv4地址
prefer-site-local-address: true # 优先使用本地地址
或者使用 properties 格式:
snailjob.network.preferred-networks[0]=192.168.1.0/24
snailjob.network.preferred-networks[1]=10.0.0.0/8
snailjob.network.prefer-ipv4=true
snailjob.network.prefer-site-local-address=true
这个配置会自动:
①. 扫描所有网络接口
②. 根据配置的优先顺序选择最佳 IP 地址
③. 将选中的 IP 地址注入到 snail-job.host 参数中
这样,你就不需要手动设置 snail-job.host 了,系统会根据网络配置自动选择最合适的 IP 地址。