Bladeren bron

Merge remote-tracking branch 'origin/master' into Payment

yfh 15 uur geleden
bovenliggende
commit
ccccc8464d
47 gewijzigde bestanden met toevoegingen van 560 en 238 verwijderingen
  1. 1 1
      fs-ad-new-api/Dockerfile
  2. 3 4
      fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java
  3. 14 10
      fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java
  4. 26 2
      fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java
  5. 6 4
      fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java
  6. 7 3
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  7. 54 41
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  8. 3 1
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  9. 2 14
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java
  10. 54 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/RocketMQTraceIdAspect.java
  11. 1 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  12. 8 4
      fs-live-app/src/main/java/com/fs/live/task/Task.java
  13. 2 2
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  14. 1 1
      fs-qw-api/Dockerfile
  15. 2 19
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  16. 2 2
      fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java
  17. 1 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  18. 8 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  19. 1 1
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java
  20. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveCompletionPointsRecordService.java
  21. 114 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  22. 1 0
      fs-service/src/main/java/com/fs/newAdv/constant/MqTopicConstant.java
  23. 16 7
      fs-service/src/main/java/com/fs/newAdv/domain/Lead.java
  24. 12 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/QwExternalIdBindTrackReq.java
  25. 10 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/TraceIdDto.java
  26. 8 8
      fs-service/src/main/java/com/fs/newAdv/enums/SystemEventTypeEnum.java
  27. 2 1
      fs-service/src/main/java/com/fs/newAdv/event/ConversionEventListener.java
  28. 2 3
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/BaiduAdapter.java
  29. 4 5
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/OPPOAdapter.java
  30. 2 3
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/OceanEngineAdapter.java
  31. 1 2
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/TencentAdapter.java
  32. 1 2
      fs-service/src/main/java/com/fs/newAdv/integration/adapter/VIVOAdapter.java
  33. 16 9
      fs-service/src/main/java/com/fs/newAdv/integration/client/AbstractApiClient.java
  34. 1 1
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/BaiduApiClient.java
  35. 13 11
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OPPOApiClient.java
  36. 21 2
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OceanEngineApiClient.java
  37. 1 1
      fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/VIVOApiClient.java
  38. 13 5
      fs-service/src/main/java/com/fs/newAdv/service/ILeadService.java
  39. 95 59
      fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java
  40. 1 1
      fs-service/src/main/java/com/fs/newAdv/service/impl/SiteServiceImpl.java
  41. 12 0
      fs-service/src/main/java/com/fs/newAdv/vo/ConversionParmVo.java
  42. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java
  43. 5 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  44. 1 1
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  45. 3 0
      fs-service/src/main/resources/application-druid-ylrz.yml
  46. 2 5
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java
  47. 4 0
      fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java

+ 1 - 1
fs-ad-new-api/Dockerfile

@@ -1,4 +1,4 @@
-FROM openjdk:8-jre
+FROM anolis-registry.cn-zhangjiakou.cr.aliyuncs.com/openanolis/openjdk:8-8.6
 # java版本,最好使用openjdk,而不是类似于Java:1.8
 COPY ./target/fs-ad-new-api.jar fs-ad-new-api.jar
 # 向外暴露的接口,最好与项目yml文件中的端口一致

+ 3 - 4
fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java

