描述
本文实现:
1、离线查询IP地址
2、IP地址精确到区域
3、IP地址支持国外IP
此时需要一个创建,比如我输入一个8.8.8.8的IP立马就需要返回给我一个中文地址信息,
类似于百度的IP搜索:
113.111.186.123
如果现在离线环境或者在线环境上本地部署一套IP地址查询,可以查阅本文章。
本文本地部署的结果演示(注意!本文演示的项目不包含任何一个在线API接口!!!
):
首先感谢GItHub地址:https://github.com/ljxi/GeoCN
作者的在线演示地址:
https://ipv4.netart.cn/
https://ipv4.netart.cn/113.111.186.123
这个仓库是用的Python 的 Fast API 开发的,此时我需要一个Java的,本文会有Java的项目地址。
还有一种技术,只有springboot的,返回数据不是准确,感兴趣的可以自己去看看:
ip2region仓库地址:https://gitee.com/lionsoul/ip2region
ip2region集成文章:https://cloud.tencent.com/developer/article/2373096
开始
1、下载IP库
mmdb 仓库:https://github.com/P3TERX/GeoLite.mmdb/releases(不主要)
mmdb IP库下载:https://github.com/P3TERX/GeoLite.mmdb/releases
只需要下载以下两个mmdb文件
GeoLite2-City.mmdb
GeoLite2-ASN.mmdb
如果你的网络出不了国,可以下载国内的:https://download.csdn.net/download/u014641168/91554209
还需下载:
GeoCN.mmdb
这个文件在作者的仓库中就能找到,地址:https://github.com/ljxi/GeoCN/releases/tag/Latest
如果打不开,看国内:https://download.csdn.net/download/u014641168/91554230 (我忘了整合到一块,将就着看吧)
代码
python
首先作者的代码时Python的,Python版本是3.9的,需要手动安装插件库
pip install ipaddress
pip install maxminddb
还有fastapi
1、代码如下:
import ipaddress
import maxminddb
from fastapi import FastAPI, Requestcity_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoLite2-City.mmdb')
asn_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoLite2-ASN.mmdb')
cn_reader = maxminddb.open_database('C:\\Users\\ad\\Desktop\\ip\\GeoCN.mmdb')
lang = ["zh-CN","en"]
asn_map = {9812:"东方有线",9389:"中国长城",17962:"天威视讯",17429:"歌华有线",7497:"科技网",24139:"华数",9801:"中关村",4538:"教育网",24151:"CNNIC",38019:"中国移动",139080:"中国移动",9808:"中国移动",24400:"中国移动",134810:"中国移动",24547:"中国移动",56040:"中国移动",56041:"中国移动",56042:"中国移动",56044:"中国移动",132525:"中国移动",56046:"中国移动",56047:"中国移动",56048:"中国移动",59257:"中国移动",24444:"中国移动",24445:"中国移动",137872:"中国移动",9231:"中国移动",58453:"中国移动",4134:"中国电信",4812:"中国电信",23724:"中国电信",136188:"中国电信",137693:"中国电信",17638:"中国电信",140553:"中国电信",4847:"中国电信",140061:"中国电信",136195:"中国电信",17799:"中国电信",139018:"中国电信",133776:"中国电信",58772:"中国电信",146966:"中国电信",63527:"中国电信",58539:"中国电信",58540:"中国电信",141998:"中国电信",138169:"中国电信",139203:"中国电信",58563:"中国电信",137690:"中国电信",63838:"中国电信",137694:"中国电信",137698:"中国电信",136167:"中国电信",148969:"中国电信",134764:"中国电信",134770:"中国电信",148981:"中国电信",134774:"中国电信",136190:"中国电信",140647:"中国电信",132225:"中国电信",140485:"中国电信",4811:"中国电信",131285:"中国电信",137689:"中国电信",137692:"中国电信",140636:"中国电信",140638:"中国电信",140345:"中国电信",38283:"中国电信",140292:"中国电信",140903:"中国电信",17897:"中国电信",134762:"中国电信",139019:"中国电信",141739:"中国电信",141771:"中国电信",134419:"中国电信",140276:"中国电信",58542:"中国电信",140278:"中国电信",139767:"中国电信",137688:"中国电信",137691:"中国电信",4809:"中国电信",58466:"中国电信",137687:"中国电信",134756:"中国电信",134760:"中国电信",133774:"中国电信",133775:"中国电信",4816:"中国电信",134768:"中国电信",58461:"中国电信",58519:"中国电信",58520:"中国电信",131325:"中国电信",4837:"中国联通",4808:"中国联通",134542:"中国联通",134543:"中国联通",10099:"中国联通",140979:"中国联通",138421:"中国联通",17621:"中国联通",17622:"中国联通",17816:"中国联通",140726:"中国联通",17623:"中国联通",136958:"中国联通",9929:"中国联通",58519:"中国联通",140716:"中国联通",4847:"中国联通",136959:"中国联通",135061:"中国联通",139007:"中国联通",59019:"金山云",135377:"优刻云",45062:"网易云",137718:"火山引擎",37963:"阿里云",45102:"阿里云国际",45090:"腾讯云",132203:"腾讯云国际",55967:"百度云",38365:"百度云",58519:"华为云", 55990:"华为云",136907:"华为云",4609:"澳門電訊",134773:"珠江宽频",1659:"台湾教育网",8075:"微软云",17421:"中华电信",3462:"HiNet",13335:"Cloudflare",55960:"亚马逊云",14618:"亚马逊云",16509:"亚马逊云",15169:"谷歌云",396982:"谷歌云",36492:"谷歌云",
}def get_as_info(number):r = asn_map.get(number)if r:return rdef get_des(d):for i in lang:if i in d['names']:return d['names'][i]return d['names']['en']def get_country(d):r = get_des(d)if r in ["香港", "澳门", "台湾"]:return "中国" + rreturn rdef province_match(s):arr=['内蒙古','黑龙江','河北','山西','吉林','辽宁','江苏','浙江','安徽','福建','江西','山东','河南','湖北','湖南','广东','海南','四川','贵州','云南','陕西','甘肃','青海','广西','西藏','宁夏','新疆','北京','天津','上海','重庆']for i in arr:if i in s:return ireturn ''def de_duplicate(regions):regions = filter(bool,regions)ret = [][ret.append(i) for i in regions if i not in ret]return retdef get_addr(ip, mask):network = ipaddress.ip_network(f"{ip}/{mask}", strict=False)first_ip = network.network_addressreturn f"{first_ip}/{mask}"def get_maxmind(ip: str):ret = {"ip":ip}asn_info = asn_reader.get(ip)if asn_info:as_ = {"number":asn_info["autonomous_system_number"],"name":asn_info["autonomous_system_organization"]}info = get_as_info(as_["number"])if info:as_["info"] = inforet["as"] = as_city_info, prefix = city_reader.get_with_prefix_len(ip)ret["addr"] = get_addr(ip, prefix)if not city_info:return retif "country" in city_info:country_code = city_info["country"]["iso_code"]country_name = get_country(city_info["country"])ret["country"] = {"code":country_code,"name":country_name}if "registered_country" in city_info:registered_country_code = city_info["registered_country"]["iso_code"]ret["registered_country"] = {"code":registered_country_code,"name":get_country(city_info["registered_country"])}regions = [get_des(i) for i in city_info.get('subdivisions', [])]if "city" in city_info:c = get_des(city_info["city"])if (not regions or c not in regions[-1])and c not in country_name:regions.append(c)regions = de_duplicate(regions)if regions:ret["regions"] = regionsreturn retdef get_cn(ip:str, info={}):ret, prefix = cn_reader.get_with_prefix_len(ip)if not ret:returninfo["addr"] = get_addr(ip, prefix)regions = de_duplicate([ret["province"],ret["city"],ret["districts"]])if regions:info["regions"] = regionsinfo["regions_short"] = de_duplicate([province_match(ret["province"]),ret["city"].replace('市',''),ret["districts"]])if "as" not in info:info["as"] = {}info["as"]["info"] = ret['isp']if ret['net']:info["type"] = ret['net']return retdef get_ip_info(ip):info = get_maxmind(ip)if "country" in info and info["country"]["code"] == "CN" and ("registered_country" not in info or info["registered_country"]["code"] == "CN"):get_cn(ip,info)return infodef query():while True:try:ip = input('IP: \t').strip()info = get_ip_info(ip)print(f"网段:\t{info['addr']}")if "as" in info:print(f"ISP:\t",end=' ')if "info" in info["as"]:print(info["as"]["info"],end=' ')else:print(info["as"]["name"],end=' ')if "type" in info:print(f"({info['type']})",end=' ')print(f"ASN{info['as']['number']}",end=' ')print(info['as']["name"])if "registered_country" in info and ("country" not in info or info["country"]["code"] != info["registered_country"]["code"]):print(f"注册地:\t{info['registered_country']['name']}")if "country" in info:print(f"使用地:\t{info['country']['name']}")if "regions" in info:print(f"位置: \t{' '.join(info['regions'])}")except Exception as e:print(e)raise efinally:print("\n")app = FastAPI()@app.get("/")
def api(request: Request, ip: str = None):if not ip:xff = request.headers.get("x-forwarded-for")if xff:ip = xff.split(",")[0]else:ip = request.headers.get("x-real-ip") or request.client.hostreturn get_ip_info(ip.strip())@app.get("/{ip}")
def path_api(ip):return get_ip_info(ip)if __name__ == '__main__':query()import uvicornuvicorn.run(app, host="0.0.0.0", port=8080, server_header=False, proxy_headers=True)
2、启动
uvicorn main:app --port 9000 --reload
3、浏览器访问
http://localhost:9000/113.111.186.123
Java
作者的Python代码不满足我的需求,手动转成Java格式
我的jdk1.8,maven 3.6
1、引入依赖
<!-- MaxMind DB Reader -->
<dependency><groupId>com.maxmind.db</groupId><artifactId>maxmind-db</artifactId><version>2.1.0</version>
</dependency>
2、代码类:
ASInfo.java
import lombok.Data;/*** @author: tongyao* @since: 2025-07-31 09:32*/
@Data
public class ASInfo {private Integer number;private String name;private String info;
}
CountryInfo.java
import lombok.Data;/*** @author: tongyao* @since: 2025-07-31 09:32*/
@Data
public class CountryInfo {private String code;private String name;
}
GeoCNResponse.java
import lombok.Data;@Data
public class GeoCNResponse {private String province;private String city;private String districts;private String isp;private String net;private int prefixLen;// 移除MaxMindDbConstructor注解public GeoCNResponse(String province,String city,String districts,String isp,String net,int prefixLen) {this.province = province;this.city = city;this.districts = districts;this.isp = isp;this.net = net;this.prefixLen = prefixLen;}
}
IPInfo.java
import lombok.Data;
import java.util.List;@Data
public class IPInfo {private String ip;private String addr;private String error;private String type;private CountryInfo country;private CountryInfo registeredCountry;private List<String> regions;private List<String> regionsShort;private ASInfo as;
}
GeoIPConfig.java
import com.maxmind.db.Reader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.File;
import java.io.IOException;@Configuration
public class GeoIPConfig {@Beanpublic Reader cityReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoLite2-City.mmdb"));}@Beanpublic Reader asnReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoLite2-ASN.mmdb"));}@Beanpublic Reader cnReader() throws IOException {return new Reader(new File("C:\\Users\\ad\\Desktop\\ip\\GeoCN.mmdb"));}
}
GeoIPService.java
import javax.servlet.http.HttpServletRequest;
import java.util.Map;public interface GeoIPService {IPInfo getIpInfo(String ip);IPInfo getIpInfo(HttpServletRequest request);
}
GeoIPServiceImpl.java
import com.maxmind.db.Reader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GeoIPServiceImpl implements GeoIPService {private final Reader cityReader;private final Reader asnReader;private final Reader cnReader;private final List<String> lang = Arrays.asList("zh-CN", "en");private final List<String> provinces = Arrays.asList("内蒙古", "黑龙江", "河北", "山西", "吉林", "辽宁", "江苏", "浙江","安徽", "福建", "江西", "山东", "河南", "湖北", "湖南", "广东","海南", "四川", "贵州", "云南", "陕西", "甘肃", "青海", "广西","西藏", "宁夏", "新疆", "北京", "天津", "上海", "重庆");private static final Map<Integer, String> ASN_MAP = new HashMap<Integer, String>() {{// 有线电视/网络服务商put(9812, "东方有线");put(9389, "中国长城");put(17962, "天威视讯");put(17429, "歌华有线");put(7497, "科技网");put(24139, "华数");put(9801, "中关村");put(4538, "教育网");put(24151, "CNNIC");// 中国移动put(38019, "中国移动");put(139080, "中国移动");put(9808, "中国移动");put(24400, "中国移动");put(134810, "中国移动");put(24547, "中国移动");put(56040, "中国移动");put(56041, "中国移动");put(56042, "中国移动");put(56044, "中国移动");put(132525, "中国移动");put(56046, "中国移动");put(56047, "中国移动");put(56048, "中国移动");put(59257, "中国移动");put(24444, "中国移动");put(24445, "中国移动");put(137872, "中国移动");put(9231, "中国移动");put(58453, "中国移动");// 中国电信put(4134, "中国电信");put(4812, "中国电信");put(23724, "中国电信");put(136188, "中国电信");put(137693, "中国电信");put(17638, "中国电信");put(140553, "中国电信");put(4847, "中国电信");put(140061, "中国电信");put(136195, "中国电信");put(17799, "中国电信");put(139018, "中国电信");put(133776, "中国电信");put(58772, "中国电信");put(146966, "中国电信");put(63527, "中国电信");put(58539, "中国电信");put(58540, "中国电信");put(141998, "中国电信");put(138169, "中国电信");put(139203, "中国电信");put(58563, "中国电信");put(137690, "中国电信");put(63838, "中国电信");put(137694, "中国电信");put(137698, "中国电信");put(136167, "中国电信");put(148969, "中国电信");put(134764, "中国电信");put(134770, "中国电信");put(148981, "中国电信");put(134774, "中国电信");put(136190, "中国电信");put(140647, "中国电信");put(132225, "中国电信");put(140485, "中国电信");put(4811, "中国电信");put(131285, "中国电信");put(137689, "中国电信");put(137692, "中国电信");put(140636, "中国电信");put(140638, "中国电信");put(140345, "中国电信");put(38283, "中国电信");put(140292, "中国电信");put(140903, "中国电信");put(17897, "中国电信");put(134762, "中国电信");put(139019, "中国电信");put(141739, "中国电信");put(141771, "中国电信");put(134419, "中国电信");put(140276, "中国电信");put(58542, "中国电信");put(140278, "中国电信");put(139767, "中国电信");put(137688, "中国电信");put(137691, "中国电信");put(4809, "中国电信");put(58466, "中国电信");put(137687, "中国电信");put(134756, "中国电信");put(134760, "中国电信");put(133774, "中国电信");put(133775, "中国电信");put(4816, "中国电信");put(134768, "中国电信");put(58461, "中国电信");put(58519, "中国电信");put(58520, "中国电信");put(131325, "中国电信");// 中国联通put(4837, "中国联通");put(4808, "中国联通");put(134542, "中国联通");put(134543, "中国联通");put(10099, "中国联通");put(140979, "中国联通");put(138421, "中国联通");put(17621, "中国联通");put(17622, "中国联通");put(17816, "中国联通");put(140726, "中国联通");put(17623, "中国联通");put(136958, "中国联通");put(9929, "中国联通");put(58519, "中国联通");put(140716, "中国联通");put(4847, "中国联通");put(136959, "中国联通");put(135061, "中国联通");put(139007, "中国联通");// 云服务商put(59019, "金山云");put(135377, "优刻云");put(45062, "网易云");put(137718, "火山引擎");put(37963, "阿里云");put(45102, "阿里云国际");put(45090, "腾讯云");put(132203, "腾讯云国际");put(55967, "百度云");put(38365, "百度云");put(58519, "华为云");put(55990, "华为云");put(136907, "华为云");// 其他运营商put(4609, "澳門電訊");put(134773, "珠江宽频");put(1659, "台湾教育网");put(8075, "微软云");put(17421, "中华电信");put(3462, "HiNet");put(13335, "Cloudflare");put(55960, "亚马逊云");put(14618, "亚马逊云");put(16509, "亚马逊云");put(15169, "谷歌云");put(396982, "谷歌云");put(36492, "谷歌云");}};@Autowiredpublic GeoIPServiceImpl(Reader cityReader, Reader asnReader, Reader cnReader) {this.cityReader = cityReader;this.asnReader = asnReader;this.cnReader = cnReader;}@Overridepublic IPInfo getIpInfo(String ip) {IPInfo info = new IPInfo();info.setIp(ip);try {InetAddress ipAddress = InetAddress.getByName(ip);// 处理ASN信息processAsnInfo(ipAddress, info);// 处理City信息Map<String, Object> cityInfo = cityReader.get(ipAddress,Map.class);if (cityInfo != null) {processCityInfo(cityInfo, info);// 如果是中国IP,处理GeoCN数据库if (isChinaIp(info)) {processCnInfo(ipAddress, info);}}} catch (Exception e) {e.printStackTrace();info.setError(e.getMessage());}return info;}@Overridepublic IPInfo getIpInfo(HttpServletRequest request) {String ip = Optional.ofNullable(request.getHeader("X-Forwarded-For")).map(xff -> xff.split(",")[0]).orElse(request.getRemoteAddr());return getIpInfo(ip);}private void processAsnInfo(InetAddress ipAddress, IPInfo info) throws IOException {Map<String, Object> asnInfo = asnReader.get(ipAddress,Map.class);if (asnInfo == null) return;ASInfo as = new ASInfo();as.setNumber(Integer.parseInt(asnInfo.get("autonomous_system_number").toString()));as.setName(asnInfo.get("autonomous_system_organization").toString());String ispName = getAsInfo(as.getNumber());if (ispName != null) {as.setInfo(ispName);}info.setAs(as);}private void processCityInfo(Map<String, Object> cityInfo, IPInfo info) {// 处理网络前缀Integer prefix = (Integer) cityInfo.get("prefix_len");if (prefix == null) prefix = 24; // 默认值info.setAddr(getAddr(info.getIp(), prefix));// 处理国家信息if (cityInfo.get("country") != null) {Map<String, Object> country = (Map<String, Object>) cityInfo.get("country");CountryInfo countryInfo = new CountryInfo();countryInfo.setCode((String) country.get("iso_code"));countryInfo.setName(getCountryName(country));info.setCountry(countryInfo);}// 处理注册国家信息if (cityInfo.get("registered_country") != null) {Map<String, Object> regCountry = (Map<String, Object>) cityInfo.get("registered_country");CountryInfo regCountryInfo = new CountryInfo();regCountryInfo.setCode((String) regCountry.get("iso_code"));regCountryInfo.setName(getCountryName(regCountry));info.setRegisteredCountry(regCountryInfo);}// 处理地区信息List<String> regions = new ArrayList<>();if (cityInfo.get("subdivisions") != null) {List<Map<String, Object>> subdivisions = (List<Map<String, Object>>) cityInfo.get("subdivisions");for (Map<String, Object> subd : subdivisions) {regions.add(getDescription(subd));}}// 处理城市信息if (cityInfo.get("city") != null) {Map<String, Object> city = (Map<String, Object>) cityInfo.get("city");String cityName = getDescription(city);if (regions.isEmpty() || !regions.get(regions.size() - 1).contains(cityName)) {regions.add(cityName);}}info.setRegions(deDuplicate(regions));}private void processCnInfo(InetAddress ipAddress, IPInfo info) throws IOException {Map<String, Object> cnData = cnReader.get(ipAddress,Map.class);GeoCNResponse cnInfo = new GeoCNResponse((String) cnData.get("province"),(String) cnData.get("city"),(String) cnData.get("districts"),(String) cnData.get("isp"),(String) cnData.get("net"),(Integer) cnData.getOrDefault("prefix_len", 24));if (cnInfo == null) return;// 更新地址信息info.setAddr(getAddr(info.getIp(), cnInfo.getPrefixLen()));// 处理地区信息List<String> regions = new ArrayList<>();if (cnInfo.getProvince() != null) regions.add(cnInfo.getProvince());if (cnInfo.getCity() != null) regions.add(cnInfo.getCity());if (cnInfo.getDistricts() != null) regions.add(cnInfo.getDistricts());info.setRegions(deDuplicate(regions));// 处理简短地区信息List<String> regionsShort = new ArrayList<>();if (cnInfo.getProvince() != null) regionsShort.add(provinceMatch(cnInfo.getProvince()));if (cnInfo.getCity() != null) regionsShort.add(cnInfo.getCity().replace("市", ""));if (cnInfo.getDistricts() != null) regionsShort.add(cnInfo.getDistricts());info.setRegionsShort(deDuplicate(regionsShort));// 更新ISP信息if (info.getAs() == null) {info.setAs(new ASInfo());}info.getAs().setInfo(cnInfo.getIsp());// 设置网络类型if (cnInfo.getNet() != null) {info.setType(cnInfo.getNet());}}// 辅助方法 (与Python版本完全一致的功能)private String getAsInfo(Integer number) {return ASN_MAP.get(number);}private String getDescription(Map<String, Object> geoObj) {if (geoObj.get("names") == null) return "";Map<String, String> names = (Map<String, String>) geoObj.get("names");for (String l : lang) {if (names.containsKey(l)) {return names.get(l);}}return names.get("en");}private String getCountryName(Map<String, Object> country) {String name = getDescription(country);if (Arrays.asList("香港", "澳门", "台湾").contains(name)) {return "中国" + name;}return name;}private String provinceMatch(String s) {for (String province : provinces) {if (s.contains(province)) {return province;}}return "";}private List<String> deDuplicate(List<String> regions) {return regions.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());}private String getAddr(String ip, int prefix) {try {// 简化实现,实际应使用类似Python的ipaddress计算return ip + "/" + prefix;} catch (Exception e) {return ip + "/" + prefix;}}private boolean isChinaIp(IPInfo info) {return info.getCountry() != null &&"CN".equals(info.getCountry().getCode()) &&(info.getRegisteredCountry() == null ||"CN".equals(info.getRegisteredCountry().getCode()));}
}
接口中调用即可。
项目仓库地址
https://gitee.com/Super_TongYao/offline-ip-library.git
Java运行后访问:http://localhost:9000/api/ip/8.8.8.8