# 集成 ip2region 实现离线 IP 地址定位

离线 IP 地址定位库主要用于内网或想减少对外访问 http 带来的资源消耗。(代码已兼容支持 jar 包部署)

# 1、引入依赖

1
2
3
4
5
6
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>

# 2、添加工具类 RegionUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.ruoyi.common.utils;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import org.apache.commons.io.FileUtils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

/**
* 根据ip离线查询地址
*
* @author ruoyi
*/
public class RegionUtil
{
private static final Logger log = LoggerFactory.getLogger(RegionUtil.class);
private static final String JAVA_TEMP_DIR = "java.io.tmpdir";
static DbConfig config = null;
static DbSearcher searcher = null;
/**
* 初始化IP库
*/
static
{
try
{
// 因为jar无法读取文件,复制创建临时文件
String dbPath = RegionUtil.class.getResource("/ip2region/ip2region.db").getPath();
File file = new File(dbPath);
if (!file.exists())
{
String tmpDir = System.getProperties().getProperty(JAVA_TEMP_DIR);
dbPath = tmpDir + "ip2region.db";
file = new File(dbPath);
ClassPathResource cpr = new ClassPathResource("ip2region" + File.separator + "ip2region.db");
InputStream resourceAsStream = cpr.getInputStream();
if (resourceAsStream != null)
{
FileUtils.copyInputStreamToFile(resourceAsStream, file);
}
}
config = new DbConfig();
searcher = new DbSearcher(config, dbPath);
log.info("bean [{}]", config);
log.info("bean [{}]", searcher);
}
catch (Exception e)
{
log.error("init ip region error:{}", e);
}
}

/**
* 解析IP
*
* @param ip
* @return
*/
public static String getRegion(String ip)
{
try
{
// db
if (searcher == null || StringUtils.isEmpty(ip))
{
log.error("DbSearcher is null");
return StringUtils.EMPTY;
}
long startTime = System.currentTimeMillis();
// 查询算法
int algorithm = DbSearcher.MEMORY_ALGORITYM;
Method method = null;
switch (algorithm)
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
}

DataBlock dataBlock = null;
if (Util.isIpAddress(ip) == false)
{
log.warn("warning: Invalid ip address");
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
String result = dataBlock.getRegion();
long endTime = System.currentTimeMillis();
log.debug("region use time[{}] result[{}]", endTime - startTime, result);
return result;
}catch (Exception e)
{
log.error("error:{}", e);
}
return StringUtils.EMPTY;
}
}

3、修改 AddressUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.ruoyi.common.utils.ip;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.RegionUtil;
import com.ruoyi.common.utils.StringUtils;

/**
* 获取地址类
*
* @author ruoyi
*/
public class AddressUtils
{
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);

// 未知地址
public static final String UNKNOWN = "XX XX";

public static String getRealAddressByIP2(String ip)
{
String address = UNKNOWN;
// 内网不查询
if (IpUtils.internalIp(ip))
{
return "内网IP";
}
if (RuoYiConfig.isAddressEnabled())
{
try
{
String rspStr = RegionUtil.getRegion(ip);
if (StringUtils.isEmpty(rspStr))
{
log.error("获取地理位置异常 {}", ip);
return UNKNOWN;
}
String[] obj = rspStr.split("\\|");
String region = obj[2];
String city = obj[3];

return String.format("%s %s", region, city);
}
catch (Exception e)
{
log.error("获取地理位置异常 {}", e);
}
}
return address;
}
}

4、添加离线 IP 地址库插件

下载前端插件相关包和代码实现 ruoyi / 集成 ip2region 离线地址定位.zip

链接: https://pan.baidu.com/s/13JVC9jm-Dp9PfHdDDylLCQ 提取码: y9jt

5、添加离线 IP 地址库

在 src/main/resources 下新建 ip2region 复制文件 ip2region.db 到目录下。

# 在线 Ip 地址统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// IP地址查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";

// 未知地址
public static final String UNKNOWN = "XX XX";

public static String getRealAddressByIP(String ip)
{
// 内网不查询
if (IpUtils.internalIp(ip))
{
return "内网IP";
}
if (RuoYiConfig.isAddressEnabled())
{
try
{
String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
if (StringUtils.isEmpty(rspStr))
{
log.error("获取地理位置异常 {}", ip);
return UNKNOWN;
}
JSONObject obj = JSON.parseObject(rspStr);
String region = obj.getString("pro");
String city = obj.getString("city");
return String.format("%s %s", region, city);
}
catch (Exception e)
{
log.error("获取地理位置异常 {}", ip);
}
}
return UNKNOWN;
}

返回字符串

返回字符串

# Ip 地址统计

首先利用 Aop 拦截发送的服务器的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.durtime.frontapi.aspect;