@@ -31,18 +31,17 @@ public class LandingPageController {
      * 落地页访问
      */
     @PostMapping("/h5/home")
-    public Result<LandingIndexRes> track(
+    public Result<LandingIndexRes> h5Home(
             @RequestBody LandingIndexReq req) {
-        log.info("落地页访问追踪:req={}", req);
         // 查询落地页模板
-        return Result.success(facadeService.getLandingIndexBySiteId(req.getAllParams()));
+        return Result.success(facadeService.getLandingIndexBySiteId(req.getViewUrl(),req.getAllParams()));
     }
 
     /**
      * 小程序访问
      */
     @PostMapping("/mini/home")
-    public Result<LandingIndexRes> track(@RequestBody WeChatLandingIndexReq req) {
+    public Result<LandingIndexRes> miniHome(@RequestBody WeChatLandingIndexReq req) {
         return Result.success(facadeService.getWxLandingIndexBySiteId(req));
     }
 

+ 14 - 10
fs-ad-new-api/src/main/java/com/fs/app/controller/TestController.java

@@ -1,15 +1,13 @@
 package com.fs.app.controller;
 
+import com.fs.newAdv.dto.req.TraceIdDto;
 import com.fs.newAdv.integration.client.advertiser.BaiduApiClient;
 import com.fs.newAdv.enums.SystemEventTypeEnum;
 import com.fs.newAdv.event.ConversionEventPublisher;
 import com.fs.newAdv.service.IPromotionAccountService;
 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.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * 广告商监测链接
@@ -28,15 +26,21 @@ public class TestController {
     @Autowired
     private IPromotionAccountService promotionAccountService;
 
-    @GetMapping("/test1/{traceId}")
-    public void test1(@PathVariable("traceId") String traceId) {
+    @PostMapping("/test1")
+    public void test1(@RequestBody TraceIdDto traceId) {
         log.info("模拟 当日加群 事件完成");
-        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.GROUP_TODAY);
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.GROUP_TODAY);
     }
 
-    @GetMapping("/test2/{traceId}")
-    public void test2(@PathVariable("traceId") String traceId) {
+    @PostMapping("/test2")
+    public void test2(@RequestBody TraceIdDto traceId) {
         log.info("模拟 当日加微 事件完成");
-        conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.WEI_CHAT_TODAY);
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.WEI_CHAT_TODAY);
+    }
+
+    @PostMapping("/test3")
+    public void test3(@RequestBody TraceIdDto traceId) {
+        log.info("模拟 微信授权 事件完成");
+        conversionEventPublisher.publishConversionEvent(traceId.getTraceId(), SystemEventTypeEnum.AUTH_TODAY_CREATE);
     }
 }

+ 26 - 2
fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java

@@ -1,6 +1,10 @@
 package com.fs.app.controller;
 
 import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.app.facade.IConversionService;
+import com.fs.common.result.Result;
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
+import com.fs.newAdv.enums.SystemEventTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -22,6 +26,8 @@ public class TrackingController {
     private CallbackProcessingFacadeService facadeService;
 
 
+    @Autowired
+    private IConversionService conversionService;
     /**
      * 监测链接端口
      *
@@ -29,14 +35,14 @@ public class TrackingController {
      * @param response  HTTP响应
      */
     @GetMapping("/click/{advertiserCode}")
-    public void trackBaidu(
+    public void track(
             @RequestParam Map<String, String> allParams,
             @PathVariable("advertiserCode") Long advertiserCode,
             HttpServletResponse response) {
         log.info("接收监测请求 | params={}", allParams);
         try {
             // 2. 保存点击追踪记录
-            facadeService.saveClickTrace(advertiserCode,allParams);
+            facadeService.saveClickTrace(advertiserCode, allParams);
             // 3. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
         } catch (Exception e) {
@@ -45,4 +51,22 @@ public class TrackingController {
         }
     }
 
+    /**
+     * 绑定企微用户与线索
+     */
+    @PostMapping("/bind/qwTrack")
+    public Result<String> qwExternalIdBindTrack(@RequestBody QwExternalIdBindTrackReq req) {
+        facadeService.qwExternalIdBindTrack(req);
+        return Result.success();
+    }
+
+    /**
+     * 绑定企微用户与线索
+     */
+    @PostMapping("/bind/qwTrack/test/{id1}/{id2}")
+    public Result<String> qwExternalIdBindTrack1(@PathVariable String id1, @PathVariable String id2) {
+        boolean success = conversionService.reportTrackingToAdvertiser(SystemEventTypeEnum.getByCode(id1), id2);
+        return Result.success();
+    }
+
 }

+ 6 - 4
fs-ad-new-api/src/main/java/com/fs/app/controller/WeChatController.java

@@ -50,7 +50,8 @@ public class WeChatController {
             try {
                 String access_token = advMiniConfig.getAccessToken();
                 // 判断token是否过期
-                if (advMiniConfig.getExpiresIn().isBefore(LocalDateTime.now().plusMinutes(10))) {
+                if (LocalDateTime.now().plusMinutes(10).isAfter(advMiniConfig.getExpiresIn())) {
+                    // 提前10分钟刷新Token
                     HttpResponse execute2 = HttpRequest.get("https://api.weixin.qq.com/cgi-bin/token")
                             .form("grant_type", "client_credential")
                             .form("appid", advMiniConfig.getAppId())
@@ -59,15 +60,16 @@ public class WeChatController {
                             .execute();
                     JSONObject obj = JSONObject.parseObject(execute2.body());
                     access_token = obj.getString("access_token");
+                    log.info("getSchemeUrl:{}", obj);
                     advMiniConfig.setAccessToken(access_token);
-                    advMiniConfig.setExpiresIn(LocalDateTime.now().plusSeconds(obj.getLong("expires_in")));
+                    advMiniConfig.setExpiresIn(LocalDateTime.now().plusSeconds(obj.getInteger("expires_in")));
                     advMiniConfigService.updateById(advMiniConfig);
                 }
                 Map<String, Object> map = new HashMap<>();
                 Map<String, Object> map2 = new HashMap<>();
-                map2.put("path", "/pages/shopping/productDetails");
+                map2.put("path", "pages/home/productList");
                 map2.put("query", "traceId=" + traceId);
-                map2.put("env_version", "trial");
+                    map2.put("env_version", "trial");
                 map.put("jump_wxa", map2);
                 map.put("is_expire", false);
                 HttpResponse execute = HttpRequest.post("https://api.weixin.qq.com/wxa/generatescheme?access_token=" + access_token)

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

@@ -1,5 +1,6 @@
 package com.fs.app.facade;
 
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
 import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
 import com.fs.newAdv.dto.res.LandingIndexRes;
 
@@ -10,20 +11,23 @@ public interface CallbackProcessingFacadeService {
 
     /**
      * 链化追踪点击
+     *
      * @param allParams
      */
-    void saveClickTrace(Long advertiserCode,Map<String,String> allParams);
+    void saveClickTrace(Long advertiserCode, Map<String, String> allParams);
+
     /**
      * 根据站点ID获取落地页信息
      *
-     * @param siteId
      * @return
      */
-    LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams);
+    LandingIndexRes getLandingIndexBySiteId(String viewUrl,Map<String, String> allParams);
 
     //----------------------code回调---------------------------------
     void gdtGetAuthCode(Integer code, Long state);
 
 
     LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req);
+
+    void qwExternalIdBindTrack(QwExternalIdBindTrackReq req);
 }

+ 54 - 41
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -1,22 +1,24 @@
 package com.fs.app.facade;
 
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
-import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
-import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
 import com.fs.common.exception.base.BusinessException;
 import com.fs.common.utils.RedisUtil;
+import com.fs.common.utils.SnowflakeUtil;
 import com.fs.newAdv.domain.LandingPageTemplate;
 import com.fs.newAdv.domain.Lead;
 import com.fs.newAdv.domain.Site;
+import com.fs.newAdv.dto.req.QwExternalIdBindTrackReq;
 import com.fs.newAdv.dto.req.WeChatLandingIndexReq;
 import com.fs.newAdv.dto.res.LandingIndexRes;
 import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
+import com.fs.newAdv.integration.factory.AdvertiserHandlerFactory;
 import com.fs.newAdv.service.ILandingPageTemplateService;
 import com.fs.newAdv.service.ILeadService;
 import com.fs.newAdv.service.ISiteService;
@@ -72,50 +74,53 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     public void saveClickTrace(Long advertiserCode, Map<String, String> allParams) {
         IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserCode));
         Lead lead = advertiserAdapter.adaptCallbackData(allParams);
-        Lead byTraceId = leadService.getByTraceId(lead.getTraceId());
-        if (ObjectUtil.isNotEmpty(byTraceId)) {
-            throw new BusinessException("监测信息已存在: " + lead.getTraceId());
+        Lead byClickId = leadService.getByClickId(lead.getTraceId());
+        if (ObjectUtil.isNotEmpty(byClickId)) {
+            log.info("线索已存在:{}", lead.getTraceId());
+            return;
         }
         lead.setStatus(0);
         lead.setClickTrigger(1);
         lead.setTraceRawParams(JSONUtil.toJsonStr(allParams));
+        lead.setTraceId(SnowflakeUtil.randomUUID());
         boolean saved = leadService.save(lead);
         if (!saved) {
             log.error("线索保存失败:{}", lead);
-            throw new RuntimeException("线索保存失败");
         }
     }
 
     private String getTraceIdByAdvertiser(AdvertiserTypeEnum byCode, Map<String, String> allParams) {
-        String traceId;
+        String clickId;
         switch (byCode) {
             case OCEANENGINE:
             case TENCENT:
+                clickId = allParams.get("clickid");
+                break;
             case OPPO:
-                traceId = allParams.get("click_id");
+                clickId = allParams.get("tid");
                 break;
             case BAIDU:
-                traceId = allParams.get("bd_vid");
+                clickId = allParams.get("bd_vid");
                 break;
             case VIVO:
-                traceId = allParams.get("requestId");
+                clickId = allParams.get("requestId");
                 break;
             case IQIYI:
-                traceId = allParams.get("traceId");
+                clickId = allParams.get("traceId");
                 break;
             default:
-                traceId = "ylrz_test";
+                clickId = "ylrz_test";
         }
-        if (StrUtil.isEmpty(traceId)) {
-            traceId = "ylrz_test";
+        if (StrUtil.isEmpty(clickId)) {
+            clickId = "ylrz_test"+ IdUtil.randomUUID();
         }
 
-        return traceId;
+        return clickId;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public LandingIndexRes getLandingIndexBySiteId(Map<String, String> allParams) {
+    public LandingIndexRes getLandingIndexBySiteId(String viewUrl,Map<String, String> allParams) {
         // 站点信息
         String paramsSiteId = allParams.get("siteId");
         if (ObjectUtil.isEmpty(paramsSiteId)) {
@@ -131,25 +136,25 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         // 广告商信息
         Long advertiserId = byId.getAdvertiserId();
         // 访问链路id
-        String traceId = getTraceIdByAdvertiser(Objects.requireNonNull(AdvertiserTypeEnum.getByCode(advertiserId)), allParams);
+        String clickId = getTraceIdByAdvertiser(Objects.requireNonNull(AdvertiserTypeEnum.getByCode(advertiserId)), allParams);
         // 线索信息
-        Lead byTraceId = leadService.getByTraceId(traceId);
-        boolean isNewLead = ObjectUtil.isEmpty(byTraceId);
+        Lead lead = leadService.getByClickId(clickId);
+        boolean isNewLead = ObjectUtil.isEmpty(lead);
         if (isNewLead) {
             IAdvertiserAdapter advertiserAdapter = handlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(advertiserId));
-            byTraceId = advertiserAdapter.adaptCallbackData(allParams);
-            byTraceId.setAdvertiserId(advertiserId);
-            byTraceId.setSiteId(siteId);
-            byTraceId.setTraceId(traceId);
+            lead = advertiserAdapter.adaptCallbackData(allParams);
+            lead.setAdvertiserId(advertiserId);
+            lead.setSiteId(siteId);
+            lead.setClickId(clickId);
             // 设置站点和落地页的关联
-            setSiteByIdeaId(siteId, byTraceId.getIdeaId());
+            setSiteByIdeaId(siteId, lead.getIdeaId());
         } else {
             // 检查站点和广告商信息是否异常
-            if (!Objects.equals(byTraceId.getSiteId(), siteId)) {
-                log.info("落地页站点信息异常:{}---{}", byTraceId.getSiteId(), siteId);
+            if (!Objects.equals(lead.getSiteId(), siteId)) {
+                log.info("落地页站点信息异常:{}---{}", lead.getSiteId(), siteId);
             }
-            if (!Objects.equals(byTraceId.getAdvertiserId(), advertiserId)) {
-                log.info("落地页广告商信息异常:{}---{}", byTraceId.getAdvertiserId(), advertiserId);
+            if (!Objects.equals(lead.getAdvertiserId(), advertiserId)) {
+                log.info("落地页广告商信息异常:{}---{}", lead.getAdvertiserId(), advertiserId);
             }
         }
         // 模板缓存
@@ -170,26 +175,28 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
         LandingPageTemplate landingPageTemplate = landingPageTemplateService.getById(byId.getLaunchPageId());
         JSONObject jsonObject = JSONUtil.parseObj(landingPageTemplate.getTemplateData());
         // 替换二维码链接
-        updateQrCodeInTemplate(jsonObject, traceId, byId, byTraceId);
+        updateQrCodeInTemplate(jsonObject, byId, lead);
         String templateData = JSONUtil.toJsonStr(jsonObject);
 
         // 保存或更新 线索信息
         LocalDateTime now = LocalDateTime.now();
-        byTraceId.setLandingPageRawParams(JSONUtil.toJsonStr(allParams));
-        byTraceId.setLandingPageTrigger(1);
-        byTraceId.setLandingPageTs(now);
-        byTraceId.setUpdateTime(now);
+        lead.setLandingPageRawParams(JSONUtil.toJsonStr(allParams));
+        lead.setLandingPageTrigger(1);
+        lead.setLandingPageTs(now);
+        lead.setUpdateTime(now);
+        lead.setViewUrl(viewUrl);
         if (isNewLead) {
-            leadService.save(byTraceId);
+            lead.setTraceId(SnowflakeUtil.randomUUID());
+            leadService.save(lead);
         } else {
-            leadService.updateById(byTraceId);
+            leadService.updateById(lead);
         }
 
         // 封装返回结果
         LandingIndexRes res = new LandingIndexRes();
-        redisUtil.set(TEMPLATE_DATA + traceId, templateData, 24, TimeUnit.HOURS);
+        redisUtil.set(TEMPLATE_DATA + lead.getTraceId(), templateData, 24, TimeUnit.HOURS);
         res.setTemplateData(templateData);
-        res.setTraceId(byTraceId.getTraceId());
+        res.setTraceId(lead.getTraceId());
         return res;
     }
 
@@ -202,7 +209,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     /**
      * 更新模板中的二维码信息
      */
-    private void updateQrCodeInTemplate(JSONObject templateData, String traceId, Site site, Lead lead) {
+    private void updateQrCodeInTemplate(JSONObject templateData, Site site, Lead lead) {
         JSONArray configList = templateData.getJSONArray("configList");
         if (configList == null || configList.isEmpty()) {
             return;
@@ -227,7 +234,7 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
                                                Integer allocationRule,
                                                Long allocationRuleId,
                                                Lead byTraceId) {
-        log.info("开始获取广告二维码: {} {} {} {}", launchType, allocationRule, allocationRuleId ,byTraceId);
+        log.info("开始获取广告二维码: {} {} {} {}", launchType, allocationRule, allocationRuleId, byTraceId);
         // 二维码
         String qrCode = "";
         if (allocationRule == 1) {
@@ -285,10 +292,16 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
     @Override
     public LandingIndexRes getWxLandingIndexBySiteId(WeChatLandingIndexReq req) {
         // 更新授权页访问信息记录
-        leadService.updateAuthIndex(req.getTraceId(),req.getType());
+        leadService.updateAuthIndex(req.getTraceId(), req.getType());
         String templateData = String.valueOf(redisUtil.get(TEMPLATE_DATA + req.getTraceId()));
         LandingIndexRes landingIndexRes = new LandingIndexRes();
         landingIndexRes.setTemplateData(templateData);
         return landingIndexRes;
     }
+
+    @Override
+    public void qwExternalIdBindTrack(QwExternalIdBindTrackReq req) {
+        // 广告线索处理
+        leadService.updateAddMemberLead(req.getQwExternalId(), req.getUnionid());
+    }
 }

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

@@ -96,7 +96,7 @@ public class ConversionServiceImpl implements IConversionService {
             return false;
         }
 
-        Map<String, Object> params = JSONUtil.toBean(lead.getTraceRawParams(), Map.class);
+        Map<String, Object> params = JSONUtil.toBean(lead.getLandingPageRawParams(), Map.class);
         // 构建回传参数
         Map<String, Object> conversionData = new HashMap<>();
         // ------------------------------通用参数----------
@@ -109,6 +109,8 @@ public class ConversionServiceImpl implements IConversionService {
         conversionData.put("traceId", traceId);
         conversionData.put("eventType", advertiserEventType.getAdvertiserEventType());
         conversionData.put("timestamp", System.currentTimeMillis() / 1000);
+        conversionData.put("viewUrl", lead.getViewUrl());
+
         // 添加平台适应参数
         IAdvertiserAdapter adapter = advertiserHandlerFactory.getAdapter(AdvertiserTypeEnum.getByCode(callbackAccount.getAdvertiserId()));
         adapter.uploadConversionData(conversionData, params);

+ 2 - 14
fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionTrackingMessageConsumer.java

@@ -2,17 +2,13 @@ package com.fs.app.mq.consumer;
 
 import com.fs.app.facade.IConversionService;
 import com.fs.common.annotation.DistributeLock;
-import com.fs.common.utils.RedisUtil;
-import com.fs.common.utils.TraceIdUtil;
 import com.fs.newAdv.constant.ConversionTrackingMessage;
 import com.fs.newAdv.constant.MqTopicConstant;
 import com.fs.newAdv.enums.SystemEventTypeEnum;
-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.jboss.logging.MDC;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -25,23 +21,17 @@ import org.springframework.stereotype.Component;
 @Component
 @RocketMQMessageListener(
         topic = MqTopicConstant.CONVERSION_TRACKING_TOPIC,
-        consumerGroup = MqTopicConstant.CONVERSION_TRACKING_TOPIC_CONSUMER_GROUP,
+        consumerGroup = MqTopicConstant.CONVERSION_TRACKING_GROUP,
         // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
         consumeMode = ConsumeMode.CONCURRENTLY,
         // 最大重试次数(RocketMQ默认16次)
-        maxReconsumeTimes = 16
+        maxReconsumeTimes = 3
 )
 public class ConversionTrackingMessageConsumer implements RocketMQListener<ConversionTrackingMessage> {
 
     @Autowired
     private IConversionService conversionService;
 
-    @Autowired
-    private RedisUtil redisUtil;
-
-    @Autowired
-    private ConversionLogMapper conversionLogMapper;
-
     /**
      * 消费转化消息
      *
@@ -50,8 +40,6 @@ public class ConversionTrackingMessageConsumer implements RocketMQListener<Conve
     @Override
     @DistributeLock(scene = "mq", keyExpression = "#message.traceId", waitTime = 0, errorMsg = "重复消费")
     public void onMessage(ConversionTrackingMessage message) {
-        TraceIdUtil.put(message.getTrackId());
-
         String traceId = message.getTraceId();
         SystemEventTypeEnum eventType = message.getEventType();
 

+ 54 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/RocketMQTraceIdAspect.java

@@ -0,0 +1,54 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.utils.TraceIdUtil;
+import com.fs.newAdv.constant.ConversionTrackingMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * RocketMQ消费者链路ID切面
+ * 在消费消息前设置链路ID到MDC,保证整个消费链路的日志可追踪
+ *
+ * @author zhangqin
+ */
+@Aspect
+@Component
+@Slf4j
+@Order(1) // 优先于 DistributeLockAspect 执行
+public class RocketMQTraceIdAspect {
+
+    @Pointcut("execution(* com.fs.app.mq.consumer.*.onMessage(..))")
+    public void mqConsumerPointcut() {
+    }
+
+    @Around("mqConsumerPointcut()")
+    public Object around(ProceedingJoinPoint pjp) throws Throwable {
+        String trackId = null;
+        try {
+            // 从消息参数中提取 trackId
+            Object[] args = pjp.getArgs();
+            if (args != null && args.length > 0) {
+                Object message = args[0];
+                if (message instanceof ConversionTrackingMessage) {
+                    trackId = ((ConversionTrackingMessage) message).getTrackId();
+                }
+            }
+            // 设置链路ID到MDC
+            if (trackId != null && !trackId.isEmpty()) {
+                TraceIdUtil.put(trackId);
+            } else {
+                // 如果没有 trackId,则生成一个新的
+                TraceIdUtil.init();
+            }
+            return pjp.proceed();
+        } finally {
+            // 清理MDC,避免线程复用时链路ID污染
+            TraceIdUtil.clear();
+        }
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -199,7 +199,7 @@ public class CompanyUserController extends BaseController {
                     }
                     //是否绑定
                     if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
-                        if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                        if(!StringUtil.strIsNullOrEmpty(companyUserQwListVO.getQwUserId())){
                             Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
                                     .map(Long::parseLong)
                                     .toArray(Long[]::new);

+ 8 - 4
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -40,6 +40,7 @@ import java.util.stream.Collectors;
 
 import static com.fs.common.constant.LiveKeysConstant.*;
 import static com.fs.common.constant.LiveKeysConstant.LIVE_COUPON_NUM;
+import static com.fs.live.websocket.service.WebSocketServer.USER_ENTRY_TIME_KEY;
 
 @Component
 @AllArgsConstructor
@@ -819,7 +820,6 @@ public class Task {
             if (activeLives == null || activeLives.isEmpty()) {
                 return;
             }
-
             for (Live live : activeLives) {
                 try {
                     Long liveId = live.getLiveId();
@@ -862,18 +862,22 @@ public class Task {
                             }
 
                             // 获取用户的在线观看时长
-                            Long onlineSeconds = user.getOnlineSeconds();
+                            String entryTimeKey = String.format(USER_ENTRY_TIME_KEY, liveId, userId);
+                            Long existingEntryTime = redisCache.getCacheObject(entryTimeKey);
+                            Long onlineSeconds = user.getOnlineSeconds() ==null ? 0L : user.getOnlineSeconds();
+                            if(null != existingEntryTime){
+                                onlineSeconds = onlineSeconds + ((System.currentTimeMillis() - existingEntryTime)/1000);
+                            }
                             if (onlineSeconds == null || onlineSeconds <= 0) {
                                 continue;
                             }
-
                             // 获取用户的 companyId 和 companyUserId
                             LiveUserFirstEntry liveUserFirstEntry =
                                     liveUserFirstEntryService.selectEntityByLiveIdUserIdWithCache(liveId, userId);
                             if (liveUserFirstEntry == null) {
                                 continue;
                             }
-                            
+
                             Long qwUserId = liveUserFirstEntry.getQwUserId();
                             Long externalContactId = liveUserFirstEntry.getExternalContactId();
 

+ 2 - 2
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -88,7 +88,7 @@ public class WebSocketServer {
     private static Random random = new Random();
 
     // Redis key 前缀:用户进入直播间时间
-    private static final String USER_ENTRY_TIME_KEY = "live:user:entry:time:%s:%s"; // liveId:userId
+    public static final String USER_ENTRY_TIME_KEY = "live:user:entry:time:%s:%s"; // liveId:userId
 
     // 直播间在线用户缓存
 //    private static final ConcurrentHashMap<Long, Integer> liveOnlineUsers = new ConcurrentHashMap<>();
@@ -225,7 +225,7 @@ public class WebSocketServer {
                 }
             } else {
                 // 这个用户A邀请用户b,b的业绩算a的销售的
-                if (companyUserId == -2L) {
+                if (companyId == -2L) {
                     LiveUserFirstEntry clientB = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, companyUserId);
                     companyId = clientB.getCompanyId();
                     companyUserId = clientB.getCompanyUserId();

+ 1 - 1
fs-qw-api/Dockerfile

@@ -1,4 +1,4 @@
-FROM openjdk:8-jre
+FROM anolis-registry.cn-zhangjiakou.cr.aliyuncs.com/openanolis/openjdk:8-8.6
 # java版本,最好使用openjdk,而不是类似于Java:1.8
 COPY ./target/fs-qw-api.jar fs-qw-api.jar
 # 向外暴露的接口,最好与项目yml文件中的端口一致

+ 2 - 19
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -236,24 +236,7 @@ public class QwDataCallbackService {
                                             redisCache.setCacheObject(cacheKey, "1", 10, TimeUnit.MINUTES);
 
                                             // 广告线索处理
-                                            leadService.updateAddMemberLead(externalUserID,userID,corpId,State)
-                                                    .thenAccept(result -> {
-                                                        if (StrUtil.isNotEmpty(result)) {
-                                                            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-                                                            if (qwExternalContact == null){
-                                                                try {
-                                                                    Thread.sleep(2000);
-                                                                } catch (InterruptedException e) {
-                                                                    throw new RuntimeException(e);
-                                                                }
-                                                                qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-                                                            }
-                                                            QwExternalContact temp = new QwExternalContact();
-                                                            temp.setId(qwExternalContact.getId());
-                                                            temp.setTraceId(result);
-                                                            qwExternalContactMapper.updateById(temp);
-                                                        }
-                                                    });
+                                            leadService.updateAddMemberLead(externalUserID,userID,corpId,State);
                                         } catch (Exception e) {
                                             // 7. 业务逻辑失败时,删除缓存
                                             redisCache.deleteObject(cacheKey);
@@ -431,7 +414,7 @@ public class QwDataCallbackService {
                                     if (qwGroupChatUserOld==null) {
                                         qwGroupChatUserService.insertQwGroupChatUser(qwGroupChatUser);
                                         // 群员入群 广告判断记录
-                                        leadService.updateGroupAddMemberLead(qwGroupChatUser.getUserId(),qwGroupChatUser.getChatId(),qwGroupChatUser.getCorpId(),qwGroupChatUser.getUnionid());
+                                        leadService.updateGroupAddMemberLead(qwGroupChatUser.getName(),qwGroupChatUser.getChatId(),qwGroupChatUser.getCorpId(),qwGroupChatUser.getUnionid());
                                     }else {
                                         qwGroupChatUserOld.setIsOut(1L);
                                         qwGroupChatUserOld.setCorpId(corpId);

+ 2 - 2
fs-service/src/main/java/com/fs/his/domain/FsIntegralOrder.java

@@ -126,8 +126,8 @@ public class FsIntegralOrder
     private Date createTime;
     @TableField(exist = false)
     private Date updateTime;
-
+    @TableField(exist = false)
     private Integer deliveryStatus;
-
+    @TableField(exist = false)
     private String deliveryType;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java

@@ -34,6 +34,7 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_24(24, "付费课程订阅"),
     TYPE_25(25, "直播完课积分"),
     TYPE_26(26, "直播红包积分"),
+    TYPE_27(27, "积分订单取消退回积分"),
     ;
 
 

+ 8 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -295,6 +295,7 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         FsUser user=fsUserMapper.selectFsUserByUserId(param.getUserId());
         FsUserAddress address=fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
         FsIntegralGoods integralGoods=fsIntegralGoodsMapper.selectFsIntegralGoodsByGoodsId(param.getGoodsId());
+
         if (Objects.isNull(user)) {
             return R.error("用户不存在");
         }
@@ -310,6 +311,9 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
         if(user.getIntegral()<integralGoods.getIntegral()){
             return R.error("积分不足");
         }
+        if (integralGoods.getStatus() == 0) {
+            return R.error("商品已经停用,请联系客户进行处理!");
+        }
 
         // 减库存
         if (fsIntegralGoodsMapper.subStock(integralGoods.getGoodsId(), 1) <= 0) {
@@ -811,15 +815,18 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
             FsUser fsUser = fsUserMapper.selectFsUserByUserId(fsIntegralOrder.getUserId());
             fsUser.setIntegral(fsUser.getIntegral()+Long.parseLong(fsIntegralOrder.getIntegral()));
             i = fsUserMapper.updateFsUser(fsUser);
+            Date now = new Date();
             //新增积分记录
             FsUserIntegralLogs fsUserIntegralLogs = new FsUserIntegralLogs();
             fsUserIntegralLogs.setBalance(fsUser.getIntegral());
             fsUserIntegralLogs.setBusinessId(fsIntegralOrder.getOrderId().toString());
             fsUserIntegralLogs.setUserId(fsIntegralOrder.getUserId());
-            fsUserIntegralLogs.setLogType(20);
+            fsUserIntegralLogs.setLogType(27);
             fsUserIntegralLogs.setIntegral(Long.parseLong(fsIntegralOrder.getIntegral()));
             fsUserIntegralLogs.setBusinessType(2);
             fsUserIntegralLogs.setStatus(0);
+            fsUserIntegralLogs.setCreateTime(now);
+            fsUserIntegralLogs.setUpdateTime(now);
             i = fsUserIntegralLogsMapper.insertFsUserIntegralLogs(fsUserIntegralLogs);
         }
         return i;

+ 1 - 1
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderExcelVO.java

@@ -5,7 +5,7 @@ import lombok.Data;
 
 @Data
 public class FsIntegralOrderExcelVO {
-    @Excel(name = "药品订单号",required = true)
+    @Excel(name = "订单号",required = true)
     private String orderCode;
 
     @Excel(name = "订单状态",dictType = "sys_integral_order_status")

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveCompletionPointsRecordService.java

@@ -50,4 +50,6 @@ public interface ILiveCompletionPointsRecordService {
      * @return 完课记录
      */
     LiveCompletionPointsRecord selectByUserAndDate(Long liveId, Long userId, Date date);
+
+    LiveCompletionPointsRecord createCompletionRecord(Long liveId, Long userId);
 }

+ 114 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java

@@ -269,6 +269,120 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
         return recordMapper.selectByUserAndDate(liveId, userId, date);
     }
 
+    @Override
+    public LiveCompletionPointsRecord createCompletionRecord(Long liveId, Long userId) {
+        try {
+            // 1. 获取直播信息和配置
+            Live live = liveService.selectLiveByLiveId(liveId);
+            if (live == null) {
+
+                return null;
+            }
+
+            // 2. 从数据库获取完课积分配置
+            CompletionPointsConfig config = getCompletionPointsConfig(live);
+
+            // 检查是否开启完课积分功能
+            if (!config.isEnabled()) {
+
+                return null;
+            }
+
+            // 检查配置完整性
+            Integer completionRate = config.getCompletionRate();
+            int[] pointsConfig = config.getPointsConfig();
+
+            if (completionRate == null || pointsConfig == null || pointsConfig.length == 0) {
+
+                return null;
+            }
+
+            // 3. 获取观看时长(如果为null,则从数据库累计直播+回放时长)
+            // 4.这个时间和liveWatchUser分开算
+            long actualWatchDuration = 0L;
+
+            // 4. 获取视频总时长(秒)
+            Long videoDuration = live.getDuration();
+            if (videoDuration == null || videoDuration <= 0) {
+
+                return null;
+            }
+
+            // 5. 计算完课比例
+            BigDecimal watchRate = BigDecimal.valueOf(actualWatchDuration)
+                    .multiply(BigDecimal.valueOf(100))
+                    .divide(BigDecimal.valueOf(videoDuration), 2, RoundingMode.HALF_UP);
+
+            // 限制完课比例最大值为100.00%(防止数据库字段溢出)
+            if (watchRate.compareTo(BigDecimal.valueOf(100)) > 0) {
+                watchRate = BigDecimal.valueOf(100);
+            }
+
+//            // 6. 判断是否达到完课标准
+//            if (watchRate.compareTo(BigDecimal.valueOf(completionRate)) < 0) {
+//
+//                return null;
+//            }
+
+            // 7. 检查今天是否已有完课记录
+            LocalDate today = LocalDate.now();
+            Date currentDate = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+            LiveCompletionPointsRecord todayRecord = recordMapper.selectByUserAndDate(liveId, userId, currentDate);
+            if (todayRecord != null) {
+
+                return todayRecord;
+            }
+
+            // 7. 查询最近一次完课记录(不限直播间),计算连续天数
+            LiveCompletionPointsRecord latestRecord = recordMapper.selectLatestByUser(userId);
+            int continuousDays = 1;
+
+            if (latestRecord != null) {
+                LocalDate lastDate = latestRecord.getCurrentCompletionDate()
+                        .toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+
+                long daysBetween = ChronoUnit.DAYS.between(lastDate, today);
+
+                if (daysBetween == 0) {
+                    continuousDays = latestRecord.getContinuousDays();
+
+                } else if (daysBetween == 1) {
+                    // 昨天完课了,连续天数+1
+                    continuousDays = latestRecord.getContinuousDays() + 1;
+                } else {
+                    // 中断了,重新开始
+                    continuousDays = 1;
+                }
+            }
+
+            // 8. 计算积分
+            int points = calculatePoints(continuousDays, pointsConfig);
+
+            // 9. 创建完课记录
+            LiveCompletionPointsRecord record = new LiveCompletionPointsRecord();
+            record.setLiveId(liveId);
+            record.setUserId(userId);
+            record.setWatchDuration(actualWatchDuration);
+            record.setVideoDuration(videoDuration);
+            record.setCompletionRate(watchRate);
+            record.setContinuousDays(continuousDays);
+            record.setPointsAwarded(points);
+            record.setCurrentCompletionDate(currentDate);
+            record.setReceiveStatus(0); // 未领取
+
+            if (latestRecord != null) {
+                record.setLastCompletionDate(latestRecord.getCurrentCompletionDate());
+            }
+
+            recordMapper.insertRecord(record);
+            return record;
+        } catch (Exception e) {
+            log.error("检查并创建完课记录失败, liveId={}, userId={}", liveId, userId, e);
+            throw e;
+        }
+    }
+
     /**
      * 从直播配置中获取完课积分配置
      */

+ 1 - 0
fs-service/src/main/java/com/fs/newAdv/constant/MqTopicConstant.java

@@ -24,4 +24,5 @@ public class MqTopicConstant {
      * 转化追踪consumer
      */
     public static final String CONVERSION_TRACKING_TOPIC_CONSUMER_GROUP = "conversion-topic-tracking-consumer-group";
+    public static final String CONVERSION_TRACKING_GROUP = "conversion-tracking-group";
 }

+ 16 - 7
fs-service/src/main/java/com/fs/newAdv/domain/Lead.java

@@ -31,10 +31,12 @@ public class Lead implements Serializable {
     private Long siteId;
 
     /**
-     * 链路id唯一(clickId)
+     * 链路id唯一
      */
     private String traceId;
 
+    private String clickId;
+
     /**
      * 来源平台
      */
@@ -76,16 +78,15 @@ public class Lead implements Serializable {
     /**
      * 外部联系人id 添加人id
      */
-    private String externalUserId;
-    /**
-     * 属于用户id 被添加的id
-     */
-    private String userId;
-    private String corpId;
+    private Long externalId;
     /**
      * 群Id
      */
     private String chatId;
+    /**
+     * 企微主题Id
+     */
+    private String corpId;
     /**
      * 是否添加企微 1是 0否
      */
@@ -104,6 +105,10 @@ public class Lead implements Serializable {
      */
     private String openid;
     private String unionid;
+    /**
+     * 微信名称 unionid不存在时关联
+     */
+    private String weiChatName;
     /**
      * 小程序授权1是 0否
      */
@@ -179,5 +184,9 @@ public class Lead implements Serializable {
      * 落地页访问时间
      */
     private LocalDateTime landingPageTs;
+    /**
+     * 落地页原始Url
+     */
+    private String viewUrl;
 }
 

+ 12 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/QwExternalIdBindTrackReq.java

@@ -0,0 +1,12 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class QwExternalIdBindTrackReq implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private Long qwExternalId;
+    private String unionid;
+}

+ 10 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/TraceIdDto.java

@@ -0,0 +1,10 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class TraceIdDto implements Serializable {
+    private String traceId;
+}

+ 8 - 8
fs-service/src/main/java/com/fs/newAdv/enums/SystemEventTypeEnum.java

@@ -5,14 +5,14 @@ import lombok.Getter;
 @Getter
 public enum SystemEventTypeEnum {
 
-    GROUP_TODAY("1", "当日加群"),
-    WEI_CHAT_TODAY("2", "当日加微"),
-    TO_CLASS_AND_GROUP_TODAY("3", "直播到课且当日加群"),
-    TO_CLASS_AND_WEI_CHAT_TODAY("4", "直播到课且当日加微"),
-    BUY_ORDER("5", "商品购买订单"),
-    AUTH_TODAY_CREATE("6", "微信授权且当日创建"),
-    COMPLETE_CLASS_AND_GROUP_TODAY("7", "直播完课且当日加群"),
-    COMPLETE_CLASS_AND_WEI_CHAT_TODAY("8", "直播完课且当日加微");
+    GROUP_TODAY("event1", "当日加群"),
+    WEI_CHAT_TODAY("event2", "当日加微"),
+    TO_CLASS_AND_GROUP_TODAY("event3", "直播到课且当日加群"),
+    TO_CLASS_AND_WEI_CHAT_TODAY("event4", "直播到课且当日加微"),
+    BUY_ORDER("event5", "商品购买订单"),
+    AUTH_TODAY_CREATE("event6", "微信授权且当日创建"),
+    COMPLETE_CLASS_AND_GROUP_TODAY("event7", "直播完课且当日加群"),
+    COMPLETE_CLASS_AND_WEI_CHAT_TODAY("event8", "直播完课且当日加微");
 
     private final String code;
     private final String description;

+ 2 - 1
fs-service/src/main/java/com/fs/newAdv/event/ConversionEventListener.java

@@ -58,11 +58,12 @@ public class ConversionEventListener {
                 new SendCallback() {
                     @Override
                     public void onSuccess(SendResult sendResult) {
+                        log.error("转化消息发送成功:{}", trackId);
                     }
 
                     @Override
                     public void onException(Throwable e) {
-                        log.error("转化消息发送失败:{}", trackId);
+                        log.error("转化消息发送失败:{}", trackId,e);
                         // TODO: 记录到数据库,后续补偿
                     }
                 }

+ 2 - 3
fs-service/src/main/java/com/fs/newAdv/integration/adapter/BaiduAdapter.java

@@ -28,9 +28,8 @@ public class BaiduAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("百度数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("bd_vid"));
+        lead.setClickId(rawData.get("bd_vid"));
         lead.setAdvertiserId(AdvertiserTypeEnum.BAIDU.getCode());
         lead.setIp(rawData.get("ip"));
         lead.setIdeaId(rawData.get("aid"));
@@ -54,7 +53,7 @@ public class BaiduAdapter implements IAdvertiserAdapter {
 
     @Override
     public void uploadConversionData(Map<String, Object> conversionData, Map<String, Object> params) {
-        conversionData.put("logidUrl",params.get("logidUrl"));
+
     }
 }
 

+ 4 - 5
fs-service/src/main/java/com/fs/newAdv/integration/adapter/OPPOAdapter.java

@@ -29,10 +29,9 @@ public class OPPOAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("腾讯数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("request_id"));
-        lead.setAdvertiserId(AdvertiserTypeEnum.TENCENT.getCode());
+        lead.setClickId(rawData.get("request_id"));
+        lead.setAdvertiserId(AdvertiserTypeEnum.OPPO.getCode());
         lead.setIp(rawData.get("ip"));
         if (StrUtil.isNotEmpty(rawData.get("dynamic_creative_id"))){
             lead.setIdeaId(rawData.get("dynamic_creative_id"));
@@ -50,7 +49,7 @@ public class OPPOAdapter implements IAdvertiserAdapter {
                     Instant.ofEpochMilli(Long.parseLong(rawData.get("click_time"))),
                     ZoneId.systemDefault()));
         } catch (Exception e) {
-            log.error("时间转换异常", e);
+            log.error("时间转换异常");
         }
         return lead;
     }
@@ -63,8 +62,8 @@ public class OPPOAdapter implements IAdvertiserAdapter {
     @Override
     public void uploadConversionData(Map<String, Object> conversionData, Map<String, Object> params) {
         // ------------------------------oppo参数----------
-        conversionData.put("tid", params.get("tid"));
         conversionData.put("lbid", params.get("lbid"));
+        conversionData.put("pageId", params.get("pageId"));
     }
 }
 

+ 2 - 3
fs-service/src/main/java/com/fs/newAdv/integration/adapter/OceanEngineAdapter.java

@@ -29,9 +29,8 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("巨量引擎数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("track_id"));
+        lead.setClickId(rawData.get("track_id"));
         lead.setAdvertiserId(AdvertiserTypeEnum.OCEANENGINE.getCode());
         if (StrUtil.isNotEmpty(rawData.get("cid"))) {
             lead.setIdeaId(rawData.get("cid"));
@@ -52,7 +51,7 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
                     Instant.ofEpochMilli(Long.parseLong(rawData.get("ts"))),
                     ZoneId.systemDefault()));
         } catch (Exception e) {
-            log.error("时间转换异常", e);
+            log.error("时间转换异常");
         }
         return lead;
     }

+ 1 - 2
fs-service/src/main/java/com/fs/newAdv/integration/adapter/TencentAdapter.java

@@ -28,9 +28,8 @@ public class TencentAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("腾讯数据适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("request_id"));
+        lead.setClickId(rawData.get("request_id"));
         lead.setAdvertiserId(AdvertiserTypeEnum.TENCENT.getCode());
         lead.setIp(rawData.get("ip"));
         if (StrUtil.isNotEmpty(rawData.get("dynamic_creative_id"))){

+ 1 - 2
fs-service/src/main/java/com/fs/newAdv/integration/adapter/VIVOAdapter.java

@@ -28,9 +28,8 @@ public class VIVOAdapter implements IAdvertiserAdapter {
      */
     @Override
     public Lead adaptCallbackData(Map<String, String> rawData) {
-        log.info("VIVO适配:{}", rawData);
         Lead lead = new Lead();
-        lead.setTraceId(rawData.get("requestId"));
+        lead.setClickId(rawData.get("requestId"));
         lead.setAdvertiserId(AdvertiserTypeEnum.VIVO.getCode());
         lead.setIp(rawData.get("ip"));
         lead.setIdeaId(rawData.get("creativeId"));

+ 16 - 9
fs-service/src/main/java/com/fs/newAdv/integration/client/AbstractApiClient.java

@@ -11,7 +11,6 @@ import com.fs.newAdv.service.IApiCallLogService;
 import com.fs.newAdv.service.IPromotionAccountService;
 import com.fs.newAdv.vo.AccessTokenVo;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.formula.functions.T;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import java.time.LocalDateTime;
@@ -39,21 +38,29 @@ public abstract class AbstractApiClient implements IApiClient {
     protected boolean executeWithLog(AdvertiserTypeEnum advertiserType, String apiUrl, Map<String, Object> params, ApiCall action) {
         long start = System.currentTimeMillis();
         boolean callStatus = false;
-        String responseBody = "";
+        JSONObject jsonObject = null;
         try {
             log.info("[{}] 调用开始, 参数: {}", apiUrl, params);
             HttpResponse result = action.call();
-            JSONObject jsonObject = JSONUtil.parseObj(result.body());
-            Integer code = (Integer) jsonObject.get("code");
-            if (ObjectUtil.isNotEmpty(code) && (code == 0 || code == 200)) {
+            jsonObject = JSONUtil.parseObj(result.body());
+            // 通用状态
+            Integer code = jsonObject.getInt("code");
+            // 百度状态
+            Integer status = -1;
+            JSONObject header = jsonObject.getJSONObject("header");
+            if (ObjectUtil.isNotEmpty(header)) {
+                status = header.getInt("status");
+            }
+            if ((ObjectUtil.isNotEmpty(code) && (code == 0 || code == 200 ))
+            || (ObjectUtil.isNotEmpty(status) && status == 0)
+            ) {
                 callStatus = true;
             }
-            responseBody = JSONUtil.toJsonStr(jsonObject);
-            log.info("[{}] 调用成功, 耗时: {} ms, 返回结果: {}", apiUrl, System.currentTimeMillis() - start, result);
+            log.info("[{}] 调用成功, 耗时: {} ms, 返回结果: {}", apiUrl, System.currentTimeMillis() - start, jsonObject);
         } catch (Exception e) {
             log.error("[{}] 调用失败, 耗时: {} ms, 错误信息: {}", apiUrl, System.currentTimeMillis() - start, e.getMessage(), e);
         }
-        saveApiCallLog(advertiserType, apiUrl, params, responseBody, callStatus, start);
+        saveApiCallLog(advertiserType, apiUrl, params, jsonObject != null ? JSONUtil.toJsonStr(jsonObject) : null, callStatus, start);
         return callStatus;
     }
 
@@ -74,7 +81,7 @@ public abstract class AbstractApiClient implements IApiClient {
     protected String getAccessToken(Long promotionAccountId) {
         PromotionAccount byId = promotionAccountService.getById(promotionAccountId);
         // 判断token是否过期 提前1小时刷新
-        if (byId.getExpireTime().isBefore(LocalDateTime.now().plusHours(1))) {
+        if (ObjectUtil.isEmpty(byId.getExpireTime()) || byId.getExpireTime().isBefore(LocalDateTime.now().plusHours(1))) {
             // 获取请求参数
             IApiClient apiClient = advertiserHandlerFactory.getApiClient(AdvertiserTypeEnum.getByCode(byId.getAdvertiserId()));
             IAccessTokenClient tokenClient = (IAccessTokenClient) apiClient;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/BaiduApiClient.java

@@ -85,7 +85,7 @@ public class BaiduApiClient extends AbstractApiClient implements IAccessTokenCli
         Map<String, Object> conversion = new HashMap<>();
 
         // 点击ID(必填)
-        String logidUrl = (String) conversionData.get("logidUrl");
+        String logidUrl = (String) conversionData.get("viewUrl");
         if (StrUtil.isBlank(logidUrl)) {
             throw new ThirdPartyException("落地页不能为空");
         }

+ 13 - 11
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OPPOApiClient.java

@@ -8,10 +8,13 @@ import com.fs.newAdv.domain.SiteStatistics;
 import com.fs.newAdv.enums.AdvertiserTypeEnum;
 import com.fs.newAdv.integration.client.AbstractApiClient;
 import com.fs.common.constant.SystemConstant;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.util.crypto.SHA1;
 import org.springframework.stereotype.Component;
 
+import java.nio.charset.Charset;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -45,15 +48,15 @@ public class OPPOApiClient extends AbstractApiClient {
             // 构建请求参数
             Map<String, Object> requestBody = buildConversionParams(conversionData);
             Long timestamp = (Long) conversionData.get("timestamp");
-            String ownerId = (String) conversionData.get("ownerId");
+            String ownerId = (String) conversionData.get("adAccountId");
             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);
+            String appKey = (String) conversionData.get("appSecret");
+            String sign = Hashing.sha1().hashString(appId + appKey + timestamp, Charset.defaultCharset()).toString();
+            String token = BaseEncoding.base64().encode((ownerId + "," + appId + "," + timestamp + "," + sign).getBytes());
             // 发送HTTP请求
             return HttpRequest.post(CONVERSION_API_URL)
                     .header("Content-Type", "application/json")
-                    .header("Authorization", "Bearer  " + token)
+                    .header("Authorization", "Bearer " + token)
                     .body(JSONUtil.toJsonStr(requestBody))
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
@@ -70,18 +73,17 @@ public class OPPOApiClient extends AbstractApiClient {
         Map<String, Object> params = new HashMap<>();
 
         // 落地页Id:投放广告到投放
-        params.put("pageId", conversionData.get("traceId"));
+        params.put("pageId", conversionData.get("pageId"));
         // 广告主id:对应广告主自提供
-        params.put("adAccountId", conversionData.get("adAccountId"));
+        params.put("ownerId", conversionData.get("adAccountId"));
         // 用户IP:广告主收集
-        // params.put("ip", conversionData.get("ip"));
-        params.put("ip", "192.168.1.1");
+        params.put("ip", "127.0.0.1");
         // traceId:播放时追加在url上
-        params.put("tid", conversionData.get("tid"));
+        params.put("tid", conversionData.get("traceId"));
         // 流量号:播放时追加在URL上
         params.put("lbid ", conversionData.get("lbid"));
         // 事件
-        params.put("transformType ", conversionData.get("eventType"));
+        params.put("transformType", conversionData.get("eventType"));
         return params;
     }
 

+ 21 - 2
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/OceanEngineApiClient.java

@@ -18,8 +18,11 @@ import com.fs.newAdv.vo.AccessTokenVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.client.utils.URIBuilder;
 import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import java.math.BigDecimal;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.Collections;
@@ -63,9 +66,16 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
         return executeWithLog(AdvertiserTypeEnum.OCEANENGINE, CONVERSION_API_URL, conversionData, () -> {
             // 构建请求参数
             Map<String, Object> requestBody = buildConversionParams(conversionData);
+/*            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(CONVERSION_API_URL)
+                    .queryParam("callback", requestBody.get("traceId")) // 核心参数,监测链接透传的内容
+                    .queryParam("event_type", requestBody.get("event_type"))
+                    .queryParam("conv_time", requestBody.get("conv_time"));
+            URI uri = builder.build().encode(StandardCharsets.UTF_8).toUri();
+            log.info("准备调用巨量引擎回传接口, URL: {}", uri);*/
+
             // 发送HTTP请求
             return HttpRequest.get(CONVERSION_API_URL)
-                    .form(JSONUtil.toJsonStr(requestBody))
+                    .form(requestBody)
                     .timeout(SystemConstant.API_TIMEOUT)
                     .execute();
         });
@@ -94,6 +104,7 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
 
     @Override
     public SiteStatistics getDataReport(PromotionAccount account, String ideaId, String startDate, String endDate) {
+        log.info("开始获取头条报表数据:{} {} {} {}",account.getId(), ideaId,startDate,endDate);
         // 构建请求参数
         Map<String, Object> map = new HashMap<>();
         map.put("advertiser_id", Long.valueOf(account.getAdAccountId()));
@@ -142,8 +153,15 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
                 .header("Access-Token", getAccessToken(account.getId()))
                 .timeout(SystemConstant.API_TIMEOUT)
                 .execute();
-        JSONObject jsonObject = JSONUtil.parseObj(execute.body());
+        String body = execute.body();
+        log.info("头条报表数据返回:{} ",body);
+        JSONObject jsonObject = JSONUtil.parseObj(body);
         JSONObject data = jsonObject.getJSONObject("data");
+        int code = jsonObject.getInt("code");
+        if (code != 0){
+            log.error("头条报表数据返回异常:{} ",body);
+            return new SiteStatistics();
+        }
         JSONArray rows = data.getJSONArray("rows");
         JSONObject jsonObject1 = rows.getJSONObject(0);
         JSONObject jsonObject2 = jsonObject1.getJSONObject("metrics");
@@ -200,5 +218,6 @@ public class OceanEngineApiClient extends AbstractApiClient implements IAccessTo
                 .expireTime(LocalDateTime.now().plusSeconds(data.getLong("refresh_token_expires_in")))
                 .build();
     }
+
 }
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/integration/client/advertiser/VIVOApiClient.java

@@ -82,7 +82,7 @@ public class VIVOApiClient extends AbstractApiClient {
 
     @Override
     public AdvertiserTypeEnum getAdvertiserType() {
-        return AdvertiserTypeEnum.OPPO;
+        return AdvertiserTypeEnum.VIVO;
     }
 
     @Override

+ 13 - 5
fs-service/src/main/java/com/fs/newAdv/service/ILeadService.java

@@ -16,6 +16,7 @@ import java.util.concurrent.CompletableFuture;
 public interface ILeadService extends IService<Lead> {
 
     Lead getByTraceId(String traceId);
+    Lead getByClickId(String clickId);
 
     /**
      * 用户加企业群线索处理
@@ -28,12 +29,12 @@ public interface ILeadService extends IService<Lead> {
 
     /**
      * 用户添加企业微信线索处理
-     * @param externalUserID
-     * @param userID
-     * @param corpId
-     * @param state
      */
-    CompletableFuture<String> updateAddMemberLead(String externalUserID, String userID, String corpId, String state);
+    void updateAddMemberLead(Long externalId, String unionId);
+    /**
+     * 用户添加企业微信线索处理
+     */
+    void updateAddMemberLead(String externalUserID,String userID,String corpId,String State);
     /**
      * 小程序授权线索处理
      * @param traceId
@@ -43,6 +44,13 @@ public interface ILeadService extends IService<Lead> {
      */
     void weChatAuthorizationLead(String traceId, String unionId, String maOpenId, String phone);
 
+    /**
+     * 小程序获取头像昵称线索处理
+     * @param traceId
+     * @param weiChatName
+     */
+    void weChatNameLead(String traceId, String weiChatName);
+
     /**
      * 小程序授权落地页访问线索处理
      * @param traceId

+ 95 - 59
fs-service/src/main/java/com/fs/newAdv/service/impl/LeadServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.newAdv.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -11,17 +12,14 @@ import com.fs.newAdv.mapper.LeadMapper;
 import com.fs.newAdv.service.ILeadService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qwApi.domain.QwExternalContactResult;
-import com.fs.qwApi.service.QwApiService;
+import com.fs.qw.service.IQwExternalContactService;
 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.LocalDate;
-import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CompletableFuture;
 
 /**
  * 域名管理Service实现类
@@ -36,9 +34,9 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
 
     @Autowired
     private ConversionEventPublisher conversionEventPublisher;
-
     @Autowired
-    private QwApiService qwApiService;
+    private IQwExternalContactService qwExternalContactService;
+
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
 
@@ -52,27 +50,40 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
         return list.isEmpty() ? null : list.get(0);
     }
 
+    @Override
+    public Lead getByClickId(String clickId) {
+        List<Lead> list = this.list(new LambdaQueryWrapper<Lead>()
+                .eq(Lead::getClickId, clickId));
+        if (list.size() > 1) {
+            log.error("查询投流点击信息不唯一:{}", clickId);
+        }
+        return list.isEmpty() ? null : list.get(0);
+    }
+
     @Override
     @Async
-    public void updateGroupAddMemberLead(String userId, String chatId, String corpId, String unionid) {
+    public void updateGroupAddMemberLead(String name, String chatId, String corpId, String unionid) {
+        log.info("用户加群线索信息:{} {} {} {}", name, chatId, corpId, unionid);
+        LambdaQueryWrapper<Lead> last = new LambdaQueryWrapper<Lead>();
+        if (StrUtil.isNotEmpty(unionid)) {
+            last.eq(Lead::getUnionid, unionid);
+        } else if (StrUtil.isNotEmpty(name)) {
+            last.eq(Lead::getWeiChatName, name);
+        }
+        last.eq(Lead::getAddContactQw, 0).last("LIMIT 1");
         // 末次归因逻辑
-        Lead lead = this.getOne(new LambdaQueryWrapper<Lead>()
-                .eq(Lead::getUnionid, unionid)
-                .eq(Lead::getAddContactQw, 0)
-                .last("LIMIT 1"));
+        Lead lead = this.getOne(last);
         if (lead != null) {
-            lead.setCorpId(corpId);
             lead.setChatId(chatId);
-            lead.setUserId(userId);
             lead.setAddContactQw(1);
+            lead.setCorpId(corpId);
             this.updateById(lead);
-
             if (ObjectUtil.isNotEmpty(lead.getLandingPageTs()) && lead.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
                 // 当日加群事件回调
                 conversionEventPublisher.publishConversionEvent(lead.getTraceId(), SystemEventTypeEnum.GROUP_TODAY);
             }
         } else {
-            log.info("用户加群线索信息不存在:{}", userId);
+            log.info("用户加群线索信息不存在:{}", name);
         }
 
 
@@ -80,65 +91,90 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
 
     @Override
     @Async
-    public CompletableFuture<String> updateAddMemberLead(String externalUserID, String userID, String corpId, String state) {
-        QwExternalContactResult externalContactResult = qwApiService.getExternalcontact(externalUserID, corpId);
-        String unionid = externalContactResult.getExternal_contact().getUnionid();
+    public void updateAddMemberLead(Long externalId, String unionid) {
+        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(externalId);
+        if (qwExternalContact == null) {
+            log.info("外部联系人信息不存在:{}", externalId);
+            return;
+        }
+        qwExternalContact.setUnionid(unionid);
+        this.updateAddMemberLead(qwExternalContact);
+    }
+
+    @Override
+    public void updateAddMemberLead(String externalUserID, String userID, String corpId, String State) {
+        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
+        if (qwExternalContact == null) {
+            log.info("外部联系人信息不存在:{} {} {}", externalUserID, userID, corpId);
+            return;
+        }
+        this.updateAddMemberLead(qwExternalContact);
+    }
+
+    private void updateAddMemberLead(QwExternalContact qwExternalContact) {
+        log.info("用户加微线索信息:{}", qwExternalContact);
+        LambdaQueryWrapper<Lead> last = new LambdaQueryWrapper<Lead>();
+        if (StrUtil.isNotEmpty(qwExternalContact.getUnionid())) {
+            last.eq(Lead::getUnionid, qwExternalContact.getUnionid());
+        } else if (StrUtil.isNotEmpty(qwExternalContact.getName())) {
+            last.eq(Lead::getWeiChatName, qwExternalContact.getName());
+        }
+        last.eq(Lead::getAddContactQwGroup, 0).last("LIMIT 1");
         // 末次归因逻辑
-        Lead lead = this.getOne(new LambdaQueryWrapper<Lead>()
-                .eq(Lead::getUnionid, unionid)
-                .eq(Lead::getAddContactQwGroup, 0)
-                .last("LIMIT 1"));
+        Lead lead = this.getOne(last);
         if (lead != null) {
-            lead.setExternalUserId(externalUserID);
-            lead.setCorpId(corpId);
-            lead.setUserId(userID);
+            lead.setExternalId(qwExternalContact.getId());
             lead.setAddContactQwGroup(1);
             this.updateById(lead);
-
             // 绑定企微用户线索关系
-            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalByExternalIdAndCompanyIdToIdAndFs(externalUserID, userID, corpId);
-            if (qwExternalContact != null){
-                QwExternalContact temp = new QwExternalContact();
-                temp.setId(qwExternalContact.getId());
-                temp.setTraceId(lead.getTraceId());
-                qwExternalContactMapper.updateById(temp);
-            }else {
-                log.info("广告归因企微用户信息不存在:{} {} {} ", externalUserID, userID,corpId);
-            }
+            QwExternalContact temp = new QwExternalContact();
+            temp.setId(qwExternalContact.getId());
+            temp.setTraceId(lead.getTraceId());
+            qwExternalContactMapper.updateById(temp);
             if (ObjectUtil.isNotEmpty(lead.getLandingPageTs()) && lead.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
                 // 当日加微事件回调
                 conversionEventPublisher.publishConversionEvent(lead.getTraceId(), SystemEventTypeEnum.WEI_CHAT_TODAY);
             }
-            return CompletableFuture.completedFuture(lead.getTraceId());
+
         } else {
-            log.info("广告归因线索不存在:{} {} {}", externalUserID, userID,corpId);
+            log.info("广告归因线索不存在:{}", qwExternalContact);
         }
-        return CompletableFuture.completedFuture(null);
     }
 
     @Override
     @Async
     public void weChatAuthorizationLead(String traceId, String unionId, String maOpenId, String phone) {
-       try{
-           log.info("用户微信授权线索信息:{}", traceId);
-           Lead byTraceId = this.getByTraceId(traceId);
-           if (byTraceId == null) {
-               return;
-           }
-           this.update(new LambdaUpdateWrapper<Lead>()
-                   .eq(Lead::getTraceId, traceId)
-                   .set(ObjectUtil.isNotEmpty(unionId),Lead::getUnionid, unionId)
-                   .set(ObjectUtil.isNotEmpty(phone),Lead::getPhone, phone)
-                   .set(ObjectUtil.isNotEmpty(maOpenId),Lead::getOpenid, maOpenId)
-                   .set(Lead::getMiniAuth, 1));
-           if (ObjectUtil.isNotEmpty(byTraceId.getLandingPageTs()) && byTraceId.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
-               // 微信授权且当日创建事件
-               log.info("用户微信授权线索事件回传:{}", traceId);
-               conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.AUTH_TODAY_CREATE);
-           }
-       }catch (Exception e){
-           e.printStackTrace();
-       }
+        log.info("用户微信授权线索信息:{} {} {} {}", traceId, unionId, phone, maOpenId);
+        Lead byTraceId = this.getByTraceId(traceId);
+        if (byTraceId == null) {
+            return;
+        }
+        this.update(new LambdaUpdateWrapper<Lead>()
+                .eq(Lead::getTraceId, traceId)
+                .set(ObjectUtil.isNotEmpty(unionId), Lead::getUnionid, unionId)
+                .set(ObjectUtil.isNotEmpty(phone), Lead::getPhone, phone)
+                .set(ObjectUtil.isNotEmpty(maOpenId), Lead::getOpenid, maOpenId)
+                .set(Lead::getMiniAuth, 1));
+        if (ObjectUtil.isNotEmpty(byTraceId.getLandingPageTs()) && byTraceId.getLandingPageTs().toLocalDate().isEqual(LocalDate.now())) {
+            // 微信授权且当日创建事件
+            log.info("用户微信授权线索事件回传:{}", traceId);
+            conversionEventPublisher.publishConversionEvent(traceId, SystemEventTypeEnum.AUTH_TODAY_CREATE);
+        }
+    }
+
+    @Override
+    @Async
+    public void weChatNameLead(String traceId, String weiChatName) {
+        log.info("用户微信昵称线索信息:{} {}", traceId, weiChatName);
+        Lead byTraceId = this.getByTraceId(traceId);
+        if (byTraceId == null || StrUtil.isEmpty(weiChatName)) {
+            return;
+        }
+        this.update(new LambdaUpdateWrapper<Lead>()
+                .eq(Lead::getTraceId, traceId)
+                .isNull(Lead::getWeiChatName)
+                .set(Lead::getWeiChatName, weiChatName)
+        );
     }
 
     @Override
@@ -161,7 +197,7 @@ public class LeadServiceImpl extends ServiceImpl<LeadMapper, Lead> implements IL
         boolean update = this.update(new LambdaUpdateWrapper<Lead>()
                 .eq(Lead::getTraceId, traceId)
                 .set(Lead::getMiniLaunchIndexCount, 1));
-        if (!update){
+        if (!update) {
             log.error("发起进入小程序失败:{}", traceId);
         }
     }

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/service/impl/SiteServiceImpl.java

@@ -25,7 +25,7 @@ public class SiteServiceImpl extends ServiceImpl<SiteMapper, Site> implements IS
     @Transactional(rollbackFor = Exception.class)
     public void createSite(Site site) {
         this.save(site);
-        site.setSiteUrl("https://" + site.getLaunchDomain() + "/#/pages/index/index?siteId=" + site.getId());
+        site.setSiteUrl("https://" + site.getLaunchDomain() + "/pages/index/index?siteId=" + site.getId());
         this.updateById(site);
     }
 

+ 12 - 0
fs-service/src/main/java/com/fs/newAdv/vo/ConversionParmVo.java

@@ -0,0 +1,12 @@
+package com.fs.newAdv.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 回传通用参数类
+ */
+@Data
+public class ConversionParmVo implements Serializable {
+}

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java

@@ -71,7 +71,7 @@ public class AsyncChatSopService {
 
     private void processInternal(ChatSopRuleTimeVO timeVO, QwSopTemp temp, Map<String, QwUserVO> qwUserMap, Map<String, QwGroupChat> groupChatMap) {
         if(StringUtils.isNotEmpty(timeVO.getChatId())){
-            List<SopUserLogs> list = Arrays.stream(timeVO.getChatId().split(",")).map(e -> {
+            List<SopUserLogs> list = Arrays.stream(timeVO.getChatId().split(",")).filter(StringUtils::isNotEmpty).map(e -> {
                 SopUserLogs sopUserLogs = new SopUserLogs();
                 sopUserLogs.setSopId(timeVO.getId());
                 sopUserLogs.setSopTempId(temp.getId());

+ 5 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -143,4 +143,9 @@ public class QwExternalContactVO {
      * 联系我分组名称
      */
     private String wayGroupName;
+
+    /**
+     * 广告链路id
+     */
+    private String traceId;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -1049,7 +1049,7 @@ public class SopUserLogsServiceImpl implements ISopUserLogsService {
         List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(vo.getChatIds().split(","));
         Map<String, QwGroupChat> groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
 
-        List<SopUserLogs> list = Arrays.stream(vo.getChatIds().split(",")).map(e -> {
+        List<SopUserLogs> list = Arrays.stream(vo.getChatIds().split(",")).filter(StringUtils::isNotEmpty).distinct().map(e -> {
             SopUserLogs sopUserLogs = new SopUserLogs();
             sopUserLogs.setSopId(qwSop.getId());
             sopUserLogs.setSopTempId(qwSop.getTempId());

+ 3 - 0
fs-service/src/main/resources/application-druid-ylrz.yml

@@ -142,6 +142,9 @@ rocketmq:
         group: conversion-tracking-group
         access-key: ak19op47padq83a6882e2edb # 替换为实际的 accessKey
         secret-key: sk4ca8a0dbdac7ed75 # 替换为实际的 secretKey
+    consumer:
+        access-key: ak19op47padq83a6882e2edb # 替换为实际的 accessKey
+        secret-key: sk4ca8a0dbdac7ed75 # 替换为实际的 secretKey
 openIM:
     secret: openIM123
     userID: imAdmin

+ 2 - 5
fs-user-app/src/main/java/com/fs/app/controller/live/LiveCompletionPointsController.java

@@ -152,9 +152,7 @@ public class LiveCompletionPointsController extends AppBaseController {
             
             // 3. 如果没有记录,查询直播间配置并生成记录
             if (record == null) {
-                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, null);
-                // 重新查询
-                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+                record = completionPointsRecordService.createCompletionRecord(liveId, userId);
             }
 
             // 4. 计算剩余时长
@@ -252,8 +250,7 @@ public class LiveCompletionPointsController extends AppBaseController {
             // 6. 更新完课记录中的看课时长
             if (record == null) {
                 // 如果没有记录,先创建记录
-                completionPointsRecordService.checkAndCreateCompletionRecord(liveId, userId, finalWatchDuration);
-                record = completionPointsRecordMapper.selectLatestByUserAndLiveId(liveId, userId);
+                record = completionPointsRecordService.createCompletionRecord(liveId, userId);
             } else {
                 // 更新现有记录的看课时长
                 Long currentWatchDuration = record.getWatchDuration() != null 

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java

@@ -244,6 +244,7 @@ public class WxUserScrmController extends AppBaseController {
             if (!wxService.getUserService().checkUserInfo(session.getSessionKey(), param.getRawData(), param.getSignature())) {
                 return R.error("user check failed");
             }
+
             // 解密用户信息
             WxMaUserInfo userInfo = wxService.getUserService().getUserInfo(session.getSessionKey(), param.getEncryptedData(), param.getIv());
             FsUserScrm user=userService.selectFsUserById(Long.parseLong(getUserId()));
@@ -251,6 +252,9 @@ public class WxUserScrmController extends AppBaseController {
             user.setAvatar(userInfo.getAvatarUrl());
             user.setIsWeixinAuth(1);
             userService.updateFsUser(user);
+
+            // 广告线索
+            leadService.weChatNameLead(param.getTraceId(), userInfo.getNickName());
             return R.ok();
         } catch (WxErrorException e) {
             e.printStackTrace();