java根据ip定位地理位置

       在项目开发中,我们可能会遇到这样的需求:需要在登录日志或者操作日志中记录客户端ip所在的地理位置。目前根据ip定位地理位置的第三方api有好几个,淘宝、新浪、百度等,这三种其实也有些缺点的:淘宝,开始几次可以成功根据ip获取对应地理位置,但后面就莫名其妙开始不行,直接通过浏览器获取又可以;新浪,之前一直可以,但最近不知道为什么不行了,访问直接报错(可能api修改了或者取消了吧);百度,需要申请百度地图开发者平台的开发者账号,请求时接口中需要加上百度提供的秘钥等信息,我试了下,好像不能定位国外的ip。

       所以,试了上面三个之后,决定换一种思路,不请求第三方api,改为直接在本地保存ip地址数据库的形式,于是纯真ip地址数据库成了我最好的选择,安装完成之后只需要用到qqwry.dat文件,并且每周会更新这个文件(点击ip.exe,在弹出的界面中点击“在线升级”即可),下面介绍一下使用的流程(文章末尾附2018.07.20版下载地址):

       首先,将qqwry.dat复制至项目的目录(自己定义),以便在项目中读取这个文件。

       其次,就是核心工作啦,根据ip到qqwry.dat中查找对应的地理位置信息,网上有很多方法,下面给出我找到的:

       IPLocation.java

/**
 * 用来封装ip相关信息,目前只有两个字段,ip所在的国家和地区
 *
 */
public class IPLocation {

	private String country;  
    private String area;  
      
    public IPLocation() {  
        country = area = "";  
    }  
      
    public IPLocation getCopy() {  
        IPLocation ret = new IPLocation();  
        ret.country = country;  
        ret.area = area;  
        return ret;  
    }  
  
    public String getCountry() {  
        return country;  
    }  
  
    public void setCountry(String country) {  
        this.country = country;  
    }  
  
    public String getArea() {  
        return area;  
    }  
  
    public void setArea(String area) {  
        //如果为局域网,纯真IP地址库的地区会显示CZ88.NET,这里把它去掉  
        if(area.trim().equals("CZ88.NET")){  
            this.area="本机或本网络";  
        }else{  
            this.area = area;  
        }  
    }
	
}

       Message.java


public class Message {

	public static String bad_ip_file="IP地址库文件错误";  
	public static String unknown_country="未知国家";  
	public static String unknown_area="未知地区";
	
}

       IPSeeker.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.huangbl.test.base.IPLocation;
import com.huangbl.test.base.Message;

public class IPSeeker {

	private static final Logger logger = LoggerFactory.getLogger(IPSeeker.class);
	
	//纯真IP数据库名  
    private String IP_FILE = "";  
    //保存的文件夹  
    private String INSTALL_DIR = "";
	
    // 一些固定常量,比如记录长度等等  
    private static final int IP_RECORD_LENGTH = 7;  
    private static final byte REDIRECT_MODE_1 = 0x01;  
    private static final byte REDIRECT_MODE_2 = 0x02;  
      
    // 随机文件访问类 
    private RandomAccessFile ipFile;  
    // 起始地区的开始和结束的绝对偏移  
    private long ipBegin, ipEnd;  
    // 为提高效率而采用的临时变量  
    private IPLocation loc;
    private byte[] buf; 
    private byte[] b4; 
    private byte[] b3;
      
