假设需要设计一套公用IP工具类,功能上需要实现某个IP是否合法,其通过与可允许访问IP列表(可以是单个、多个、区间)进行比较得到判定结果。另外,对调用者提供IP检查工具(比如判断一个字符串是否是ipv4或ipv6格式)。
设计需要达到以下几点要求:
1).第一版本只需要支持IPv4格式;
2).对于调用者而言进行IP合法性检验时只允许通过工厂类得到实例,以便统一管理;
3).预留扩展功能,使得支持ipv6的时候只增加具体的实现类即可。
4).需要有明确的异常信息。具体实现如下:
package common.util.ip;import common.util.ip.exception.IPFormatException;public interface IPValidator {

/**
 * 根据传入的源IP地址和目的IP地址进行匹配检验
 * @param {String} srcIP --源IP地址
 * @param {String} destIP --目的IP
 * @throws IPFormatException
 */
public boolean singleValidate(String srcIP, String destIP) throws IPFormatException;
/**
 * 根据传入的源IP地址和目的IP地址进行匹配检验
 * @param {String[]} multipleIP --源IP地址组
 * @param {String} destIP --目的IP(只能是一个IP)
 * @throws IPFormatException
 */
public boolean multipleValidate(String[] multipleIP, String destIP) throws IPFormatException;
/**
 * 根据传入的源IP地址和目的IP地址进行匹配检验
 * @param {String} regionStartIP --源IP起始地址
 * @param {String} regionEndIP --源IP结束地址
 * @param {String} destIP --目的IP
 * @throws IPFormatException
 */
public boolean regionValidate(String regionStartIP, String regionEndIP, String destIP) throws IPFormatException;

}
package common.util.ip.impl;import common.util.ip.IPFormatChecker;
import common.util.ip.IPValidator;
import common.util.ip.exception.IPFormatException;/**
 * IPv4校验类
 * @author 莫取网名
 */
class IPv4Validator implements IPValidator{ public boolean singleValidate(String srcIP, String destIP) throws IPFormatException{
if(IPFormatChecker.checkIPv4(srcIP) && IPFormatChecker.checkIPv4(destIP)){
return destIP.equals(srcIP);
}
return false;
}
public boolean multipleValidate(String[] multipleIP, String destIP) throws IPFormatException {
if(!IPFormatChecker.checkIPv4(destIP))
return false;

int ipCount=multipleIP.length;
for(int i=0;i<ipCount;i++){
if(IPFormatChecker.checkIPv4(multipleIP[i])){
if(destIP.equals(multipleIP[i])){
return true;
}
}
}
return false;
}
public boolean regionValidate(String regionStartIP, String regionEndIP, String destIP) throws IPFormatException {
if(!IPFormatChecker.checkIPv4(regionStartIP))
return false;

if(!IPFormatChecker.checkIPv4(regionEndIP))
return false;

if(!IPFormatChecker.checkIPv4(destIP))
return false;

//由于.号在正则表达式中是特殊符号,所以split的参数需要进行转义
String[] sSectionStartIPNums = regionStartIP.split("\\."); // 得到源IP段起始地址的4位数字串
String[] sSectionEndIPNums = regionEndIP.split("\\."); // 得到源IP段结束地址的4位数字串
String[] sDestIPNums = destIP.split("\\."); //得到目的IP地址的4位数字串
long ipSectionStart = 0L, ipSectionEnd = 0L, ipDest = 0L;
for (int i = 0; i < 4; i++) {//将三个IP的数字串转换成32位的长整型数据
ipSectionStart = ipSectionStart << 8 | Integer.parseInt(sSectionStartIPNums[i]);
ipSectionEnd = ipSectionEnd << 8 | Integer.parseInt(sSectionEndIPNums[i]);
ipDest = ipDest << 8 | Integer.parseInt(sDestIPNums[i]);
}

//如果起始IP大于结束IP,则进行交换
if (ipSectionStart > ipSectionEnd) {
long temp = ipSectionStart;
ipSectionStart = ipSectionEnd;
ipSectionEnd = temp;
}

return ipSectionStart <= ipDest && ipDest <= ipSectionEnd;
}}
package common.util.ip.impl;import common.util.ip.IPValidator;/**
 * IP检验类工厂
 * @author 莫取网名
 */
public class IPValidatorFactory { public static IPValidator getInstance(String className) {
IPValidator ipValidator = null;
try {
// 利用反射获取类型
ipValidator = (IPValidator) Class.forName(className).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} return ipValidator;
}
}
package common.util.ip.exception;/**
 * IP格式异常类
 * @author 莫取网名
 *
 */
public class IPFormatException extends Exception { private static final long serialVersionUID = 2018923238888254267L; public IPFormatException() {} public IPFormatException(String message) {
super(message);
} public IPFormatException(Throwable cause) {
super(cause);
} public IPFormatException(String message, Throwable cause) {
super(message, cause);
}
}
package common.util.ip;import common.util.ip.exception.IPFormatException;
/**
 * IP格式检查类
 * @author 莫取网名
 */
