瀏覽代碼

feat: 集成ip2region 实现离线IP地址定位库

lishuyan 3 年之前
父節點
當前提交
2c534d82ce

+ 9 - 0
pom.xml

@@ -41,6 +41,8 @@
         <elasticsearch.version>7.14.0</elasticsearch.version>
         <skywalking-toolkit.version>8.14.0</skywalking-toolkit.version>
         <bouncycastle.version>1.72</bouncycastle.version>
+        <!-- 离线IP地址定位库 -->
+        <ip2region.version>2.6.6</ip2region.version>
 
         <!-- 临时修复 snakeyaml 漏洞 -->
         <snakeyaml.version>1.33</snakeyaml.version>
@@ -348,6 +350,13 @@
                 <version>${fastjson.version}</version>
             </dependency>
 
+            <!-- 离线IP地址定位库 ip2region -->
+            <dependency>
+                <groupId>org.lionsoul</groupId>
+                <artifactId>ip2region</artifactId>
+                <version>${ip2region.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 

+ 6 - 0
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysOperLog.java

@@ -98,6 +98,12 @@ public class SysOperLog implements Serializable {
     @ExcelProperty(value = "操作地址")
     private String operIp;
 
+    /**
+     * 操作地点
+     */
+    @ExcelProperty(value = "操作地点")
+    private String operLocation;
+
     /**
      * 请求参数
      */

+ 6 - 4
ruoyi-auth/src/main/java/com/ruoyi/auth/listener/UserActionListener.java

@@ -9,7 +9,7 @@ import cn.hutool.http.useragent.UserAgentUtil;
 import com.ruoyi.common.core.constant.CacheConstants;
 import com.ruoyi.common.core.enums.UserType;
 import com.ruoyi.common.core.utils.ServletUtils;
-import com.ruoyi.common.core.utils.ip.AddressUtils;
+import com.ruoyi.common.core.utils.ip.IpAddressUtil;
 import com.ruoyi.common.redis.utils.RedisUtils;
 import com.ruoyi.common.satoken.utils.LoginHelper;
 import com.ruoyi.system.api.domain.SysUserOnline;
@@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import javax.servlet.http.HttpServletRequest;
 import java.time.Duration;
 
 /**
@@ -39,12 +40,13 @@ public class UserActionListener implements SaTokenListener {
     public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
         UserType userType = UserType.getUserType(loginId.toString());
         if (userType == UserType.SYS_USER) {
-            UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
-            String ip = ServletUtils.getClientIP();
+            HttpServletRequest request = ServletUtils.getRequest();
+            UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
+            String ip = IpAddressUtil.getIp(request);
             LoginUser user = LoginHelper.getLoginUser();
             SysUserOnline userOnline = new SysUserOnline();
             userOnline.setIpaddr(ip);
-            userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
+            userOnline.setLoginLocation(IpAddressUtil.getCityInfo(ip));
             userOnline.setBrowser(userAgent.getBrowser().getName());
             userOnline.setOs(userAgent.getOs().getName());
             userOnline.setLoginTime(System.currentTimeMillis());

+ 6 - 0
ruoyi-common/ruoyi-common-core/pom.xml

@@ -104,6 +104,12 @@
             <artifactId>spring-boot-configuration-processor</artifactId>
         </dependency>
 
+        <!-- 离线IP地址定位库 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 86 - 0
ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ip/IpAddressUtil.java

@@ -0,0 +1,86 @@
+package com.ruoyi.common.core.utils.ip;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.net.Ipv4Util;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import com.ruoyi.common.core.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * 根据ip地址定位工具类,离线方式
+ * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
+ *
+ * @author lishuyan
+ */
+@Slf4j
+public class IpAddressUtil {
+
+    private static final String LOCAL_REMOTE_HOST = "0:0:0:0:0:0:0:1";
+
+    private static final Searcher searcher;
+
+    /**
+     * 获取客户端ip
+     */
+    public static String getIp(HttpServletRequest request) {
+        if (ObjectUtil.isEmpty(request)) {
+            return Ipv4Util.LOCAL_IP;
+        } else {
+            try {
+                String remoteHost = ServletUtil.getClientIP(request);
+                return LOCAL_REMOTE_HOST.equals(remoteHost) ? Ipv4Util.LOCAL_IP : remoteHost;
+            } catch (Exception e) {
+                return Ipv4Util.LOCAL_IP;
+            }
+        }
+    }
+
+    static {
+        String fileName = "/ip2region.xdb";
+        File existFile = FileUtil.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+        if (!FileUtil.exist(existFile)) {
+            InputStream resourceAsStream = IpAddressUtil.class.getResourceAsStream(fileName);
+            if (ObjectUtil.isEmpty(resourceAsStream)) {
+                throw new ServiceException(">>>>>>>> IpAddressUtil初始化失败,原因:IP地址库数据不存在!");
+            }
+            FileUtil.writeFromStream(resourceAsStream, existFile);
+        }
+
+        String dbPath = existFile.getPath();
+
+        // 1、从 dbPath 加载整个 xdb 到内存。
+        byte[] cBuff;
+        try {
+            cBuff = Searcher.loadContentFromFile(dbPath);
+        } catch (Exception e) {
+            throw new ServiceException(">>>>>>>> IpAddressUtil初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
+        }
+        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
+        try {
+            searcher = Searcher.newWithBuffer(cBuff);
+        } catch (Exception e) {
+            throw new ServiceException(">>>>>>>> IpAddressUtil初始化失败,原因:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据IP地址离线获取城市
+     */
+    public static String getCityInfo(String ip) {
+        try {
+            ip = ip.trim();
+            // 3、执行查询
+            String region = searcher.search(ip);
+            return region.replace("0|", "").replace("|0", "");
+        } catch (Exception e) {
+            log.error("IP地址离线获取城市异常 {}", ip);
+            return "未知";
+        }
+    }
+}

二進制
ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb


+ 3 - 1
ruoyi-common/ruoyi-common-log/src/main/java/com/ruoyi/common/log/aspect/LogAspect.java

@@ -7,6 +7,7 @@ import com.ruoyi.common.core.utils.JsonUtils;
 import com.ruoyi.common.core.utils.ServletUtils;
 import com.ruoyi.common.core.utils.SpringUtils;
 import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.ip.IpAddressUtil;
 import com.ruoyi.common.log.annotation.Log;
 import com.ruoyi.common.log.enums.BusinessStatus;
 import com.ruoyi.common.log.event.OperLogEvent;
@@ -68,8 +69,9 @@ public class LogAspect {
             OperLogEvent operLog = new OperLogEvent();
             operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
             // 请求的地址
-            String ip = ServletUtils.getClientIP();
+            String ip = IpAddressUtil.getIp(ServletUtils.getRequest());
             operLog.setOperIp(ip);
+            operLog.setOperLocation(IpAddressUtil.getCityInfo(ip));
             operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
             operLog.setOperName(LoginHelper.getUsername());