Lin Qilong 1 săptămână în urmă
părinte
comite
1b1ef3a213

+ 6 - 0
pom.xml

@@ -95,6 +95,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.maxmind.geoip2</groupId>
+            <artifactId>geoip2</artifactId>
+            <version>2.13.1</version>
+        </dependency>
+
     </dependencies>
     <build>
         <finalName>sh-model-gateway</finalName>

+ 86 - 0
src/main/java/cn/com/goldenwater/core/builder/MapBuilder.java

@@ -0,0 +1,86 @@
+package cn.com.goldenwater.core.builder;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Map构建器
+ *
+ * @author LinQiLong
+ * @date 2024/2/22 16:50
+ */
+public class MapBuilder<K, V> {
+
+    private final Map<K, V> map;
+
+    private MapBuilder() {
+        this.map = new HashMap<>(16);
+    }
+
+    private MapBuilder(Map<K, V> map) {
+        this.map = Objects.isNull(map) ? new HashMap<>(16) : map;
+    }
+
+    public static <K, V> MapBuilder<K, V> builder() {
+        return builder(null);
+    }
+
+    public static <K, V> MapBuilder<K, V> builder(Map<K, V> map) {
+        return new MapBuilder<K, V>(map);
+    }
+
+    public static <K, V> Map<K, V> of() {
+        return new MapBuilder<K, V>().build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1) {
+        return new MapBuilder<K, V>().put(k1, v1).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).put(k6, v6).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).put(k6, v6).put(k7, v7).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).put(k6, v6).put(k7, v7).put(k8, v8).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).put(k6, v6).put(k7, v7).put(k8, v8).put(k9, v9).build();
+    }
+
+    public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
+        return new MapBuilder<K, V>().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).put(k6, v6).put(k7, v7).put(k8, v8).put(k9, v9).put(k10, v10).build();
+    }
+
+    public MapBuilder<K, V> put(K key, V value) {
+        this.map.put(key, value);
+        return this;
+    }
+
+    public Map<K, V> build() {
+        return this.map;
+    }
+
+}

+ 11 - 3
src/main/java/cn/com/goldenwater/domain/GatewayRoutes.java

@@ -1,6 +1,5 @@
 package cn.com.goldenwater.domain;
 
-import cn.com.goldenwater.utils.JsonUtils;
 import cn.com.goldenwater.utils.StringUtils;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
@@ -8,6 +7,7 @@ import org.springframework.cloud.gateway.filter.FilterDefinition;
 import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.List;
 
 @Data