public class IPFormatChecker {

// private final static String IP_V4_REGULAR = "^((25[0-5]|2[0-4]\\d|[0-1]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[0-1]?\\d\\d?)$";
private final static String IP_V4_REGULAR = "((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
private final static String IP_V6_REGULAR = ""; //尚未定义(可自行定义)

/**
 * 检查某个IPv4的格式是否正确
 * @param {String} ip
 * @return {boolean}
 * @throws IPFormatException
 */
public static boolean checkIPv4(String ip) throws IPFormatException{
if(ip==null || "".equals(ip)){
throw new IPFormatException("IPv4地址不能为空!");
}

if(!ip.matches(IP_V4_REGULAR)){
throw new IPFormatException("IPv4["+ip+"]格式或内容不正确!");
}

return true;
}


/**
 * 检查某个IPv6的格式是否正确
 * @param {String} ip
 * @return {boolean}
 * @throws IPFormatException
 */
public static boolean checkIPv6(String ip) throws IPFormatException{
if(ip==null || "".equals(ip)){
throw new IPFormatException("IPv6地址不能为空!");
}

if(!ip.matches(IP_V6_REGULAR)){
throw new IPFormatException("IPv6["+ip+"]格式或内容不正确!");
}

return false;
}
}
package common.test;import common.util.ip.IPValidator;
import common.util.ip.exception.IPFormatException;
import common.util.ip.impl.IPValidatorFactory;public class IPTester {

public static void main(String[] args) throws IPFormatException {
String destIP = "192.168.10.1120"; //目标IP
IPValidator ipValidator = (IPValidator) IPValidatorFactory.getInstance("common.util.ip.impl.IPv4Validator");

String singleIP = "192.168.10.116";
System.out.println(ipValidator.singleValidate(singleIP,destIP)); //测试单个IP

String manyIP = "192.168.10.119,192.168.10.116,192.168.10.118";
System.out.println(ipValidator.multipleValidate(manyIP.split(","),destIP)); //测试多个IP

String regionStartIP = "192.168.10.111", regionEndIP = "192.168.11.111";
System.out.println(ipValidator.regionValidate(regionStartIP, regionEndIP, destIP)); //测试区间IP
}
}代码简易讲解:
1).通过定义接口,规范化开放的方法,尽全面的考虑可能需要的标准性的方法;
2).在工厂类中使用反射机制达到对扩展开放,对修改关闭的效果;
3).将工厂类与实现类放在同一个包中,但限制具体实现类IPv4Validator的包访问控制(class前不加任何修饰),使得工具调用者IPTester无法直接调用具体的实现类进行对象的构造,增加类安全性,及统一规范控制管理。
4).不将IPFormatChecker类中相关的方法放在相关类的原因,是因为这种校验具有通用性,可以对外开放。所以不需要也不必放在IPValidator接口中定义相关的方法以免增加接口的冗余。接口不能强迫子类去实现不是很有必要的方法。

解决方案 »

  1.   

    哇~很清晰啊。
    兄台有判断一ip是否在 某ip范围段内的方法吗? 思路也行。
      

  2.   

    设计需求有两点不明白的地方:
    一、本次设计应该是一个工具组件,承担两方面工作,一个是IP地址合法性校验,另一个是IP访问权限校验。
        我不明白的访问权限的校验部分,一般来说程序里面都会维护一个IP访问列表的,但是,楼主的程序里面没有这个列表。并且,访问权限不应该通过 判断源地址与目标地址相同 这个条件来判断,你想啊,我要寄信给别人,结果发件人和收件人相同才让发,这样的信,我们能发吗?
    二、三种类型的IP数据的校验(单个、多个、区域)
        单个IP的校验好理解,多个IP的校验应该就是重复多次的单个IP校验的过程,我不明白的是区域校验。我在学网络的时候,网络区域的概念应该是网段的概念,也就是说,某个区域应该是某个网段,通过掩码辅助表示的那种。最简单的就是A类、B类、C类网段,当然,我们做的肯定是无类区间的那种网段。设计上面没有什么不明白的,思路清晰。但是,也存在可优化的部分。比如:
    工厂模式里面,由于生产的产品是行为类型(只为方法调用,不为保存数据)的产品,所以,我们可以采用单例工厂,每次获得的都是同一个Action对象(这个对象的调用逻辑里面不存在数据共享和线程安全问题)。
    如果想采用多例工厂的设计模式,不妨给每个单例起个名字或者ID,每个ID对应产品的类名称,这样可以反射到对应的实现类,在更换实现类的时候,只需更改对应关系即可。当然了,这样就比较复杂了,还不如与Spring集成。
      

  3.   


    1.既然是工具组件,只需要负责数据处理或逻辑加工。而IP访问列表本身属于数据模型,它可以存在于配置文件中,也可以是数据库中,同时也可以来自网络I/O流。所以,在这里不必关心IP访问列表,具体应该交给专门负责读写IP访问列表的Reader或Writer类。判断源地址与目标地址相同,此处的源地址也就是从访问列表中获取的。
    2.是有A类、B类、C类网段,但本文提供的代码没有进行细分。
    在下写的也是抛砖引玉之用,希望大家多多交流!