1.需求
产品要求:按用户ip自动获取城市。
2.解决
在网上找了挺久的,找到一个是线上版本和离线版本
1.线上版本
线上链接
还有离线版本经过技术主管的选型决定使用离线版本,因为跟我们的实际情况贴近。
2.离线版本开源项目ip2region
1.导入Maven包
GitHub地址:https://github.com/lionsoul2014/ip2region
它有分新版跟旧版不建议旧版因为有坑,我已经踩过建议喜新厌旧。
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
2.导入ip2region.xdb
注意是放在resources底下
3.获取IP地址工具类
package io.geekidea.springbootplus.framework.util;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 获取IP地址工具类
* @author geekidea
* @date 2018-11-08
*/
@Slf4j
public final class IpUtil {
private static final String UNKNOWN = "unknown";
private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1";
private IpUtil(){
throw new AssertionError();
}
/**
* 获取请求用户的IP地址
* @return
*/
public static String getRequestIp() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return getRequestIp(request);
}
/**
* 获取请求用户的IP地址
* @param request
* @return
*/
public static String getRequestIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (IPV6_LOCAL.equals(ip)){
ip = getLocalhostIp();
}
return ip;
}
public static String getLocalhostIp(){
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
}
return null;
}
/**
* 获取城市信息(如果后面ip2region.db有更新,https://github.com/lionsoul2014/ip2region)
*
* @param ip ip
* @return
*/
public static String getCityInfo(String ip) throws Exception {
//注解的代码运行本地可以,打包就会报错
//本地运行jar报java.io.FileNotFoundException: file:\C:\Users\Administrator\Desktop\user-system-2.0.jar!\BOOT-INF\lib\user-framework-2.0.jar!\ip2region.xdb (文件名、目录名或卷标语法不正确。)。
// String dbPath = Objects.requireNonNull(IpUtil.class.getResource("/ip2region.xdb")).getPath();
// File file = new File(dbPath);
// if ( file.exists() == false ) {
// log.error("xdb文件Error: Invalid ip2region.xdb file");
// }
// 获取ip库路径
ClassPathResource classPathResource = new ClassPathResource("ip2region.xdb");
if (classPathResource.getClassLoader() == null){
log.error("存储路径发生错误,没有被发现");
return null;
}
InputStream inputStream = classPathResource.getInputStream();
byte[] bytes = IoUtil.readBytes(inputStream);
Searcher searcher = Searcher.newWithBuffer(bytes);
//Searcher searcher = Searcher.newWithFileOnly(dbPath);
String result = searcher.search(ip);
if (StrUtil.isEmpty(result)) {
log.error("获取地理位置异常 {}", ip);
return "XX-XX-XX";
}
searcher.close();
return result;
}
}
4.如何修改ip2region.xdb
我的要求是要把里面的省市区跟我的数据库里面的数据进行匹配。
ip2region.xdb文件是有ip.mergetes.txt生成的,所以我们要在txt文件进行修改数据
数据修改完成查看文档有提供一个可视化工具。
5.serviceImpl实现类
@Override
public ApiResult queryBasicRegionByIP(HttpServletRequest request) throws Exception {
String requestIp = IpUtil.getRequestIp(request);
String cityInfo = IpUtil.getCityInfo(requestIp);
//获取的数据格式 中国|0|北京|北京市|电信
//下面就是业务代码了,我的是进行数据库匹配获取省市code
IpInfo ipInfo=new IpInfo();
}
返回对象
package yunping.com.system.entity;
import lombok.Data;
/**
* 域名信息.
*
* @author
* @date 2023年5月12日10:18:54
*/
@Data
public class IpInfo {
/*** 国家 */
private String country;
/*** 地区 */
private String region;
/*** 省 */
private String provinceId;
/*** 省 */
private String provinceNamee;
/*** 市 */
private String cityId;
/*** 市 */
private String cityName;
/*** 运营商 */
private String isp;
/*** 获取用户的ip地址 */
private String requestIp;
}
6.打包故障
有的项目打包,可能会出现空指针问题,原因是会把 ip2region.xdb 编译,这样就容易造成本来 9.6M 的内存变成了 15.2M,所以我们需要加一个插件配置,目的是过滤后不需要转码。注意查看target里面的xdb文件大小
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xdb</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
7.feign调用服务问题
比如A调用B,B要获取A的信息这个时候A需要弄一个拦截器FeignBasicAuthRequestInterceptor
package com.yunping.framework.core.filter;
import com.yunping.framework.common.constant.AsapConstants;
import com.yunping.framework.util.TraceIdUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
* 功能描述: Feign请求拦截器(设置请求头,传递登录信息)
* @Auther: Solming
* @Date: 2021/1/13 17:06
* @return: null
**/
@Slf4j
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (ObjectUtils.isEmpty(requestAttributes)){
return ;
}
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
// 跳过 content-length
if ("content-length".equals(name)){
continue;
}
requestTemplate.header(name, values);
}
} else {
log.info("feign interceptor error header:{}", requestTemplate);
}
}
}
调用
@FeignClient(
name = "${feign.url.userCenter}",configuration = FeignSupportConfig.class
)
public interface UserFeignService {
//通过ip地址,获取省市
@RequestMapping(value = "/api/userCenter/queryBasicRegionByIP", method = RequestMethod.POST,
consumes = "application/json;charset=UTF-8"
)
ApiResult queryBasicRegionByIP();
}
8.关于nginx代理问题。
在匹配的location {}模块中,添加如下参数。
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header uri $uri;
proxy_set_header client_origin $http_origin;
9.参考资料
https://blog.csdn.net/qq_44138925/article/details/128843196
https://blog.csdn.net/wanglei9876/article/details/82146157
https://my.oschina.net/mdxlcj/blog/1922106
https://blog.csdn.net/weixin_43845227/article/details/111678032