diff --git a/pom.xml b/pom.xml index 9c500ec..1a1f665 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,34 @@ p6spy 3.8.5 + + + + org.lionsoul + ip2region + 1.7.2 + + + + + com.maxmind.geoip2 + geoip2 + 2.6.0 + + + + + commons-io + commons-io + 2.13.0 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + diff --git a/src/main/java/com/dd/admin/business/operationLog/entity/OperationLog.java b/src/main/java/com/dd/admin/business/operationLog/entity/OperationLog.java index 60bdca2..e598040 100644 --- a/src/main/java/com/dd/admin/business/operationLog/entity/OperationLog.java +++ b/src/main/java/com/dd/admin/business/operationLog/entity/OperationLog.java @@ -65,6 +65,10 @@ public class OperationLog implements Serializable { @TableField("OPER_IP") private String operIp; + @ApiModelProperty(value = "操作ip") + @TableField("OPER_IP_ADDRESS") + private String operIpAddress; + @ApiModelProperty(value = "请求url") @TableField("OPER_URL") private String operUrl; diff --git a/src/main/java/com/dd/admin/common/aop/operationLog/aop/OperLogAspect.java b/src/main/java/com/dd/admin/common/aop/operationLog/aop/OperLogAspect.java index bc562b9..7fbd118 100644 --- a/src/main/java/com/dd/admin/common/aop/operationLog/aop/OperLogAspect.java +++ b/src/main/java/com/dd/admin/common/aop/operationLog/aop/OperLogAspect.java @@ -13,6 +13,7 @@ import com.dd.admin.business.operationLog.service.OperationLogService; import com.dd.admin.common.model.result.ResultBean; import com.dd.admin.common.security.SecurityUtil; import com.dd.admin.common.security.model.JwtUser; +import com.dd.admin.common.utils.AddressUtils; import com.dd.admin.common.utils.IPUtils; import com.dd.admin.common.utils.StringUtil; import com.dd.admin.system.user.domain.UserVo; @@ -144,6 +145,7 @@ public class OperLogAspect { operlog.setOperResponseParam(JSON.toJSONString(keys)); // 返回结果 operlog.setOperIp(IPUtils.getIpAddr(request)); // 请求IP + operlog.setOperIpAddress(AddressUtils.getRealAddress(operlog.getOperIp())); operlog.setOperUrl(request.getRequestURI()); // 请求URI try { diff --git a/src/main/java/com/dd/admin/common/utils/AddressUtils.java b/src/main/java/com/dd/admin/common/utils/AddressUtils.java new file mode 100644 index 0000000..a71c56c --- /dev/null +++ b/src/main/java/com/dd/admin/common/utils/AddressUtils.java @@ -0,0 +1,79 @@ +package com.dd.admin.common.utils; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 获取地址类 + * + * @author ruoyi + */ +public class AddressUtils { + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // 未知地址 + public static final String UNKNOWN = "未知地址"; + + public static String getRealAddress(String ip) { + // 内网不查询 + if (internalIp(ip)) { + return "内网IP"; + } + try { + String rspStr = RegionUtil.getRegion(ip); + if (StringUtils.isNotEmpty(rspStr)) { + 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.toString()); + } + // ali地域查询 + return PureNetUtils.getAlibaba(ip); + } + + /* 判断是否是内网IP */ + public static boolean internalIp(String ipAddress) { + boolean isInnerIp = false; + long ipNum = getIpNum(ipAddress); + /* + * 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 + * 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址 + */ + long aBegin = getIpNum("10.0.0.0"); + long aEnd = getIpNum("10.255.255.255"); + long bBegin = getIpNum("172.16.0.0"); + long bEnd = getIpNum("172.31.255.255"); + long cBegin = getIpNum("192.168.0.0"); + long cEnd = getIpNum("192.168.255.255"); + isInnerIp = isInner(ipNum, aBegin, aEnd) + || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) + || ipAddress.equals("127.0.0.1"); + return isInnerIp; + } + + /* 获取IP数 */ + private static long getIpNum(String ipAddress) { + String[] ip = ipAddress.split("\\."); + long a = Integer.parseInt(ip[0]); + long b = Integer.parseInt(ip[1]); + long c = Integer.parseInt(ip[2]); + long d = Integer.parseInt(ip[3]); + return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d; + } + + private static boolean isInner(long userIp, long begin, long end) { + return (userIp >= begin) && (userIp <= end); + } + + +public static void main(String[] args) { + String realAddress = getRealAddress("117.136.79.113"); + System.out.println("realAddress = " + realAddress); + } +} + diff --git a/src/main/java/com/dd/admin/common/utils/PureNetUtils.java b/src/main/java/com/dd/admin/common/utils/PureNetUtils.java new file mode 100644 index 0000000..bbd8e24 --- /dev/null +++ b/src/main/java/com/dd/admin/common/utils/PureNetUtils.java @@ -0,0 +1,132 @@ +package com.dd.admin.common.utils; + + +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * 网络访问工具类 + */ +public class PureNetUtils { + + private static final Logger log = LoggerFactory.getLogger(PureNetUtils.class); + + /** + * get方法直接调用post方法 + * + * @param url 网络地址 + * @return 返回网络数据 + */ + public static String get(String url) { + return post(url, null); + } + + /** + * 设定post方法获取网络资源,如果参数为null,实际上设定为get方法 + * + * @param url 网络地址 + * @param param 请求参数键值对 + * @return 返回读取数据 + */ + public static String post(String url, Map param) { + HttpURLConnection conn = null; + try { + URL u = new URL(url); + conn = (HttpURLConnection) u.openConnection(); + StringBuilder sb = null; + if (param != null) {// 如果请求参数不为空 + sb = new StringBuilder(); + /* + * A URL connection can be used for input and/or output. Set the + * DoOutput flag to true if you intend to use the URL connection + * for output, false if not. The default is false. + */ + // 默认为false,post方法需要写入参数,设定true + conn.setDoOutput(true); + // 设定post方法,默认get + conn.setRequestMethod("POST"); + // 获得输出流 + OutputStream out = conn.getOutputStream(); + // 对输出流封装成高级输出流 + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); + // 将参数封装成键值对的形式 + for (Map.Entry s : param.entrySet()) { + sb.append(s.getKey()).append("=").append(s.getValue()).append("&"); + } + // 将参数通过输出流写入 + writer.write(sb.deleteCharAt(sb.toString().length() - 1).toString()); + writer.close();// 一定要关闭,不然可能出现参数不全的错误 + sb = null; + } + conn.connect();// 建立连接 + sb = new StringBuilder(); + // 获取连接状态码 + int recode = conn.getResponseCode(); + BufferedReader reader = null; + if (recode == 200) { + // Returns an input stream that reads from this open connection + // 从连接中获取输入流 + InputStream in = conn.getInputStream(); + // 对输入流进行封装 + reader = new BufferedReader(new InputStreamReader(in)); + String str = null; + sb = new StringBuilder(); + // 从输入流中读取数据 + while ((str = reader.readLine()) != null) { + sb.append(str).append(System.getProperty("line.separator")); + } + // 关闭输入流 + reader.close(); + if (sb.toString().length() == 0) { + return null; + } + return sb.toString().substring(0, + sb.toString().length() - System.getProperty("line.separator").length()); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (conn != null)// 关闭连接 + conn.disconnect(); + } + return null; + } + + /** + * 获取 ip 所属地址 + * + * @param ip IP 地址 + * @return 所属地址 + */ + public static String getAlibaba(String ip) { + Map map = new HashMap<>(); + map.put("ip", ip); + map.put("accessKey", "alibaba-inc"); + String result = PureNetUtils.post("http://ip.taobao.com/outGetIpInfo", map); + log.info("{} => POST: http://ip.taobao.com/outGetIpInfo || result: {}", ip, result); + String address = null; + if (StringUtils.isNotBlank(result)) { + JSONObject jsonObject = JSONObject.parseObject(result); + // 请求成功,解析响应数据 + if ("query success".equals(jsonObject.get("msg"))) { + JSONObject dataMap = JSONObject.parseObject(jsonObject.getString("data")); + String country = dataMap.getString("country"); + String region = dataMap.getString("region"); + String city = dataMap.getString("city"); + address = country + region + city; + } + } + return address; + } + +} + diff --git a/src/main/java/com/dd/admin/common/utils/RegionUtil.java b/src/main/java/com/dd/admin/common/utils/RegionUtil.java new file mode 100644 index 0000000..1b88bd7 --- /dev/null +++ b/src/main/java/com/dd/admin/common/utils/RegionUtil.java @@ -0,0 +1,87 @@ +package com.dd.admin.common.utils; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Objects; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +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 = Objects.requireNonNull(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(); + 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.toString()); + } + } + + /** + * 解析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(); + // 查询算法 + Method method = searcher.getClass().getMethod("memorySearch", String.class); + + DataBlock dataBlock = null; + if (!Util.isIpAddress(ip)) { + 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.toString()); + } + return StringUtils.EMPTY; + } + +} diff --git a/src/main/resources/ip2region/ip2region.db b/src/main/resources/ip2region/ip2region.db new file mode 100644 index 0000000..3b6a296 Binary files /dev/null and b/src/main/resources/ip2region/ip2region.db differ diff --git a/src/test/java/com/dd/admin/AdminApplicationTests.java b/src/test/java/com/dd/admin/AdminApplicationTests.java index 84d4633..c7b58d0 100644 --- a/src/test/java/com/dd/admin/AdminApplicationTests.java +++ b/src/test/java/com/dd/admin/AdminApplicationTests.java @@ -1,13 +1,28 @@ package com.dd.admin; +import com.dd.admin.business.operationLog.entity.OperationLog; +import com.dd.admin.business.operationLog.service.OperationLogService; +import com.dd.admin.common.utils.AddressUtils; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.util.List; + @SpringBootTest class AdminApplicationTests { + @Autowired + OperationLogService operationLogService; @Test void contextLoads() { + List list = operationLogService.list(); + list.stream().forEach(operationLog -> { + String realAddress = AddressUtils.getRealAddress(operationLog.getOperIp()); + System.out.println(realAddress); + operationLog.setOperIpAddress(realAddress); + }); + operationLogService.updateBatchById(list); } }