Browse Source

coding:投流代码提交

zhangqin 3 weeks ago
parent
commit
44d792de08
26 changed files with 604 additions and 402 deletions
  1. 11 0
      fs-ad-new-api/pom.xml
  2. 27 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java
  3. 7 5
      fs-ad-new-api/src/main/java/com/fs/app/enums/AdvertiserTypeEnum.java
  4. 3 3
      fs-ad-new-api/src/main/java/com/fs/app/enums/ConversionTypeEnum.java
  5. 6 0
      fs-ad-new-api/src/main/java/com/fs/app/enums/event/IEventType.java
  6. 3 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  7. 47 37
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  8. 32 32
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  9. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/OceanEngineAdapter.java
  10. 65 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/AbstractApiClient.java
  11. 0 167
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/GDTApiClient.java
  12. 8 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/IAccessTokenClient.java
  13. 13 80
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/BaiduApiClient.java
  14. 123 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/GDTApiClient.java
  15. 103 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/OPPOApiClient.java
  16. 11 48
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/OceanEngineApiClient.java
  17. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/OceanEngineCallbackStrategy.java
  18. 11 6
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer.java
  19. 111 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer2.java
  20. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java
  21. 1 9
      fs-service/src/main/java/com/fs/newAdv/domain/ApiCallLog.java
  22. 4 0
      fs-service/src/main/java/com/fs/newAdv/domain/Lead.java
  23. 8 0
      fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java
  24. 2 3
      fs-service/src/main/java/com/fs/newAdv/service/IApiCallLogService.java
  25. 4 9
      fs-service/src/main/java/com/fs/newAdv/service/impl/ApiCallLogServiceImpl.java
  26. 1 0
      pom.xml

+ 11 - 0
fs-ad-new-api/pom.xml

@@ -80,6 +80,17 @@
                     <warName>${project.artifactId}</warName>
                 </configuration>
             </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <compilerArgs>
+                        <arg>-parameters</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+
         </plugins>
         <finalName>${project.artifactId}</finalName>
     </build>

+ 27 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/CallbackController.java

@@ -0,0 +1,27 @@
+package com.fs.app.controller;
+
+import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.app.facade.CallbackProcessingFacadeServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping("/callBack")
+public class CallbackController {
+
+    @Autowired
+    private CallbackProcessingFacadeService facadeService;
+
+
+    @GetMapping("/gdt/getAuthCode")
+    public void gdtGetAuthCode(
+            @RequestParam("authorization_code") Integer code,
+            @RequestParam("state") Long state) {
+        facadeService.gdtGetAuthCode(code, state);
+    }
+}

+ 7 - 5
fs-ad-new-api/src/main/java/com/fs/app/enums/AdvertiserTypeEnum.java

@@ -11,17 +11,19 @@ import lombok.Getter;
 @Getter
 public enum AdvertiserTypeEnum {
 
-    BAIDU(1L,"BAIDU", "百度"),
-    OCEAN_ENGINE(2L,"OCEAN_ENGINE", "巨量引擎"),
-    SINA(3L,"SINA", "新浪"),
-    GDT(4L,"GDT", "广点通");
+    BAIDU(1L, "BAIDU", "百度"),
+    OCEANENGINE(2L, "OCEANENGINE", "巨量引擎"),
+    GDT(3L, "GDT", "广点通"),
+    OPPO(4L, "OPPO", "OPPO"),
+    VIVO(5L, "VIVO", "VIVO"),
+    iQIYI(6L, "iQIYI", "爱奇艺");
 
     private final Long code;
     private final String name;
     private final String des;
 
 
-    AdvertiserTypeEnum(Long code, String name,String des) {
+    AdvertiserTypeEnum(Long code, String name, String des) {
         this.code = code;
         this.name = name;
         this.des = des;

+ 3 - 3
fs-ad-new-api/src/main/java/com/fs/app/enums/ConversionTypeEnum.java

@@ -12,7 +12,7 @@ import java.util.Map;
 @Getter
 public enum ConversionTypeEnum {
 
-    FORM_SUBMIT("FORM_SUBMIT","表单提交", 1),
+    FORM_SUBMIT("FORM_SUBMIT","当日加群", 1),
     CLICK("CLICK","点击", 2),
     VIEW("VIEW","曝光", 3);
 
@@ -43,10 +43,10 @@ public enum ConversionTypeEnum {
     static {
         //---------------表单提交
         FORM_SUBMIT.advertiserTypeMap.put(AdvertiserTypeEnum.BAIDU, "3");
-        FORM_SUBMIT.advertiserTypeMap.put(AdvertiserTypeEnum.OCEAN_ENGINE, "hc");
+        FORM_SUBMIT.advertiserTypeMap.put(AdvertiserTypeEnum.OCEANENGINE, "hc");
         //---------------点击
         CLICK.advertiserTypeMap.put(AdvertiserTypeEnum.BAIDU, "1");
-        CLICK.advertiserTypeMap.put(AdvertiserTypeEnum.OCEAN_ENGINE, "clk");
+        CLICK.advertiserTypeMap.put(AdvertiserTypeEnum.OCEANENGINE, "clk");
         //---------------曝光
 
     }

+ 6 - 0
fs-ad-new-api/src/main/java/com/fs/app/enums/event/IEventType.java

@@ -0,0 +1,6 @@
+package com.fs.app.enums.event;
+
+public interface IEventType {
+    String getCode();
+    String getDesc();
+}

+ 3 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java

@@ -23,4 +23,7 @@ public interface CallbackProcessingFacadeService {
      * @return
      */
     LandingIndexRes getLandingIndexBySiteId(Long siteId);
+
+    //----------------------code回调---------------------------------
+    void gdtGetAuthCode(Integer code, Long state);
 }

+ 47 - 37
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -5,42 +5,34 @@ import cn.hutool.json.JSONUtil;
 import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.app.enums.ConversionTypeEnum;
 import com.fs.app.event.ConversionEventPublisher;
-import com.fs.app.integration.client.BaiduApiClient;
+import com.fs.app.integration.client.IAccessTokenClient;
+import com.fs.app.integration.client.IApiClient;
 import com.fs.app.integration.factory.AdvertiserHandlerFactory;
 import com.fs.app.mq.message.ClickMessage;
 import com.fs.common.exception.base.BusinessException;
-import com.fs.newAdv.domain.ClickTrace;
-import com.fs.newAdv.domain.LandingPageTemplate;
-import com.fs.newAdv.domain.Lead;
-import com.fs.newAdv.domain.Site;
+import com.fs.newAdv.domain.*;
 import com.fs.newAdv.dto.req.LeadSubmitRequest;
 import com.fs.newAdv.dto.res.LandingIndexRes;
-import com.fs.newAdv.service.IClickTraceService;
-import com.fs.newAdv.service.ILandingPageTemplateService;
-import com.fs.newAdv.service.ILeadService;
-import com.fs.newAdv.service.ISiteService;
+import com.fs.newAdv.service.*;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 @Service
 @Slf4j
 public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFacadeService {
-    @Autowired
-    private IClickTraceService clickTraceService;
-
 
     @Autowired
     private AdvertiserHandlerFactory handlerFactory;
 
     @Autowired
     private ILeadService leadService;
-
+    @Autowired
     private ISiteService siteService;
 
     @Autowired
@@ -50,6 +42,8 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     private IClickTraceService iClickTraceService;
     @Autowired
     private ILandingPageTemplateService landingPageTemplateService;
+    @Autowired
+    private IPromotionAccountService promotionAccountService;
 
     @Override
     public void saveClickTrace(AdvertiserTypeEnum trackType, Map<String, String> allParams) {
@@ -75,20 +69,27 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
      * @param allParams
      * @return traceId 线索id
      */
-    private Map<String, String> getTraceIdByPlatformParams(Map<String, String> allParams) {
+    private Map<String, String> getTraceIdByPlatformParams(Long advertiserId, Map<String, String> allParams) {
         Map<String, String> traceId = new HashMap<>();
-        if (StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+        if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.BAIDU) && StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+            traceId.put("traceId", allParams.get("bd_vid"));
+        } else if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.OCEANENGINE) && StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+            traceId.put("traceId", allParams.get("bd_vid"));
+        }else if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.GDT) && StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+            traceId.put("traceId", allParams.get("bd_vid"));
+        }else if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.OPPO) && StrUtil.isNotEmpty(allParams.get("pageId"))) {
+            traceId.put("traceId", allParams.get("pageId"));
+        }else if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.VIVO) && StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+            traceId.put("traceId", allParams.get("bd_vid"));
+        }else if (Objects.equals(AdvertiserTypeEnum.getByCode(advertiserId), AdvertiserTypeEnum.iQIYI) && StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
             traceId.put("traceId", allParams.get("bd_vid"));
-            return traceId;
-        } else {
-            throw new BusinessException("回传参数错误 缺少traceId");
         }