    public IPSeeker()  {
        //文件名(qqwry.dat),保存在properties文件中
    	this.IP_FILE = PropertiesUtil.get("ip_file");
        //qqwry.dat所在目录,保存在properties文件中
    	this.INSTALL_DIR = PropertiesUtil.get("ip_dir");
        loc = new IPLocation();
        buf = new byte[100];
        b4 = new byte[4];
        b3 = new byte[3];
        try {  
            ipFile = new RandomAccessFile(IP_FILE, "r");  
        } catch (FileNotFoundException e) {  
            // 如果找不到这个文件,再尝试再当前目录下搜索,这次全部改用小写文件名  
            //     因为有些系统可能区分大小写导致找不到ip地址信息文件  
            String filename = new File(IP_FILE).getName().toLowerCase();  
            File[] files = new File(INSTALL_DIR).listFiles();  
            for(int i = 0; i < files.length; i++) {  
                if(files[i].isFile()) {  
                    if(files[i].getName().toLowerCase().equals(filename)) {  
                        try {  
                            ipFile = new RandomAccessFile(files[i], "r");  
                        } catch (FileNotFoundException e1) {  
                            logger.error("IP地址信息文件没有找到,IP显示功能将无法使用",e1);  
                            ipFile = null;  
                        }  
                        break;  
                    }  
                }  
            }  
        }   
        // 如果打开文件成功,读取文件头信息  
        if(ipFile != null) {  
            try {  
                ipBegin = readLong4(0);  
                ipEnd = readLong4(4);  
                if(ipBegin == -1 || ipEnd == -1) {  
                    ipFile.close();  
                    ipFile = null;  
                }             
            } catch (IOException e) {  
                logger.error("IP地址信息文件格式有错误,IP显示功能将无法使用",e);  
                ipFile = null;  
            }             
        }
    }  
      
    public IPLocation getIPLocation(String ip){  
        IPLocation location = getIPLocation(getIpByteArrayFromString(ip)); 
        return location;
    }  
      
    /** 
     * 根据ip搜索ip信息文件,得到IPLocation结构,所搜索的ip参数从类成员ip中得到 
     * @param ip 要查询的IP 
     * @return IPLocation结构 
     */  
    private IPLocation getIPLocation(byte[] ip) {  
        IPLocation info = null;  
        long offset = locateIP(ip);  
        if(offset != -1)  
            info = getIPLocation(offset);  
        if(info == null) {  
            info = new IPLocation();
            info.setCountry(Message.unknown_country);  
            info.setArea(Message.unknown_area);  
        }  
        return info;  
    }     
  
    /** 
     * 从offset位置读取4个字节为一个long,因为java为big-endian格式,所以没办法 
     * 用了这么一个函数来做转换 
     * @param offset 
     * @return 读取的long值,返回-1表示读取文件失败 
     */  
    private long readLong4(long offset) {  
        long ret = 0;  
        try {  
            ipFile.seek(offset);  
            ret |= (ipFile.readByte() & 0xFF);  
            ret |= ((ipFile.readByte() << 8) & 0xFF00);  
            ret |= ((ipFile.readByte() << 16) & 0xFF0000);  
            ret |= ((ipFile.readByte() << 24) & 0xFF000000);  
            return ret;  
        } catch (IOException e) {  
            return -1;  
        }  
    }  
  
    /** 
     * 从offset位置读取3个字节为一个long,因为java为big-endian格式,所以没办法 
     * 用了这么一个函数来做转换 
     * @param offset 整数的起始偏移 
     * @return 读取的long值,返回-1表示读取文件失败 
     */  
    private long readLong3(long offset) {  
        long ret = 0;  
        try {  
            ipFile.seek(offset);  
            ipFile.readFully(b3);  
            ret |= (b3[0] & 0xFF);  
            ret |= ((b3[1] << 8) & 0xFF00);  
            ret |= ((b3[2] << 16) & 0xFF0000);  
            return ret;  
        } catch (IOException e) {  
            return -1;  
        }  
    }     
      
    /** 
     * 从当前位置读取3个字节转换成long 
     * @return 读取的long值,返回-1表示读取文件失败 
     */  
    private long readLong3() {  
        long ret = 0;  
        try {  
            ipFile.readFully(b3);  
            ret |= (b3[0] & 0xFF);  
            ret |= ((b3[1] << 8) & 0xFF00);  
            ret |= ((b3[2] << 16) & 0xFF0000);  
            return ret;  
        } catch (IOException e) {  
            return -1;  
        }  
    }  
    
    /** 
     * 从offset位置读取四个字节的ip地址放入ip数组中,读取后的ip为big-endian格式,但是 
     * 文件中是little-endian形式,将会进行转换 
     * @param offset 
     * @param ip 
     */  
    private void readIP(long offset, byte[] ip) {  
        try {  
            ipFile.seek(offset);  
            ipFile.readFully(ip);  
            byte temp = ip[0];  
            ip[0] = ip[3];  
            ip[3] = temp;  
            temp = ip[1];  
            ip[1] = ip[2];  
            ip[2] = temp;  
        } catch (IOException e) {  
            logger.error("",e);  
        }  
    }  
      