@@ -23,7 +23,11 @@ public class GatewayRoutes implements Serializable {
 
     public List<PredicateDefinition> getPredicateDefinition() {
         if (this.predicates != null) {
-            List<PredicateDefinition> predicateDefinitionList = JsonUtils.jsonToList(this.predicates, PredicateDefinition.class);
+            PredicateDefinition predicateDefinition = new PredicateDefinition();
+            predicateDefinition.setName("Path");
+            predicateDefinition.addArg("_genkey_0", this.predicates);
+            List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
+            predicateDefinitionList.add(predicateDefinition);
             return predicateDefinitionList;
         } else {
             return null;
@@ -32,7 +36,11 @@ public class GatewayRoutes implements Serializable {
 
     public List<FilterDefinition> getFilterDefinition() {
         if (StringUtils.isNoneBlank(this.filters)) {
-            List<FilterDefinition> filterDefinitionList = JsonUtils.jsonToList(this.filters, FilterDefinition.class);
+            FilterDefinition filterDefinition = new FilterDefinition();
+            filterDefinition.setName("StripPrefix");
+            filterDefinition.addArg("_genkey_0", this.filters);
+            List<FilterDefinition> filterDefinitionList = new ArrayList<>();
+            filterDefinitionList.add(filterDefinition);
             return filterDefinitionList;
         } else {
             return null;

+ 35 - 0
src/main/java/cn/com/goldenwater/domain/PtGateway.java

@@ -0,0 +1,35 @@
+package cn.com.goldenwater.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author lql
+ * @date 2025-8-26
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName
+public class PtGateway implements Serializable {
+
+    private String id;
+
+    private String name;
+
+    private String path;
+
+    private String serviceId;
+
+    private String url;
+
+    private String retryable;
+
+    private String enabled;
+
+}

+ 112 - 0
src/main/java/cn/com/goldenwater/domain/PtService.java

@@ -0,0 +1,112 @@
+package cn.com.goldenwater.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * entity:PtService
+ *
+ * @author lql
+ * @date 2025-8-26
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName
+public class PtService implements Serializable {
+
+    @TableId(value = "SRV_ID", type = IdType.ASSIGN_UUID)
+    private String srvId;
+
+    /** 模型id */
+    private String mdid;
+    /** 目录ID */
+    private String cateCode;
+    /** 服务名称 */
+    private String name;
+    /** 服务详情 */
+    private String intro;
+    /** 服务来源 */
+    private String serviceSource;
+    /** 服务所属业务部门 */
+    private String serviceDept;
+    /** 业务部门管理人 */
+    private String manageName;
+    /** 服务开发单位 */
+    private String developUnit;
+    /** 服务开发单位联系方式 */
+    private String developContacter;
+    /** 服务运维单位 */
+    private String maintainUnit;
+    /** 服务运维单位联系人 */
+    private String maintainContacer;
+    /** 更新周期 */
+    private String upCycl;
+    /** 开放条件 */
+    private String openCndtn;
+    /** 所属区县 */
+    private String adName;
+    /** 数据领域 */
+    private String dataField;
+    /** 数据范围 */
+    private String dataRange;
+    /** 创建人ID */
+    private String userId;
+    /** 接口状态 */
+    private String status;
+    /** 备注 */
+    private String note;
+
+    /** 后台请求调用的方式 */
+    private String type;
+    /** 请求地址 */
+    private String url;
+    /** 请求方式 */
+    private String rqtype;
+    /** 返回类型 */
+    private String rptype;
+    /** 返回信息 */
+    private String rpcontent;
+    /** 代理地址 */
+    private String proxyAddress;
+    /** 代理路径 */
+    private String proxyPath;
+    /** 示例 */
+    private String example;
+
+    /** 申请量 */
+    private Integer cnt;
+    /** 访问量 */
+    private Integer viewNum;
+    /** 申请量 */
+    private Integer applyNum;
+    /** 数据量 */
+    private Integer dataNum;
+
+    /** 录入时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date tm;
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date uptm;
+    /** 发布时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date rlstm;
+    /** 接口发布时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date releaseTime;
+
+    /** 排序 */
+    private Integer sort;
+
+}

+ 62 - 0
src/main/java/cn/com/goldenwater/domain/PtServiceLog.java

@@ -0,0 +1,62 @@
+package cn.com.goldenwater.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 服务日志
+ *
+ * @author lql
+ * @date 2025-8-26
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName
+public class PtServiceLog implements Serializable {
+
+    private Long logId;
+
+    /** 服务id */
+    private String serId;
+
+    /** 模型id */
+    private String mdId;
+
+    /** 请求时间戳 */
+    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
+    private Date tm;
+
+    /** 请求信息 */
+    private String senText;
+
+    /** 返回信息 */
+    private String returnText;
+
+    /** 请求耗时(毫秒) */
+    private Long execTm;
+
+    /** 状态码 */
+    private Long statusCode;
+
+    /** 客户端ip */
+    private String clientIp;
+
+    /** 用户id */
+    private String userId;
+
+    /** 地理位置 */
+    private String geoCity;
+
+    /** 服务名称 */
+    private String serviceName;
+
+}

+ 68 - 24
src/main/java/cn/com/goldenwater/filter/ResponseLogGlobalFilter.java

@@ -1,9 +1,15 @@
 package cn.com.goldenwater.filter;
 
+import cn.com.goldenwater.domain.PtService;
+import cn.com.goldenwater.domain.PtServiceLog;
+import cn.com.goldenwater.service.PtServiceLogService;
+import cn.com.goldenwater.service.PtServiceService;
+import cn.com.goldenwater.utils.GeoipUtils;
 import cn.com.goldenwater.utils.JsonUtils;
 import com.alibaba.fastjson2.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import org.reactivestreams.Publisher;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.context.annotation.Configuration;
@@ -21,21 +27,34 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 @Slf4j
 @Configuration
 public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
 
+    @Autowired
+    private PtServiceLogService ptServiceLogService;
+
+    @Autowired
+    private PtServiceService ptServiceService;
+
     @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         try {
             ServerHttpResponse originalResponse = exchange.getResponse();
             DataBufferFactory bufferFactory = originalResponse.bufferFactory();
             HttpStatus statusCode = originalResponse.getStatusCode();
+
+            // 记录请求开始时间
+            long startTime = System.currentTimeMillis();
+
             if (statusCode != HttpStatus.OK) {
-                return chain.filter(exchange);//降级处理返回数据
+                return chain.filter(exchange); // 降级处理返回数据
             }
+
             ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                 @Override
                 public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
@@ -54,6 +73,8 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
                             //排除Excel导出,不是application/json不打印。若请求是上传图片则在最上面判断。
                             MediaType contentType = originalResponse.getHeaders().getContentType();
                             if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
+                                // 即使不是JSON也记录日志
+                                saveServiceLog(exchange, new String(content), System.currentTimeMillis() - startTime, originalResponse.getStatusCode().value());
                                 return bufferFactory.wrap(content);
                             }
 
@@ -66,6 +87,9 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
                             rspArgs.add(result);
                             log.info("<-- {} {}\n{}", rspArgs.toArray());
 
+                            // 保存服务日志
+                            saveServiceLog(exchange, result, System.currentTimeMillis() - startTime, originalResponse.getStatusCode().value());
+
                             getDelegate().getHeaders().setContentLength(result.getBytes().length);
                             return bufferFactory.wrap(result.getBytes());
                         }));
@@ -88,33 +112,53 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
         return Ordered.HIGHEST_PRECEDENCE;
     }
 
-    //返回统一的JSON日期数据 2024-02-23 11:00, null转空字符串
+    // 返回统一的JSON日期数据 2024-02-23 11:00, null转空字符串
     private String modifyBody(String jsonStr) {
         JSONObject json = JsonUtils.jsonToPojo(jsonStr, JSONObject.class);
         return JsonUtils.objectToJson(json);
     }
 
-//    public void setJSONObjectWriteNullStringAsEmpty() {
-//    JSON.toJSONString(data,SerializerFeature.WriteNullStringAsEmpty);
-//    JSONObject jsonObj = JSON.parseObject(data, Feature.AllowISO8601DateFormat);
-//    JSON.toJSONString(data,SerializerFeature.DisableCircularReferenceDetect);
-//    JSONObject.DEFFAULT_DATE_FORMAT ="yyyy-MM-dd HH:mm";
-//    JSONObject.toJSONString(jsonObject,SerializerFeature.WriteDateUseDateFormat);
-//
-//    String dataJson = JSON.toJSONString(data, (ValueFilter) (object, name, value) -> {
-//        log.info("data:{} ", data);
-//
-//        log.info("object:{}, name:{}, value:{}", object, name, value);
-//        if (value == null) {
-//            return "";
-//        }
-//        return value;
-//    });
-//    JSONObject jsonObject = new JSONObject();
-//    把json对象转换成字节数组
-//    byte[] bits = data.getBytes(StandardCharsets.UTF_8);
-//    DataBuffer buffer = originalResponse.bufferFactory().wrap(bits);
-//    originalResponse.writeWith(Mono.just(buffer));
-//    }
+    /**
+     * 保存服务日志到数据库
+     */
+    private void saveServiceLog(ServerWebExchange exchange, String responseContent, long execTime, int statusCode) {
+        try {
+            PtServiceLog serviceLog = new PtServiceLog();
+            // 设置日志字段
+            serviceLog.setTm(new Date());
+            serviceLog.setSenText(exchange.getRequest().getURI().toString());
+            serviceLog.setReturnText(responseContent);
+            serviceLog.setExecTm(execTime);
+            serviceLog.setStatusCode((long) statusCode);
+
+            // 获取客户端IP
+            String clientIp = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
+            if (clientIp == null || clientIp.isEmpty()) {
+                clientIp = exchange.getRequest().getRemoteAddress() != null ?
+                        exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
+            }
+            serviceLog.setClientIp(clientIp);
+
+            Map<String, Object> geoCity = GeoipUtils.getGeoCity(clientIp);
+            if (geoCity != null) {
+                serviceLog.setGeoCity((String) geoCity.get("city"));
+            }
+
+            // 通过URL获取服务信息
+            String requestUrl = exchange.getRequest().getURI().getPath();
+            PtService ptService = ptServiceService.getServiceByUrl(requestUrl);
+            if (ptService != null) {
+                serviceLog.setServiceName(ptService.getName());
+                serviceLog.setSerId(ptService.getSrvId());
+                serviceLog.setMdId(ptService.getMdid());
+            } else {
+                serviceLog.setServiceName("未找到服务信息");
+            }
 
+            // 异步保存日志
+            ptServiceLogService.save(serviceLog);
+        } catch (Exception e) {
+            log.error("保存服务日志异常: ", e);
+        }
+    }
 }

+ 13 - 0
src/main/java/cn/com/goldenwater/mapper/PtGatewayMapper.java

@@ -0,0 +1,13 @@
+package cn.com.goldenwater.mapper;
+
+import cn.com.goldenwater.domain.PtGateway;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface PtGatewayMapper extends BaseMapper<PtGateway> {
+
+
+}

+ 13 - 0
src/main/java/cn/com/goldenwater/mapper/PtServiceLogMapper.java

@@ -0,0 +1,13 @@
+package cn.com.goldenwater.mapper;
+
+import cn.com.goldenwater.domain.PtServiceLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface PtServiceLogMapper extends BaseMapper<PtServiceLog> {
+
+
+}

+ 13 - 0
src/main/java/cn/com/goldenwater/mapper/PtServiceMapper.java

@@ -0,0 +1,13 @@
+package cn.com.goldenwater.mapper;
+
+import cn.com.goldenwater.domain.PtService;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface PtServiceMapper extends BaseMapper<PtService> {
+
+
+}

+ 15 - 0
src/main/java/cn/com/goldenwater/service/PtGatewayService.java

@@ -0,0 +1,15 @@
+package cn.com.goldenwater.service;
+
+import cn.com.goldenwater.domain.PtGateway;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+* @author lql
+* @date 2025-8-26
+*/
+public interface PtGatewayService extends IService<PtGateway> {
+
+
+}

+ 15 - 0
src/main/java/cn/com/goldenwater/service/PtServiceLogService.java

@@ -0,0 +1,15 @@
+package cn.com.goldenwater.service;
+
+import cn.com.goldenwater.domain.PtServiceLog;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+* @author lql
+* @date 2025-8-26
+*/
+public interface PtServiceLogService extends IService<PtServiceLog> {
+
+
+}

+ 16 - 0
src/main/java/cn/com/goldenwater/service/PtServiceService.java

@@ -0,0 +1,16 @@
+package cn.com.goldenwater.service;
+
+import cn.com.goldenwater.domain.PtService;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+* @author lql
+* @date 2025-8-26
+*/
+public interface PtServiceService extends IService<PtService> {
+
+    PtService getServiceByUrl(String url);
+
+}

+ 25 - 0
src/main/java/cn/com/goldenwater/service/impl/PtGatewayServiceImpl.java

@@ -0,0 +1,25 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.domain.PtGateway;
+import cn.com.goldenwater.mapper.PtGatewayMapper;
+import cn.com.goldenwater.service.PtGatewayService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author lql
+ * @date 2025-8-26
+ */
+@Slf4j
+@Service
+@Transactional
+public class PtGatewayServiceImpl extends ServiceImpl<PtGatewayMapper, PtGateway> implements PtGatewayService {
+
+    @Override
+    public boolean updateById(PtGateway ptGateway) {
+        return super.updateById(ptGateway);
+    }
+
+}

+ 25 - 0
src/main/java/cn/com/goldenwater/service/impl/PtServiceLogServiceImpl.java

@@ -0,0 +1,25 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.domain.PtServiceLog;
+import cn.com.goldenwater.mapper.PtServiceLogMapper;
+import cn.com.goldenwater.service.PtServiceLogService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author lql
+ * @date 2025-8-26
+ */
+@Slf4j
+@Service
+@Transactional
+public class PtServiceLogServiceImpl extends ServiceImpl<PtServiceLogMapper, PtServiceLog> implements PtServiceLogService {
+
+    @Override
+    public boolean updateById(PtServiceLog ptServiceLog) {
+        return super.updateById(ptServiceLog);
+    }
+
+}

+ 27 - 0
src/main/java/cn/com/goldenwater/service/impl/PtServiceServiceImpl.java

@@ -0,0 +1,27 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.domain.PtService;
+import cn.com.goldenwater.mapper.PtServiceMapper;
+import cn.com.goldenwater.service.PtServiceService;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author lql
+ * @date 2025-8-26
+ */
+@Slf4j
+@Service
+@Transactional
+public class PtServiceServiceImpl extends ServiceImpl<PtServiceMapper, PtService> implements PtServiceService {
+
+    @Override
+    public PtService getServiceByUrl(String url) {
+        QueryWrapper<PtService> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("url", url);
+        return getOne(queryWrapper);
+    }
+}

+ 104 - 0
src/main/java/cn/com/goldenwater/utils/GeoipUtils.java

@@ -0,0 +1,104 @@
+package cn.com.goldenwater.utils;
+
+import cn.com.goldenwater.core.builder.MapBuilder;
+import com.maxmind.geoip2.DatabaseReader;
+import com.maxmind.geoip2.model.CityResponse;
+import com.maxmind.geoip2.record.City;
+import com.maxmind.geoip2.record.Country;
+import com.maxmind.geoip2.record.Location;
+import com.maxmind.geoip2.record.Subdivision;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+public class GeoipUtils {
+
+    // 使用单例模式缓存DatabaseReader实例,避免重复创建
+    private static volatile DatabaseReader databaseReader;
+    private static final Object lock = new Object();
+
+    /**
+     * 获取DatabaseReader实例
+     * @return DatabaseReader实例
+     */
+    private static DatabaseReader getDatabaseReader() {
+        if (databaseReader == null) {
+            synchronized (lock) {
+                if (databaseReader == null) {
+                    try {
+                        InputStream databaseStream = GeoipUtils.class.getResourceAsStream("/GeoLite2-City.mmdb");
+                        if (databaseStream == null) {
+                            log.error("无法找到GeoLite2-City.mmdb文件");
+                            return null;
+                        }
+                        databaseReader = new DatabaseReader.Builder(databaseStream).build();
+                    } catch (IOException e) {
+                        log.error("初始化GeoIP数据库失败", e);
+                        return null;
+                    }
+                }
+            }
+        }
+        return databaseReader;
+    }
+
+    /**
+     * 根据IP地址获取地理位置信息
+     * @param ip IP地址
+     * @return 包含地理位置信息的Map
+     */
+    public static Map<String, Object> getGeoCity(String ip) {
+        // 参数校验
+        if (ip == null || ip.isEmpty()) {
+            log.warn("IP地址为空");
+            return null;
+        }
+
+        // 获取DatabaseReader实例
+        DatabaseReader reader = getDatabaseReader();
+        if (reader == null) {
+            return null;
+        }
+
+        try {
+            InetAddress ipAddress = InetAddress.getByName(ip);
+            CityResponse response = reader.city(ipAddress);
+
+            Country country = response.getCountry();
+            Subdivision subdivision = response.getMostSpecificSubdivision();
+            City city = response.getCity();
+            Location location = response.getLocation();
+
+            // 构建返回结果
+            return MapBuilder.of(
+                    "country", country.getNames().get("zh-CN"),
+                    "subdivision", subdivision.getNames().get("zh-CN"),
+                    "city", city.getNames().get("zh-CN"),
+                    "latitude", location.getLatitude(),
+                    "longitude", location.getLongitude()
+            );
+        } catch (Exception e) {
+            log.error("获取地理位置信息失败,IP: {}", ip, e);
+        }
+        return null;
+    }
+
+    /**
+     * 关闭DatabaseReader资源
+     */
+    public static void close() {
+        if (databaseReader != null) {
+            try {
+                databaseReader.close();
+                databaseReader = null;
+            } catch (IOException e) {
+                log.error("关闭GeoIP数据库失败", e);
+            }
+        }
+    }
+}

BIN
src/main/resources/GeoLite2-City.mmdb