Snail-Job 的客户端 IP 获取机制可以改造为类似 Spring Cloud 的 cloud.inetutils.preferred-networks 配置方式

Viewed 46

Snail-Job 的客户端 IP 获取机制可以改造为类似 Spring Cloud 的 cloud.inetutils.preferred-networks 配置方式,实现网络段优先配置并自动替换 snail-job.host 参数。

实现思路
我们需要创建几个核心组件:

  1. 配置类:读取 snailjob.network 配置项
  2. 网络工具类:根据配置选择最佳 IP
  3. 自动配置类:将选定的 IP 注入到 snail-job.host 参数中

代码实现

  1. 首先,创建配置类来读取网络优先配置:
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;
    }
}
  1. 接下来,创建网络工具类来实现 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;
        }
    }
}
  1. 最后,创建自动配置类,将获取到的 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 地址。

2 Answers

这个我已经跟作者提过了 但是你这个单独加一个配置不太友好 既然用了spring的工具自然是跟spring的用法保持一致的 这样所有跟spring有关的中间件或者依赖都会生效

建议基于这个来改com.aizuda.snailjob.common.core.util.NetUtil , 你这个可以作为默认的ip获取 用户指定 > 动态匹配 > 本地 , 改完后提个pr 1.7.0-beta1分支哈,顺便把服务端的ip也改了