    /** 
     * 把类成员ip和beginIp比较,注意这个beginIp是big-endian的 
     * @param ip 要查询的IP 
     * @param beginIp 和被查询IP相比较的IP 
     * @return 相等返回0,ip大于beginIp则返回1,小于返回-1。 
     */  
    private int compareIP(byte[] ip, byte[] beginIp) {  
        for(int i = 0; i < 4; i++) {  
            int r = compareByte(ip[i], beginIp[i]);  
            if(r != 0) {
            	return r;
            } 
        }  
        return 0;  
    }  
      
    /** 
     * 把两个byte当作无符号数进行比较 
     * @param b1 
     * @param b2 
     * @return 若b1大于b2则返回1,相等返回0,小于返回-1 
     */  
    private int compareByte(byte b1, byte b2) {  
        if((b1 & 0xFF) > (b2 & 0xFF)) { // 比较是否大于  
            return 1;  
        } else if((b1 ^ b2) == 0) {// 判断是否相等  
            return 0;  
        } else {
            return -1;
        }  
    }  
      
    /** 
     * 这个方法将根据ip的内容,定位到包含这个ip国家地区的记录处,返回一个绝对偏移 
     * 方法使用二分法查找。 
     * @param ip 要查询的IP 
     * @return 如果找到了,返回结束IP的偏移,如果没有找到,返回-1 
     */  
    private long locateIP(byte[] ip) {  
        long m = 0;  
        int r;  
        // 比较第一个ip项  
        readIP(ipBegin, b4);  
        r = compareIP(ip, b4);  
        if(r == 0) {
        	return ipBegin;  
        } else if(r < 0) { 
        	return -1;
        } 
        // 开始二分搜索  
        for(long i = ipBegin, j = ipEnd; i < j; ) {  
            m = getMiddleOffset(i, j);  
            readIP(m, b4);  
            r = compareIP(ip, b4);  
            // log.debug(Utils.getIpStringFromBytes(b));  
            if(r > 0) { 
            	i = m;  
            } else if(r < 0) {  
                if(m == j) {  
                    j -= IP_RECORD_LENGTH;  
                    m = j;  
                } else { 
                	j = m;
                }  
            } else { 
                return readLong3(m + 4); 
            } 
        }  
        // 如果循环结束了,那么i和j必定是相等的,这个记录为最可能的记录,但是并非  
        // 肯定就是,还要检查一下,如果是,就返回结束地址区的绝对偏移  
        m = readLong3(m + 4);  
        readIP(m, b4);  
        r = compareIP(ip, b4);  
        if(r <= 0) { 
        	return m;
        } else {
        	return -1;
        }  
    }  
      
    /** 
     * 得到begin偏移和end偏移中间位置记录的偏移 
     * @param begin 
     * @param end 
     * @return 
     */  
    private long getMiddleOffset(long begin, long end) {  
        long records = (end - begin) / IP_RECORD_LENGTH;  
        records >>= 1;  
        if(records == 0) { 
        	records = 1;  
        }
        return begin + records * IP_RECORD_LENGTH;  
    }  
      
    /** 
     * 给定一个ip国家地区记录的偏移,返回一个IPLocation结构 
     * @param offset 国家记录的起始偏移 
     * @return IPLocation对象 
     */  
    private IPLocation getIPLocation(long offset) {  
        try {  
            // 跳过4字节ip  
            ipFile.seek(offset + 4);  
            // 读取第一个字节判断是否标志字节  
            byte b = ipFile.readByte();  
            if(b == REDIRECT_MODE_1) {  
                // 读取国家偏移  
                long countryOffset = readLong3();  
                // 跳转至偏移处  
                ipFile.seek(countryOffset);  
                // 再检查一次标志字节,因为这个时候这个地方仍然可能是个重定向  
                b = ipFile.readByte();  
                if(b == REDIRECT_MODE_2) {  
                    loc.setCountry(readString(readLong3()));  
                    ipFile.seek(countryOffset + 4);  
                } else {
                    loc.setCountry(readString(countryOffset)); 
                } 
                // 读取地区标志  
                loc.setArea(readArea(ipFile.getFilePointer()));  
            } else if(b == REDIRECT_MODE_2) {  
                loc.setCountry(readString(readLong3()));  
                loc.setArea(readArea(offset + 8));  
            } else {  
                loc.setCountry(readString(ipFile.getFilePointer() - 1));  
                loc.setArea( readArea(ipFile.getFilePointer()));  
            }  
            return loc;  
        } catch (IOException e) {  
            return null;  
        }  
    } 
      