-
+        return traceId;
     }
 
     @Override
     public void updateClickTrace(ClickMessage clickMessage) {
-        Map<String, String> params = getTraceIdByPlatformParams(clickMessage.getAllParams());
+        Map<String, String> params = getTraceIdByPlatformParams(null,clickMessage.getAllParams());
         Site site = siteService.getById(clickMessage.getSiteId());
         ClickTrace byTraceId = iClickTraceService.getByTraceId(params.get("traceId"));
         byTraceId.setSiteId(site.getId());
@@ -112,7 +113,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
                 trace.setCampaignId(allParams.get("pid"));
                 trace.setIp(allParams.get("ip"));
                 break;
-            case SINA:
+            case OCEANENGINE:
                 break;
         }
 
@@ -120,12 +121,15 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
 
     @Override
     public void submitForm(LeadSubmitRequest request) {
-        Map<String, String> params = getTraceIdByPlatformParams(request.getRawParams());
+        Site byId = siteService.getById(request.getSiteId());
+        if (byId == null) {
+            throw new BusinessException("站点不存在");
+        }
+        Map<String, String> params = getTraceIdByPlatformParams(byId.getAdvertiserId(), request.getRawParams());
         String traceId = params.get("traceId");
         if (StrUtil.isEmpty(traceId)) {
             throw new BusinessException("缺少traceId");
         }
-        Site byId = siteService.getById(request.getSiteId());
         // 2. 构建Lead对象
         Lead lead = new Lead();
         lead.setSiteId(request.getSiteId());
@@ -158,6 +162,25 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         return res;
     }
 
+    @Override
+    public void gdtGetAuthCode(Integer code, Long state) {
+        if (code == null || state == null) {
+            return;
+        }
+        PromotionAccount byId = promotionAccountService.getById(state);
+        if (byId == null) {
+            return;
+        }
+        IApiClient apiClient = handlerFactory.getApiClient(AdvertiserTypeEnum.GDT);
+        if (apiClient instanceof IAccessTokenClient) {
+            IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;
+            Map<String, String> accessToken = tokenClient.getAccessToken(code, byId.getAppId(), byId.getAppSecret(), byId.getCallbackUrl());
+            byId.setAccessToken(accessToken.get("access_token"));
+            byId.setRefreshToken(accessToken.get("refresh_token"));
+        }
+
+    }
+
     public void saveLeadAndTriggerConversion(Lead lead) {
         // 1. 保存线索到数据库
         boolean saved = leadService.save(lead);
@@ -177,17 +200,4 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         );
     }
 
-
-    /**
-     * 转换为String Map
-     */
-    private Map<String, String> convertToStringMap(Map<String, Object> map) {
-        Map<String, String> result = new HashMap<>();
-        map.forEach((key, value) -> {
-            if (value != null) {
-                result.put(key, value.toString());
-            }
-        });
-        return result;
-    }
 }

+ 32 - 32
fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java

@@ -1,27 +1,21 @@
 package com.fs.app.facade;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.app.enums.ConversionTypeEnum;
-import com.fs.app.integration.client.BaiduApiClient;
 import com.fs.app.integration.client.IApiClient;
-import com.fs.app.integration.client.OceanEngineApiClient;
 import com.fs.app.integration.factory.AdvertiserHandlerFactory;
-import com.fs.common.constant.RedisKeyConstant;
-import com.fs.common.exception.base.BusinessException;
 import com.fs.common.utils.RedisUtil;
 import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.*;
+import com.fs.newAdv.domain.ConversionLog;
+import com.fs.newAdv.domain.Lead;
+import com.fs.newAdv.domain.PromotionAccount;
+import com.fs.newAdv.domain.Site;
 import com.fs.newAdv.mapper.ConversionLogMapper;
 import com.fs.newAdv.mapper.ConversionTargetMapper;