import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.durtime.common.utils.DateUtils;
import com.durtime.common.utils.StringUtils;
import com.durtime.common.utils.ip.AddressUtils;
import com.durtime.common.utils.ip.IpUtils;
import com.durtime.frontapi.article.service.impl.VisitService;
import com.durtime.noapp.domain.AppVisitRecord;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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.text.SimpleDateFormat;
import java.util.Date;

/**
* @author kaile
* @className FrontLogAspect
* @date 2023/3/9
**/

@Aspect
@Component
public class FrontLogAspect {

protected Logger log = LoggerFactory.getLogger(getClass());


@Autowired
private VisitService visitService;

//要切入的地方,前端访问的接口
@Pointcut("execution(* com.durtime.frontapi.controller.FrontArticleController.getList(..))" +
" || execution(* com.durtime.frontapi.controller.FrontArticleController.getById(..))" +
" || execution(* com.durtime.frontapi.controller.FrontUserController.getCurrentUser(..))")
public void logpoint(){}

/**
* 半个小时只记录一次ip
*/
@Before("logpoint()")
public void beforeAction() {

//记录开始时间
long beginTime = System.currentTimeMillis();
// 从请求头中获取所需要的信息
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();

UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
//获取操作系统
//得到用户的浏览器名
String browser = ua.getBrowser().toString();
System.out.println("浏览器 "+browser);
//得到用户的操作系统名
String os = ua.getPlatform().toString();
System.out.println("os "+os);

// 获取请求地址
Object requestPath = request.getRequestURI();
String ip = IpUtils.getIpAddr(request);
//格式换开始时间
String optTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());

String realAddressByIP2 = AddressUtils.getRealAddressByIP2(ip);

String province = new String("内网IP");
String city = new String("内网IP");
String country = new String("内网IP");
//不是内网ip,进行省市的划分
if (!IpUtils.internalIp(ip)){
String[] s = realAddressByIP2.split(" ");
province = s[0];
city = s[1];
}

log.debug("*****************************************************************************************************************");
log.debug("[访问时间]>>>>> " + optTime);
log.debug("[访问 IP]>>>>> " + ip);
log.debug("[访问 地址]>>>>> " + AddressUtils.getRealAddressByIP(ip));
log.debug("[访问 地址]>>>>> " + province+ " "+city);
log.debug("[访问路由]>>>>> " + requestPath);
log.debug("[耗费时间]>>>>> " + (System.currentTimeMillis() - beginTime) + " ms");
log.debug("*****************************************************************************************************************\n");


//如果ip过期,先进行浏览量的增加
if(StringUtils.isEmpty(visitService.getRedisIp(ip))){
AppVisitRecord appVisitRecord = new AppVisitRecord();

appVisitRecord.setIp(ip);
appVisitRecord.setCountry(country);
appVisitRecord.setCity(city);
appVisitRecord.setProvince(province);
appVisitRecord.setOs(os);
appVisitRecord.setBrowser(browser);
appVisitRecord.setCreateTime(DateUtils.getNowDate());
visitService.addRecord(appVisitRecord);
visitService.doCountByPass();

//新增ip
visitService.addIp(ip);
}
}
}

访问量统计服务
VisitService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.durtime.frontapi.article.service.impl;

import com.durtime.noapp.domain.AppVisitRecord;
import com.durtime.noapp.service.IAppVisitRecordService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* @author kaile
* @className VisitService
* @date 2023/2/1
**/
@Service
public class VisitService {

protected Logger log = LoggerFactory.getLogger(getClass());
private String prefix = "vc:";

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private IAppVisitRecordService appVisitRecordService;

/**
* 初始化访问量
* @return
*/
public boolean initVisitCount(){
redisTemplate.opsForValue().set("visitcount",0);
return true;
}

/**
* 获取网站访问量 visitcount
* @return
*/
public int getVisitCount(){
int visitcount =(int) redisTemplate.opsForValue().get("visitcount");
log.info("visitcount:"+visitcount);
return visitcount;
}

/**
* 获取ip
* @return
*/
public String getRedisIp(String ip){
String Ip =(String) redisTemplate.opsForValue().get(prefix+ip);
log.info("Ip:"+Ip);
return Ip;
}

/**
* 设置网站访问量
* @param vc
* @return
*/
public boolean setVisitCount(int vc){
redisTemplate.opsForValue().set("visitcount",vc);
return true;
}


/**
* 设置ip,30分钟过期
* @param ip
* @return
*/
public boolean addIp(String ip){
redisTemplate.opsForValue().setIfAbsent(prefix+ip,"1",30, TimeUnit.MINUTES);
return false;
}

public boolean addRecord(AppVisitRecord appVisitRecord){
if (appVisitRecordService.insertAppVisitRecord(appVisitRecord)>0)
return true;
return false;

}


/**
* 半小时新增统计,等待自动过期
* @return
*/
public boolean doCountByPass(){
setVisitCount(getVisitCount()+1);
log.info("统计vc:"+getVisitCount());
return true;
}
}

# 网站访问量获取

1
visitService.getVisitCount();