    /** 
     * 从offset偏移开始解析后面的字节,读出一个地区名 
     * @param offset 地区记录的起始偏移 
     * @return 地区名字符串 
     * @throws IOException 
     */  
    private String readArea(long offset) throws IOException {  
        ipFile.seek(offset);  
        byte b = ipFile.readByte();  
        if(b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2) {  
            long areaOffset = readLong3(offset + 1);  
            if(areaOffset == 0) { 
                return Message.unknown_area;  
            } else {
                return readString(areaOffset);
            }
        } else {
            return readString(offset);
        }
    } 
      
    /** 
     * 从offset偏移处读取一个以0结束的字符串 
     * @param offset 字符串起始偏移 
     * @return 读取的字符串,出错返回空字符串 
     */  
    private String readString(long offset) {  
        try {  
            ipFile.seek(offset);  
            int i;  
            for(i = 0, buf[i] = ipFile.readByte(); buf[i] != 0; buf[++i] = ipFile.readByte()) {};  
            if(i != 0) {   
                return getString(buf, 0, i, "GBK");  
            }
        } catch (IOException e) {             
            logger.error("",e);  
        }  
        return "";  
    }
    
    /** 
     * 从ip的字符串形式得到字节数组形式 
     * @param ip 字符串形式的ip 
     * @return 字节数组形式的ip 
     */  
    private static byte[] getIpByteArrayFromString(String ip) {  
        byte[] ret = new byte[4];  
        StringTokenizer st = new StringTokenizer(ip, ".");  
        try {  
            ret[0] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);  
            ret[1] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);  
            ret[2] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);  
            ret[3] = (byte)(Integer.parseInt(st.nextToken()) & 0xFF);  
        } catch (Exception e) {  
        	logger.error("从ip的字符串形式得到字节数组形式报错", e);  
        }  
        return ret;  
    }  
      
    /** 
     * 根据某种编码方式将字节数组转换成字符串 
     * @param b 字节数组 
     * @param offset 要转换的起始位置 
     * @param len 要转换的长度 
     * @param encoding 编码方式 
     * @return 如果encoding不支持,返回一个缺省编码的字符串 
     */  
    private static String getString(byte[] b, int offset, int len, String encoding) {  
        try {  
            return new String(b, offset, len, encoding);  
        } catch (UnsupportedEncodingException e) {  
            return new String(b, offset, len);  
        }  
    }
    
}

       PropertiesUtil.java


import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** 
 * 读取properties文件
 */
public class PropertiesUtil {

	private static final Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
	
	private static Map<String, String> map = null;
	
	public static String get(String key){
		if(map == null) {
			load();
		}
		return map.get(key);
	}
	
	public static Integer getInt(String key){
		if(map == null) {
			load();
		}
		String value = map.get(key);
		return Integer.parseInt(value);
	}
	
	synchronized static private void load(){
		logger.info("开始加载properties文件内容......");
		map = new HashMap<String, String>();
		Properties props = null;
		InputStream in = null;
		try {
			props = new Properties();
			in = PropertiesUtil.class.getClassLoader().getResourceAsStream("project.properties");
            //防止properties中的中文读取出来后乱码
			props.load(new InputStreamReader(in, "utf-8"));
			Set<Entry<Object, Object>> entries = props.entrySet();
			Iterator<Entry<Object, Object>> it =  entries.iterator();
			Entry<Object, Object> entry = null;
			String key = null;
			String value = null;
			while(it.hasNext()){
				entry = it.next();
				key = (String)entry.getKey();
				value = (String)entry.getValue();
				map.put(key, value);
			}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("读取properties文件出现异常......");
		} finally {
			try {
				if(null != in) {
                    in.close();
                }
			} catch (Exception e2) {
				e2.printStackTrace();
				logger.error("properties文件流关闭出现异常......");
			}
		}
		logger.info("properties文件内容加载完成......");
	}
	
}

纯真ip地址数据库V2018.07.20下载地址:https://download.csdn.net/download/genaro26/10561277

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值