-import com.fs.newAdv.service.ICallbackAccountService;
-import com.fs.newAdv.service.IClickTraceService;
-import com.fs.newAdv.service.ILeadService;
-import com.fs.newAdv.service.ISiteService;
+import com.fs.newAdv.service.*;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
@@ -42,6 +36,8 @@ public class ConversionServiceImpl implements IConversionService {
     private ISiteService siteService;
     @Autowired
     private ICallbackAccountService callbackAccountService;
+    @Autowired
+    private IPromotionAccountService promotionAccountService;
 
 
     @Autowired
@@ -62,9 +58,9 @@ public class ConversionServiceImpl implements IConversionService {
     private ILeadService leadService;
 
     @Override
-    public boolean reportToAdvertiser(AdvertiserTypeEnum advertiser,Long siteId, ConversionTypeEnum eventType, String traceId, Long leadId,Double value) {
+    public boolean reportToAdvertiser(AdvertiserTypeEnum advertiser, Long siteId, ConversionTypeEnum eventType, String traceId, Long leadId, Double value) {
         log.info("开始回传转化数据:广告商={},站点ID={},点击ID={},事件类型={}",
-                advertiser,siteId, traceId, eventType);
+                advertiser, siteId, traceId, eventType);
         // 广告商client
         IApiClient apiClient = advertiserHandlerFactory.getApiClient(advertiser);
         // 询站点信息
@@ -75,33 +71,38 @@ public class ConversionServiceImpl implements IConversionService {
         }
 
         // 查询回传账号
-        CallbackAccount callbackAccount = callbackAccountService.getById(site.getCallbackAccountId());
-        if (callbackAccount == null) {
+        PromotionAccount promotionAccount = promotionAccountService.getById(site.getPromotionAccountId());
+        if (promotionAccount == null) {
             log.error("回传账号不存在:{}", site.getPromotionAccountId());
             return false;
         }
 
-        // 获取AccessToken
-        String accessToken = callbackAccount.getOcpcToken();
-
-
         Lead lead = leadService.getById(leadId);
+        Map<String, Object> params = JSONUtil.toBean(lead.getRawParams(), Map.class);
         // 构建回传参数
         Map<String, Object> conversionData = new HashMap<>();
-        // token
-        conversionData.put("token", accessToken);
-        // 点击id
-        conversionData.put("traceId", lead.getTraceId());
-        // 百度落地页
-        conversionData.put("logidUrl", lead.getViewUrl());
+        // ------------------------------通用参数----------
+        conversionData.put("ownerId", promotionAccount.getAdAccountId());
+        conversionData.put("appId", promotionAccount.getAppId());
+        conversionData.put("appSecret", promotionAccount.getAppSecret());
+        conversionData.put("token", promotionAccount.getAccessToken());
+        conversionData.put("refreshToken", promotionAccount.getRefreshToken());
+        conversionData.put("ip", lead.getIp());
+        conversionData.put("value", value);
+        // 链路唯一id
+        conversionData.put("traceId", traceId);
         // 事件
-        conversionData.put("eventType", eventType.getAdvertiserType(AdvertiserTypeEnum.BAIDU));
-        // 回传时间
+        conversionData.put("eventType", eventType.getAdvertiserType(advertiser));
         conversionData.put("timestamp", System.currentTimeMillis() / 1000);
+        // ------------------------------百度参数----------
+        // 百度落地页
+        conversionData.put("logidUrl", lead.getViewUrl());
+
+
+        // ------------------------------oppo参数----------
+        conversionData.put("tid", params.get("tid"));
+        conversionData.put("lbid", params.get("lbid"));
 
-        if (value != null && value > 0) {
-            conversionData.put("value", value);
-        }
 
         // 调用百度API回传
         boolean b = apiClient.reportConversion(conversionData);
@@ -114,7 +115,6 @@ public class ConversionServiceImpl implements IConversionService {
     }
 
 
-
     /**
      * 构建巨量引擎转化数据
      */
@@ -148,7 +148,7 @@ public class ConversionServiceImpl implements IConversionService {
         log.setConversionType(eventType.getCode());
         log.setConversionEvent(eventType.getDesc());
         log.setCallbackParams(JSONUtil.toJsonStr(conversionData));
-        log.setCallbackStatus(status? 1 : 2);
+        log.setCallbackStatus(status ? 1 : 2);
         log.setRetryCount(0);
         log.setTraceId(clickId);
         log.setCreateTime(LocalDateTime.now());

+ 1 - 1
fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/OceanEngineAdapter.java

@@ -52,7 +52,7 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
 
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.OCEAN_ENGINE;
+        return AdvertiserTypeEnum.OCEANENGINE;
     }
 }
 

+ 65 - 0
fs-ad-new-api/src/main/java/com/fs/app/integration/client/AbstractApiClient.java

@@ -0,0 +1,65 @@
+package com.fs.app.integration.client;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.service.IApiCallLogService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Map;
+
+@Slf4j
+public abstract class AbstractApiClient implements IApiClient {
+
+    @Autowired
+    private IApiCallLogService apiCallLogService;
+
+
+    /**
+     * 构建上下文信息 (traceId, userId等)
+     */
+    protected abstract String buildConversionParams(Map<String, Object> conversionData);
+
+    /**
+     * 统一封装API调用日志和异常处理逻辑
+     */
+    protected boolean executeWithLog(AdvertiserTypeEnum advertiserType,String apiUrl, Map<String, Object> params, ApiCall action) {
+        long start = System.currentTimeMillis();
+        String responseBody;
+        boolean callStatus = false;
+        try {
+            log.info("[{}] 调用开始, 参数: {}", apiUrl, params);
+            HttpResponse result = action.call();
+            responseBody = result.body();
+            log.info("[{}] 调用成功, 耗时: {} ms, 返回结果: {}", apiUrl, System.currentTimeMillis() - start, result);
+        } catch (Exception e) {
+            log.error("[{}] 调用失败, 耗时: {} ms, 错误信息: {}", apiUrl, System.currentTimeMillis() - start, e.getMessage(), e);
+            responseBody = "";
+        }
+        if (StrUtil.isNotEmpty(responseBody)) {
+            Integer code = JSONUtil.parseObj(responseBody).getInt("code");
+            if (ObjectUtil.isNotEmpty(code) && code == 0) {
+                callStatus = true;
+            }
+        }
+        saveApiCallLog(advertiserType,apiUrl, params, responseBody, callStatus, start);
+        return callStatus;
+    }
+
+    /**
+     * 保存日志到数据库或日志系统
+     */
+    protected void saveApiCallLog(AdvertiserTypeEnum advertiserType,String apiUrl, Map<String, Object> params
+            , String response,boolean callStatus,long  start) {
+        apiCallLogService.saveApiCallLog(advertiserType.getCode(),apiUrl, JSONUtil.toJsonStr(params), response, callStatus,start);
+    }
+
+
+    @FunctionalInterface
+    protected interface ApiCall {
+        HttpResponse call() throws Exception;
+    }
+}

+ 0 - 167
fs-ad-new-api/src/main/java/com/fs/app/integration/client/GDTApiClient.java

@@ -1,167 +0,0 @@
-package com.fs.app.integration.client;
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
-import com.fs.app.enums.AdvertiserTypeEnum;
-import com.fs.common.constant.SystemConstant;
-import com.fs.common.exception.ThirdPartyException;
-import com.fs.newAdv.service.IApiCallLogService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 腾讯广点通API客户端
- * 用于调用巨量引擎的API接口(转化回传等)
- *
- * @author zhangqin
- * @date 2025-11-03
- * @see <a href="https://developers.e.qq.com/v3.0/docs/api/leads_action_type_report/add">巨量引擎API文档</a>
- */
-@Slf4j
-@Component
-public class GDTApiClient implements IApiClient{
-
-    /**
-     * 巨量引擎转化回传API地址
-     */
-    private static final String CONVERSION_API_URL = "https://api.e.qq.com/user_actions/add";
-
-    @Autowired
-    private IApiCallLogService apiCallLogService;
-
-    /**
-     * 回传转化数据
-     *
-     * @param conversionData 转化数据
-     * @return 是否成功
-     */
-    @Override
-    public boolean reportConversion(Map<String, Object> conversionData) {
-        String apiUrl = CONVERSION_API_URL;
-        long startTime = System.currentTimeMillis();
-
-        log.info("开始回传转化数据到广点通,URL:{}", apiUrl);
-
-        try {
-            // 构建请求参数
-            Map<String, Object> requestParams = buildConversionParams(conversionData);
-            String requestBody = JSONUtil.toJsonStr(requestParams);
-
-            // 发送HTTP请求
-            HttpResponse response = HttpRequest.post(apiUrl)
-                    .header("Access-Token", (String) conversionData.get("token"))
-                    .header("Content-Type", "application/json")
-                    .body(requestBody)
-                    .timeout(SystemConstant.API_TIMEOUT)
-                    .execute();
-
-            String responseBody = response.body();
-            int statusCode = response.getStatus();
-
-            log.info("巨量引擎API响应:状态码={},响应={}", statusCode, responseBody);
-
-            // 记录API调用日志
-            apiCallLogService.saveApiCallLog(apiUrl, "POST", requestBody, statusCode, responseBody,
-                    statusCode == 200 ? 1 : 2, null, startTime);
-
-            // 解析响应
-            if (statusCode == 200) {
-                JSONObject result = JSONUtil.parseObj(responseBody);
-                Integer code = result.getInt("code");
-                if (code != null && code == 0) {
-                    log.info("巨量引擎转化回传成功");
-                    return true;
-                } else {
-                    String message = result.getStr("message", "未知错误");
-                    log.error("巨量引擎转化回传失败:code={},message={}", code, message);
-                }
-            } else {
-                log.error("巨量引擎API调用失败,状态码:{}", statusCode);
-            }
-        } catch (Exception e) {
-            log.error("调用巨量引擎API异常", e);
-            apiCallLogService.saveApiCallLog(apiUrl, "POST", JSONUtil.toJsonStr(conversionData),
-                    0, null, 2, e.getMessage(), startTime);
-        }
-        return false;
-    }
-
-    /**
-     * 构建转化回传参数
-     *
-     * @param conversionData 转化数据
-     * @return 请求参数
-     */
-    private Map<String, Object> buildConversionParams(Map<String, Object> conversionData) {
-        Map<String, Object> params = new HashMap<>();
-
-        // 必填参数
-        params.put("event_type", conversionData.get("eventType")); // 转化类型
-        params.put("context", buildContext(conversionData)); // 上下文信息
-
-        // 可选参数
-        if (conversionData.containsKey("value")) {
-            params.put("value", conversionData.get("value")); // 转化价值
-        }
-
-        if (conversionData.containsKey("description")) {
-            params.put("description", conversionData.get("description")); // 描述
-        }
-
-        return params;
-    }
-
-    /**
-     * 构建上下文信息
-     *
-     * @param conversionData 转化数据
-     * @return 上下文Map
-     */
-    private Map<String, Object> buildContext(Map<String, Object> conversionData) {
-        Map<String, Object> context = new HashMap<>();
-
-        // 必填:点击ID
-        context.put("ad", buildAdContext(conversionData));
-
-        // 用户信息
-        if (conversionData.containsKey("userId")) {
-            Map<String, Object> user = new HashMap<>();
-            user.put("user_id", conversionData.get("userId"));
-            context.put("user", user);
-        }
-
-        return context;
-    }
-
-    /**
-     * 构建广告上下文
-     *
-     * @param conversionData 转化数据
-     * @return 广告上下文
-     */
-    private Map<String, Object> buildAdContext(Map<String, Object> conversionData) {
-        Map<String, Object> ad = new HashMap<>();
-
-        // 点击ID(必填)
-        String clickId = (String) conversionData.get("traceId");
-        if (StrUtil.isBlank(clickId)) {
-            throw new ThirdPartyException("点击ID不能为空");
-        }
-        ad.put("callback", clickId);
-
-        return ad;
-    }
-
-    @Override
-    public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.BAIDU;
-    }
-}
-

+ 8 - 0
fs-ad-new-api/src/main/java/com/fs/app/integration/client/IAccessTokenClient.java

@@ -0,0 +1,8 @@
+package com.fs.app.integration.client;
+
+import java.util.Map;
+
+public interface IAccessTokenClient extends IApiClient {
+    Map<String, String> getAccessToken(Integer code, String appId, String appSecret, String callbackUrl);
+    Map<String, String> refreshAccessToken(String appId, String appSecret, String refreshToken);
+}

+ 13 - 80
fs-ad-new-api/src/main/java/com/fs/app/integration/client/BaiduApiClient.java → fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/BaiduApiClient.java

@@ -1,21 +1,15 @@
-package com.fs.app.integration.client;
+package com.fs.app.integration.client.advertiser;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
-import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.integration.client.AbstractApiClient;
 import com.fs.common.constant.SystemConstant;
 import com.fs.common.exception.ThirdPartyException;
-import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.ApiCallLog;
-import com.fs.newAdv.mapper.ApiCallLogMapper;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -31,17 +25,13 @@ import java.util.Map;
  */
 @Slf4j
 @Component
-public class BaiduApiClient implements IApiClient{
+public class BaiduApiClient extends AbstractApiClient {
 
     /**
      * 百度OCPC转化回传API地址
      */
     private static final String CONVERSION_API_URL = "https://ocpc.baidu.com/ocpcapi/api/uploadConvertData";
 
-
-    @Autowired
-    private ApiCallLogMapper apiCallLogMapper;
-
     /**
      * 回传转化数据到百度
      *
@@ -50,44 +40,14 @@ public class BaiduApiClient implements IApiClient{
      */
     @Override
     public boolean reportConversion(Map<String, Object> conversionData) {
-        String apiUrl = CONVERSION_API_URL;
-        long startTime = System.currentTimeMillis();
-
-        log.info("开始回传转化数据到百度,URL:{}", CONVERSION_API_URL);
-
-        try {
-            // 构建请求参数
-            Map<String, Object> requestParams = buildConversionParams(conversionData);
-            String requestBody = JSONUtil.toJsonStr(requestParams);
-
+        return executeWithLog(AdvertiserTypeEnum.OCEANENGINE, CONVERSION_API_URL, conversionData, () -> {
             // 发送HTTP请求
-            HttpResponse response = HttpRequest.post(CONVERSION_API_URL)
+            return HttpRequest.post(CONVERSION_API_URL)
                     .header("Content-Type", "application/json")
-                    .body(requestBody)
+                    .body(buildConversionParams(conversionData))
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
-
-            String responseBody = response.body();
-            int statusCode = response.getStatus();
-            log.info("百度API响应:状态码={},响应={}", statusCode, responseBody);
-
-            // 记录API调用日志
-            saveApiCallLog(apiUrl, "POST", requestBody, statusCode, responseBody,
-                    statusCode == 200 ? 1 : 2, null, startTime);
-
-            // 解析响应
-            if (statusCode == 200) {
-                JSONObject result = JSONUtil.parseObj(responseBody);
-                log.info("百度转化回传成功");
-                return true;
-            }
-        } catch (Exception e) {
-            log.error("调用百度API异常", e);
-            saveApiCallLog(apiUrl, "POST", JSONUtil.toJsonStr(conversionData),
-                    0, null, 2, e.getMessage(), startTime);
-            throw new ThirdPartyException("调用百度API异常:" + e.getMessage(), e);
-        }
-        return false;
+        });
     }
 
     /**
@@ -96,7 +56,7 @@ public class BaiduApiClient implements IApiClient{
      * @param conversionData 转化数据
      * @return 请求参数
      */
-    private Map<String, Object> buildConversionParams(Map<String, Object> conversionData) {
+    protected String buildConversionParams(Map<String, Object> conversionData) {
         Map<String, Object> params = new HashMap<>();
 
         // 必填参数
@@ -135,10 +95,10 @@ public class BaiduApiClient implements IApiClient{
         conversionList.add(conversion);
         params.put("conversionTypes", conversionList);
 
-        return params;
+        return JSONUtil.toJsonStr(params);
     }
 
-  /*  *//**
+    /*  *//**
      * 获取访问令牌(百度OAuth2.0)
      *
      * @param apiKey    API Key
@@ -176,7 +136,9 @@ public class BaiduApiClient implements IApiClient{
         }
     }*/
 
-/*    *//**
+    /*    */
+
+    /**
      * 查询账户报告数据
      *
      * @param accessToken 访问令牌
@@ -229,35 +191,6 @@ public class BaiduApiClient implements IApiClient{
             throw new ThirdPartyException("查询报告异常:" + e.getMessage(), e);
         }
     }*/
-
-    /**
-     * 保存API调用日志
-     */
-    private void saveApiCallLog(String apiUrl, String method, String requestParams,
-                                Integer responseStatus, String responseBody,
-                                Integer callStatus, String errorMsg, long startTime) {
-        try {
-            ApiCallLog log = new ApiCallLog();
-            log.setId(SnowflakeUtil.nextId());
-            log.setAdvertiserId(1L); // 百度
-            log.setAdvertiserName("百度");
-            log.setApiName("转化回传API");
-            log.setApiUrl(apiUrl);
-            log.setRequestMethod(method);
-            log.setRequestParams(requestParams);
-            log.setResponseStatus(responseStatus);
-            log.setResponseBody(responseBody);
-            log.setCallStatus(callStatus);
-            log.setErrorMsg(errorMsg);
-            log.setCostTime(System.currentTimeMillis() - startTime);
-            log.setCreateTime(LocalDateTime.now());
-
-            apiCallLogMapper.insert(log);
-        } catch (Exception e) {
-            log.error("保存API调用日志失败", e);
-        }
-    }
-
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
         return AdvertiserTypeEnum.BAIDU;

+ 123 - 0
fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/GDTApiClient.java

@@ -0,0 +1,123 @@
+package com.fs.app.integration.client.advertiser;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.integration.client.AbstractApiClient;
+import com.fs.app.integration.client.IAccessTokenClient;
+import com.fs.common.constant.SystemConstant;
+import com.fs.common.utils.SnowflakeUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广点通API客户端
+ * 用于调用巨量引擎的API接口(转化回传等)
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ * @see <a href="https://developers.e.qq.com/v3.0/docs/api/leads_action_type_report/add">巨量引擎API文档</a>
+ */
+@Slf4j
+@Component
+public class GDTApiClient extends AbstractApiClient implements IAccessTokenClient {
+
+    /**
+     * 巨量引擎转化回传API地址
+     */
+    private static final String CONVERSION_API_URL = "https://api.e.qq.com/user_actions/add";
+
+    /**
+     * 回传转化数据
+     *
+     * @param conversionData 转化数据
+     * @return 是否成功
+     */
+    @Override
+    public boolean reportConversion(Map<String, Object> conversionData) {
+        return executeWithLog(AdvertiserTypeEnum.OCEANENGINE,CONVERSION_API_URL, conversionData, () -> {
+            // 发送HTTP请求
+            return HttpRequest.post(CONVERSION_API_URL)
+                    .form("access_token", conversionData.get("timestamp"))
+                    .form("timestamp", conversionData.get("timestamp"))
+                    .form("nonce", SnowflakeUtil.nextIdStr())
+                    .body(buildConversionParams(conversionData))
+                    .timeout(SystemConstant.API_TIMEOUT)
+                    .execute();
+        });
+    }
+
+    /**
+     * 构建转化回传参数
+     *
+     * @param conversionData 转化数据
+     * @return 请求参数
+     */
+    protected String buildConversionParams(Map<String, Object> conversionData) {
+        Map<String, Object> params = new HashMap<>();
+
+        // 必填参数
+        params.put("account_id", conversionData.get("account_id")); // 转化类型
+        params.put("user_action_set_id", conversionData.get("user_action_set_id")); // 上下文信息
+
+        List<Map<String, Object>> actionsList = new ArrayList<>();
+        Map<String, Object> actions = new HashMap<>();
+        params.put("actions", actionsList);
+        actionsList.add(actions);
+        actions.put("action_type", conversionData.get("eventType"));
+        actions.put("action_time", conversionData.get("timestamp"));
+        return JSONUtil.toJsonStr(params);
+    }
+
+
+    @Override
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.GDT;
+    }
+
+
+    @Override
+    public Map<String, String> getAccessToken(Integer code, String appId, String appSecret, String callbackUrl) {
+        // 发送HTTP请求
+        HttpResponse response = HttpRequest.post(CONVERSION_API_URL)
+                .form("client_id", appId)
+                .form("client_secret", appSecret)
+                .form("grant_type", "authorization_code")
+                .form("authorization_code", code)
+                .form("redirect_uri", callbackUrl)
+                .timeout(SystemConstant.API_TIMEOUT)
+                .execute();
+        String responseBody = response.body();
+        JSONObject jsonObject = JSONUtil.parseObj(responseBody);
+        Map<String, String> map = new HashMap<>();
+        map.put("access_token", jsonObject.getJSONObject("data").getStr("access_token"));
+        map.put("refresh_token", jsonObject.getJSONObject("data").getStr("refresh_token"));
+        return map;
+    }
+
+    @Override
+    public Map<String, String> refreshAccessToken(String appId, String appSecret, String refreshToken) {
+        // 发送HTTP请求
+        HttpResponse response = HttpRequest.post(CONVERSION_API_URL)
+                .form("client_id", appId)
+                .form("client_secret", appSecret)
+                .form("grant_type", "refresh_token")
+                .form("refresh_token", refreshToken)
+                .timeout(SystemConstant.API_TIMEOUT)
+                .execute();
+        String responseBody = response.body();
+        JSONObject jsonObject = JSONUtil.parseObj(responseBody);
+        Map<String, String> map = new HashMap<>();
+        map.put("access_token", jsonObject.getJSONObject("data").getStr("access_token"));
+        map.put("refresh_token", jsonObject.getJSONObject("data").getStr("refresh_token"));
+        return map;
+    }
+}
+

+ 103 - 0
fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/OPPOApiClient.java

@@ -0,0 +1,103 @@
+package com.fs.app.integration.client.advertiser;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.crypto.digest.MD5;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.fs.ad.yk.utils.Md5Util;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.integration.client.AbstractApiClient;
+import com.fs.app.integration.client.IAccessTokenClient;
+import com.fs.common.constant.SystemConstant;
+import com.fs.common.utils.SnowflakeUtil;
+import com.fs.newAdv.service.IApiCallLogService;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.util.crypto.SHA1;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广点通API客户端
+ * 用于调用巨量引擎的API接口(转化回传等)
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ * @see <a href="https://adsfs.heytapimage.com/res/v2/default/market/doc/H5%E6%95%B0%E6%8D%AE%E5%9B%9E%E4%BC%A0%E8%BD%AC%E5%8C%96Api%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A33_0_3.pdf">巨量引擎API文档</a>
+ */
+@Slf4j
+@Component
+public class OPPOApiClient extends AbstractApiClient  {
+
+    /**
+     * 巨量引擎转化回传API地址
+     */
+    private static final String CONVERSION_API_URL = "https://sapi.ads.oppomobile.com/v1/clue/sendData";
+    private static final String SALT = "e0u6fnlag06lc3pl";
+
+    /**
+     * 回传转化数据
+     *
+     * @param conversionData 转化数据
+     * @return 是否成功
+     */
+    @Override
+    public boolean reportConversion(Map<String, Object> conversionData) {
+        return executeWithLog(AdvertiserTypeEnum.OPPO,CONVERSION_API_URL, conversionData, () -> {
+            // 构建请求参数
+            String requestBody = buildConversionParams(conversionData);
+            Long timestamp = (Long) conversionData.get("timestamp");
+            String ownerId = (String) conversionData.get("ownerId");
+            String appId = (String) conversionData.get("appId");
+            String appKey = (String) conversionData.get("appKey");
+            String sign = SHA1.gen(appId+appKey+timestamp);
+            String token = Base64.encode(ownerId+","+appId+","+timestamp+","+sign);
+            // 发送HTTP请求
+            return HttpRequest.post(CONVERSION_API_URL)
+                    .header("Content-Type", "application/json")
+                    .header("Authorization","Bearer  "+token)
+                    .body(requestBody)
+                    .timeout(SystemConstant.API_TIMEOUT)
+                    .execute();
+        });
+    }
+
+    /**
+     * 构建转化回传参数
+     *
+     * @param conversionData 转化数据
+     * @return 请求参数
+     */
+    protected  String buildConversionParams(Map<String, Object> conversionData) {
+        Map<String, Object> params = new HashMap<>();
+
+        // 落地页Id:投放广告到投放
+        params.put("pageId", conversionData.get("traceId"));
+        // 广告主id:对应广告主自提供
+        params.put("ownerId", conversionData.get("ownerId"));
+        // 用户IP:广告主收集
+        // params.put("ip", conversionData.get("ip"));
+        params.put("ip", "192.168.1.1");
+        // traceId:播放时追加在url上
+        params.put("tid", conversionData.get("tid"));
+        // 流量号:播放时追加在URL上
+        params.put("lbid ", conversionData.get("lbid"));
+        // 事件
+        params.put("transformType ", conversionData.get("eventType"));
+        return JSONUtil.toJsonStr(params);
+    }
+
+
+
+    @Override
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.OPPO;
+    }
+}
+

+ 11 - 48
fs-ad-new-api/src/main/java/com/fs/app/integration/client/OceanEngineApiClient.java → fs-ad-new-api/src/main/java/com/fs/app/integration/client/advertiser/OceanEngineApiClient.java

@@ -1,4 +1,4 @@
-package com.fs.app.integration.client;
+package com.fs.app.integration.client.advertiser;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpRequest;
@@ -6,8 +6,10 @@ import cn.hutool.http.HttpResponse;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.integration.client.AbstractApiClient;
 import com.fs.common.constant.SystemConstant;
 import com.fs.common.exception.ThirdPartyException;
+import com.fs.common.utils.SnowflakeUtil;
 import com.fs.newAdv.service.IApiCallLogService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -26,16 +28,13 @@ import java.util.Map;
  */
 @Slf4j
 @Component
-public class OceanEngineApiClient implements IApiClient{
+public class OceanEngineApiClient extends AbstractApiClient {
 
     /**
      * 巨量引擎转化回传API地址
      */
     private static final String CONVERSION_API_URL = "https://ad.oceanengine.com/open_api/2/conversion/";
 
-    @Autowired
-    private IApiCallLogService apiCallLogService;
-
     /**
      * 回传转化数据到巨量引擎
      *
@@ -44,53 +43,17 @@ public class OceanEngineApiClient implements IApiClient{
      */
     @Override
     public boolean reportConversion(Map<String, Object> conversionData) {
-        String apiUrl = CONVERSION_API_URL;
-        long startTime = System.currentTimeMillis();
-
-        log.info("开始回传转化数据到巨量引擎,URL:{}", apiUrl);
-
-        try {
+        return executeWithLog(AdvertiserTypeEnum.OCEANENGINE,CONVERSION_API_URL, conversionData, () -> {
             // 构建请求参数
-            Map<String, Object> requestParams = buildConversionParams(conversionData);
-            String requestBody = JSONUtil.toJsonStr(requestParams);
-
+            String requestBody = buildConversionParams(conversionData);
             // 发送HTTP请求
-            HttpResponse response = HttpRequest.post(apiUrl)
+            return HttpRequest.post(CONVERSION_API_URL)
                     .header("Access-Token", (String) conversionData.get("token"))
                     .header("Content-Type", "application/json")
                     .body(requestBody)
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
-
-            String responseBody = response.body();
-            int statusCode = response.getStatus();
-
-            log.info("巨量引擎API响应:状态码={},响应={}", statusCode, responseBody);
-
-            // 记录API调用日志
-            apiCallLogService.saveApiCallLog(apiUrl, "POST", requestBody, statusCode, responseBody,
-                    statusCode == 200 ? 1 : 2, null, startTime);
-
-            // 解析响应
-            if (statusCode == 200) {
-                JSONObject result = JSONUtil.parseObj(responseBody);
-                Integer code = result.getInt("code");
-                if (code != null && code == 0) {
-                    log.info("巨量引擎转化回传成功");
-                    return true;
-                } else {
-                    String message = result.getStr("message", "未知错误");
-                    log.error("巨量引擎转化回传失败:code={},message={}", code, message);
-                }
-            } else {
-                log.error("巨量引擎API调用失败,状态码:{}", statusCode);
-            }
-        } catch (Exception e) {
-            log.error("调用巨量引擎API异常", e);
-            apiCallLogService.saveApiCallLog(apiUrl, "POST", JSONUtil.toJsonStr(conversionData),
-                    0, null, 2, e.getMessage(), startTime);
-        }
-        return false;
+        });
     }
 
     /**
@@ -99,7 +62,7 @@ public class OceanEngineApiClient implements IApiClient{
      * @param conversionData 转化数据
      * @return 请求参数
      */
-    private Map<String, Object> buildConversionParams(Map<String, Object> conversionData) {
+    protected String buildConversionParams(Map<String, Object> conversionData) {
         Map<String, Object> params = new HashMap<>();
 
         // 必填参数
@@ -115,7 +78,7 @@ public class OceanEngineApiClient implements IApiClient{
             params.put("description", conversionData.get("description")); // 描述
         }
 
-        return params;
+        return JSONUtil.toJsonStr(params);
     }
 
     /**
@@ -161,7 +124,7 @@ public class OceanEngineApiClient implements IApiClient{
 
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.BAIDU;
+        return AdvertiserTypeEnum.OCEANENGINE;
     }
 }
 

+ 1 - 1
fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/OceanEngineCallbackStrategy.java

@@ -41,7 +41,7 @@ public class OceanEngineCallbackStrategy implements ICallbackStrategy {
 
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.OCEAN_ENGINE;
+        return AdvertiserTypeEnum.OCEANENGINE;
     }
 }
 

+ 11 - 6
fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer.java

@@ -5,6 +5,7 @@ import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.app.enums.ConversionTypeEnum;
 import com.fs.app.facade.IConversionService;
 import com.fs.app.mq.message.ConversionMessage;
+import com.fs.common.annotation.DistributeLock;
 import com.fs.common.utils.RedisUtil;
 import com.fs.newAdv.mapper.ConversionLogMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -49,7 +50,14 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
      * @param message 转化消息
      */
     @Override
+    @DistributeLock(scene = "mq", keyExpression = "#message.getMessageId", waitTime = 0, errorMsg = "任务已执行")
     public void onMessage(ConversionMessage message) {
+        log.info("消费者1开始消费 | message={}", message);
+        try {
+            Thread.sleep(1000L);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
         AdvertiserTypeEnum advertiser = message.getAdvertiser();
         String traceId = message.getTraceId();
         String messageId = message.getMessageId();
@@ -67,17 +75,14 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
         }
 
         // 2. 调用广告平台API回传
-        boolean success = conversionService.reportToAdvertiser(advertiser,siteId, eventType,
+        boolean success = conversionService.reportToAdvertiser(advertiser, siteId, eventType,
                 traceId, leadId, null);
 
+        markAsProcessed(messageId);
         if (success) {
-            // 3. 标记为已处理
-            markAsProcessed(messageId);
-
             log.info("转化回传成功 | traceId={}, eventType={}", traceId, eventType);
         } else {
-            // 抛出异常触发RocketMQ重试
-            throw new RuntimeException("回传失败,触发重试");
+            log.info("转化回传失败 | traceId={}, eventType={}", traceId, eventType);
         }
     }
 

+ 111 - 0
fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer2.java

@@ -0,0 +1,111 @@
+package com.fs.app.mq.consumer;
+
+import com.fs.app.constant.MqTopicConstant;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.enums.ConversionTypeEnum;
+import com.fs.app.facade.IConversionService;
+import com.fs.app.mq.message.ConversionMessage;
+import com.fs.common.annotation.DistributeLock;
+import com.fs.common.utils.RedisUtil;
+import com.fs.newAdv.mapper.ConversionLogMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.ConsumeMode;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 转化消息消费者
+ * 多线程并发消费,支持自动重试
+ *
+ * @author zhangqin
+ */
+@Slf4j
+@Component
+@RocketMQMessageListener(
+        topic = MqTopicConstant.CONVERSION_TOPIC,
+        consumerGroup = "conversion-topic-consumer2-group",
+        // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
+        consumeMode = ConsumeMode.CONCURRENTLY,
+        // 最大重试次数(RocketMQ默认16次)
+        maxReconsumeTimes = 16
+)
+public class ConversionMessageConsumer2 implements RocketMQListener<ConversionMessage> {
+
+    @Autowired
+    private IConversionService conversionService;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    @Autowired
+    private ConversionLogMapper conversionLogMapper;
+
+    /**
+     * 消费转化消息
+     *
+     * @param message 转化消息
+     */
+    @Override
+    @DistributeLock(scene = "mq", keyExpression = "#message.getMessageId", waitTime = 0, errorMsg = "任务已执行")
+    public void onMessage(ConversionMessage message) {
+        log.info("消费者2开始消费 | message={}", message);
+        try {
+            Thread.sleep(1000L);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        AdvertiserTypeEnum advertiser = message.getAdvertiser();
+        String traceId = message.getTraceId();
+        String messageId = message.getMessageId();
+        Long siteId = message.getSiteId();
+        Long leadId = message.getLeadId();
+        ConversionTypeEnum eventType = message.getEventType();
+
+        log.info("消费者接收到转化消息 | traceId={}, advertiser={}, eventType={}",
+                traceId, advertiser, eventType.getDesc());
+
+        // 1. 幂等性校验(防止重复消费)
+        if (!checkIdempotent(messageId)) {
+            log.warn("消息已处理,跳过 | messageId={}", messageId);
+            return;
+        }
+
+        // 2. 调用广告平台API回传
+        boolean success = conversionService.reportToAdvertiser(advertiser, siteId, eventType,
+                traceId, leadId, null);
+
+        markAsProcessed(messageId);
+        if (success) {
+            log.info("转化回传成功 | traceId={}, eventType={}", traceId, eventType);
+        } else {
+            log.info("转化回传失败 | traceId={}, eventType={}", traceId, eventType);
+        }
+    }
+
+    /**
+     * 幂等性校验(Redis)
+     *
+     * @param messageId 消息ID
+     * @return true=未处理,false=已处理
+     */
+    private boolean checkIdempotent(String messageId) {
+        String key = "conversion:processed:" + messageId;
+        Boolean success = redisUtil.setIfAbsent(key, "1", 7, TimeUnit.DAYS);
+        return Boolean.TRUE.equals(success);
+    }
+
+    /**
+     * 标记消息已处理
+     *
+     * @param messageId 消息ID
+     */
+    private void markAsProcessed(String messageId) {
+        String key = "conversion:processed:" + messageId;
+        redisUtil.set(key, "1", 7, TimeUnit.DAYS);
+    }
+}
+

+ 1 - 1
fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java

@@ -37,7 +37,7 @@ public class ConversionRetryTask {
      * 转化回传重试任务
      * cron: 每10分钟执行
      */
-    @Scheduled(cron = "0 */10 * * * ?")
+    @Scheduled(cron = "0 */1 * * * ?")
     @DistributeLock(scene = "task", key = "conversion_retry", waitTime = 0, errorMsg = "任务已执行")
     public void execute() {
         // 查询待重试的转化记录

+ 1 - 9
fs-service/src/main/java/com/fs/newAdv/domain/ApiCallLog.java

@@ -29,11 +29,6 @@ public class ApiCallLog implements Serializable {
      */
     private Long advertiserId;
 
-    /**
-     * 广告商名称
-     */
-    private String advertiserName;
-
     /**
      * API名称
      */
@@ -44,10 +39,7 @@ public class ApiCallLog implements Serializable {
      */
     private String apiUrl;
 
-    /**
-     * 请求方法
-     */
-    private String requestMethod;
+
 
     /**
      * 请求参数

+ 4 - 0
fs-service/src/main/java/com/fs/newAdv/domain/Lead.java

@@ -57,6 +57,10 @@ public class Lead implements Serializable {
      * 状态:0=新线索,1=已联系,2=已转化,3=无效
      */
     private Integer status;
+
+
+    private Integer ip;
+    /**
     /**
      * 创建时间
      */

+ 8 - 0
fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java

@@ -99,6 +99,14 @@ public class PromotionAccount implements Serializable {
      * 应用授权链接
      */
     private String authUrl;
+    /**
+     * 应用授权链接
+     */
+    private String accessToken;
+    /**
+     * 应用授权链接
+     */
+    private String refreshToken;
 
     /**
      * 创建时间

+ 2 - 3
fs-service/src/main/java/com/fs/newAdv/service/IApiCallLogService.java

@@ -10,8 +10,7 @@ import com.fs.newAdv.domain.ApiCallLog;
  */
 public interface IApiCallLogService extends IService<ApiCallLog> {
 
-    void saveApiCallLog(String apiUrl, String method, String requestParams,
-                        Integer responseStatus, String responseBody,
-                        Integer callStatus, String errorMsg, long startTime);
+    void saveApiCallLog(Long advertiserType,String apiUrl, String requestParams, String responseBody,
+                        boolean callStatus, long startTime);
 }
 

+ 4 - 9
fs-service/src/main/java/com/fs/newAdv/service/impl/ApiCallLogServiceImpl.java

@@ -17,21 +17,16 @@ import java.time.LocalDateTime;
 @Service
 public class ApiCallLogServiceImpl extends ServiceImpl<ApiCallLogMapper, ApiCallLog> implements IApiCallLogService {
     @Override
-    public void saveApiCallLog(String apiUrl, String method, String requestParams,
-                               Integer responseStatus, String responseBody,
-                               Integer callStatus, String errorMsg, long startTime) {
+    public void saveApiCallLog(Long advertiserType,String apiUrl, String requestParams, String responseBody,
+                               boolean callStatus, long startTime) {
         ApiCallLog log = new ApiCallLog();
         log.setId(SnowflakeUtil.nextId());
-        log.setAdvertiserId(2L); // 巨量引擎
-        log.setAdvertiserName("巨量引擎");
+        log.setAdvertiserId(advertiserType); // 巨量引擎
         log.setApiName("转化回传API");
         log.setApiUrl(apiUrl);
-        log.setRequestMethod(method);
         log.setRequestParams(requestParams);
-        log.setResponseStatus(responseStatus);
         log.setResponseBody(responseBody);
-        log.setCallStatus(callStatus);
-        log.setErrorMsg(errorMsg);
+        log.setCallStatus(callStatus?1:2);
         log.setCostTime(System.currentTimeMillis() - startTime);
         log.setCreateTime(LocalDateTime.now());
         this.save(log);

+ 1 - 0
pom.xml

@@ -282,6 +282,7 @@
         <module>fs-repeat-api</module>
         <module>fs-ipad-task</module>
         <module>fs-websocket</module>
+        <module>fs-ad-new-api</module>
     </modules>
 
     <packaging>pom</packaging>