Browse Source

coding:投流代码提交

zhangqin 3 weeks ago
parent
commit
217eba6ec1
68 changed files with 1517 additions and 2962 deletions
  1. 0 119
      fs-ad-new-api/src/main/java/com/fs/app/config/MybatisPlusConfig.java
  2. 5 0
      fs-ad-new-api/src/main/java/com/fs/app/constant/MqTopicConstant.java
  3. 42 17
      fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java
  4. 0 45
      fs-ad-new-api/src/main/java/com/fs/app/controller/LeadController.java
  5. 0 139
      fs-ad-new-api/src/main/java/com/fs/app/controller/RocketMqTestController.java
  6. 14 242
      fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java
  7. 0 67
      fs-ad-new-api/src/main/java/com/fs/app/dto/req/LeadSubmitRequest.java
  8. 11 7
      fs-ad-new-api/src/main/java/com/fs/app/enums/AdvertiserTypeEnum.java
  9. 95 0
      fs-ad-new-api/src/main/java/com/fs/app/enums/ConversionTypeEnum.java
  10. 3 2
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEvent.java
  11. 11 31
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventListener.java
  12. 3 4
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventPublisher.java
  13. 14 14
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  14. 117 224
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  15. 102 208
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  16. 6 4
      fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java
  17. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/BaiduAdapter.java
  18. 3 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/IAdvertiserAdapter.java
  19. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/OceanEngineAdapter.java
  20. 32 42
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/BaiduApiClient.java
  21. 20 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/IApiClient.java
  22. 30 4
      fs-ad-new-api/src/main/java/com/fs/app/integration/factory/AdvertiserHandlerFactory.java
  23. 0 104
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/AbstractCallbackHandler.java
  24. 0 209
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/BaiduCallbackHandler.java
  25. 0 203
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/OceanEngineCallbackHandler.java
  26. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/BaiduCallbackStrategy.java
  27. 3 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/ICallbackStrategy.java
  28. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/OceanEngineCallbackStrategy.java
  29. 0 65
      fs-ad-new-api/src/main/java/com/fs/app/manager/ConversionManager.java
  30. 46 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ClickMessageConsumer.java
  31. 0 117
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionDLQConsumer.java
  32. 35 78
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer.java
  33. 26 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/message/ClickMessage.java
  34. 16 15
      fs-ad-new-api/src/main/java/com/fs/app/mq/message/ConversionMessage.java
  35. 19 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/message/MqMessageDto.java
  36. 54 76
      fs-ad-new-api/src/main/java/com/fs/app/mq/producer/ConversionMessageProducer.java
  37. 35 33
      fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java
  38. 0 126
      fs-ad-new-api/src/main/java/com/fs/app/task/DailyArchiveTask.java
  39. 0 3
      fs-ad-new-api/src/main/java/com/fs/app/task/DataSyncTask.java
  40. 0 82
      fs-ad-new-api/src/main/java/com/fs/app/task/MonthlyArchiveTask.java
  41. 0 69
      fs-ad-new-api/src/main/java/com/fs/app/task/RedisDataPersistTask.java
  42. 0 34
      fs-ad-new-api/src/main/java/com/fs/app/task/Task.java
  43. 0 72
      fs-ad-new-api/src/main/java/com/fs/app/task/TaskCompensationTask.java
  44. 0 109
      fs-ad-new-api/src/main/java/com/fs/app/task/WeeklyArchiveTask.java
  45. 0 31
      fs-ad-new-api/src/main/java/com/fs/framework/annotation/DistributedLock.java
  46. 117 0
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributeLockAspect.java
  47. 0 77
      fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributedLockAspect.java
  48. 57 0
      fs-ad-new-api/src/main/java/com/fs/framework/config/AsyncConfig.java
  49. 1 0
      fs-ad-new-api/src/main/resources/application.yml
  50. 366 0
      fs-ad-new-api/src/main/resources/static/landing.html
  51. 60 0
      fs-common/src/main/java/com/fs/common/annotation/DistributeLock.java
  52. 13 0
      fs-common/src/main/java/com/fs/common/constant/DistributeLockConstant.java
  53. 24 0
      fs-common/src/main/java/com/fs/common/exception/DistributeLockException.java
  54. 4 0
      fs-common/src/main/java/com/fs/common/utils/RedisUtil.java
  55. 7 23
      fs-company/src/main/java/com/fs/company/controller/newAdv/SiteController.java
  56. 8 7
      fs-service/src/main/java/com/fs/newAdv/domain/CallbackAccount.java
  57. 7 22
      fs-service/src/main/java/com/fs/newAdv/domain/ClickTrace.java
  58. 8 15
      fs-service/src/main/java/com/fs/newAdv/domain/ConversionLog.java
  59. 5 54
      fs-service/src/main/java/com/fs/newAdv/domain/Lead.java
  60. 4 0
      fs-service/src/main/java/com/fs/newAdv/domain/Site.java
  61. 23 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/LandingIndexReq.java
  62. 26 0
      fs-service/src/main/java/com/fs/newAdv/dto/req/LeadSubmitRequest.java
  63. 15 0
      fs-service/src/main/java/com/fs/newAdv/dto/res/LandingIndexRes.java
  64. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/ConversionLogMapper.java
  65. 2 34
      fs-service/src/main/java/com/fs/newAdv/service/IClickTraceService.java
  66. 9 3
      fs-service/src/main/java/com/fs/newAdv/service/ISiteService.java
  67. 2 120
      fs-service/src/main/java/com/fs/newAdv/service/impl/ClickTraceServiceImpl.java
  68. 8 1
      fs-service/src/main/java/com/fs/newAdv/service/impl/SiteServiceImpl.java

+ 0 - 119
fs-ad-new-api/src/main/java/com/fs/app/config/MybatisPlusConfig.java

@@ -1,119 +0,0 @@
-//package com.fs.app.config;
-//
-//import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
-//import org.apache.ibatis.reflection.MetaObject;
-//import org.springframework.context.annotation.Bean;
-//import org.springframework.context.annotation.Configuration;
-//
-//import java.time.LocalDateTime;
-//
-///**
-// * MyBatis-Plus配置类
-// *
-// * @author zhangqin
-// * @date 2025-11-03
-// */
-//@Configuration
-//public class MybatisPlusConfig {
-//
-////    @Autowired
-////    private Environment env;
-////
-////    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
-////
-////    public static String setTypeAliasesPackage(String typeAliasesPackage)
-////    {
-////        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
-////        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
-////        List<String> allResult = new ArrayList<String>();
-////        try
-////        {
-////            for (String aliasesPackage : typeAliasesPackage.split(","))
-////            {
-////                List<String> result = new ArrayList<String>();
-////                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
-////                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
-////                Resource[] resources = resolver.getResources(aliasesPackage);
-////                if (resources != null && resources.length > 0)
-////                {
-////                    MetadataReader metadataReader = null;
-////                    for (Resource resource : resources)
-////                    {
-////                        if (resource.isReadable())
-////                        {
-////                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
-////                            try
-////                            {
-////                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
-////                            }
-////                            catch (ClassNotFoundException e)
-////                            {
-////                                e.printStackTrace();
-////                            }
-////                        }
-////                    }
-////                }
-////                if (result.size() > 0)
-////                {
-////                    HashSet<String> hashResult = new HashSet<String>(result);
-////                    allResult.addAll(hashResult);
-////                }
-////            }
-////            if (allResult.size() > 0)
-////            {
-////                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
-////            }
-////            else
-////            {
-////                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
-////            }
-////        }
-////        catch (IOException e)
-////        {
-////            e.printStackTrace();
-////        }
-////        return typeAliasesPackage;
-////    }
-////    /**
-////     * 分页插件
-////     */
-////    @Bean
-////    public SqlSessionFactory sqlSessionFactorys(DataSource dataSource) throws Exception
-////    {
-////        String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage");
-////        String mapperLocations = env.getProperty("mybatis-plus.mapperLocations");
-////        String configLocation = env.getProperty("mybatis-plus.configLocation");
-////        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
-////        VFS.addImplClass(SpringBootVFS.class);
-////
-////        final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
-////        sessionFactory.setDataSource(dataSource);
-////        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
-////        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
-////        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
-////        // 添加MyBatis-Plus分页插件
-////        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
-////        sessionFactory.setPlugins(new PaginationInterceptor[]{paginationInterceptor});
-////        return sessionFactory.getObject();
-////    }
-//
-//    /**
-//     * 自动填充处理器
-//     */
-//    @Bean
-//    public MetaObjectHandler metaObjectHandler() {
-//        return new MetaObjectHandler() {
-//            @Override
-//            public void insertFill(MetaObject metaObject) {
-//                setFieldValByName("createTime", LocalDateTime.now(), metaObject);
-//                setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
-//            }
-//
-//            @Override
-//            public void updateFill(MetaObject metaObject) {
-//                setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
-//            }
-//        };
-//    }
-//}
-//

+ 5 - 0
fs-ad-new-api/src/main/java/com/fs/app/constant/MqTopicConstant.java

@@ -0,0 +1,5 @@
+package com.fs.app.constant;
+
+public class MqTopicConstant {
+    public static final String CONVERSION_TOPIC = "conversion-topic";
+}

+ 42 - 17
fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java

@@ -1,16 +1,18 @@
 package com.fs.app.controller;
 
+import cn.hutool.core.util.StrUtil;
 import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.app.mq.message.ClickMessage;
+import com.fs.app.mq.producer.ConversionMessageProducer;
 import com.fs.common.result.Result;
+import com.fs.newAdv.dto.req.LandingIndexReq;
+import com.fs.newAdv.dto.req.LeadSubmitRequest;
+import com.fs.newAdv.dto.res.LandingIndexRes;
 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;
+import org.springframework.web.bind.annotation.*;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
 import java.util.Map;
 
 /**
@@ -22,27 +24,50 @@ import java.util.Map;
  */
 @Slf4j
 @RestController
-@RequestMapping("/api/landing")
+@RequestMapping("/landing")
 public class LandingPageController {
 
     @Autowired
-    private CallbackProcessingFacadeService callbackProcessingFacadeService;
+    private CallbackProcessingFacadeService callbackFacade;
+
+    @Autowired
+    private ConversionMessageProducer messageProducer;
 
     /**
      * 落地页访问
      */
-    @GetMapping("/track")
-    public Result<Map<String, Object>> track(
+    @GetMapping("/index")
+    public Result<LandingIndexRes> track(
             @RequestParam Map<String, String> allParams,
-            // @ApiParam("站点ID")
-            @RequestParam(required = false) Long siteId,
-            HttpServletRequest request,
-            HttpServletResponse response) {
-
-        log.info("落地页访问追踪:siteId={},params={}", siteId, allParams);
-        return callbackProcessingFacadeService.saveClickTrace(allParams, siteId, request, response);
+            @Valid @RequestBody LandingIndexReq req) {
+        log.info("落地页访问追踪:req={},params={}", req, allParams);
+        Long siteId = req.getSiteId();
+        // 1. 验证站点ID
+        if (siteId == null) {
+            String siteIdStr = allParams.get("site_id");
+            if (StrUtil.isNotBlank(siteIdStr)) {
+                req.setSiteId(Long.parseLong(siteIdStr));
+            } else {
+                return Result.error(400, "缺少站点ID参数");
+            }
+        }
+        // 完善线索追踪信息
+        ClickMessage message = new ClickMessage();
+        message.setSiteId(siteId);
+        messageProducer.sendClickMessage(message);
+        // 查询落地页模板
+        LandingIndexRes res = callbackFacade.getLandingIndexBySiteId(siteId);
+        return Result.success(res);
     }
 
 
+    /**
+     * 提交表单/落地按钮点击
+     */
+    @PostMapping("/submit")
+    public Result<String> submitForm(@RequestBody LeadSubmitRequest request) {
+        callbackFacade.submitForm(request);
+        return Result.success();
+    }
 }
 

+ 0 - 45
fs-ad-new-api/src/main/java/com/fs/app/controller/LeadController.java

@@ -1,45 +0,0 @@
-package com.fs.app.controller;
-
-import com.fs.app.dto.req.LeadSubmitRequest;
-import com.fs.app.facade.CallbackProcessingFacadeService;
-import com.fs.common.result.Result;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.validation.Valid;
-import java.util.Map;
-
-/**
- * 线索管理Controller
- *
- * @author zhangqin
- * @date 2025-11-05
- */
-@Slf4j
-@RestController
-@RequestMapping("/leads")
-public class LeadController {
-
-    @Autowired
-    private CallbackProcessingFacadeService callbackProcessingFacadeService;
-
-
-    /**
-     * 提交表单(落地页注册)
-     */
-    @PostMapping("/submit")
-    public Result<Map<String, Object>> submitForm(
-            @RequestBody @Valid LeadSubmitRequest request,
-            HttpServletRequest httpRequest) {
-        return callbackProcessingFacadeService.submitForm(request, httpRequest);
-
-    }
-
-
-}
-

+ 0 - 139
fs-ad-new-api/src/main/java/com/fs/app/controller/RocketMqTestController.java

@@ -1,139 +0,0 @@
-package com.fs.app.controller;
-
-import com.fs.app.mq.message.ConversionMessage;
-import com.fs.app.mq.producer.ConversionMessageProducer;
-import com.fs.common.result.Result;
-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.RestController;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * RocketMQ 测试接口
- * 用于测试 RocketMQ 连接和消息发送
- *
- * @author zhangqin
- * @date 2025-11-05
- */
-@Slf4j
-@RestController
-@RequestMapping("/api/test/mq")
-public class RocketMqTestController {
-
-    @Autowired
-    private ConversionMessageProducer messageProducer;
-
-    /**
-     * 发送测试消息
-     * <p>
-     * 访问:GET /api/test/mq/send
-     *
-     * @return 发送结果
-     */
-    @GetMapping("/send")
-    public Result<Map<String, Object>> sendTestMessage() {
-        try {
-            log.info("开始发送测试消息到 RocketMQ");
-
-            // 构建测试消息
-            ConversionMessage message = new ConversionMessage();
-            message.setSiteId(100L);
-            message.setClickId("TEST_CLICK_ID_" + System.currentTimeMillis());
-            message.setAdvertiser("BAIDU");
-            message.setEventType("TEST_EVENT");
-            message.setValue(0.0);
-            message.setLeadId(null);
-
-            // 发送消息
-            messageProducer.sendConversionMessage(message);
-
-            Map<String, Object> result = new HashMap<>();
-            result.put("message", "测试消息发送成功");
-            result.put("clickId", message.getClickId());
-            result.put("topic", "event-feedback");
-            result.put("timestamp", System.currentTimeMillis());
-
-            log.info("测试消息发送成功 | clickId={}", message.getClickId());
-
-            return Result.success(result);
-
-        } catch (Exception e) {
-            log.error("测试消息发送失败", e);
-            return Result.error(500, "测试消息发送失败: " + e.getMessage());
-        }
-    }
-
-    /**
-     * 同步发送测试消息
-     * <p>
-     * 访问:GET /api/test/mq/send-sync
-     *
-     * @return 发送结果
-     */
-    @GetMapping("/send-sync")
-    public Result<Map<String, Object>> sendTestMessageSync() {
-        try {
-            log.info("开始同步发送测试消息到 RocketMQ");
-
-            // 构建测试消息
-            ConversionMessage message = new ConversionMessage();
-            message.setSiteId(100L);
-            message.setClickId("TEST_SYNC_CLICK_ID_" + System.currentTimeMillis());
-            message.setAdvertiser("OCEANENGINE");
-            message.setEventType("TEST_SYNC_EVENT");
-            message.setValue(0.0);
-            message.setLeadId(null);
-
-            // 同步发送消息
-            boolean success = messageProducer.sendConversionMessageSync(message);
-
-            Map<String, Object> result = new HashMap<>();
-            result.put("success", success);
-            result.put("message", success ? "测试消息同步发送成功" : "测试消息同步发送失败");
-            result.put("clickId", message.getClickId());
-            result.put("topic", "event-feedback");
-            result.put("timestamp", System.currentTimeMillis());
-
-            log.info("测试消息同步发送{} | clickId={}", success ? "成功" : "失败", message.getClickId());
-
-            return success ? Result.success(result) : Result.error(500, "同步发送失败");
-
-        } catch (Exception e) {
-            log.error("测试消息同步发送失败", e);
-            return Result.error(500, "测试消息同步发送失败: " + e.getMessage());
-        }
-    }
-
-    /**
-     * 获取 RocketMQ 状态
-     * <p>
-     * 访问:GET /api/test/mq/status
-     *
-     * @return RocketMQ 状态信息
-     */
-    @GetMapping("/status")
-    public Result<Map<String, Object>> getMqStatus() {
-        try {
-            Map<String, Object> status = new HashMap<>();
-            status.put("producer", "已初始化");
-            status.put("topic", "event-feedback");
-            status.put("consumerGroup", "event-feedback-consumer-group");
-            status.put("timestamp", System.currentTimeMillis());
-
-            log.info("获取 RocketMQ 状态成功");
-
-            return Result.success(status);
-
-        } catch (Exception e) {
-            log.error("获取 RocketMQ 状态失败", e);
-            return Result.error(500, "获取状态失败: " + e.getMessage());
-        }
-    }
-}
-
-
-

+ 14 - 242
fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java

@@ -1,7 +1,8 @@
 package com.fs.app.controller;
 
 import cn.hutool.core.util.StrUtil;
-import com.fs.newAdv.service.IClickTraceService;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.facade.CallbackProcessingFacadeService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -11,81 +12,47 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.util.HashMap;
 import java.util.Map;
 
 /**
- * 广告监测控制器(Server-to-Server)
+ * 链化追踪
+ *
  * @author zhangqin
- * @date 2025-11-05
  */
 @Slf4j
 @RestController
-@RequestMapping("/api/track")
+@RequestMapping("/track")
 public class TrackingController {
 
     @Autowired
-    private IClickTraceService clickTraceService;
+    private CallbackProcessingFacadeService callback;
 
     /**
      * 百度推广监测端点
-     * <p>
-     * 监测链接示例:
-     * https://track.yourdomain.com/api/track/baidu?clkid={clkid}&site_id=100&keyword={keyword}&planid={planid}
-     * <p>
-     * 百度宏参数说明:
-     * - {clkid}: 点击ID(必填)
-     * - {keyword}: 关键词
-     * - {planid}: 推广计划ID
-     * - {unitid}: 推广单元ID
-     * - {creative}: 创意ID
      *
      * @param allParams 所有URL参数
-     * @param request   HTTP请求
      * @param response  HTTP响应
      */
     @GetMapping("/baidu")
     public void trackBaidu(
             @RequestParam Map<String, String> allParams,
-            HttpServletRequest request,
             HttpServletResponse response) {
-
-        log.info("【百度】接收监测请求 | params={}, ip={}",
-                allParams, getClientIp(request));
-
+        log.info("【百度】接收监测请求 | params={}", allParams);
         try {
             // 1. 提取百度特有参数
-            String clickId = allParams.get("clkid");
-            if (StrUtil.isBlank(clickId)) {
-                clickId = allParams.get("logid"); // 百度也可能使用 logid
-            }
-
-            if (StrUtil.isBlank(clickId)) {
+            String traceId = allParams.get("bd_id");
+            if (StrUtil.isBlank(traceId)) {
                 log.warn("【百度】监测请求缺少点击ID | params={}", allParams);
                 response.setStatus(HttpServletResponse.SC_OK);
                 return;
             }
-
-            // 2. 构建标准参数
-            Map<String, String> standardParams = new HashMap<>(allParams);
-            standardParams.put("click_id", clickId);
-            standardParams.put("source", "BAIDU");
-
-            // 3. 保存点击追踪记录
-            Long siteId = extractSiteId(allParams);
-            clickTraceService.saveClickTrace(
-                    siteId,
-                    standardParams,
-                    null,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
+            // 2. 保存点击追踪记录
+            callback.saveClickTrace(
+                    AdvertiserTypeEnum.BAIDU,
+                    allParams
             );
-
-            log.info("【百度】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
-
-            // 4. 返回 200 OK
+            // 3. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
-
         } catch (Exception e) {
             log.error("【百度】监测请求处理失败 | params={}", allParams, e);
             response.setStatus(HttpServletResponse.SC_OK);
@@ -94,16 +61,6 @@ public class TrackingController {
 
     /**
      * 巨量引擎监测端点
-     * <p>
-     * 监测链接示例:
-     * https://track.yourdomain.com/api/track/oceanengine?clickid={clickid}&site_id=100&cid={cid}&creativeid={creativeid}
-     * <p>
-     * 巨量引擎宏参数说明:
-     * - {clickid}: 点击ID(必填)
-     * - {cid}: 广告计划ID
-     * - {creativeid}: 创意ID
-     * - {aid}: 广告ID
-     * - {callback_param}: 自定义参数(也可作为点击ID)
      *
      * @param allParams 所有URL参数
      * @param request   HTTP请求
@@ -115,41 +72,8 @@ public class TrackingController {
             HttpServletRequest request,
             HttpServletResponse response) {
 
-        log.info("【巨量引擎】接收监测请求 | params={}, ip={}",
-                allParams, getClientIp(request));
-
         try {
-            // 1. 提取巨量引擎特有参数
-            String clickId = allParams.get("clickid");
-            if (StrUtil.isBlank(clickId)) {
-                clickId = allParams.get("callback_param");
-            }
 
-            if (StrUtil.isBlank(clickId)) {
-                log.warn("【巨量引擎】监测请求缺少点击ID | params={}", allParams);
-                response.setStatus(HttpServletResponse.SC_OK);
-                return;
-            }
-
-            // 2. 构建标准参数
-            Map<String, String> standardParams = new HashMap<>(allParams);
-            standardParams.put("click_id", clickId);
-            standardParams.put("source", "OCEANENGINE");
-
-            // 3. 保存点击追踪记录
-            Long siteId = extractSiteId(allParams);
-            clickTraceService.saveClickTrace(
-                    siteId,
-                    standardParams,
-                    null,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
-            );
-
-            log.info("【巨量引擎】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
-
-            // 4. 返回 200 OK
-            response.setStatus(HttpServletResponse.SC_OK);
 
         } catch (Exception e) {
             log.error("【巨量引擎】监测请求处理失败 | params={}", allParams, e);
@@ -178,9 +102,6 @@ public class TrackingController {
             HttpServletRequest request,
             HttpServletResponse response) {
 
-        log.info("【新浪】接收监测请求 | params={}, ip={}",
-                allParams, getClientIp(request));
-
         try {
             // 1. 提取新浪参数
             String clickId = allParams.get("click_id");
@@ -191,22 +112,6 @@ public class TrackingController {
                 return;
             }
 
-            // 2. 构建标准参数
-            Map<String, String> standardParams = new HashMap<>(allParams);
-            standardParams.put("click_id", clickId);
-            standardParams.put("source", "SINA");
-
-            // 3. 保存点击追踪记录
-            Long siteId = extractSiteId(allParams);
-            clickTraceService.saveClickTrace(
-                    siteId,
-                    standardParams,
-                    null,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
-            );
-
-            log.info("【新浪】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
 
             // 4. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
@@ -239,38 +144,9 @@ public class TrackingController {
             HttpServletRequest request,
             HttpServletResponse response) {
 
-        log.info("【广点通】接收监测请求 | params={}, ip={}",
-                allParams, getClientIp(request));
-
         try {
             // 1. 提取广点通参数(广点通使用大写参数名)
-            String clickId = allParams.get("click_id");
-            if (StrUtil.isBlank(clickId)) {
-                clickId = allParams.get("CLICK_ID");
-            }
 
-            if (StrUtil.isBlank(clickId)) {
-                log.warn("【广点通】监测请求缺少点击ID | params={}", allParams);
-                response.setStatus(HttpServletResponse.SC_OK);
-                return;
-            }
-
-            // 2. 构建标准参数
-            Map<String, String> standardParams = new HashMap<>(allParams);
-            standardParams.put("click_id", clickId);
-            standardParams.put("source", "GDT");
-
-            // 3. 保存点击追踪记录
-            Long siteId = extractSiteId(allParams);
-            clickTraceService.saveClickTrace(
-                    siteId,
-                    standardParams,
-                    null,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
-            );
-
-            log.info("【广点通】监测请求处理成功 | clickId={}, siteId={}", clickId, siteId);
 
             // 4. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
@@ -300,39 +176,7 @@ public class TrackingController {
             HttpServletRequest request,
             HttpServletResponse response) {
 
-        String source = allParams.getOrDefault("source", "UNKNOWN");
-        log.info("【通用】接收监测请求 | source={}, params={}, ip={}",
-                source, allParams, getClientIp(request));
-
         try {
-            // 1. 提取点击ID(支持多种参数名)
-            String clickId = extractClickId(allParams);
-
-            if (StrUtil.isBlank(clickId)) {
-                log.warn("【通用】监测请求缺少点击ID | params={}", allParams);
-                response.setStatus(HttpServletResponse.SC_OK);
-                return;
-            }
-
-            // 2. 确保参数中包含标准字段
-            Map<String, String> standardParams = new HashMap<>(allParams);
-            standardParams.put("click_id", clickId);
-            if (!standardParams.containsKey("source")) {
-                standardParams.put("source", "UNKNOWN");
-            }
-
-            // 3. 保存点击追踪记录
-            Long siteId = extractSiteId(allParams);
-            clickTraceService.saveClickTrace(
-                    siteId,
-                    standardParams,
-                    null,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
-            );
-
-            log.info("【通用】监测请求处理成功 | clickId={}, siteId={}, source={}",
-                    clickId, siteId, source);
 
             // 4. 返回 200 OK
             response.setStatus(HttpServletResponse.SC_OK);
@@ -342,76 +186,4 @@ public class TrackingController {
             response.setStatus(HttpServletResponse.SC_OK);
         }
     }
-
-    /**
-     * 提取点击ID(支持多种参数名)
-     */
-    private String extractClickId(Map<String, String> params) {
-        String clickId = params.get("click_id");
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("clickid");
-        }
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("clk_id");
-        }
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("clkid");
-        }
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("CLICK_ID");
-        }
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("callback_param");
-        }
-        if (StrUtil.isBlank(clickId)) {
-            clickId = params.get("logid");
-        }
-        return clickId;
-    }
-
-    /**
-     * 提取站点ID
-     */
-    private Long extractSiteId(Map<String, String> params) {
-        String siteIdStr = params.get("site_id");
-        if (StrUtil.isBlank(siteIdStr)) {
-            siteIdStr = params.get("siteid");
-        }
-
-        if (StrUtil.isNotBlank(siteIdStr)) {
-            try {
-                return Long.parseLong(siteIdStr);
-            } catch (NumberFormatException e) {
-                log.warn("站点ID格式错误:{}", siteIdStr);
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * 获取客户端真实IP
-     */
-    private String getClientIp(HttpServletRequest request) {
-        String ip = request.getHeader("X-Forwarded-For");
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("X-Real-IP");
-        }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("Proxy-Client-IP");
-        }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("WL-Proxy-Client-IP");
-        }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getRemoteAddr();
-        }
-
-        // 处理多个IP的情况,取第一个
-        if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
-            ip = ip.split(",")[0].trim();
-        }
-
-        return ip;
-    }
 }

+ 0 - 67
fs-ad-new-api/src/main/java/com/fs/app/dto/req/LeadSubmitRequest.java

@@ -1,67 +0,0 @@
-package com.fs.app.dto.req;
-
-import lombok.Data;
-
-import java.util.Map;
-
-/**
- * 表单提交请求DTO
- */
-@Data
-public class LeadSubmitRequest {
-    /**
-     * 姓名(必填)
-     */
-    private String name;
-
-    /**
-     * 手机号(必填)
-     */
-    private String phone;
-
-    /**
-     * 公司名称
-     */
-    private String company;
-
-    /**
-     * 邮箱
-     */
-    private String email;
-
-    /**
-     * 站点ID
-     */
-    private Long siteId;
-
-    /**
-     * 点击ID(广告平台提供)
-     */
-    private String clickId;
-
-    /**
-     * 来源平台(BAIDU, OCEANENGINE, SINA, GDT)
-     */
-    private String source;
-
-    /**
-     * 广告计划ID
-     */
-    private String campaignId;
-
-    /**
-     * 关键词
-     */
-    private String keyword;
-
-    /**
-     * 创意ID
-     */
-    private String creativeId;
-
-    /**
-     * 原始URL参数(Map格式)
-     * 包含所有广告平台传递的参数,如 bd_vid, callback, clkid 等
-     */
-    private Map<String, String> rawParams;
-}

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

@@ -11,23 +11,27 @@ import lombok.Getter;
 @Getter
 public enum AdvertiserTypeEnum {
 
-    BAIDU(1, "百度"),
-    OCEAN_ENGINE(2, "巨量引擎"),
-    SINA(3, "新浪"),
-    GDT(4, "广点通");
+    BAIDU(1L,"BAIDU", "百度"),
+    OCEAN_ENGINE(2L,"OCEAN_ENGINE", "巨量引擎"),
+    SINA(3L,"SINA", "新浪"),
+    GDT(4L,"GDT", "广点通");
 
-    private final Integer code;
+    private final Long code;
     private final String name;
+    private final String des;
 
-    AdvertiserTypeEnum(Integer code, String name) {
+
+    AdvertiserTypeEnum(Long code, String name,String des) {
         this.code = code;
         this.name = name;
+        this.des = des;
+
     }
 
     /**
      * 根据code获取枚举
      */
-    public static AdvertiserTypeEnum getByCode(Integer code) {
+    public static AdvertiserTypeEnum getByCode(Long code) {
         for (AdvertiserTypeEnum typeEnum : values()) {
             if (typeEnum.getCode().equals(code)) {
                 return typeEnum;

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

@@ -0,0 +1,95 @@
+package com.fs.app.enums;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Getter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 回传事件类型枚举
+ */
+@Getter
+public enum ConversionTypeEnum {
+
+    FORM_SUBMIT("FORM_SUBMIT","表单提交", 1),
+    CLICK("CLICK","点击", 2),
+    VIEW("VIEW","曝光", 3);
+
+    private final String code;
+    /**
+     * 系统内描述
+     */
+    private final String desc;
+
+
+    /**
+     * 系统事件类型编号
+     */
+    private final Integer systemType;
+
+    /**
+     * 各广告商对应事件类型
+     */
+    private final Map<AdvertiserTypeEnum, String> advertiserTypeMap = new HashMap<>();
+
+    ConversionTypeEnum(String code, String desc, Integer systemType) {
+        this.code = code;
+        this.desc = desc;
+        this.systemType = systemType;
+    }
+
+    // 在静态初始化块中进行广告商类型映射
+    static {
+        //---------------表单提交
+        FORM_SUBMIT.advertiserTypeMap.put(AdvertiserTypeEnum.BAIDU, "3");
+        FORM_SUBMIT.advertiserTypeMap.put(AdvertiserTypeEnum.OCEAN_ENGINE, "hc");
+        //---------------点击
+        CLICK.advertiserTypeMap.put(AdvertiserTypeEnum.BAIDU, "1");
+        CLICK.advertiserTypeMap.put(AdvertiserTypeEnum.OCEAN_ENGINE, "clk");
+        //---------------曝光
+
+    }
+
+    /**
+     * 链式绑定广告商对应事件类型
+     */
+    public ConversionTypeEnum put(AdvertiserTypeEnum advertiser, String type) {
+        if (ObjectUtil.isNotEmpty(advertiser) && ObjectUtil.isNotEmpty(type)) {
+            advertiserTypeMap.put(advertiser, type);
+        }
+        return this;
+    }
+
+    /**
+     * 获取广告商事件类型
+     */
+    public String getAdvertiserType(AdvertiserTypeEnum advertiser) {
+        if (ObjectUtil.isEmpty(advertiser)) {
+            return null;
+        }
+        return advertiserTypeMap.get(advertiser);
+    }
+
+    /**
+     * 根据系统事件类型编号获取对应枚举
+     */
+    public static ConversionTypeEnum getBySystemType(Integer systemType) {
+        if (ObjectUtil.isEmpty(systemType)) {
+            return null;
+        }
+        for (ConversionTypeEnum e : values()) {
+            if (e.getSystemType().equals(systemType)) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据系统类型 + 广告商获取对应广告商事件类型
+     */
+    public static String getAdvertiserType(ConversionTypeEnum e, AdvertiserTypeEnum advertiser) {
+        return e.getAdvertiserType(advertiser);
+    }
+}

+ 3 - 2
fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEvent.java

@@ -1,5 +1,6 @@
 package com.fs.app.event;
 
+import com.fs.app.enums.ConversionTypeEnum;
 import lombok.Getter;
 import org.springframework.context.ApplicationEvent;
 
@@ -33,7 +34,7 @@ public class ConversionEvent extends ApplicationEvent {
     /**
      * 转化事件类型(如:register、form_submit、add_wechat)
      */
-    private final String eventType;
+    private final ConversionTypeEnum eventType;
 
     /**
      * 转化价值(元)
@@ -46,7 +47,7 @@ public class ConversionEvent extends ApplicationEvent {
     private final Long leadId;
 
     public ConversionEvent(Object source, Long siteId, String clickId,
-                          String advertiser, String eventType, Double value, Long leadId) {
+                           String advertiser, ConversionTypeEnum eventType, Double value, Long leadId) {
         super(source);
         this.siteId = siteId;
         this.clickId = clickId;

+ 11 - 31
fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventListener.java

@@ -1,9 +1,8 @@
 package com.fs.app.event;
 
-import cn.hutool.core.util.StrUtil;
-
 import com.fs.app.mq.message.ConversionMessage;
 import com.fs.app.mq.producer.ConversionMessageProducer;
+import com.fs.app.enums.ConversionTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.event.EventListener;
@@ -37,37 +36,18 @@ public class ConversionEventListener {
         Long siteId = event.getSiteId();
         String clickId = event.getClickId();
         String advertiser = event.getAdvertiser();
-        String eventType = event.getEventType();
+        ConversionTypeEnum eventType = event.getEventType();
         Double value = event.getValue();
         Long leadId = event.getLeadId();
-
         log.info("接收到转化事件 | siteId={}, clickId={}, advertiser={}, eventType={}",
-                 siteId, clickId, advertiser, eventType);
-
-        try {
-            // 1. 校验参数
-            if (siteId == null || StrUtil.isBlank(clickId) || StrUtil.isBlank(advertiser)) {
-                log.error("转化事件参数不完整 | siteId={}, clickId={}, advertiser={}",
-                          siteId, clickId, advertiser);
-                return;
-            }
-
-            // 2. 构建MQ消息
-            ConversionMessage message = new ConversionMessage();
-            message.setSiteId(siteId);
-            message.setClickId(clickId);
-            message.setAdvertiser(advertiser);
-            message.setEventType(eventType);
-            message.setValue(value);
-            message.setLeadId(leadId);
-
-            // 3. 发送到MQ
-            messageProducer.sendConversionMessage(message);
-
-            log.info("转化事件已发送到MQ | clickId={}, eventType={}", clickId, eventType);
-
-        } catch (Exception e) {
-            log.error("处理转化事件失败 | clickId={}, eventType={}", clickId, eventType, e);
-        }
+                siteId, clickId, advertiser, eventType);
+        // 1. 校验参数
+        // 2. 构建MQ消息
+        ConversionMessage message = new ConversionMessage();
+        message.setSiteId(siteId);
+        message.setAdvertiser(advertiser);
+        message.setEventType(eventType);
+        // 3. 发送到MQ
+        messageProducer.sendConversionMessage(message);
     }
 }

+ 3 - 4
fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventPublisher.java

@@ -1,5 +1,6 @@
 package com.fs.app.event;
 
+import com.fs.app.enums.ConversionTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationEventPublisher;
@@ -30,13 +31,11 @@ public class ConversionEventPublisher {
      * @param leadId 线索ID(可选)
      */
     public void publishConversionEvent(Long siteId, String clickId, String advertiser,
-                                      String eventType, Double value, Long leadId) {
+                                       ConversionTypeEnum eventType, Double value, Long leadId) {
         log.info("发布转化事件 | siteId={}, clickId={}, advertiser={}, eventType={}, value={}",
                 siteId, clickId, advertiser, eventType, value);
-
         ConversionEvent event = new ConversionEvent(this, siteId, clickId,
                                                      advertiser, eventType, value, leadId);
-
         applicationEventPublisher.publishEvent(event);
     }
 
@@ -48,7 +47,7 @@ public class ConversionEventPublisher {
      * @param advertiser 广告商名称
      * @param eventType 转化事件类型
      */
-    public void publishConversionEvent(Long siteId, String clickId, String advertiser, String eventType) {
+    public void publishConversionEvent(Long siteId, String clickId, String advertiser, ConversionTypeEnum eventType) {
         publishConversionEvent(siteId, clickId, advertiser, eventType, null, null);
     }
 }

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

@@ -1,26 +1,26 @@
 package com.fs.app.facade;
 
-import com.fs.app.dto.req.LeadSubmitRequest;
-import com.fs.common.result.Result;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.mq.message.ClickMessage;
+import com.fs.newAdv.dto.req.LeadSubmitRequest;
+import com.fs.newAdv.dto.res.LandingIndexRes;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 import java.util.Map;
 
 public interface CallbackProcessingFacadeService {
+
+
+    void saveClickTrace(AdvertiserTypeEnum trackType, Map<String, String> allParams);
+
+    void updateClickTrace(ClickMessage message);
+
+    void submitForm(LeadSubmitRequest request);
+
     /**
-     * 落地页访问接口
+     * 根据站点ID获取落地页信息
      *
-     * @param allParams
      * @param siteId
-     * @param request
-     * @param response
      * @return
      */
-    Result<Map<String, Object>> saveClickTrace(Map<String, String> allParams, Long siteId, HttpServletRequest request, HttpServletResponse response);
-
-    Result<Map<String, Object>> submitForm(LeadSubmitRequest request, HttpServletRequest httpRequest);
-
-
-
+    LandingIndexRes getLandingIndexBySiteId(Long siteId);
 }

+ 117 - 224
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -3,20 +3,26 @@ package com.fs.app.facade;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
-import com.fs.app.dto.req.LeadSubmitRequest;
+import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.app.event.ConversionEventPublisher;
 import com.fs.app.integration.adapter.IAdvertiserAdapter;
 import com.fs.app.integration.client.BaiduApiClient;
 import com.fs.app.integration.factory.AdvertiserHandlerFactory;
 import com.fs.app.integration.strategy.ICallbackStrategy;
+import com.fs.app.mq.message.ClickMessage;
 import com.fs.common.exception.base.BusinessException;
-import com.fs.common.result.Result;
 import com.fs.common.utils.SnowflakeUtil;
 import com.fs.newAdv.domain.ClickTrace;
+import com.fs.newAdv.domain.LandingPageTemplate;
 import com.fs.newAdv.domain.Lead;
-import com.fs.newAdv.mapper.SiteStatisticsMapper;
+import com.fs.newAdv.domain.Site;
+import com.fs.newAdv.dto.req.LeadSubmitRequest;
+import com.fs.newAdv.dto.res.LandingIndexRes;
+import com.fs.app.enums.ConversionTypeEnum;
 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 lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -25,6 +31,7 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -39,264 +46,150 @@ public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFa
 
     @Autowired
     private AdvertiserHandlerFactory handlerFactory;
+
     @Autowired
     private ILeadService leadService;
 
-    @Autowired
-    private SiteStatisticsMapper statisticsMapper;
+    private ISiteService siteService;
 
     @Autowired
     private ConversionEventPublisher conversionEventPublisher;
 
-    @Override
-    public Result<Map<String, Object>> saveClickTrace(Map<String, String> allParams, Long siteId, HttpServletRequest request, HttpServletResponse response) {
-        try {
-            // 1. 验证站点ID
-            if (siteId == null) {
-                String siteIdStr = allParams.get("site_id");
-                if (StrUtil.isNotBlank(siteIdStr)) {
-                    siteId = Long.parseLong(siteIdStr);
-                } else {
-                    return Result.error(400, "缺少站点ID参数");
-                }
-            }
-
-            // 2. 获取或创建访客ID
-            String visitorId = getOrCreateVisitorId(request, response);
-
-            // 3. 保存点击追踪记录
-            ClickTrace trace = clickTraceService.saveClickTrace(
-                    siteId,
-                    allParams,
-                    visitorId,
-                    getClientIp(request),
-                    request.getHeader("User-Agent")
-            );
+    @Autowired
+    private IClickTraceService iClickTraceService;
+    @Autowired
+    private ILandingPageTemplateService landingPageTemplateService;
 
-            // 4. 返回追踪信息
-            Map<String, Object> result = new HashMap<>();
-            result.put("traceId", trace.getTraceId());
-            result.put("visitorId", visitorId);
-            result.put("message", "追踪成功");
-            log.info("落地页追踪成功:traceId={}", trace.getTraceId());
-            return Result.success(result);
-        } catch (Exception e) {
-            log.error("落地页追踪失败", e);
-            return Result.error(500, "追踪失败:" + e.getMessage());
-        }
+    @Override
+    public void saveClickTrace(AdvertiserTypeEnum trackType, Map<String, String> allParams) {
+        ClickTrace trace = new ClickTrace();
+        // 提取不同平台的参数
+        extractPlatformParams(trace, trackType, allParams);
+        // 保存原始参数
+        trace.setRawParams(JSONUtil.toJsonStr(allParams));
+        // 状态初始化
+        trace.setIsConverted(0);
+        trace.setSource(trackType.getName());
+        // 时间
+        trace.setCreateTime(LocalDateTime.now());
+        trace.setUpdateTime(LocalDateTime.now());
+
+        iClickTraceService.save(trace);
+        log.info("点击追踪记录保存成功:traceId={}", trace.getTraceId());
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Result<Map<String, Object>> submitForm(LeadSubmitRequest request, HttpServletRequest httpRequest) {
-        try {
-            // 2. 构建Lead对象
-            Lead lead = new Lead();
-            lead.setName(request.getName());
-            lead.setPhone(request.getPhone());
-            lead.setCompany(request.getCompany());
-            lead.setEmail(request.getEmail());
-            lead.setSiteId(request.getSiteId());
-            lead.setClickId(request.getClickId());
-            lead.setSource(request.getSource());
-            lead.setCampaignId(request.getCampaignId());
-            lead.setKeyword(request.getKeyword());
-            lead.setCreativeId(request.getCreativeId());
-            lead.setStatus(0); // 新线索
-
-            // 3. 保存原始参数JSON(优先使用前端传递的完整参数)
-            Map<String, Object> rawParams = new HashMap<>();
-            if (request.getRawParams() != null && !request.getRawParams().isEmpty()) {
-                // 使用前端传递的所有URL参数(包括bd_vid等平台特定参数)
-                rawParams.putAll(request.getRawParams());
-            } else {
-                // 向后兼容:如果没有rawParams,使用单独字段构建
-                rawParams.put("click_id", request.getClickId());
-                rawParams.put("source", request.getSource());
-                rawParams.put("site_id", request.getSiteId());
-                rawParams.put("campaign_id", request.getCampaignId());
-                rawParams.put("keyword", request.getKeyword());
-                rawParams.put("creative_id", request.getCreativeId());
-            }
-            lead.setRawParams(JSONUtil.toJsonStr(rawParams));
-
-            // 4. 获取客户端信息
-            lead.setClientIp(getClientIp(httpRequest));
-            lead.setUserAgent(httpRequest.getHeader("User-Agent"));
-
-            // 5. 保存线索并触发转化事件
-            Lead savedLead = saveLeadAndTriggerConversion(lead);
-
-            // 6. 返回结果
-            Map<String, Object> result = new HashMap<>();
-            result.put("leadId", savedLead.getId());
-            result.put("message", "提交成功,我们会尽快与您联系");
-
-            log.info("表单提交成功 | leadId={}, name={}, phone={}",
-                    savedLead.getId(), savedLead.getName(), savedLead.getPhone());
-
-            return Result.success(result);
-
-        } catch (Exception e) {
-            log.error("表单提交失败 | name={}, phone={}", request.getName(), request.getPhone(), e);
-            return Result.error(500, "提交失败,请稍后重试");
-        }
+    public void updateClickTrace(ClickMessage clickMessage) {
+        Map<String, String> params = getTraceIdByPlatformParams(clickMessage.getAllParams());
+        Site site = siteService.getById(clickMessage.getSiteId());
+        ClickTrace byTraceId = iClickTraceService.getByTraceId(params.get("traceId"));
+        byTraceId.setSiteId(site.getId());
+        byTraceId.setAdvertiserId(site.getAdvertiserId());
+        byTraceId.setAdvertiserName(site.getAdvertiserName());
+        byTraceId.setViewUrl(clickMessage.getViewUrl());
+        siteService.updateById(site);
     }
 
-    public Lead saveLeadAndTriggerConversion(Lead lead) {
-        // 1. 保存线索到数据库
-        boolean saved = leadService.save(lead);
-
-        if (!saved) {
-            log.error("线索保存失败 | phone={}", lead.getPhone());
-            throw new RuntimeException("线索保存失败");
-        }
-
-        log.info("线索保存成功 | leadId={}, name={}, phone={}, clickId={}",
-                lead.getId(), lead.getName(), lead.getPhone(), lead.getClickId());
-
-        // 2. 如果有点击ID和来源,触发转化事件
-        if (StrUtil.isNotBlank(lead.getClickId()) && StrUtil.isNotBlank(lead.getSource())) {
-            try {
-                // 发布转化事件(会自动发送到MQ)
-                conversionEventPublisher.publishConversionEvent(
-                        lead.getSiteId(),           // 站点ID
-                        lead.getClickId(),          // 点击ID
-                        lead.getSource().toUpperCase(), // 广告商(BAIDU/OCEANENGINE/SINA/GDT)
-                        "SUBMIT_FORM",              // 事件类型:提交表单
-                        null,                       // 转化价值(可选)
-                        lead.getId()                // 线索ID
-                );
-
-                log.info("转化事件已发布 | leadId={}, clickId={}, source={}",
-                        lead.getId(), lead.getClickId(), lead.getSource());
-
-            } catch (Exception e) {
-                log.error("转化事件发布失败 | leadId={}", lead.getId(), e);
-                // 不影响线索保存,只记录日志
-            }
+    /**
+     * 获取traceId和source平台信息
+     *
+     * @param allParams
+     * @return traceId 线索id
+     */
+    private Map<String, String> getTraceIdByPlatformParams(Map<String, String> allParams) {
+        Map<String, String> traceId = new HashMap<>();
+        if (StrUtil.isNotEmpty(allParams.get("bd_vid"))) {
+            traceId.put("traceId", allParams.get("bd_vid"));
+            traceId.put("source", AdvertiserTypeEnum.BAIDU.getName());
+            return traceId;
         } else {
-            log.warn("缺少点击ID或来源信息,跳过转化事件 | leadId={}, clickId={}, source={}",
-                    lead.getId(), lead.getClickId(), lead.getSource());
+            throw new BusinessException("回传参数错误");
         }
 
-        return lead;
     }
 
     /**
-     * 获取或创建访客ID(使用Cookie)
+     * 提取不同平台的参数
      */
-    private String getOrCreateVisitorId(HttpServletRequest request,
-                                        HttpServletResponse response) {
-        // 从Cookie获取
-        Cookie[] cookies = request.getCookies();
-        if (cookies != null) {
-            for (Cookie cookie : cookies) {
-                if ("visitor_id".equals(cookie.getName())) {
-                    String visitorId = cookie.getValue();
-                    if (StrUtil.isNotBlank(visitorId)) {
-                        return visitorId;
-                    }
-                }
-            }
+    private void extractPlatformParams(ClickTrace trace,
+                                       AdvertiserTypeEnum trackType,
+                                       Map<String, String> allParams) {
+        switch (trackType) {
+            case BAIDU:
+                trace.setTraceId(allParams.get("bd_vid"));
+                trace.setClickId(allParams.get("click_id"));
+                trace.setCreativeId(allParams.get("aid"));
+                trace.setCampaignId(allParams.get("pid"));
+                trace.setIp(allParams.get("ip"));
+                break;
+            case SINA:
+                break;
         }
 
-        // 生成新的访客ID
-        String visitorId = SnowflakeUtil.nextIdStr();
-        Cookie cookie = new Cookie("visitor_id", visitorId);
-        cookie.setMaxAge(30 * 24 * 60 * 60); // 30天
-        cookie.setPath("/");
-        cookie.setHttpOnly(false); // 允许JavaScript访问
-        response.addCookie(cookie);
-
-        log.info("生成新的访客ID:{}", visitorId);
-        return visitorId;
     }
 
-    /**
-     * 获取客户端真实IP
-     */
-    private String getClientIp(HttpServletRequest request) {
-        String ip = request.getHeader("X-Forwarded-For");
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("X-Real-IP");
-        }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("Proxy-Client-IP");
-        }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getHeader("WL-Proxy-Client-IP");
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submitForm(LeadSubmitRequest request) {
+        Map<String, String> params = getTraceIdByPlatformParams(request.getRawParams());
+        String traceId = params.get("traceId");
+        String source = params.get("source");
+        if (StrUtil.isEmpty(traceId)) {
+            throw new BusinessException("缺少traceId");
         }
-        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
-            ip = request.getRemoteAddr();
+        // 2. 构建Lead对象
+        Lead lead = new Lead();
+        lead.setSiteId(request.getSiteId());
+        lead.setViewUrl(request.getViewUrl());
+        lead.setSource(source);
+        lead.setTraceId(traceId);
+        lead.setStatus(0); // 新线索
+
+        // 3. 保存原始参数JSON(优先使用前端传递的完整参数)
+        Map<String, Object> rawParams = new HashMap<>();
+        if (request.getRawParams() != null && !request.getRawParams().isEmpty()) {
+            // 使用前端传递的所有URL参数(包括bd_vid等平台特定参数)
+            rawParams.putAll(request.getRawParams());
         }
+        lead.setRawParams(JSONUtil.toJsonStr(rawParams));
 
-        // 处理多个IP的情况,取第一个
-        if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
-            ip = ip.split(",")[0].trim();
-        }
+        // 4. 获取客户端信息
 
-        return ip;
+        // 5. 保存线索并触发转化事件
+        saveLeadAndTriggerConversion(lead);
     }
 
+    @Override
+    public LandingIndexRes getLandingIndexBySiteId(Long siteId) {
+        Site byId = siteService.getById(siteId);
+        LandingPageTemplate byId1 = landingPageTemplateService.getById(byId.getLaunchPageId());
+        LandingIndexRes res = new LandingIndexRes();
+        res.setTemplateData(byId1.getTemplateData());
+        return res;
+    }
 
-    public boolean handleCallback(Integer advertiserType, Map<String, Object> callbackData, String sign) {
-        log.info("处理广告商 {} 的回调数据", advertiserType);
-
-        try {
-            // 1. 获取对应的适配器和策略
-            IAdvertiserAdapter adapter = handlerFactory.getAdapter(advertiserType);
-            ICallbackStrategy strategy = handlerFactory.getStrategy(advertiserType);
-
-            // 2. 验证签名
-            Map<String, String> signParams = convertToStringMap(callbackData);
-            if (!strategy.verifySign(signParams, sign)) {
-                log.error("签名验证失败");
-                return false;
-            }
-
-            // 3. 适配数据格式
-            Map<String, Object> adaptedData = adapter.adaptCallbackData(callbackData);
-
-            // 4. 执行策略处理
-            strategy.handleCallback(adaptedData);
-
-            // 5. 更新统计数据
-            updateSiteStatistics(
-                    MapUtil.getLong(adaptedData, "siteId"),
-                    MapUtil.getLong(adaptedData, "impressionCount"),
-                    MapUtil.getLong(adaptedData, "clickCount"),
-                    MapUtil.getDouble(adaptedData, "cost")
-            );
-
-            return true;
-
-        } catch (Exception e) {
-            log.error("处理回调数据失败", e);
-            throw new BusinessException("处理回调数据失败:" + e.getMessage());
+    public void saveLeadAndTriggerConversion(Lead lead) {
+        // 1. 保存线索到数据库
+        boolean saved = leadService.save(lead);
+        if (!saved) {
+            log.error("线索保存失败 | phone={}", lead.getPhone());
+            throw new RuntimeException("线索保存失败");
         }
+        // 2. 如果有点击ID和来源,触发转化事件
+        // 发布转化事件(会自动发送到MQ)
+        conversionEventPublisher.publishConversionEvent(
+                lead.getSiteId(),           // 站点ID
+                lead.getTraceId(),          // 点击ID
+                lead.getSource().toUpperCase(), // 广告商(BAIDU/OCEANENGINE/SINA/GDT)
+                ConversionTypeEnum.FORM_SUBMIT,              // 事件类型:提交表单
+                null,                       // 转化价值(可选)
+                lead.getId()                // 线索ID
+        );
     }
 
 
-    public void updateSiteStatistics(Long siteId, Long impressionCount, Long clickCount, Double cost) {
-        log.info("更新站点 {} 统计数据:展示={}, 点击={}, 花费={}", siteId, impressionCount, clickCount, cost);
-
-        if (siteId == null) {
-            return;
-        }
-
-        // 更新展示数
-        if (impressionCount != null && impressionCount > 0) {
-            statisticsMapper.incrementImpressionCount(siteId, impressionCount);
-        }
 
-        // 更新点击数
-        if (clickCount != null && clickCount > 0) {
-            statisticsMapper.incrementClickCount(siteId, clickCount);
-        }
 
-        // TODO: 更新花费等其他指标
-    }
 
     /**
      * 转换为String Map

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

@@ -1,22 +1,24 @@
 package com.fs.app.facade;
 
 import cn.hutool.core.util.StrUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.OceanEngineApiClient;
 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.CallbackAccount;
 import com.fs.newAdv.domain.ConversionLog;
-import com.fs.newAdv.domain.ConversionTarget;
 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.mapper.PromotionAccountMapper;
-import com.fs.newAdv.mapper.SiteMapper;
+import com.fs.newAdv.service.ICallbackAccountService;
 import com.fs.newAdv.service.IClickTraceService;
+import com.fs.newAdv.service.ISiteService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
@@ -37,10 +39,10 @@ import java.util.Map;
 public class ConversionServiceImpl implements IConversionService {
 
     @Autowired
-    private SiteMapper siteMapper;
-
+    private ISiteService siteService;
     @Autowired
-    private PromotionAccountMapper promotionAccountMapper;
+    private ICallbackAccountService callbackAccountService;
+
 
     @Autowired
     private ConversionLogMapper conversionLogMapper;
@@ -62,135 +64,95 @@ public class ConversionServiceImpl implements IConversionService {
 
     @Override
     @Async("asyncExecutor")
-    public boolean reportToOceanEngine(Long siteId, String eventType, String clickId, Double value) {
-        log.info("开始回传转化数据到巨量引擎:站点ID={},点击ID={},事件类型={}",
-                siteId, clickId, eventType);
-
-        try {
-            // 1. 校验clickId
-            if (StrUtil.isBlank(clickId)) {
-                log.error("点击ID为空,无法回传");
-                return false;
-            }
-
-            // 2. 查询站点信息
-            Site site = siteMapper.selectById(siteId);
-            if (site == null) {
-                throw new BusinessException("站点不存在");
-            }
-
-            // 3. 查询推广账号信息
-            PromotionAccount account = promotionAccountMapper.selectById(site.getPromotionAccountId());
-            if (account == null) {
-                throw new BusinessException("推广账号不存在");
-            }
-
-            // 3. 验证必要参数
-            if (StrUtil.isBlank(clickId)) {
-                throw new BusinessException("点击ID不能为空");
-            }
-
-            // 4. 获取AccessToken
-            String accessToken = getAccessToken(account);
-
-            // 5. 构建转化数据
-            Map<String, Object> conversionData = buildOceanEngineConversionData(
-                    clickId, eventType, value);
-
-            // 6. 保存转化日志(待回传状态)
-            ConversionLog conversionLog = saveConversionLog(
-                    siteId, site.getAdvertiserId(), site.getAdvertiserName(),
-                    eventType, clickId, conversionData, 0);
-
-            // 7. 调用巨量引擎API回传
-            boolean success = oceanEngineApiClient.reportConversion(accessToken, conversionData);
-
-            // 8. 更新转化日志状态
-            if (success) {
-                conversionLog.setCallbackStatus(1); // 成功
-                conversionLog.setSuccessTime(LocalDateTime.now());
-                log.info("巨量引擎转化回传成功,日志ID:{}", conversionLog.getId());
-            } else {
-                conversionLog.setCallbackStatus(2); // 失败
-                conversionLog.setErrorMsg("回传失败");
-                log.error("巨量引擎转化回传失败,日志ID:{}", conversionLog.getId());
-            }
-            conversionLog.setUpdateTime(LocalDateTime.now());
-            conversionLogMapper.updateById(conversionLog);
-
-            return success;
-
-        } catch (Exception e) {
-            log.error("回传转化数据到巨量引擎失败", e);
-            return false;
+    public boolean reportToOceanEngine(Long siteId, ConversionTypeEnum eventType, String traceId, Double value) {
+  /*      log.info("开始回传转化数据到巨量引擎:站点ID={},点击ID={},事件类型={}",
+                siteId, traceId, eventType);
+
+        // 查询站点信息
+        Site site = siteService.getById(siteId);
+        if (site == null) {
+            throw new BusinessException("站点不存在");
+        }
+
+        // 查询回传账号
+        CallbackAccount callbackAccount = callbackAccountService.getById(site.getCallbackAccountId());
+        if (callbackAccount == null) {
+            throw new BusinessException("回传账号不存在");
         }
+
+        // 4. 获取AccessToken
+        String accessToken = callbackAccount.get;
+
+        // 5. 构建转化数据
+        Map<String, Object> conversionData = buildOceanEngineConversionData(
+                clickId, eventType, value);
+
+        // 6. 保存转化日志(待回传状态)
+        ConversionLog conversionLog = saveConversionLog(
+                siteId, site.getAdvertiserId(), site.getAdvertiserName(),
+                eventType, clickId, conversionData, 0);
+
+        // 7. 调用巨量引擎API回传
+        boolean success = oceanEngineApiClient.reportConversion(accessToken, conversionData);
+
+        // 8. 更新转化日志状态
+        if (success) {
+            conversionLog.setCallbackStatus(1); // 成功
+            conversionLog.setSuccessTime(LocalDateTime.now());
+            log.info("巨量引擎转化回传成功,日志ID:{}", conversionLog.getId());
+        } else {
+            conversionLog.setCallbackStatus(2); // 失败
+            conversionLog.setErrorMsg("回传失败");
+            log.error("巨量引擎转化回传失败,日志ID:{}", conversionLog.getId());
+        }
+        conversionLog.setUpdateTime(LocalDateTime.now());
+        conversionLogMapper.updateById(conversionLog);*/
+
+        return false;
     }
 
     @Override
     @Async("asyncExecutor")
-    public boolean reportToBaidu(Long siteId, String eventType, String clickId, Double value) {
+    public boolean reportToBaidu(Long siteId, ConversionTypeEnum eventType, String traceId, Double value) {
         log.info("开始回传转化数据到百度:站点ID={},点击ID={},事件类型={}",
-                siteId, clickId, eventType);
-
-        try {
-            // 1. 校验clickId
-            if (StrUtil.isBlank(clickId)) {
-                log.error("点击ID为空,无法回传");
-                return false;
-            }
-
-            // 2. 查询站点信息
-            Site site = siteMapper.selectById(siteId);
-            if (site == null) {
-                log.error("站点不存在:{}", siteId);
-                return false;
-            }
-
-            // 3. 查询推广账号
-            PromotionAccount account = promotionAccountMapper.selectById(site.getPromotionAccountId());
-            if (account == null) {
-                log.error("推广账号不存在:{}", site.getPromotionAccountId());
-                return false;
-            }
-
-            // 4. 查询转化目标配置
-            LambdaQueryWrapper<ConversionTarget> targetWrapper = new LambdaQueryWrapper<>();
-            targetWrapper.eq(ConversionTarget::getSiteId, siteId);
-            targetWrapper.eq(ConversionTarget::getCallbackEvent, eventType);
-            ConversionTarget target = conversionTargetMapper.selectOne(targetWrapper);
-
-            if (target == null) {
-                log.warn("未找到转化目标配置:站点ID={},事件类型={}", siteId, eventType);
-                return false;
-            }
-
-            // 4. 获取AccessToken
-            String accessToken = getBaiduAccessToken(account);
-
-            // 5. 构建回传参数
-            Map<String, Object> conversionData = new HashMap<>();
-            conversionData.put("accessToken", accessToken);
-            conversionData.put("logId", clickId); // 百度使用logId
-            conversionData.put("conversionType", target.getConversionType());
-            conversionData.put("conversionTime", System.currentTimeMillis() / 1000);
-
-            if (value != null && value > 0) {
-                conversionData.put("value", value);
-            }
-
-            // 6. 调用百度API回传
-            boolean success = baiduApiClient.reportConversion(accessToken, conversionData);
-
-            if (success) {
-                log.info("百度转化回传成功:站点ID={},点击ID={}", siteId, clickId);
-            }
-
-            return success;
-
-        } catch (Exception e) {
-            log.error("百度转化回传失败", e);
+                siteId, traceId, eventType);
+        // 询站点信息
+        Site site = siteService.getById(siteId);
+        if (site == null) {
+            log.error("站点不存在:{}", siteId);
+            return false;
+        }
+
+        // 查询回传账号
+        CallbackAccount callbackAccount = callbackAccountService.getById(site.getCallbackAccountId());
+        if (callbackAccount == null) {
+            log.error("回传账号不存在:{}", site.getPromotionAccountId());
             return false;
         }
+
+        // 获取AccessToken
+        String accessToken = callbackAccount.getOcpcToken();
+
+
+        // 构建回传参数
+        Map<String, Object> conversionData = new HashMap<>();
+        conversionData.put("token", accessToken);
+        conversionData.put("traceId", traceId); // 百度使用logId
+        conversionData.put("newType", eventType.getAdvertiserType(AdvertiserTypeEnum.BAIDU));
+        conversionData.put("conversionTime", System.currentTimeMillis() / 1000);
+
+        if (value != null && value > 0) {
+            conversionData.put("convertValue", value);
+        }
+
+        // 调用百度API回传
+        boolean b = baiduApiClient.reportConversion(conversionData);
+
+        // 保存转化日志(待回传状态)
+        ConversionLog conversionLog = saveConversionLog(
+                siteId, site.getAdvertiserId(), site.getAdvertiserName(),
+                eventType, traceId, conversionData, b);
+        return b;
     }
 
     /**
@@ -208,111 +170,43 @@ public class ConversionServiceImpl implements IConversionService {
         return oceanEngineApiClient.getAccessToken(appId, appSecret);
     }
 
-    /**
-     * 获取百度AccessToken
-     */
-    private String getBaiduAccessToken(PromotionAccount account) {
-        String apiKey = account.getAppId(); // 百度使用API Key
-        String secretKey = account.getAppSecret(); // 百度使用Secret Key
-
-        if (StrUtil.isBlank(apiKey) || StrUtil.isBlank(secretKey)) {
-            throw new BusinessException("推广账号未配置API Key或Secret Key");
-        }
-
-        // 优先从缓存获取
-        String cacheKey = RedisKeyConstant.BAIDU_ACCESS_TOKEN_PREFIX + account.getId();
-        Object cachedObj = redisUtil.get(cacheKey);
-        String cachedToken = cachedObj != null ? cachedObj.toString() : null;
-
-        if (StrUtil.isNotBlank(cachedToken)) {
-            return cachedToken;
-        }
-
-        // 调用API获取Token
-        String accessToken = baiduApiClient.getAccessToken(apiKey, secretKey);
-
-        // 缓存Token(百度Token有效期为30天,缓存25天)
-        redisUtil.set(cacheKey, accessToken, 25 * 24 * 60 * 60L, java.util.concurrent.TimeUnit.SECONDS);
-
-        return accessToken;
-    }
-
     /**
      * 构建巨量引擎转化数据
      */
     private Map<String, Object> buildOceanEngineConversionData(
             String clickId, String eventType, Double value) {
 
-        Map<String, Object> data = new HashMap<>();
+  /*      Map<String, Object> data = new HashMap<>();
         data.put("clickId", clickId);
         data.put("eventType", mapEventType(eventType));
 
         if (value != null && value > 0) {
             data.put("value", value);
-        }
-
-        return data;
-    }
-
-    /**
-     * 映射事件类型
-     * 将系统内部的事件类型映射为巨量引擎的事件类型
-     */
-    private String mapEventType(String eventType) {
-        // 巨量引擎常用事件类型:
-        // FORM_SUBMIT - 表单提交
-        // CONSULT - 在线咨询
-        // PHONE - 电话咨询
-        // DOWNLOAD - 下载
-        // REGISTER - 注册
-        // PAY - 付费
-
-        if (StrUtil.isBlank(eventType)) {
-            return "FORM_SUBMIT";
-        }
+        }*/
 
-        switch (eventType.toUpperCase()) {
-            case "REGISTER":
-            case "USER_REGISTER":
-                return "REGISTER";
-            case "ADD_WECHAT":
-            case "WECHAT_ADD":
-                return "CONSULT";
-            case "PHONE_CALL":
-                return "PHONE";
-            case "FORM_SUBMIT":
-                return "FORM_SUBMIT";
-            case "DOWNLOAD":
-                return "DOWNLOAD";
-            case "PAY":
-            case "ORDER_PAY":
-                return "PAY";
-            default:
-                return "FORM_SUBMIT";
-        }
+        return null;
     }
 
     /**
      * 保存转化日志
      */
     private ConversionLog saveConversionLog(Long siteId, Long advertiserId,
-                                           String advertiserName, String eventType,
-                                           String clickId,
-                                           Map<String, Object> conversionData,
-                                           Integer status) {
+                                            String advertiserName, ConversionTypeEnum eventType,
+                                            String clickId,
+                                            Map<String, Object> conversionData,
+                                            boolean status) {
         ConversionLog log = new ConversionLog();
         log.setId(SnowflakeUtil.nextId());
         log.setSiteId(siteId);
         log.setAdvertiserId(advertiserId);
         log.setAdvertiserName(advertiserName);
-        log.setConversionType(eventType);
-        log.setConversionEvent(mapEventType(eventType));
-        log.setCallbackParams(cn.hutool.json.JSONUtil.toJsonStr(conversionData));
-        log.setCallbackStatus(status);
+        log.setConversionType(eventType.getCode());
+        log.setConversionEvent(eventType.getDesc());
+        log.setCallbackParams(JSONUtil.toJsonStr(conversionData));
+        log.setCallbackStatus(status? 1 : 2);
         log.setRetryCount(0);
-        log.setClickId(clickId);  // 使用clickId而不是userId
+        log.setTraceId(clickId);
         log.setCreateTime(LocalDateTime.now());
-
         conversionLogMapper.insert(log);
         return log;
     }

+ 6 - 4
fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java

@@ -1,5 +1,7 @@
 package com.fs.app.facade;
 
+import com.fs.app.enums.ConversionTypeEnum;
+
 /**
  * 转化回传服务接口
  *
@@ -14,21 +16,21 @@ public interface IConversionService {
      *
      * @param siteId 站点ID
      * @param eventType 事件类型
-     * @param clickId 点击ID(广告平台提供)
+     * @param traceId 点击ID(广告平台提供)
      * @param value 转化价值
      * @return 是否成功
      */
-    boolean reportToOceanEngine(Long siteId, String eventType, String clickId, Double value);
+    boolean reportToOceanEngine(Long siteId, ConversionTypeEnum eventType, String traceId, Double value);
 
     /**
      * 回传转化数据到百度
      *
      * @param siteId 站点ID
      * @param eventType 事件类型
-     * @param clickId 点击ID(广告平台提供)
+     * @param traceId 点击ID(广告平台提供)
      * @param value 转化价值
      * @return 是否成功
      */
-    boolean reportToBaidu(Long siteId, String eventType, String clickId, Double value);
+    boolean reportToBaidu(Long siteId, ConversionTypeEnum eventType, String traceId, Double value);
 }
 

+ 2 - 2
fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/BaiduAdapter.java

@@ -52,8 +52,8 @@ public class BaiduAdapter implements IAdvertiserAdapter {
     }
 
     @Override
-    public Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.BAIDU.getCode();
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.BAIDU;
     }
 }
 

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

@@ -1,5 +1,7 @@
 package com.fs.app.integration.adapter;
 
+import com.fs.app.enums.AdvertiserTypeEnum;
+
 import java.util.Map;
 
 /**
@@ -31,6 +33,6 @@ public interface IAdvertiserAdapter {
     /**
      * 获取广告商类型
      */
-    Integer getAdvertiserType();
+    AdvertiserTypeEnum getAdvertiserType();
 }
 

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

@@ -51,8 +51,8 @@ public class OceanEngineAdapter implements IAdvertiserAdapter {
     }
 
     @Override
-    public Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.OCEAN_ENGINE.getCode();
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.OCEAN_ENGINE;
     }
 }
 

+ 32 - 42
fs-ad-new-api/src/main/java/com/fs/app/integration/client/BaiduApiClient.java

@@ -5,6 +5,7 @@ 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.common.utils.SnowflakeUtil;
@@ -30,17 +31,13 @@ import java.util.Map;
  */
 @Slf4j
 @Component
-public class BaiduApiClient {
+public class BaiduApiClient implements IApiClient{
 
     /**
      * 百度OCPC转化回传API地址
      */
     private static final String CONVERSION_API_URL = "https://ocpc.baidu.com/ocpcapi/api/uploadConvertData";
 
-    /**
-     * 百度OAuth Token地址
-     */
-    private static final String TOKEN_API_URL = "https://openapi.baidu.com/oauth/2.0/token";
 
     @Autowired
     private ApiCallLogMapper apiCallLogMapper;
@@ -48,15 +45,15 @@ public class BaiduApiClient {
     /**
      * 回传转化数据到百度
      *
-     * @param accessToken 访问令牌
      * @param conversionData 转化数据
      * @return 是否成功
      */
-    public boolean reportConversion(String accessToken, Map<String, Object> conversionData) {
+    @Override
+    public boolean reportConversion(Map<String, Object> conversionData) {
         String apiUrl = CONVERSION_API_URL;
         long startTime = System.currentTimeMillis();
 
-        log.info("开始回传转化数据到百度,URL:{}", apiUrl);
+        log.info("开始回传转化数据到百度,URL:{}", CONVERSION_API_URL);
 
         try {
             // 构建请求参数
@@ -64,7 +61,7 @@ public class BaiduApiClient {
             String requestBody = JSONUtil.toJsonStr(requestParams);
 
             // 发送HTTP请求
-            HttpResponse response = HttpRequest.post(apiUrl)
+            HttpResponse response = HttpRequest.post(CONVERSION_API_URL)
                     .header("Content-Type", "application/json")
                     .body(requestBody)
                     .timeout(SystemConstant.API_TIMEOUT)
@@ -72,7 +69,6 @@ public class BaiduApiClient {
 
             String responseBody = response.body();
             int statusCode = response.getStatus();
-
             log.info("百度API响应:状态码={},响应={}", statusCode, responseBody);
 
             // 记录API调用日志
@@ -82,27 +78,16 @@ public class BaiduApiClient {
             // 解析响应
             if (statusCode == 200) {
                 JSONObject result = JSONUtil.parseObj(responseBody);
-
-                // 百度API成功的标识
-                if (result.containsKey("success") && result.getBool("success")) {
-                    log.info("百度转化回传成功");
-                    return true;
-                } else {
-                    String message = result.getStr("message", "未知错误");
-                    log.error("百度转化回传失败:{}", message);
-                    throw new ThirdPartyException("百度回传失败:" + message);
-                }
-            } else {
-                log.error("百度API调用失败,状态码:{}", statusCode);
-                throw new ThirdPartyException("百度API调用失败,状态码:" + statusCode);
+                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;
     }
 
     /**
@@ -115,9 +100,9 @@ public class BaiduApiClient {
         Map<String, Object> params = new HashMap<>();
 
         // 必填参数
-        String token = (String) conversionData.get("accessToken");
+        String token = (String) conversionData.get("token");
         if (StrUtil.isBlank(token)) {
-            throw new ThirdPartyException("AccessToken不能为空");
+            throw new ThirdPartyException("token不能为空");
         }
         params.put("token", token);
 
@@ -126,14 +111,14 @@ public class BaiduApiClient {
         Map<String, Object> conversion = new HashMap<>();
 
         // 点击ID(必填)
-        String logId = (String) conversionData.get("logId");
-        if (StrUtil.isBlank(logId)) {
-            throw new ThirdPartyException("点击ID不能为空");
+        String logidUrl = (String) conversionData.get("logidUrl");
+        if (StrUtil.isBlank(logidUrl)) {
+            throw new ThirdPartyException("落地页");
         }
-        conversion.put("logidUrl", logId);
+        conversion.put("logidUrl", logidUrl);
 
         // 转化类型(必填)
-        Integer conversionType = (Integer) conversionData.get("conversionType");
+        Integer conversionType = (Integer) conversionData.get("newType");
         if (conversionType == null) {
             conversionType = 1; // 默认为1(激活)
         }
@@ -147,8 +132,8 @@ public class BaiduApiClient {
         conversion.put("convertTime", conversionTime);
 
         // 转化价值(可选)
-        if (conversionData.containsKey("value")) {
-            conversion.put("value", conversionData.get("value"));
+        if (conversionData.containsKey("convertValue")) {
+            conversion.put("convertValue", conversionData.get("convertValue"));
         }
 
         conversionList.add(conversion);
@@ -157,13 +142,13 @@ public class BaiduApiClient {
         return params;
     }
 
-    /**
+  /*  *//**
      * 获取访问令牌(百度OAuth2.0)
      *
-     * @param apiKey API Key
+     * @param apiKey    API Key
      * @param secretKey Secret Key
      * @return 访问令牌
-     */
+     *//*
     public String getAccessToken(String apiKey, String secretKey) {
         log.info("开始获取百度AccessToken");
 
@@ -193,16 +178,16 @@ public class BaiduApiClient {
             log.error("获取百度AccessToken异常", e);
             throw new ThirdPartyException("获取AccessToken异常:" + e.getMessage(), e);
         }
-    }
+    }*/
 
-    /**
+/*    *//**
      * 查询账户报告数据
      *
      * @param accessToken 访问令牌
-     * @param startDate 开始日期(yyyyMMdd)
-     * @param endDate 结束日期(yyyyMMdd)
+     * @param startDate   开始日期(yyyyMMdd)
+     * @param endDate     结束日期(yyyyMMdd)
      * @return 报告数据
-     */
+     *//*
     public Map<String, Object> getAccountReport(String accessToken, String startDate, String endDate) {
         log.info("查询百度账户报告:{}到{}", startDate, endDate);
 
@@ -247,7 +232,7 @@ public class BaiduApiClient {
             log.error("查询百度账户报告异常", e);
             throw new ThirdPartyException("查询报告异常:" + e.getMessage(), e);
         }
-    }
+    }*/
 
     /**
      * 保存API调用日志
@@ -276,5 +261,10 @@ public class BaiduApiClient {
             log.error("保存API调用日志失败", e);
         }
     }
+
+    @Override
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.BAIDU;
+    }
 }
 

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

@@ -0,0 +1,20 @@
+package com.fs.app.integration.client;
+
+import com.fs.app.enums.AdvertiserTypeEnum;
+
+import java.util.Map;
+
+/**
+ * 回调处理策略接口(策略模式)
+ * 定义不同广告平台的回调处理策略
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+public interface IApiClient {
+
+    boolean reportConversion(Map<String, Object> conversionData);
+
+    AdvertiserTypeEnum getAdvertiserType();
+}
+

+ 30 - 4
fs-ad-new-api/src/main/java/com/fs/app/integration/factory/AdvertiserHandlerFactory.java

@@ -1,7 +1,9 @@
 package com.fs.app.integration.factory;
 
 
+import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.app.integration.adapter.IAdvertiserAdapter;
+import com.fs.app.integration.client.IApiClient;
 import com.fs.app.integration.strategy.ICallbackStrategy;
 import com.fs.common.exception.base.BusinessException;
 import lombok.extern.slf4j.Slf4j;
@@ -29,16 +31,20 @@ public class AdvertiserHandlerFactory {
 
     @Autowired
     private List<ICallbackStrategy> callbackStrategies;
+    @Autowired
+    private List<IApiClient> apiClients;
 
     /**
      * 适配器缓存
      */
-    private final Map<Integer, IAdvertiserAdapter> adapterMap = new ConcurrentHashMap<>();
+    private final Map<AdvertiserTypeEnum, IAdvertiserAdapter> adapterMap = new ConcurrentHashMap<>();
 
     /**
      * 策略缓存
      */
-    private final Map<Integer, ICallbackStrategy> strategyMap = new ConcurrentHashMap<>();
+    private final Map<AdvertiserTypeEnum, ICallbackStrategy> strategyMap = new ConcurrentHashMap<>();
+
+    private final Map<AdvertiserTypeEnum, IApiClient> apiClientMap = new ConcurrentHashMap<>();
 
     /**
      * 初始化工厂
@@ -55,6 +61,11 @@ public class AdvertiserHandlerFactory {
             strategyMap.put(strategy.getAdvertiserType(), strategy);
         }
 
+        // 初始化策略Map
+        for (IApiClient apiClient : apiClients) {
+            apiClientMap.put(apiClient.getAdvertiserType(), apiClient);
+        }
+
         log.info("广告商处理器工厂初始化完成,适配器数量:{},策略数量:{}",
                 adapterMap.size(), strategyMap.size());
     }
@@ -65,7 +76,7 @@ public class AdvertiserHandlerFactory {
      * @param advertiserType 广告商类型
      * @return 适配器
      */
-    public IAdvertiserAdapter getAdapter(Integer advertiserType) {
+    public IAdvertiserAdapter getAdapter(AdvertiserTypeEnum advertiserType) {
         IAdvertiserAdapter adapter = adapterMap.get(advertiserType);
         if (adapter == null) {
             throw new BusinessException("不支持的广告商类型:" + advertiserType);
@@ -79,12 +90,27 @@ public class AdvertiserHandlerFactory {
      * @param advertiserType 广告商类型
      * @return 回调策略
      */
-    public ICallbackStrategy getStrategy(Integer advertiserType) {
+    public ICallbackStrategy getStrategy(AdvertiserTypeEnum advertiserType) {
         ICallbackStrategy strategy = strategyMap.get(advertiserType);
         if (strategy == null) {
             throw new BusinessException("不支持的广告商类型:" + advertiserType);
         }
         return strategy;
     }
+
+
+    /**
+     * 获取回调策略
+     *
+     * @param advertiserType 广告商类型
+     * @return 回调策略
+     */
+    public IApiClient getApiClient(AdvertiserTypeEnum advertiserType) {
+        IApiClient apiClient = apiClientMap.get(advertiserType);
+        if (apiClient == null) {
+            throw new BusinessException("不支持的广告商类型:" + advertiserType);
+        }
+        return apiClient;
+    }
 }
 

+ 0 - 104
fs-ad-new-api/src/main/java/com/fs/app/integration/handler/AbstractCallbackHandler.java

@@ -1,104 +0,0 @@
-package com.fs.app.integration.handler;
-
-import cn.hutool.json.JSONUtil;
-
-import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.CallbackLog;
-import com.fs.newAdv.mapper.CallbackLogMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import java.time.LocalDateTime;
-import java.util.Map;
-
-/**
- * 回调处理抽象类(模板方法模式)
- * 定义回调处理的统一流程
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-public abstract class AbstractCallbackHandler {
-
-    @Autowired
-    private CallbackLogMapper callbackLogMapper;
-
-    /**
-     * 模板方法:处理回调的统一流程
-     *
-     * @param rawData 原始数据
-     * @return 是否处理成功
-     */
-    public boolean process(Map<String, Object> rawData) {
-        long startTime = System.currentTimeMillis();
-        CallbackLog callbackLog = new CallbackLog();
-
-        try {
-            // 1. 数据验证
-            log.info("步骤1:数据验证");
-            boolean valid = validate(rawData);
-            if (!valid) {
-                log.error("数据验证失败:{}", rawData);
-                saveLog(callbackLog, rawData, 2, "数据验证失败", startTime);
-                return false;
-            }
-
-            // 2. 数据转换
-            log.info("步骤2:数据转换");
-            Map<String, Object> convertedData = convert(rawData);
-
-            // 3. 业务处理
-            log.info("步骤3:业务处理");
-            handle(convertedData);
-
-            // 4. 保存日志
-            log.info("步骤4:保存成功日志");
-            saveLog(callbackLog, rawData, 1, null, startTime);
-
-            return true;
-
-        } catch (Exception e) {
-            log.error("回调处理异常:{}", e.getMessage(), e);
-            saveLog(callbackLog, rawData, 2, e.getMessage(), startTime);
-            return false;
-        }
-    }
-
-    /**
-     * 数据验证(子类实现)
-     */
-    protected abstract boolean validate(Map<String, Object> rawData);
-
-    /**
-     * 数据转换(子类实现)
-     */
-    protected abstract Map<String, Object> convert(Map<String, Object> rawData);
-
-    /**
-     * 业务处理(子类实现)
-     */
-    protected abstract void handle(Map<String, Object> convertedData);
-
-    /**
-     * 获取广告商类型(子类实现)
-     */
-    protected abstract Integer getAdvertiserType();
-
-    /**
-     * 保存回调日志
-     */
-    private void saveLog(CallbackLog log, Map<String, Object> rawData, Integer status, String errorMsg, long startTime) {
-        log.setId(SnowflakeUtil.nextId());
-        log.setAdvertiserId(getAdvertiserType().longValue());
-        log.setCallbackType("数据回调");
-        log.setRequestParams(JSONUtil.toJsonStr(rawData));
-        log.setProcessStatus(status);
-        log.setErrorMsg(errorMsg);
-        log.setProcessTime(System.currentTimeMillis() - startTime);
-        log.setCreateTime(LocalDateTime.now());
-
-        callbackLogMapper.insert(log);
-    }
-}
-

+ 0 - 209
fs-ad-new-api/src/main/java/com/fs/app/integration/handler/BaiduCallbackHandler.java

@@ -1,209 +0,0 @@
-/*
-package com.fs.app.integration.handler;
-
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
-
-import com.fs.app.enums.AdvertiserTypeEnum;
-import com.fs.app.integration.adapter.BaiduAdapter;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-
-*/
-/**
- * 百度回调处理器
- * 基于模板方法模式,实现百度特定的回调处理逻辑
- *
- * @author zhangqin
- * @date 2025-11-03
- * @deprecated 该Handler已废弃!
- *
- * 原设计:处理广告平台的回调数据
- * 新设计:广告平台通过URL参数传递数据,由LandingPageController接收并保存到adv_click_trace表
- *
- * 请使用ClickTraceService代替!
- * @see com.crm.adv.service.IClickTraceService
- *//*
-
-@Slf4j
-@Component
-public class BaiduCallbackHandler extends AbstractCallbackHandler {
-
-    @Autowired
-    private BaiduAdapter baiduAdapter;
-
-    @Override
-    protected boolean validate(Map<String, Object> rawData) {
-        log.info("百度回调数据验证");
-
-        // 验证必填字段
-        if (MapUtil.isEmpty(rawData)) {
-            log.error("回调数据为空");
-            return false;
-        }
-
-        // 验证点击ID(百度使用clk_id或logid字段)
-        String clkId = MapUtil.getStr(rawData, "clk_id");
-        String logId = MapUtil.getStr(rawData, "logid");
-
-        if (StrUtil.isBlank(clkId) && StrUtil.isBlank(logId)) {
-            log.error("缺少点击ID(clk_id或logid)");
-            return false;
-        }
-
-        // 验证站点ID
-        if (!rawData.containsKey("site_id")) {
-            log.error("缺少站点ID");
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    protected Map<String, Object> convert(Map<String, Object> rawData) {
-        log.info("百度回调数据转换");
-
-        // 使用适配器转换数据格式
-        Map<String, Object> convertedData = baiduAdapter.adaptCallbackData(rawData);
-
-        // 补充百度特有字段
-        convertedData.put("platform", "baidu");
-        convertedData.put("platformName", "百度");
-
-        // 处理点击ID(百度可能使用clk_id或logid)
-        String clkId = MapUtil.getStr(rawData, "clk_id");
-        String logId = MapUtil.getStr(rawData, "logid");
-        String clickId = StrUtil.isNotBlank(clkId) ? clkId : logId;
-
-        convertedData.put("clickId", clickId);
-        convertedData.put("originalClickId", clickId);
-        convertedData.put("logId", clickId); // 百度回传需要logId
-
-        // 处理回调类型
-        String callbackType = MapUtil.getStr(rawData, "callback_type", "click");
-        convertedData.put("callbackType", callbackType);
-
-        // 处理时间戳
-        Long timestamp = MapUtil.getLong(rawData, "timestamp");
-        if (timestamp != null) {
-            convertedData.put("timestamp", timestamp);
-        }
-
-        // 处理计划ID
-        String planId = MapUtil.getStr(rawData, "plan_id");
-        if (StrUtil.isNotBlank(planId)) {
-            convertedData.put("planId", planId);
-        }
-
-        // 处理单元ID
-        String unitId = MapUtil.getStr(rawData, "unit_id");
-        if (StrUtil.isNotBlank(unitId)) {
-            convertedData.put("unitId", unitId);
-        }
-
-        log.info("百度数据转换完成:{}", convertedData);
-        return convertedData;
-    }
-
-    @Override
-    protected void handle(Map<String, Object> convertedData) {
-        log.info("百度回调业务处理");
-
-        Long siteId = MapUtil.getLong(convertedData, "siteId");
-        String callbackType = MapUtil.getStr(convertedData, "callbackType");
-
-        // 根据回调类型处理不同的业务逻辑
-        switch (callbackType) {
-            case "impression":
-            case "show":
-                // 处理展示回调
-                handleShowCallback(convertedData);
-                break;
-            case "click":
-                // 处理点击回调
-                handleClickCallback(convertedData);
-                break;
-            case "convert":
-            case "conversion":
-                // 处理转化回调
-                handleConversionCallback(convertedData);
-                break;
-            default:
-                // 默认处理
-                handleDefaultCallback(convertedData);
-                break;
-        }
-
-        log.info("百度回调处理完成,站点ID:{}", siteId);
-    }
-
-    */
-/**
-     * 处理展示回调
-     *//*
-
-    private void handleShowCallback(Map<String, Object> data) {
-        log.info("处理百度展示回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long impressionCount = MapUtil.getLong(data, "impressionCount", 1L);
-
-        // TODO: 更新站点展示数统计
-        log.info("站点 {} 展示数 +{}", siteId, impressionCount);
-    }
-
-    */
-/**
-     * 处理点击回调
-     *//*
-
-    private void handleClickCallback(Map<String, Object> data) {
-        log.info("处理百度点击回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long clickCount = MapUtil.getLong(data, "clickCount", 1L);
-        Double cost = MapUtil.getDouble(data, "cost", 0.0);
-
-        // TODO: 更新站点点击数和花费统计
-        log.info("站点 {} 点击数 +{},花费 +{}", siteId, clickCount, cost);
-    }
-
-    */
-/**
-     * 处理转化回调
-     *//*
-
-    private void handleConversionCallback(Map<String, Object> data) {
-        log.info("处理百度转化回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        String logId = MapUtil.getStr(data, "logId");
-
-        // TODO: 记录转化数据
-        log.info("站点 {} 转化,logId:{}", siteId, logId);
-    }
-
-    */
-/**
-     * 处理默认回调
-     *//*
-
-    private void handleDefaultCallback(Map<String, Object> data) {
-        log.info("处理百度默认回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long impressionCount = MapUtil.getLong(data, "impressionCount");
-        Long clickCount = MapUtil.getLong(data, "clickCount");
-        Double cost = MapUtil.getDouble(data, "cost");
-
-        // 统一更新统计数据
-        log.info("站点 {} 数据更新:展示={},点击={},花费={}",
-                siteId, impressionCount, clickCount, cost);
-    }
-
-    @Override
-    protected Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.BAIDU.getCode();
-    }
-}
-*/

+ 0 - 203
fs-ad-new-api/src/main/java/com/fs/app/integration/handler/OceanEngineCallbackHandler.java

@@ -1,203 +0,0 @@
-/*
-package com.fs.app.integration.handler;
-
-import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.StrUtil;
-import com.crm.adv.common.enums.AdvertiserTypeEnum;
-import com.crm.adv.integration.adapter.OceanEngineAdapter;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-
-*/
-/**
- * 巨量引擎回调处理器
- * 基于模板方法模式,实现巨量引擎特定的回调处理逻辑
- *
- * @author zhangqin
- * @date 2025-11-03
- * @deprecated 该Handler已废弃!
- *
- * 原设计:处理广告平台的回调数据
- * 新设计:广告平台通过URL参数传递数据,由LandingPageController接收并保存到adv_click_trace表
- *
- * 请使用ClickTraceService代替!
- * @see com.crm.adv.service.IClickTraceService
- *//*
-
-@Deprecated
-@Slf4j
-@Component
-public class OceanEngineCallbackHandler extends AbstractCallbackHandler {
-
-    @Autowired
-    private OceanEngineAdapter oceanEngineAdapter;
-
-    @Override
-    protected boolean validate(Map<String, Object> rawData) {
-        log.info("巨量引擎回调数据验证");
-
-        // 验证必填字段
-        if (MapUtil.isEmpty(rawData)) {
-            log.error("回调数据为空");
-            return false;
-        }
-
-        // 验证点击ID(巨量引擎使用clickid字段)
-        String clickId = MapUtil.getStr(rawData, "clickid");
-        if (StrUtil.isBlank(clickId)) {
-            log.error("缺少点击ID(clickid)");
-            return false;
-        }
-
-        // 验证站点ID
-        if (!rawData.containsKey("site_id")) {
-            log.error("缺少站点ID");
-            return false;
-        }
-
-        // 验证回调类型
-        String callbackType = MapUtil.getStr(rawData, "callback_type");
-        if (StrUtil.isBlank(callbackType)) {
-            log.warn("未指定回调类型,使用默认类型");
-        }
-
-        return true;
-    }
-
-    @Override
-    protected Map<String, Object> convert(Map<String, Object> rawData) {
-        log.info("巨量引擎回调数据转换");
-
-        // 使用适配器转换数据格式
-        Map<String, Object> convertedData = oceanEngineAdapter.adaptCallbackData(rawData);
-
-        // 补充巨量引擎特有字段
-        convertedData.put("platform", "oceanengine");
-        convertedData.put("platformName", "巨量引擎");
-
-        // 处理点击ID
-        String clickId = MapUtil.getStr(rawData, "clickid");
-        convertedData.put("clickId", clickId);
-        convertedData.put("originalClickId", clickId);
-
-        // 处理回调类型
-        String callbackType = MapUtil.getStr(rawData, "callback_type", "data");
-        convertedData.put("callbackType", callbackType);
-
-        // 处理时间戳
-        Long timestamp = MapUtil.getLong(rawData, "timestamp");
-        if (timestamp != null) {
-            convertedData.put("timestamp", timestamp);
-        }
-
-        // 处理转化类型
-        String convertType = MapUtil.getStr(rawData, "convert_type");
-        if (StrUtil.isNotBlank(convertType)) {
-            convertedData.put("convertType", convertType);
-        }
-
-        log.info("巨量引擎数据转换完成:{}", convertedData);
-        return convertedData;
-    }
-
-    @Override
-    protected void handle(Map<String, Object> convertedData) {
-        log.info("巨量引擎回调业务处理");
-
-        Long siteId = MapUtil.getLong(convertedData, "siteId");
-        String callbackType = MapUtil.getStr(convertedData, "callbackType");
-
-        // 根据回调类型处理不同的业务逻辑
-        switch (callbackType) {
-            case "show":
-                // 处理展示回调
-                handleShowCallback(convertedData);
-                break;
-            case "click":
-                // 处理点击回调
-                handleClickCallback(convertedData);
-                break;
-            case "convert":
-                // 处理转化回调
-                handleConvertCallback(convertedData);
-                break;
-            case "data":
-            default:
-                // 处理数据回调(默认)
-                handleDataCallback(convertedData);
-                break;
-        }
-
-        log.info("巨量引擎回调处理完成,站点ID:{}", siteId);
-    }
-
-    */
-/**
-     * 处理展示回调
-     *//*
-
-    private void handleShowCallback(Map<String, Object> data) {
-        log.info("处理巨量引擎展示回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long impressionCount = MapUtil.getLong(data, "impressionCount", 1L);
-
-        // TODO: 更新站点展示数统计
-        log.info("站点 {} 展示数 +{}", siteId, impressionCount);
-    }
-
-    */
-/**
-     * 处理点击回调
-     *//*
-
-    private void handleClickCallback(Map<String, Object> data) {
-        log.info("处理巨量引擎点击回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long clickCount = MapUtil.getLong(data, "clickCount", 1L);
-        Double cost = MapUtil.getDouble(data, "cost", 0.0);
-
-        // TODO: 更新站点点击数和花费统计
-        log.info("站点 {} 点击数 +{},花费 +{}", siteId, clickCount, cost);
-    }
-
-    */
-/**
-     * 处理转化回调
-     *//*
-
-    private void handleConvertCallback(Map<String, Object> data) {
-        log.info("处理巨量引擎转化回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        String convertType = MapUtil.getStr(data, "convertType");
-
-        // TODO: 记录转化数据
-        log.info("站点 {} 转化,类型:{}", siteId, convertType);
-    }
-
-    */
-/**
-     * 处理数据回调(默认)
-     *//*
-
-    private void handleDataCallback(Map<String, Object> data) {
-        log.info("处理巨量引擎数据回调");
-        Long siteId = MapUtil.getLong(data, "siteId");
-        Long impressionCount = MapUtil.getLong(data, "impressionCount");
-        Long clickCount = MapUtil.getLong(data, "clickCount");
-        Double cost = MapUtil.getDouble(data, "cost");
-
-        // 统一更新统计数据
-        log.info("站点 {} 数据更新:展示={},点击={},花费={}",
-                siteId, impressionCount, clickCount, cost);
-    }
-
-    @Override
-    protected Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.OCEAN_ENGINE.getCode();
-    }
-}
-
-*/

+ 2 - 2
fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/BaiduCallbackStrategy.java

@@ -44,8 +44,8 @@ public class BaiduCallbackStrategy implements ICallbackStrategy {
     }
 
     @Override
-    public Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.BAIDU.getCode();
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.BAIDU;
     }
 }
 

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

@@ -1,5 +1,7 @@
 package com.fs.app.integration.strategy;
 
+import com.fs.app.enums.AdvertiserTypeEnum;
+
 import java.util.Map;
 
 /**
@@ -30,6 +32,6 @@ public interface ICallbackStrategy {
     /**
      * 获取广告商类型
      */
-    Integer getAdvertiserType();
+    AdvertiserTypeEnum getAdvertiserType();
 }
 

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

@@ -40,8 +40,8 @@ public class OceanEngineCallbackStrategy implements ICallbackStrategy {
     }
 
     @Override
-    public Integer getAdvertiserType() {
-        return AdvertiserTypeEnum.OCEAN_ENGINE.getCode();
+    public AdvertiserTypeEnum getAdvertiserType() {
+        return AdvertiserTypeEnum.OCEAN_ENGINE;
     }
 }
 

+ 0 - 65
fs-ad-new-api/src/main/java/com/fs/app/manager/ConversionManager.java

@@ -1,65 +0,0 @@
-package com.fs.app.manager;
-
-import com.fs.app.event.ConversionEventPublisher;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-/**
- * 转化管理器
- * 通用业务处理层,处理转化相关业务
- *
- * @author zhangqin
- * @date 2025-11-03
- * @updated 2025-11-05 修改为使用clickId和advertiser
- */
-@Slf4j
-@Component
-public class ConversionManager {
-
-    @Autowired
-    private ConversionEventPublisher conversionEventPublisher;
-
-    /**
-     * 处理用户转化
-     * 当用户发生转化行为时调用此方法
-     *
-     * @param siteId 站点ID
-     * @param clickId 点击ID(广告平台提供)
-     * @param advertiser 广告商名称(BAIDU, OCEANENGINE, SINA, GDT)
-     * @param eventType 事件类型(如:SUBMIT_FORM、REGISTER、PAY)
-     * @param value 转化价值
-     * @param leadId 线索ID(可选)
-     */
-    public void handleConversion(Long siteId, String clickId, String advertiser,
-                                String eventType, Double value, Long leadId) {
-        log.info("处理转化 | siteId={}, clickId={}, advertiser={}, eventType={}, value={}",
-                siteId, clickId, advertiser, eventType, value);
-
-        // 发布转化事件,异步处理回传
-        conversionEventPublisher.publishConversionEvent(siteId, clickId, advertiser,
-                                                         eventType, value, leadId);
-
-        // TODO: 其他业务逻辑,如更新统计数据等
-    }
-
-    /**
-     * 处理用户转化(简化版,无价值和线索ID)
-     *
-     * @param siteId 站点ID
-     * @param clickId 点击ID
-     * @param advertiser 广告商名称
-     * @param eventType 事件类型
-     */
-    public void handleConversion(Long siteId, String clickId, String advertiser, String eventType) {
-        handleConversion(siteId, clickId, advertiser, eventType, null, null);
-    }
-
-    /**
-     * 批量处理转化
-     */
-    public void batchHandleConversions() {
-        // TODO: 批量处理转化逻辑
-        log.info("批量处理转化");
-    }
-}

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

@@ -0,0 +1,46 @@
+package com.fs.app.mq.consumer;
+
+import com.fs.app.facade.CallbackProcessingFacadeService;
+import com.fs.app.mq.message.ClickMessage;
+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;
+
+/**
+ * 转化消息消费者
+ * 多线程并发消费,支持自动重试
+ *
+ * @author zhangqin
+ * @date 2025-11-05
+ */
+@Slf4j
+@Component
+@RocketMQMessageListener(
+        topic = "landing-page-click-topic",
+        consumerGroup = "landing-page-click-topic-consumer-group",
+        // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
+        consumeMode = ConsumeMode.CONCURRENTLY,
+        // 最大重试次数(RocketMQ默认16次)
+        maxReconsumeTimes = 16
+)
+public class ClickMessageConsumer implements RocketMQListener<ClickMessage> {
+
+
+    @Autowired
+    private CallbackProcessingFacadeService facadeService;
+    /**
+     * 消费转化消息
+     *
+     * @param message 转化消息
+     */
+    @Override
+    public void onMessage(ClickMessage message) {
+        facadeService.updateClickTrace(message);
+    }
+
+
+}
+

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

@@ -1,117 +0,0 @@
-package com.fs.app.mq.consumer;
-
-
-import com.fs.app.mq.message.ConversionMessage;
-import com.fs.newAdv.domain.ConversionLog;
-import com.fs.newAdv.mapper.ConversionLogMapper;
-import lombok.extern.slf4j.Slf4j;
-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;
-
-/**
- * 转化消息死信队列消费者
- * 处理超过最大重试次数的失败消息
- *
- * @author zhangqin
- * @date 2025-11-05
- */
-@Slf4j
-@Component
-@RocketMQMessageListener(
-    // 死信队列Topic格式:%DLQ%消费组名称
-    topic = "%DLQ%event-feedback-consumer-group",
-    consumerGroup = "dlq-consumer-group"
-)
-public class ConversionDLQConsumer implements RocketMQListener<ConversionMessage> {
-
-    @Autowired
-    private ConversionLogMapper conversionLogMapper;
-
-    /**
-     * 处理死信队列消息
-     *
-     * @param message 转化消息
-     */
-    @Override
-    public void onMessage(ConversionMessage message) {
-        String clickId = message.getClickId();
-        String eventType = message.getEventType();
-
-        log.error("========== 转化消息进入死信队列 ==========");
-        log.error("clickId={}, eventType={}, advertiser={}, messageId={}",
-                  clickId, eventType, message.getAdvertiser(), message.getMessageId());
-        log.error("详细信息: siteId={}, value={}, leadId={}, conversionLogId={}",
-                  message.getSiteId(), message.getValue(),
-                  message.getLeadId(), message.getConversionLogId());
-        log.error("==========================================");
-
-        try {
-            // 1. 记录到数据库,标记为最终失败
-            recordFailedConversion(message);
-
-            // 2. 发送告警通知(预留接口,后期接入企微)
-            sendAlert(message);
-
-        } catch (Exception e) {
-            log.error("处理死信队列消息异常 | clickId={}, eventType={}", clickId, eventType, e);
-        }
-    }
-
-    /**
-     * 记录失败的转化到数据库
-     *
-     * @param message 转化消息
-     */
-    private void recordFailedConversion(ConversionMessage message) {
-        try {
-            if (message.getConversionLogId() != null) {
-                // 更新现有记录
-                ConversionLog conversionLog = new ConversionLog();
-                conversionLog.setId(message.getConversionLogId());
-                conversionLog.setCallbackStatus(2); // 2=失败
-                conversionLog.setSuccessTime(null);
-                conversionLog.setErrorMsg("超过最大重试次数,进入死信队列,需人工介入");
-                conversionLogMapper.updateById(conversionLog);
-
-                log.info("更新转化日志状态为死信 | conversionLogId={}", message.getConversionLogId());
-            }
-        } catch (Exception e) {
-            log.error("记录失败转化异常 | clickId={}", message.getClickId(), e);
-        }
-    }
-
-    /**
-     * 发送告警通知
-     * 预留接口,后期接入企业微信/钉钉等
-     *
-     * @param message 转化消息
-     */
-    private void sendAlert(ConversionMessage message) {
-        // TODO: 后期接入企业微信告警
-        log.warn("【告警】转化回传失败 | clickId={}, eventType={}, advertiser={}",
-                 message.getClickId(), message.getEventType(), message.getAdvertiser());
-
-        // 告警内容示例:
-        // String alertMessage = String.format(
-        //     "【CRM广告系统告警】\n" +
-        //     "类型:转化回传失败\n" +
-        //     "广告商:%s\n" +
-        //     "点击ID:%s\n" +
-        //     "事件类型:%s\n" +
-        //     "站点ID:%s\n" +
-        //     "时间:%s\n" +
-        //     "备注:超过最大重试次数,需人工介入处理",
-        //     message.getAdvertiser(),
-        //     message.getClickId(),
-        //     message.getEventType(),
-        //     message.getSiteId(),
-        //     LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
-        // );
-        //
-        // // 发送到企业微信
-        // wechatAlertService.send(alertMessage);
-    }
-}
-

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

@@ -1,20 +1,19 @@
 package com.fs.app.mq.consumer;
 
 import cn.hutool.core.util.StrUtil;
+import com.fs.app.constant.MqTopicConstant;
+import com.fs.app.enums.ConversionTypeEnum;
+import com.fs.app.facade.IConversionService;
 import com.fs.app.mq.message.ConversionMessage;
-import com.fs.newAdv.domain.ConversionLog;
+import com.fs.common.utils.RedisUtil;
 import com.fs.newAdv.mapper.ConversionLogMapper;
-import com.fs.app.facade.IConversionService;
 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.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.PostConstruct;
-import java.time.LocalDateTime;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -22,17 +21,16 @@ import java.util.concurrent.TimeUnit;
  * 多线程并发消费,支持自动重试
  *
  * @author zhangqin
- * @date 2025-11-05
  */
 @Slf4j
 @Component
 @RocketMQMessageListener(
-    topic = "event-feedback",
-    consumerGroup = "event-feedback-consumer-group",
-    // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
-    consumeMode = ConsumeMode.CONCURRENTLY,
-    // 最大重试次数(RocketMQ默认16次)
-    maxReconsumeTimes = 16
+        topic = MqTopicConstant.CONVERSION_TOPIC,
+        consumerGroup = "conversion-topic-consumer-group",
+        // 并发消费模式(多线程并发消费,线程数由RocketMQ自动管理)
+        consumeMode = ConsumeMode.CONCURRENTLY,
+        // 最大重试次数(RocketMQ默认16次)
+        maxReconsumeTimes = 16
 )
 public class ConversionMessageConsumer implements RocketMQListener<ConversionMessage> {
 
@@ -40,25 +38,11 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
     private IConversionService conversionService;
 
     @Autowired
-    private StringRedisTemplate redisTemplate;
+    private RedisUtil redisUtil;
 
     @Autowired
     private ConversionLogMapper conversionLogMapper;
 
-    /**
-     * 初始化检查
-     */
-    @PostConstruct
-    public void init() {
-        log.info("========================================");
-        log.info("RocketMQ Consumer 初始化成功");
-        log.info("Topic: event-feedback");
-        log.info("Consumer Group: event-feedback-consumer-group");
-        log.info("Consume Mode: CONCURRENTLY");
-        log.info("Max Reconsume Times: 16");
-        log.info("========================================");
-    }
-
     /**
      * 消费转化消息
      *
@@ -66,12 +50,13 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
      */
     @Override
     public void onMessage(ConversionMessage message) {
+        String advertiser = message.getAdvertiser();
+        String traceId = message.getTraceId();
         String messageId = message.getMessageId();
-        String clickId = message.getClickId();
-        String eventType = message.getEventType();
+        ConversionTypeEnum eventType = message.getEventType();
 
-        log.info("接收到转化消息 | clickId={}, eventType={}, messageId={}",
-                 clickId, eventType, messageId);
+        log.info("消费者接收到转化消息 | traceId={}, advertiser={}, eventType={}",
+                traceId, advertiser, eventType.getDesc());
 
         try {
             // 1. 幂等性校验(防止重复消费)
@@ -87,22 +72,15 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
                 // 3. 标记为已处理
                 markAsProcessed(messageId);
 
-                // 4. 更新转化日志状态
-                updateConversionLogStatus(message, true);
-
-                log.info("转化回传成功 | clickId={}, eventType={}", clickId, eventType);
+                log.info("转化回传成功 | traceId={}, eventType={}", traceId, eventType);
             } else {
                 // 抛出异常触发RocketMQ重试
                 throw new RuntimeException("回传失败,触发重试");
             }
 
         } catch (Exception e) {
-            log.error("处理转化消息失败 | clickId={}, eventType={}, error={}",
-                      clickId, eventType, e.getMessage(), e);
-
-            // 更新转化日志状态为失败
-            updateConversionLogStatus(message, false);
-
+            log.error("处理转化消息失败 | traceId={}, eventType={}, error={}",
+                    traceId, eventType, e.getMessage(), e);
             // 抛出异常,RocketMQ会自动重试
             throw new RuntimeException("处理转化消息失败: " + e.getMessage(), e);
         }
@@ -116,8 +94,7 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
      */
     private boolean checkIdempotent(String messageId) {
         String key = "conversion:processed:" + messageId;
-        Boolean success = redisTemplate.opsForValue()
-            .setIfAbsent(key, "1", 7, TimeUnit.DAYS);
+        Boolean success = redisUtil.setIfAbsent(key, "1", 7, TimeUnit.DAYS);
         return Boolean.TRUE.equals(success);
     }
 
@@ -128,7 +105,7 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
      */
     private void markAsProcessed(String messageId) {
         String key = "conversion:processed:" + messageId;
-        redisTemplate.opsForValue().set(key, "1", 7, TimeUnit.DAYS);
+        redisUtil.set(key, "1", 7, TimeUnit.DAYS);
     }
 
     /**
@@ -141,64 +118,44 @@ public class ConversionMessageConsumer implements RocketMQListener<ConversionMes
         String advertiser = message.getAdvertiser();
 
         if (StrUtil.isBlank(advertiser)) {
-            log.error("广告商名称为空 | clickId={}", message.getClickId());
+            log.error("广告商名称为空 | traceId={}", message.getTraceId());
             return false;
         }
 
         switch (advertiser.toUpperCase()) {
             case "BAIDU":
                 return conversionService.reportToBaidu(
-                    message.getSiteId(),
-                    message.getEventType(),
-                    message.getClickId(),
-                    message.getValue()
+                        message.getSiteId(),
+                        message.getEventType(),
+                        message.getTraceId(),
+                        null
                 );
 
-            case "OCEANENGINE":
+            case "OCEAN_ENGINE":
                 return conversionService.reportToOceanEngine(
-                    message.getSiteId(),
-                    message.getEventType(),
-                    message.getClickId(),
-                    message.getValue()
+                        message.getSiteId(),
+                        message.getEventType(),
+                        message.getTraceId(),
+                        null
                 );
 
             case "SINA":
                 // TODO: 实现新浪API调用
-                log.warn("新浪平台回传暂未实现 | clickId={}", message.getClickId());
+                log.warn("新浪平台回传暂未实现 | clickId={}", message.getTraceId());
                 return false;
 
             case "GDT":
                 // TODO: 实现广点通API调用
-                log.warn("广点通平台回传暂未实现 | clickId={}", message.getClickId());
+                log.warn("广点通平台回传暂未实现 | clickId={}", message.getTraceId());
                 return false;
 
             default:
                 log.error("未知广告商 | advertiser={}, clickId={}",
-                          advertiser, message.getClickId());
+                        advertiser, message.getTraceId());
                 return false;
         }
     }
 
-    /**
-     * 更新转化日志状态
-     *
-     * @param message 转化消息
-     * @param success 是否成功
-     */
-    private void updateConversionLogStatus(ConversionMessage message, boolean success) {
-        try {
-            if (message.getConversionLogId() != null) {
-                ConversionLog conversionLog = new ConversionLog();
-                conversionLog.setId(message.getConversionLogId());
-                conversionLog.setCallbackStatus(success ? 1 : 2); // 1=成功,2=失败
-                conversionLog.setSuccessTime(success ? LocalDateTime.now() : null);
-                conversionLog.setErrorMsg(success ? null : "回传失败");
-                conversionLogMapper.updateById(conversionLog);
-            }
-        } catch (Exception e) {
-            log.error("更新转化日志状态失败 | conversionLogId={}",
-                      message.getConversionLogId(), e);
-        }
-    }
+
 }
 

+ 26 - 0
fs-ad-new-api/src/main/java/com/fs/app/mq/message/ClickMessage.java

@@ -0,0 +1,26 @@
+package com.fs.app.mq.message;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 转化回传消息实体
+ *
+ * @author zhangqin
+ * @date 2025-11-05
+ */
+@Data
+public class ClickMessage extends MqMessageDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long siteId;
+
+    private String viewUrl;
+
+    private Map<String, String> allParams;
+}
+
+

+ 16 - 15
fs-ad-new-api/src/main/java/com/fs/app/mq/message/ConversionMessage.java

@@ -1,5 +1,6 @@
 package com.fs.app.mq.message;
 
+import com.fs.app.enums.ConversionTypeEnum;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -11,7 +12,7 @@ import java.io.Serializable;
  * @date 2025-11-05
  */
 @Data
-public class ConversionMessage implements Serializable {
+public class ConversionMessage extends MqMessageDto implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
@@ -21,9 +22,9 @@ public class ConversionMessage implements Serializable {
     private Long siteId;
 
     /**
-     * 点击ID(广告平台提供)
+     * 链路id
      */
-    private String clickId;
+    private String traceId;
 
     /**
      * 广告商名称:BAIDU, OCEANENGINE, SINA, GDT
@@ -33,33 +34,33 @@ public class ConversionMessage implements Serializable {
     /**
      * 事件类型:SUBMIT_FORM(提交表单), REGISTER(注册), PAY(支付)等
      */
-    private String eventType;
+    private ConversionTypeEnum eventType;
 
-    /**
+   /* *//**
      * 转化价值(可选)
-     */
+     *//*
     private Double value;
 
-    /**
+    *//**
      * 消息时间戳
-     */
+     *//*
     private Long timestamp;
 
-    /**
+    *//**
      * 消息ID(用于幂等性校验)
      * 格式:clickId_eventType_timestamp
-     */
+     *//*
     private String messageId;
 
-    /**
+    *//**
      * 线索ID(可选,用于关联业务数据)
-     */
+     *//*
     private Long leadId;
 
-    /**
+    *//**
      * 转化日志ID(可选,用于更新转化记录状态)
-     */
-    private Long conversionLogId;
+     *//*
+    private Long conversionLogId;*/
 }
 
 

+ 19 - 0
fs-ad-new-api/src/main/java/com/fs/app/mq/message/MqMessageDto.java

@@ -0,0 +1,19 @@
+package com.fs.app.mq.message;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class MqMessageDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 消息唯一键
+     */
+    private String messageId;
+
+    /**
+     * 时间戳
+     */
+    private Long timestamp;
+}

+ 54 - 76
fs-ad-new-api/src/main/java/com/fs/app/mq/producer/ConversionMessageProducer.java

@@ -1,6 +1,8 @@
 package com.fs.app.mq.producer;
 
 import cn.hutool.core.util.StrUtil;
+import com.fs.app.constant.MqTopicConstant;
+import com.fs.app.mq.message.ClickMessage;
 import com.fs.app.mq.message.ConversionMessage;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.client.producer.SendCallback;
@@ -9,8 +11,6 @@ import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.PostConstruct;
-
 /**
  * 转化消息生产者
  *
@@ -24,38 +24,63 @@ public class ConversionMessageProducer {
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
 
+
     /**
-     * 初始化检查
+     * 发送转化回传消息(异步)
+     *
+     * @param message 转化消息
      */
-    @PostConstruct
-    public void init() {
-        try {
-            log.info("========================================");
-            log.info("RocketMQ Producer 初始化成功");
-            log.info("Producer Group: {}", rocketMQTemplate.getProducer().getProducerGroup());
-            log.info("NameServer: {}", rocketMQTemplate.getProducer().getNamesrvAddr());
-            log.info("========================================");
-        } catch (Exception e) {
-            log.error("RocketMQ Producer 初始化失败", e);
+    public void sendConversionMessage(ConversionMessage message) {
+        // 1. 设置消息ID(用于幂等性校验)
+        if (StrUtil.isBlank(message.getMessageId())) {
+            message.setMessageId(buildMessageId(message));
         }
+
+        // 2. 设置时间戳
+        if (message.getTimestamp() == null) {
+            message.setTimestamp(System.currentTimeMillis());
+        }
+
+        // 3. 异步发送消息
+        rocketMQTemplate.asyncSend(
+                MqTopicConstant.CONVERSION_TOPIC,
+                message,
+                new SendCallback() {
+                    @Override
+                    public void onSuccess(SendResult sendResult) {
+                        log.info("转化消息发送成功 | traceId={}, eventType={}, msgId={}, queueId={}",
+                                message.getTraceId(),
+                                message.getEventType().getDesc(),
+                                sendResult.getMsgId(),
+                                sendResult.getMessageQueue().getQueueId());
+                    }
+
+                    @Override
+                    public void onException(Throwable e) {
+                        log.error("转化消息发送失败 | clickId={}, eventType={}, error={}",
+                                message.getTraceId(),
+                                message.getEventType(),
+                                e.getMessage(),
+                                e);
+                        // TODO: 记录到数据库,后续补偿
+                    }
+                }
+        );
     }
 
+
     /**
-     * 发送转化回传消息(异步)
+     * 发送点击同步消息(异步)
      *
      * @param message 转化消息
      */
-    public void sendConversionMessage(ConversionMessage message) {
+    public void sendClickMessage(ClickMessage message) {
         try {
             // 1. 设置消息ID(用于幂等性校验)
-            if (StrUtil.isBlank(message.getMessageId())) {
-                message.setMessageId(buildMessageId(message));
-            }
+
 
             // 2. 设置时间戳
-            if (message.getTimestamp() == null) {
-                message.setTimestamp(System.currentTimeMillis());
-            }
+
 
             // 3. 异步发送消息
             rocketMQTemplate.asyncSend(
@@ -64,84 +89,37 @@ public class ConversionMessageProducer {
                     new SendCallback() {
                         @Override
                         public void onSuccess(SendResult sendResult) {
-                            log.info("转化消息发送成功 | clickId={}, eventType={}, msgId={}, queueId={}",
-                                    message.getClickId(),
-                                    message.getEventType(),
-                                    sendResult.getMsgId(),
-                                    sendResult.getMessageQueue().getQueueId());
+                            log.info("转化消息发送成功");
                         }
 
                         @Override
                         public void onException(Throwable e) {
-                            log.error("转化消息发送失败 | clickId={}, eventType={}, error={}",
-                                    message.getClickId(),
-                                    message.getEventType(),
-                                    e.getMessage(),
-                                    e);
+                            log.error("转化消息发送失败");
                             // TODO: 记录到数据库,后续补偿
                         }
                     }
             );
 
         } catch (Exception e) {
-            log.error("发送转化消息异常 | clickId={}, eventType={}",
-                    message.getClickId(),
-                    message.getEventType(),
-                    e);
+            log.error("发送转化消息异常", e);
         }
     }
 
-    /**
-     * 发送转化回传消息(同步,用于测试或关键场景)
-     *
-     * @param message 转化消息
-     * @return 是否发送成功
-     */
-    public boolean sendConversionMessageSync(ConversionMessage message) {
-        try {
-            // 1. 设置消息ID和时间戳
-            if (StrUtil.isBlank(message.getMessageId())) {
-                message.setMessageId(buildMessageId(message));
-            }
-            if (message.getTimestamp() == null) {
-                message.setTimestamp(System.currentTimeMillis());
-            }
-
-            // 2. 同步发送消息
-            SendResult sendResult = rocketMQTemplate.syncSend(
-                    "event-feedback",
-                    message
-            );
-
-            log.info("转化消息同步发送成功 | clickId={}, eventType={}, msgId={}",
-                    message.getClickId(),
-                    message.getEventType(),
-                    sendResult.getMsgId());
-
-            return true;
-
-        } catch (Exception e) {
-            log.error("转化消息同步发送失败 | clickId={}, eventType={}",
-                    message.getClickId(),
-                    message.getEventType(),
-                    e);
-            return false;
-        }
-    }
 
     /**
      * 构建消息ID(用于幂等性)
-     * 格式:clickId_eventType_timestamp
+     * 格式:traceId_timestamp
      *
      * @param message 转化消息
      * @return 消息ID
      */
     private String buildMessageId(ConversionMessage message) {
-        return String.format("%s_%s_%d",
-                message.getClickId(),
-                message.getEventType(),
+        return String.format("%s_%d",
+                message.getTraceId(),
                 System.currentTimeMillis());
     }
+
+
 }
 
 

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

@@ -1,6 +1,11 @@
 package com.fs.app.task;
 
 
+import cn.hutool.json.JSONUtil;
+import com.fs.app.enums.AdvertiserTypeEnum;
+import com.fs.app.integration.client.IApiClient;
+import com.fs.app.integration.factory.AdvertiserHandlerFactory;
+import com.fs.common.annotation.DistributeLock;
 import com.fs.common.constant.SystemConstant;
 import com.fs.newAdv.domain.ConversionLog;
 import com.fs.newAdv.mapper.ConversionLogMapper;
@@ -9,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -24,43 +30,38 @@ public class ConversionRetryTask {
 
     @Autowired
     private ConversionLogMapper conversionLogMapper;
+    @Autowired
+    private AdvertiserHandlerFactory advertiserHandlerFactory;
 
     /**
      * 转化回传重试任务
      * cron: 每10分钟执行
      */
     @Scheduled(cron = "0 */10 * * * ?")
+    @DistributeLock(scene = "task", key = "conversion_retry", waitTime = 0, errorMsg = "任务已执行")
     public void execute() {
-        log.info("========== 转化回传重试任务开始 ==========");
-
-        try {
-            // 查询待重试的转化记录
-            List<ConversionLog> pendingList = conversionLogMapper.selectPendingConversions();
-            log.info("查询到 {} 条待重试的转化记录", pendingList.size());
-
-            int successCount = 0;
-            int failCount = 0;
-
-            for (ConversionLog conversionLog : pendingList) {
-                try {
-                    // 重试回传
-                    boolean success = retryConversion(conversionLog);
-                    if (success) {
-                        successCount++;
-                    } else {
-                        failCount++;
-                    }
-                } catch (Exception e) {
-                    log.error("重试转化回传失败,ID:{}", conversionLog.getId(), e);
+        // 查询待重试的转化记录
+        List<ConversionLog> pendingList = conversionLogMapper.selectPendingConversions();
+        log.info("查询到 {} 条待重试的转化记录", pendingList.size());
+
+        int successCount = 0;
+        int failCount = 0;
+
+        for (ConversionLog conversionLog : pendingList) {
+            try {
+                // 重试回传
+                boolean success = retryConversion(conversionLog);
+                if (success) {
+                    successCount++;
+                } else {
                     failCount++;
                 }
+            } catch (Exception e) {
+                log.error("重试转化回传失败,ID:{}", conversionLog.getId(), e);
+                failCount++;
             }
-
-            log.info("========== 转化回传重试任务完成,成功:{},失败:{} ==========", successCount, failCount);
-
-        } catch (Exception e) {
-            log.error("转化回传重试任务执行失败", e);
         }
+        log.info("========== 转化回传重试任务完成,成功:{},失败:{} ==========", successCount, failCount);
     }
 
     /**
@@ -68,10 +69,12 @@ public class ConversionRetryTask {
      */
     private boolean retryConversion(ConversionLog conversionLog) {
         log.info("重试转化回传,ID:{},重试次数:{}", conversionLog.getId(), conversionLog.getRetryCount());
-
-        // TODO: 调用第三方平台API进行回传
-        // 使用工厂模式获取对应的回传处理器
-
+        IApiClient apiClient = advertiserHandlerFactory.getApiClient(AdvertiserTypeEnum.getByCode(conversionLog.getAdvertiserId()));
+        boolean b = apiClient.reportConversion(JSONUtil.parseObj(conversionLog.getCallbackParams()));
+        if (b) {
+            conversionLog.setCallbackStatus(1);
+            conversionLog.setSuccessTime(LocalDateTime.now());
+        }
         // 更新重试次数
         conversionLog.setRetryCount(conversionLog.getRetryCount() + 1);
 
@@ -80,10 +83,9 @@ public class ConversionRetryTask {
             conversionLog.setCallbackStatus(2); // 失败
             log.warn("转化回传重试次数已达上限,ID:{}", conversionLog.getId());
         }
-
+        conversionLog.setUpdateTime(LocalDateTime.now());
         conversionLogMapper.updateById(conversionLog);
-
-        return false;
+        return b;
     }
 }
 

+ 0 - 126
fs-ad-new-api/src/main/java/com/fs/app/task/DailyArchiveTask.java

@@ -1,126 +0,0 @@
-package com.fs.app.task;
-
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-
-import com.fs.framework.annotation.DistributedLock;
-import com.fs.app.enums.TaskStatusEnum;
-import com.fs.app.enums.TaskTypeEnum;
-import com.fs.common.constant.RedisKeyConstant;
-import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.ScheduleTaskLog;
-import com.fs.newAdv.domain.SiteStatistics;
-import com.fs.newAdv.domain.SiteStatisticsDaily;
-import com.fs.newAdv.mapper.ScheduleTaskLogMapper;
-import com.fs.newAdv.mapper.SiteStatisticsDailyMapper;
-import com.fs.newAdv.mapper.SiteStatisticsMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-
-/**
- * 日统计归档定时任务
- * 每天00:05执行,归档昨日数据
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Component
-public class DailyArchiveTask {
-
-    @Autowired
-    private SiteStatisticsMapper siteStatisticsMapper;
-
-    @Autowired
-    private SiteStatisticsDailyMapper dailyMapper;
-
-    @Autowired
-    private ScheduleTaskLogMapper scheduleTaskLogMapper;
-
-    /**
-     * 日归档任务
-     * cron: 每天00:05执行
-     */
-    @Scheduled(cron = "0 5 0 * * ?")
-    @DistributedLock(key = RedisKeyConstant.TASK_LOCK_PREFIX + "DAILY_ARCHIVE")
-    public void execute() {
-        LocalDate yesterday = LocalDate.now().minusDays(1);
-        // LocalDate使用toString()方法,默认格式就是yyyy-MM-dd
-        String batchNo = yesterday.toString() + "-daily";
-        log.info("========== 日归档任务开始,批次号:{} ==========", batchNo);
-
-        ScheduleTaskLog taskLog = createTaskLog(batchNo);
-
-        try {
-            // 查询所有站点统计数据
-            QueryWrapper<SiteStatistics> wrapper = new QueryWrapper<>();
-            List<SiteStatistics> statisticsList = siteStatisticsMapper.selectList(wrapper);
-
-            int count = 0;
-            for (SiteStatistics statistics : statisticsList) {
-                try {
-                    archiveDaily(statistics, yesterday);
-                    count++;
-                } catch (Exception e) {
-                    log.error("归档站点 {} 数据失败", statistics.getSiteId(), e);
-                }
-            }
-
-            // 更新任务日志
-            updateTaskLog(taskLog, TaskStatusEnum.SUCCESS, count, null);
-            log.info("========== 日归档任务完成,成功归档 {} 条数据 ==========", count);
-
-        } catch (Exception e) {
-            log.error("日归档任务执行失败", e);
-            updateTaskLog(taskLog, TaskStatusEnum.FAILED, 0, e.getMessage());
-        }
-    }
-
-    /**
-     * 归档单个站点的日统计数据
-     */
-    private void archiveDaily(SiteStatistics statistics, LocalDate statDate) {
-        SiteStatisticsDaily daily = new SiteStatisticsDaily();
-        BeanUtils.copyProperties(statistics, daily);
-        daily.setId(SnowflakeUtil.nextId());
-        daily.setStatDate(statDate);
-        daily.setCreateTime(LocalDateTime.now());
-
-        dailyMapper.insert(daily);
-        log.info("站点 {} 日统计数据归档完成", statistics.getSiteId());
-    }
-
-    /**
-     * 创建任务日志
-     */
-    private ScheduleTaskLog createTaskLog(String batchNo) {
-        ScheduleTaskLog log = new ScheduleTaskLog();
-        log.setId(SnowflakeUtil.nextId());
-        log.setTaskType(TaskTypeEnum.DAILY_ARCHIVE.getCode());
-        log.setBatchNo(batchNo);
-        log.setExecuteStatus(TaskStatusEnum.RUNNING.getCode());
-        log.setStartTime(LocalDateTime.now());
-        log.setCreateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.insert(log);
-        return log;
-    }
-
-    /**
-     * 更新任务日志
-     */
-    private void updateTaskLog(ScheduleTaskLog log, TaskStatusEnum status, Integer count, String errorMsg) {
-        log.setExecuteStatus(status.getCode());
-        log.setProcessDataCount(count.longValue());
-        log.setEndTime(LocalDateTime.now());
-        log.setErrorMsg(errorMsg);
-        log.setUpdateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.updateById(log);
-    }
-}
-

+ 0 - 3
fs-ad-new-api/src/main/java/com/fs/app/task/DataSyncTask.java

@@ -1,10 +1,8 @@
 package com.fs.app.task;
 
 import cn.hutool.core.date.DateUtil;
-import com.fs.framework.annotation.DistributedLock;
 import com.fs.app.enums.TaskStatusEnum;
 import com.fs.app.enums.TaskTypeEnum;
-import com.fs.common.constant.RedisKeyConstant;
 import com.fs.common.utils.SnowflakeUtil;
 import com.fs.newAdv.domain.PromotionAccount;
 import com.fs.newAdv.domain.ScheduleTaskLog;
@@ -40,7 +38,6 @@ public class DataSyncTask {
      * cron: 每2小时执行一次
      */
     @Scheduled(cron = "0 0 */2 * * ?")
-    @DistributedLock(key = RedisKeyConstant.TASK_LOCK_PREFIX + "DATA_SYNC")
     public void execute() {
         String batchNo = DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd-HH");
         log.info("========== 数据同步任务开始,批次号:{} ==========", batchNo);

+ 0 - 82
fs-ad-new-api/src/main/java/com/fs/app/task/MonthlyArchiveTask.java

@@ -1,82 +0,0 @@
-package com.fs.app.task;
-
-import cn.hutool.core.date.DateUtil;
-
-import com.fs.framework.annotation.DistributedLock;
-import com.fs.app.enums.TaskStatusEnum;
-import com.fs.app.enums.TaskTypeEnum;
-import com.fs.common.constant.RedisKeyConstant;
-import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.ScheduleTaskLog;
-import com.fs.newAdv.mapper.ScheduleTaskLogMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDateTime;
-
-/**
- * 月统计归档定时任务
- * 每月1号00:15执行,归档上月数据
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Component
-public class MonthlyArchiveTask {
-
-    @Autowired
-    private ScheduleTaskLogMapper scheduleTaskLogMapper;
-
-    /**
-     * 月归档任务
-     * cron: 每月1号00:15执行
-     */
-    @Scheduled(cron = "0 15 0 1 * ?")
-    @DistributedLock(key = RedisKeyConstant.TASK_LOCK_PREFIX + "MONTHLY_ARCHIVE")
-    public void execute() {
-        LocalDateTime lastMonth = LocalDateTime.now().minusMonths(1);
-        String statMonth = DateUtil.format(lastMonth, "yyyy-MM");
-        String batchNo = statMonth + "-monthly";
-
-        log.info("========== 月归档任务开始,批次号:{},统计月:{} ==========", batchNo, statMonth);
-
-        ScheduleTaskLog taskLog = createTaskLog(batchNo);
-
-        try {
-            // TODO: 归档上月数据逻辑
-            int count = 0;
-
-            updateTaskLog(taskLog, TaskStatusEnum.SUCCESS, count, null);
-            log.info("========== 月归档任务完成,成功归档 {} 条数据 ==========", count);
-
-        } catch (Exception e) {
-            log.error("月归档任务执行失败", e);
-            updateTaskLog(taskLog, TaskStatusEnum.FAILED, 0, e.getMessage());
-        }
-    }
-
-    private ScheduleTaskLog createTaskLog(String batchNo) {
-        ScheduleTaskLog log = new ScheduleTaskLog();
-        log.setId(SnowflakeUtil.nextId());
-        log.setTaskType(TaskTypeEnum.MONTHLY_ARCHIVE.getCode());
-        log.setBatchNo(batchNo);
-        log.setExecuteStatus(TaskStatusEnum.RUNNING.getCode());
-        log.setStartTime(LocalDateTime.now());
-        log.setCreateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.insert(log);
-        return log;
-    }
-
-    private void updateTaskLog(ScheduleTaskLog log, TaskStatusEnum status, Integer count, String errorMsg) {
-        log.setExecuteStatus(status.getCode());
-        log.setProcessDataCount(count.longValue());
-        log.setEndTime(LocalDateTime.now());
-        log.setErrorMsg(errorMsg);
-        log.setUpdateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.updateById(log);
-    }
-}
-

+ 0 - 69
fs-ad-new-api/src/main/java/com/fs/app/task/RedisDataPersistTask.java

@@ -1,69 +0,0 @@
-package com.fs.app.task;
-
-
-import com.fs.common.utils.RedisUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-/**
- * Redis数据持久化定时任务
- * 每小时执行一次,将Redis中的统计数据同步到MySQL
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Component
-public class RedisDataPersistTask {
-
-    @Autowired
-    private RedisUtil redisUtil;
-
-    /**
-     * Redis数据持久化任务
-     * cron: 每小时执行
-     */
-    @Scheduled(cron = "0 0 * * * ?")
-    public void execute() {
-        log.info("========== Redis数据持久化任务开始 ==========");
-
-        try {
-            // TODO: 将Redis中的计数器数据同步到数据库
-            persistImpressionData();
-            persistClickData();
-            persistConversionData();
-
-            log.info("========== Redis数据持久化任务完成 ==========");
-
-        } catch (Exception e) {
-            log.error("Redis数据持久化任务执行失败", e);
-        }
-    }
-
-    /**
-     * 持久化展示数据
-     */
-    private void persistImpressionData() {
-        log.info("持久化展示数据");
-        // 扫描所有展示计数器key,更新到数据库
-    }
-
-    /**
-     * 持久化点击数据
-     */
-    private void persistClickData() {
-        log.info("持久化点击数据");
-        // 扫描所有点击计数器key,更新到数据库
-    }
-
-    /**
-     * 持久化转化数据
-     */
-    private void persistConversionData() {
-        log.info("持久化转化数据");
-        // 扫描所有转化计数器key,更新到数据库
-    }
-}
-

+ 0 - 34
fs-ad-new-api/src/main/java/com/fs/app/task/Task.java

@@ -1,34 +0,0 @@
-package com.fs.app.task;
-
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.fs.ad.domain.AdHtmlClickLog;
-import com.fs.ad.service.IAdHtmlClickLogService;
-import com.fs.ad.service.impl.AdHtmlClickLogServiceImpl;
-import com.fs.common.core.redis.RedisCacheT;
-import lombok.AllArgsConstructor;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-@Component
-@AllArgsConstructor
-public class Task {
-    private RedisCacheT<AdHtmlClickLog> redisCache;
-    private IAdHtmlClickLogService adHtmlClickLogService;
-
-//    @Scheduled(cron = "0/30 * * * * ?")
-    @Scheduled(cron = "0 0/30 * * * ?")
-    public void saveLog() {
-        List<AdHtmlClickLog> list = redisCache.getCacheListByPattern(AdHtmlClickLogServiceImpl.AD_LOG_KEY + "*");
-        list.stream().filter(e -> e.getId() == null).forEach(e -> {
-            String key = AdHtmlClickLogServiceImpl.genKey(e.getVid(), e.getType(), e.getClickType());
-            adHtmlClickLogService.save(e);
-            redisCache.setCacheObject(key, e, LocalDateTime.now().until(e.getLastTime(), ChronoUnit.MINUTES), TimeUnit.MINUTES);
-        });
-        list.stream().filter(e -> e.getId() != null).forEach(e -> adHtmlClickLogService.update(e, new QueryWrapper<AdHtmlClickLog>().eq("vid", e.getVid()).eq("click_type", e.getClickType())));
-    }
-}

+ 0 - 72
fs-ad-new-api/src/main/java/com/fs/app/task/TaskCompensationTask.java

@@ -1,72 +0,0 @@
-package com.fs.app.task;
-
-
-import com.fs.newAdv.domain.ScheduleTaskLog;
-import com.fs.newAdv.mapper.ScheduleTaskLogMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-/**
- * 任务补偿定时任务
- * 每30分钟执行一次,检查失败或超时的任务并重试
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Component
-public class TaskCompensationTask {
-
-    @Autowired
-    private ScheduleTaskLogMapper scheduleTaskLogMapper;
-
-    /**
-     * 任务补偿检查
-     * cron: 每30分钟执行
-     */
-    @Scheduled(cron = "0 */30 * * * ?")
-    public void execute() {
-        log.info("========== 任务补偿检查开始 ==========");
-
-        try {
-            // 查询需要补偿的任务
-            List<ScheduleTaskLog> tasksNeedCompensation = scheduleTaskLogMapper.selectTasksNeedCompensation();
-            log.info("查询到 {} 个需要补偿的任务", tasksNeedCompensation.size());
-
-            for (ScheduleTaskLog task : tasksNeedCompensation) {
-                try {
-                    compensateTask(task);
-                } catch (Exception e) {
-                    log.error("补偿任务失败,任务类型:{},批次号:{}", task.getTaskType(), task.getBatchNo(), e);
-                }
-            }
-
-            log.info("========== 任务补偿检查完成 ==========");
-
-        } catch (Exception e) {
-            log.error("任务补偿检查失败", e);
-        }
-    }
-
-    /**
-     * 补偿任务
-     */
-    private void compensateTask(ScheduleTaskLog task) {
-        log.info("开始补偿任务:类型={},批次号={},状态={}",
-                task.getTaskType(), task.getBatchNo(), task.getExecuteStatus());
-
-        // TODO: 根据任务类型调用对应的任务执行逻辑
-        // 可以使用策略模式实现不同任务的补偿逻辑
-
-        // 更新重试次数
-        task.setRetryCount(task.getRetryCount() + 1);
-        scheduleTaskLogMapper.updateById(task);
-
-        log.info("任务补偿完成:类型={},批次号={}", task.getTaskType(), task.getBatchNo());
-    }
-}
-

+ 0 - 109
fs-ad-new-api/src/main/java/com/fs/app/task/WeeklyArchiveTask.java

@@ -1,109 +0,0 @@
-package com.fs.app.task;
-
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.fs.framework.annotation.DistributedLock;
-import com.fs.app.enums.TaskStatusEnum;
-import com.fs.app.enums.TaskTypeEnum;
-import com.fs.common.constant.RedisKeyConstant;
-import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.domain.ScheduleTaskLog;
-import com.fs.newAdv.domain.SiteStatisticsDaily;
-import com.fs.newAdv.mapper.ScheduleTaskLogMapper;
-import com.fs.newAdv.mapper.SiteStatisticsDailyMapper;
-import com.fs.newAdv.mapper.SiteStatisticsWeeklyMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
-
-import java.time.DayOfWeek;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-
-/**
- * 周统计归档定时任务
- * 每周一00:10执行,归档上周数据
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Component
-public class WeeklyArchiveTask {
-
-    @Autowired
-    private SiteStatisticsDailyMapper dailyMapper;
-
-    @Autowired
-    private SiteStatisticsWeeklyMapper weeklyMapper;
-
-    @Autowired
-    private ScheduleTaskLogMapper scheduleTaskLogMapper;
-
-    /**
-     * 周归档任务
-     * cron: 每周一00:10执行
-     */
-    @Scheduled(cron = "0 10 0 ? * MON")
-    @DistributedLock(key = RedisKeyConstant.TASK_LOCK_PREFIX + "WEEKLY_ARCHIVE")
-    public void execute() {
-        LocalDate lastMonday = LocalDate.now().minusWeeks(1).with(DayOfWeek.MONDAY);
-        LocalDate lastSunday = lastMonday.plusDays(6);
-        // 使用LocalDate的get方法获取年份和周数
-        int year = lastMonday.getYear();
-        int weekOfYear = lastMonday.get(java.time.temporal.WeekFields.ISO.weekOfWeekBasedYear());
-        String statWeek = year + "-W" + String.format("%02d", weekOfYear);
-        String batchNo = statWeek + "-weekly";
-
-        log.info("========== 周归档任务开始,批次号:{},统计周:{} ==========", batchNo, statWeek);
-
-        ScheduleTaskLog taskLog = createTaskLog(batchNo);
-
-        try {
-            // 查询上周的日统计数据,按站点分组聚合
-            QueryWrapper<SiteStatisticsDaily> wrapper = new QueryWrapper<>();
-            wrapper.between("stat_date", lastMonday, lastSunday);
-            List<SiteStatisticsDaily> dailyList = dailyMapper.selectList(wrapper);
-
-            // TODO: 按站点分组聚合数据
-            int count = 0;
-
-            // 更新任务日志
-            updateTaskLog(taskLog, TaskStatusEnum.SUCCESS, count, null);
-            log.info("========== 周归档任务完成,成功归档 {} 条数据 ==========", count);
-
-        } catch (Exception e) {
-            log.error("周归档任务执行失败", e);
-            updateTaskLog(taskLog, TaskStatusEnum.FAILED, 0, e.getMessage());
-        }
-    }
-
-    /**
-     * 创建任务日志
-     */
-    private ScheduleTaskLog createTaskLog(String batchNo) {
-        ScheduleTaskLog log = new ScheduleTaskLog();
-        log.setId(SnowflakeUtil.nextId());
-        log.setTaskType(TaskTypeEnum.WEEKLY_ARCHIVE.getCode());
-        log.setBatchNo(batchNo);
-        log.setExecuteStatus(TaskStatusEnum.RUNNING.getCode());
-        log.setStartTime(LocalDateTime.now());
-        log.setCreateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.insert(log);
-        return log;
-    }
-
-    /**
-     * 更新任务日志
-     */
-    private void updateTaskLog(ScheduleTaskLog log, TaskStatusEnum status, Integer count, String errorMsg) {
-        log.setExecuteStatus(status.getCode());
-        log.setProcessDataCount(count.longValue());
-        log.setEndTime(LocalDateTime.now());
-        log.setErrorMsg(errorMsg);
-        log.setUpdateTime(LocalDateTime.now());
-        scheduleTaskLogMapper.updateById(log);
-    }
-}
-

+ 0 - 31
fs-ad-new-api/src/main/java/com/fs/framework/annotation/DistributedLock.java

@@ -1,31 +0,0 @@
-package com.fs.framework.annotation;
-
-import java.lang.annotation.*;
-
-/**
- * 分布式锁注解
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface DistributedLock {
-
-    /**
-     * 锁的key
-     */
-    String key();
-
-    /**
-     * 锁的过期时间(秒)
-     */
-    long expireTime() default 300;
-
-    /**
-     * 获取锁的等待时间(秒)
-     */
-    long waitTime() default 10;
-}
-

+ 117 - 0
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributeLockAspect.java

@@ -0,0 +1,117 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DistributeLock;
+import com.fs.common.constant.DistributeLockConstant;
+import com.fs.common.exception.DistributeLockException;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.StandardReflectionParameterNameDiscoverer;
+import org.springframework.core.annotation.Order;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+@Order(Integer.MIN_VALUE + 1)
+public class DistributeLockAspect {
+
+    private RedissonClient redissonClient;
+
+    public DistributeLockAspect(RedissonClient redissonClient) {
+        this.redissonClient = redissonClient;
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(DistributeLockAspect.class);
+
+    @Around("@annotation(com.fs.common.annotation.DistributeLock)")
+    public Object process(ProceedingJoinPoint pjp) throws Throwable {
+        Object response = null;
+        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
+        DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);
+
+        String key = distributeLock.key();
+        if (DistributeLockConstant.NONE_KEY.equals(key)) {
+            if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())) {
+                throw new DistributeLockException("no lock key found...");
+            }
+            SpelExpressionParser parser = new SpelExpressionParser();
+            Expression expression = parser.parseExpression(distributeLock.keyExpression());
+
+            EvaluationContext context = new StandardEvaluationContext();
+            // 获取参数值
+            Object[] args = pjp.getArgs();
+
+            // 获取运行时参数的名称
+            StandardReflectionParameterNameDiscoverer discoverer
+                    = new StandardReflectionParameterNameDiscoverer();
+            String[] parameterNames = discoverer.getParameterNames(method);
+
+            // 将参数绑定到context中
+            if (parameterNames != null) {
+                for (int i = 0; i < parameterNames.length; i++) {
+                    context.setVariable(parameterNames[i], args[i]);
+                }
+            }
+
+            // 解析表达式,获取结果
+            key = String.valueOf(expression.getValue(context));
+        }
+
+        String scene = distributeLock.scene();
+
+        String lockKey = scene + "#" + key;
+
+        int expireTime = distributeLock.expireTime();
+        int waitTime = distributeLock.waitTime();
+        RLock rLock= redissonClient.getLock(lockKey);
+        try {
+            boolean lockResult = false;
+            if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
+                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
+                    LOG.info(String.format("lock for key : %s", lockKey));
+                    rLock.lock();
+                } else {
+                    LOG.info(String.format("lock for key : %s , expire : %s", lockKey, expireTime));
+                    rLock.lock(expireTime, TimeUnit.MILLISECONDS);
+                }
+                lockResult = true;
+            } else {
+                if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
+                    LOG.info(String.format("try lock for key : %s , wait : %s", lockKey, waitTime));
+                    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
+                } else {
+                    LOG.info(String.format("try lock for key : %s , expire : %s , wait : %s", lockKey, expireTime, waitTime));
+                    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
+                }
+            }
+
+            if (!lockResult) {
+                LOG.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
+                throw new DistributeLockException(distributeLock.errorMsg());
+            }
+
+
+            LOG.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
+            response = pjp.proceed();
+        }  finally {
+            if (rLock.isHeldByCurrentThread()) {
+                rLock.unlock();
+                LOG.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
+            }
+        }
+        return response;
+    }
+}

+ 0 - 77
fs-ad-new-api/src/main/java/com/fs/framework/aspectj/DistributedLockAspect.java

@@ -1,77 +0,0 @@
-package com.fs.framework.aspectj;
-
-import cn.hutool.core.util.StrUtil;
-import com.fs.framework.annotation.DistributedLock;
-import com.fs.common.exception.base.BusinessException;
-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.reflect.MethodSignature;
-import org.redisson.api.RLock;
-import org.redisson.api.RedissonClient;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.lang.reflect.Method;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 分布式锁切面
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Slf4j
-@Aspect
-@Component
-public class DistributedLockAspect {
-
-    @Autowired
-    private RedissonClient redissonClient;
-
-    @Around("@annotation(com.fs.framework.annotation.DistributedLock)")
-    public Object around(ProceedingJoinPoint point) throws Throwable {
-        MethodSignature signature = (MethodSignature) point.getSignature();
-        Method method = signature.getMethod();
-        DistributedLock lockAnnotation = method.getAnnotation(DistributedLock.class);
-
-        String lockKey = lockAnnotation.key();
-        long expireTime = lockAnnotation.expireTime();
-        long waitTime = lockAnnotation.waitTime();
-
-        if (StrUtil.isBlank(lockKey)) {
-            throw new BusinessException("分布式锁key不能为空");
-        }
-
-        RLock lock = redissonClient.getLock(lockKey);
-        boolean acquired = false;
-
-        try {
-            // 尝试获取锁
-            acquired = lock.tryLock(waitTime, expireTime, TimeUnit.SECONDS);
-
-            if (!acquired) {
-                log.warn("获取分布式锁失败,key:{}", lockKey);
-                throw new BusinessException("系统繁忙,请稍后再试");
-            }
-
-            log.info("获取分布式锁成功,key:{}", lockKey);
-
-            // 执行业务方法
-            return point.proceed();
-
-        } catch (InterruptedException e) {
-            log.error("获取分布式锁被中断,key:{}", lockKey, e);
-            Thread.currentThread().interrupt();
-            throw new BusinessException("获取锁失败");
-        } finally {
-            // 释放锁
-            if (acquired && lock.isHeldByCurrentThread()) {
-                lock.unlock();
-                log.info("释放分布式锁成功,key:{}", lockKey);
-            }
-        }
-    }
-}
-

+ 57 - 0
fs-ad-new-api/src/main/java/com/fs/framework/config/AsyncConfig.java

@@ -0,0 +1,57 @@
+package com.fs.framework.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 异步任务配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Configuration
+public class AsyncConfig implements AsyncConfigurer {
+
+    @Bean(name = "asyncExecutor")
+    @Override
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+        // 核心线程数
+        executor.setCorePoolSize(10);
+
+        // 最大线程数
+        executor.setMaxPoolSize(50);
+
+        // 队列容量
+        executor.setQueueCapacity(1000);
+
+        // 线程存活时间(秒)
+        executor.setKeepAliveSeconds(60);
+
+        // 线程名称前缀
+        executor.setThreadNamePrefix("async-task-");
+
+        // 拒绝策略:调用者运行
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+
+        // 等待所有任务完成后再关闭线程池
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+
+        // 等待时间
+        executor.setAwaitTerminationSeconds(60);
+
+        executor.initialize();
+
+        log.info("异步线程池初始化完成");
+        return executor;
+    }
+}
+

+ 1 - 0
fs-ad-new-api/src/main/resources/application.yml

@@ -5,3 +5,4 @@ server:
 spring:
   profiles:
     active: dev
+

+ 366 - 0
fs-ad-new-api/src/main/resources/static/landing.html

@@ -0,0 +1,366 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>产品落地页 - CRM系统</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Microsoft YaHei', Arial, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding: 20px;
+        }
+
+        .container {
+            background: white;
+            border-radius: 20px;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            max-width: 500px;
+            width: 100%;
+            padding: 40px;
+        }
+
+        h1 {
+            color: #333;
+            text-align: center;
+            margin-bottom: 10px;
+            font-size: 28px;
+        }
+
+        .subtitle {
+            color: #666;
+            text-align: center;
+            margin-bottom: 30px;
+            font-size: 14px;
+        }
+
+        .form-group {
+            margin-bottom: 20px;
+        }
+
+        label {
+            display: block;
+            color: #555;
+            font-size: 14px;
+            margin-bottom: 8px;
+            font-weight: 500;
+        }
+
+        input[type="text"],
+        input[type="tel"],
+        input[type="email"] {
+            width: 100%;
+            padding: 12px 15px;
+            border: 2px solid #e0e0e0;
+            border-radius: 8px;
+            font-size: 16px;
+            transition: border-color 0.3s;
+        }
+
+        input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+
+        .submit-btn {
+            width: 100%;
+            padding: 14px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            border-radius: 8px;
+            font-size: 16px;
+            font-weight: 600;
+            cursor: pointer;
+            transition: transform 0.2s;
+        }
+
+        .submit-btn:hover {
+            transform: translateY(-2px);
+        }
+
+        .submit-btn:active {
+            transform: translateY(0);
+        }
+
+        .success-message {
+            display: none;
+            background: #4caf50;
+            color: white;
+            padding: 15px;
+            border-radius: 8px;
+            text-align: center;
+            margin-top: 20px;
+        }
+
+        .error-message {
+            display: none;
+            background: #f44336;
+            color: white;
+            padding: 15px;
+            border-radius: 8px;
+            text-align: center;
+            margin-top: 20px;
+        }
+
+        .trace-info {
+            background: #f5f5f5;
+            padding: 10px;
+            border-radius: 5px;
+            font-size: 12px;
+            color: #666;
+            margin-bottom: 20px;
+            word-break: break-all;
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>🚀 投流落地页</h1>
+        <p class="subtitle">填写信息,免费试用30天</p>
+
+        <div class="trace-info" id="traceInfo" style="display: none;">
+            <strong>追踪ID:</strong> <span id="traceIdDisplay">-</span>
+        </div>
+
+        <form id="registerForm">
+            <div class="form-group">
+                <label for="name">姓名 *</label>
+                <input type="text" id="name" name="name" required placeholder="请输入您的姓名">
+            </div>
+
+            <div class="form-group">
+                <label for="phone">手机号 *</label>
+                <input type="tel" id="phone" name="phone" required
+                       pattern="^1[3-9]\d{9}$" placeholder="请输入11位手机号">
+            </div>
+
+            <div class="form-group">
+                <label for="company">公司名称</label>
+                <input type="text" id="company" name="company" placeholder="请输入您的公司名称">
+            </div>
+
+            <div class="form-group">
+                <label for="email">邮箱</label>
+                <input type="email" id="email" name="email" placeholder="请输入您的邮箱">
+            </div>
+
+            <button type="submit" class="submit-btn">立即注册</button>
+        </form>
+
+        <div class="success-message" id="successMessage">
+            ✅ 注册成功!我们的工作人员会尽快与您联系。
+        </div>
+
+        <div class="error-message" id="errorMessage">
+            ❌ 注册失败,请稍后重试。
+        </div>
+    </div>
+
+    <script>
+        // API基础路径
+        const API_BASE = '';
+
+        // 页面加载时初始化
+        window.addEventListener('DOMContentLoaded', function() {
+            // 1. 从URL获取参数
+            const urlParams = new URLSearchParams(window.location.search);
+            const allParams = {};
+            for (const [key, value] of urlParams.entries()) {
+                allParams[key] = value;
+            }
+
+            // 2. 获取或生成traceId
+            let traceId = urlParams.get('trace_id') || localStorage.getItem('trace_id');
+
+            // 3. 如果URL中有广告参数,调用追踪接口
+            if (urlParams.has('site_id') || urlParams.has('clk_id') || urlParams.has('callback')) {
+                trackLanding(allParams);
+            } else if (traceId) {
+                // 显示已有的追踪ID
+                displayTraceId(traceId);
+            }
+        });
+
+        /**
+         * 调用落地页追踪接口
+         */
+        async function trackLanding(params) {
+            try {
+                const queryString = new URLSearchParams(params).toString();
+                const response = await fetch(`${API_BASE}/landing/track?${queryString}`, {
+                    method: 'GET',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    }
+                });
+
+                const result = await response.json();
+
+                if (result.code === 200 && result.data) {
+                    const traceId = result.data.traceId;
+
+                    // 保存到localStorage
+                    localStorage.setItem('trace_id', traceId);
+                    localStorage.setItem('visitor_id', result.data.visitorId);
+
+                    // 显示追踪ID
+                    displayTraceId(traceId);
+
+                    console.log('落地页追踪成功:', result.data);
+                } else {
+                    console.error('落地页追踪失败:', result.message);
+                }
+            } catch (error) {
+                console.error('追踪接口调用失败:', error);
+            }
+        }
+
+        /**
+         * 显示追踪ID
+         */
+        function displayTraceId(traceId) {
+            document.getElementById('traceIdDisplay').textContent = traceId;
+            document.getElementById('traceInfo').style.display = 'block';
+        }
+
+        /**
+         * 表单提交处理
+         */
+        document.getElementById('registerForm').addEventListener('submit', async function(e) {
+            e.preventDefault();
+
+            // 1. 收集表单数据
+            const formData = {
+                name: document.getElementById('name').value,
+                phone: document.getElementById('phone').value,
+                company: document.getElementById('company').value,
+                email: document.getElementById('email').value
+            };
+
+            // 2. 从URL获取所有广告参数
+            const urlParams = new URLSearchParams(window.location.search);
+
+            // 2.1 收集所有URL参数(包括平台特定参数如 bd_vid)
+            const allRawParams = {};
+            for (const [key, value] of urlParams.entries()) {
+                allRawParams[key] = value;
+            }
+
+            // 2.2 提取点击ID(支持多种参数名)
+            const clickId = urlParams.get('click_id') ||
+                           urlParams.get('clickid') ||
+                           urlParams.get('clkid') ||
+                           urlParams.get('callback_param') ||
+                           urlParams.get('CLICK_ID') ||
+                           urlParams.get('bd_vid');  // 百度特殊参数
+
+            // 2.3 提取来源平台
+            const source = urlParams.get('source') ||
+                          urlParams.get('platform') ||
+                          urlParams.get('advertiser');
+
+            // 2.4 提取其他常用参数
+            const siteId = urlParams.get('site_id') || urlParams.get('siteid');
+            const campaignId = urlParams.get('campaign_id') || urlParams.get('cid');
+            const keyword = urlParams.get('keyword') || urlParams.get('kw');
+            const creativeId = urlParams.get('creative_id') || urlParams.get('creative');
+
+            // 3. 构建完整的请求数据
+            const submitData = {
+                ...formData,
+                siteId: siteId ? parseInt(siteId) : null,
+                clickId: clickId,
+                source: source ? source.toUpperCase() : null,
+                campaignId: campaignId,
+                keyword: keyword,
+                creativeId: creativeId,
+                rawParams: allRawParams  // 包含所有URL参数(包括bd_vid等平台特定参数)
+            };
+
+            try {
+                // 4. 提交到线索接口(会自动触发转化事件)
+                const response = await fetch(`${API_BASE}/leads/submit`, {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify(submitData)
+                });
+
+                const result = await response.json();
+
+                if (result.code === 200) {
+                    console.log('表单提交成功:', result.data);
+                    showSuccessMessage(result.data.message || '提交成功,我们会尽快与您联系');
+                } else {
+                    showErrorMessage(result.message || '提交失败');
+                }
+
+            } catch (error) {
+                console.error('表单提交失败:', error);
+                showErrorMessage('网络错误,请稍后重试');
+            }
+        });
+
+        /**
+         * 关联用户ID到追踪记录
+         */
+        async function bindUserToTrace(traceId, userId) {
+            try {
+                const response = await fetch(`${API_BASE}/landing/bind-user`, {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/x-www-form-urlencoded'
+                    },
+                    body: `traceId=${traceId}&userId=${userId}`
+                });
+
+                const result = await response.json();
+
+                if (result.code === 200) {
+                    console.log('用户ID关联成功');
+                } else {
+                    console.warn('用户ID关联失败:', result.message);
+                }
+            } catch (error) {
+                console.error('关联接口调用失败:', error);
+            }
+        }
+
+        /**
+         * 显示成功消息
+         */
+        function showSuccessMessage(message) {
+            const successDiv = document.getElementById('successMessage');
+            if (message) {
+                successDiv.innerHTML = '✅ ' + message;
+            }
+            successDiv.style.display = 'block';
+            document.getElementById('errorMessage').style.display = 'none';
+            document.getElementById('registerForm').style.display = 'none';
+        }
+
+        /**
+         * 显示错误消息
+         */
+        function showErrorMessage(message) {
+            const errorDiv = document.getElementById('errorMessage');
+            errorDiv.textContent = '❌ ' + message;
+            errorDiv.style.display = 'block';
+            document.getElementById('successMessage').style.display = 'none';
+        }
+    </script>
+</body>
+</html>
+

+ 60 - 0
fs-common/src/main/java/com/fs/common/annotation/DistributeLock.java

@@ -0,0 +1,60 @@
+package com.fs.common.annotation;
+
+import com.fs.common.constant.DistributeLockConstant;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 分布式锁注解
+ *
+ * @author Hollis
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DistributeLock {
+
+    /**
+     * 锁的场景
+     *
+     * @return
+     */
+    public String scene();
+
+    /**
+     * 加锁的key,优先取key(),如果没有,则取keyExpression()
+     *
+     * @return
+     */
+    public String key() default DistributeLockConstant.NONE_KEY;
+
+    /**
+     * SPEL表达式:
+     * <pre>
+     *     #id
+     *     #insertResult.id
+     * </pre>
+     *
+     * @return
+     */
+    public String keyExpression() default DistributeLockConstant.NONE_KEY;
+
+    /**
+     * 超时时间,毫秒
+     * 默认情况下不设置超时时间,会自动续期
+     *
+     * @return
+     */
+    public int expireTime() default DistributeLockConstant.DEFAULT_EXPIRE_TIME;
+
+    public String errorMsg() default DistributeLockConstant.ERROR_MSG;
+
+    /**
+     * 加锁等待时长,毫秒
+     * 默认情况下不设置等待时长,会一直等待直到获取到锁
+     * @return
+     */
+    public int waitTime() default DistributeLockConstant.DEFAULT_WAIT_TIME;
+}

+ 13 - 0
fs-common/src/main/java/com/fs/common/constant/DistributeLockConstant.java

@@ -0,0 +1,13 @@
+package com.fs.common.constant;
+
+public class DistributeLockConstant {
+
+    public static final String NONE_KEY = "NONE";
+
+    public static final String DEFAULT_OWNER = "DEFAULT";
+
+    public static final int DEFAULT_EXPIRE_TIME = -1;
+
+    public static final int DEFAULT_WAIT_TIME = Integer.MAX_VALUE;
+    public static final String ERROR_MSG  = "请勿重复操作";
+}

+ 24 - 0
fs-common/src/main/java/com/fs/common/exception/DistributeLockException.java

@@ -0,0 +1,24 @@
+package com.fs.common.exception;
+
+
+public class DistributeLockException extends RuntimeException {
+
+    public DistributeLockException() {
+    }
+
+    public DistributeLockException(String message) {
+        super(message);
+    }
+
+    public DistributeLockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public DistributeLockException(Throwable cause) {
+        super(cause);
+    }
+
+    public DistributeLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 4 - 0
fs-common/src/main/java/com/fs/common/utils/RedisUtil.java

@@ -116,5 +116,9 @@ public class RedisUtil {
     public Boolean hHasKey(String key, String field) {
         return redisTemplate.opsForHash().hasKey(key, field);
     }
+
+    public Boolean setIfAbsent(String key, String number, int i, TimeUnit timeUnit) {
+        return redisTemplate.opsForValue().setIfAbsent(key, number, i, timeUnit);
+    }
 }
 

+ 7 - 23
fs-company/src/main/java/com/fs/company/controller/newAdv/SiteController.java

@@ -3,9 +3,7 @@ package com.fs.company.controller.newAdv;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.result.Result;
 import com.fs.newAdv.domain.Site;
-import com.fs.newAdv.domain.SiteStatistics;
-import com.fs.newAdv.mapper.SiteMapper;
-import com.fs.newAdv.mapper.SiteStatisticsMapper;
+import com.fs.newAdv.service.ISiteService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -24,14 +22,11 @@ import java.util.List;
 public class SiteController {
 
     @Autowired
-    private SiteMapper siteMapper;
-
-    @Autowired
-    private SiteStatisticsMapper statisticsMapper;
+    private ISiteService siteService;
 
     @GetMapping("/list")
     public Result<List<Site>> list() {
-        List<Site> list = siteMapper.selectList(new QueryWrapper<>());
+        List<Site> list = siteService.list(new QueryWrapper<>());
         return Result.success(list);
     }
 
@@ -40,7 +35,7 @@ public class SiteController {
      */
     @GetMapping("/{id}")
     public Result<Site> getById(@PathVariable Long id) {
-        Site site = siteMapper.selectById(id);
+        Site site = siteService.getById(id);
         return Result.success(site);
     }
 
@@ -49,7 +44,7 @@ public class SiteController {
      */
     @PostMapping
     public Result<Void> create(@RequestBody Site site) {
-        siteMapper.insert(site);
+        siteService.createSite(site);
         return Result.success();
     }
 
@@ -59,7 +54,7 @@ public class SiteController {
     @PutMapping("/{id}")
     public Result<Void> update(@PathVariable Long id, @RequestBody Site site) {
         site.setId(id);
-        siteMapper.updateById(site);
+        siteService.updateById(site);
         return Result.success();
     }
 
@@ -68,19 +63,8 @@ public class SiteController {
      */
     @DeleteMapping("/{id}")
     public Result<Void> delete(@PathVariable Long id) {
-        siteMapper.deleteById(id);
+        siteService.removeById(id);
         return Result.success();
     }
-
-    /**
-     * 查询站点统计数据
-     */
-    @GetMapping("/{id}/statistics")
-    public Result<SiteStatistics> getStatistics(@PathVariable Long id) {
-        QueryWrapper<SiteStatistics> wrapper = new QueryWrapper<>();
-        wrapper.eq("site_id", id);
-        SiteStatistics statistics = statisticsMapper.selectOne(wrapper);
-        return Result.success(statistics);
-    }
 }
 

+ 8 - 7
fs-service/src/main/java/com/fs/newAdv/domain/CallbackAccount.java

@@ -29,21 +29,22 @@ public class CallbackAccount implements Serializable {
      * 回传账号名称
      */
     private String callbackAccountName;
-
     /**
-     * 广告商转化类型
+     * ocpcId 回传账号id
      */
-    private String advertiserConversionType;
-
+    private String ocpcId;
     /**
-     * 回传数据类型
+     * ocpcId 回传账号token
      */
-    private String callbackDataType;
-
+    private String ocpcToken;
     /**
      * 着陆页URL
      */
     private String landingPageUrl;
+    /**
+     * 转换事件逗号分隔
+     */
+    private String conversionEvent;
 
     /**
      * 创建时间

+ 7 - 22
fs-service/src/main/java/com/fs/newAdv/domain/ClickTrace.java

@@ -24,11 +24,11 @@ public class ClickTrace implements Serializable {
     /**
      * 主键ID
      */
-    @TableId(type = IdType.INPUT)
+    @TableId(type = IdType.AUTO)
     private Long id;
 
     /**
-     * 追踪ID(唯一标识)
+     * 追踪ID(第三方唯一标识)
      */
     private String traceId;
 
@@ -62,11 +62,6 @@ public class ClickTrace implements Serializable {
      */
     private String creativeId;
 
-    /**
-     * 关键词
-     */
-    private String keyword;
-
     /**
      * 来源平台(baidu/oceanengine/sina/gdt)
      */
@@ -77,11 +72,6 @@ public class ClickTrace implements Serializable {
      */
     private String rawParams;
 
-    /**
-     * 访客ID(Cookie/设备ID)
-     */
-    private String visitorId;
-
     /**
      * 用户ID(注册后关联)
      */
@@ -93,24 +83,19 @@ public class ClickTrace implements Serializable {
     private String ip;
 
     /**
-     * User-Agent
+     * 是否回传:0=未回传,1=已回传
      */
-    private String userAgent;
-
+    private Integer isConverted;
     /**
-     * 是否已转化:0=未转化,1=已转化
+     * 落地页访问地址
      */
-    private Integer isConverted;
+    private String viewUrl;
 
     /**
-     * 转化时间
+     * 回传时间
      */
     private LocalDateTime conversionTime;
 
-    /**
-     * 访问时间
-     */
-    private LocalDateTime visitTime;
 
     /**
      * 创建时间

+ 8 - 15
fs-service/src/main/java/com/fs/newAdv/domain/ConversionLog.java

@@ -1,6 +1,7 @@
 package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -60,7 +61,7 @@ public class ConversionLog implements Serializable {
     private String callbackParams;
 
     /**
-     * 回传状态(0待回传 1成功 2失败)
+     * 回传状态(1成功 2失败)
      */
     private Integer callbackStatus;
 
@@ -75,30 +76,22 @@ public class ConversionLog implements Serializable {
     private LocalDateTime successTime;
 
     /**
-     * 错误信息
+     * 链路id(广告平台提供)
      */
-    private String errorMsg;
-
-    /**
-     * 用户ID
-     */
-    private Long userId;
-
-    /**
-     * 点击ID(广告平台提供)
-     */
-    private String clickId;
+    private String traceId;
 
     /**
      * 创建时间
      */
-    @TableField(fill = FieldFill.INSERT)
+    @TableField(value = "create_time", strategy = FieldStrategy.NOT_NULL)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;
 
     /**
      * 更新时间
      */
-    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @TableField(value = "update_time", strategy = FieldStrategy.NOT_NULL)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime updateTime;
 }
 

+ 5 - 54
fs-service/src/main/java/com/fs/newAdv/domain/Lead.java

@@ -8,7 +8,7 @@ import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
- * 线索表
+ * 线索落地页点击
  *
  * @author zhangqin
  * @date 2025-11-05
@@ -26,85 +26,36 @@ public class Lead implements Serializable {
     private Long id;
 
     /**
-     * 姓名
-     */
-    private String name;
-
-    /**
-     * 手机号
+     * phone
      */
     private String phone;
 
-    /**
-     * 公司名称
-     */
-    private String company;
-
-    /**
-     * 邮箱
-     */
-    private String email;
-
     /**
      * 站点ID
      */
     private Long siteId;
 
     /**
-     * 点击ID(广告平台提供)
+     * 链路id唯一
      */
-    private String clickId;
+    private String traceId;
 
     /**
      * 来源平台(BAIDU, OCEANENGINE, SINA, GDT)
      */
     private String source;
 
-    /**
-     * 广告计划ID
-     */
-    private String campaignId;
-
-    /**
-     * 关键词
-     */
-    private String keyword;
-
-    /**
-     * 创意ID
-     */
-    private String creativeId;
-
     /**
      * 原始参数JSON
      */
     private String rawParams;
 
-    /**
-     * 访客ID
-     */
-    private String visitorId;
-
-    /**
-     * 客户端IP
-     */
-    private String clientIp;
-
-    /**
-     * User-Agent
-     */
-    private String userAgent;
+    private String viewUrl;
 
     /**
      * 状态:0=新线索,1=已联系,2=已转化,3=无效
      */
     private Integer status;
-
-    /**
-     * 备注
-     */
-    private String remark;
-
     /**
      * 创建时间
      */

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

@@ -134,6 +134,10 @@ public class Site implements Serializable {
      * 转化目标ID
      */
     private Long conversionTargetId;
+    /**
+     * 站点URL
+     */
+    private String siteUrl;
 
     /**
      * 创建时间

+ 23 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/LandingIndexReq.java

@@ -0,0 +1,23 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class LandingIndexReq implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 站点Id
+     */
+    @NotNull(message = "站点Id不能为空")
+    private Long siteId;
+    /**
+     * 落地页地址
+     */
+    @NotBlank(message = "落地页地址不能为空")
+    private String viewUrl;
+
+}

+ 26 - 0
fs-service/src/main/java/com/fs/newAdv/dto/req/LeadSubmitRequest.java

@@ -0,0 +1,26 @@
+package com.fs.newAdv.dto.req;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 表单提交请求DTO
+ */
+@Data
+public class LeadSubmitRequest {
+
+    /**
+     * 站点ID
+     */
+    private Long siteId;
+    /**
+     * 落地页拼接后的url
+     */
+    private String viewUrl;
+    /**
+     * 原始URL参数(Map格式)
+     * 包含所有广告平台传递的参数,如 bd_vid, callback, clkid 等
+     */
+    private Map<String, String> rawParams;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/newAdv/dto/res/LandingIndexRes.java

@@ -0,0 +1,15 @@
+package com.fs.newAdv.dto.res;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class LandingIndexRes implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // 模板数据
+    private String templateData;
+
+
+}

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/mapper/ConversionLogMapper.java

@@ -19,7 +19,7 @@ public interface ConversionLogMapper extends BaseMapper<ConversionLog> {
     /**
      * 查询待回传的转化记录(失败且重试次数小于3)
      */
-    @Select("SELECT * FROM adv_conversion_log WHERE callback_status IN (0, 2) AND retry_count < 3 LIMIT 100")
+    @Select("SELECT * FROM adv_conversion_log WHERE callback_status = 2 AND retry_count < 3 LIMIT 100")
     List<ConversionLog> selectPendingConversions();
 }
 

+ 2 - 34
fs-service/src/main/java/com/fs/newAdv/service/IClickTraceService.java

@@ -1,31 +1,15 @@
 package com.fs.newAdv.service;
 
 
+import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.newAdv.domain.ClickTrace;
 
-import java.util.Map;
-
 /**
  * 点击追踪服务接口
  *
  * @author zhangqin
- * @date 2025-11-04
  */
-public interface IClickTraceService {
-
-    /**
-     * 保存点击追踪记录
-     *
-     * @param siteId    站点ID
-     * @param params    URL参数
-     * @param visitorId 访客ID
-     * @param ip        IP地址
-     * @param userAgent User-Agent
-     * @return 追踪记录
-     */
-    ClickTrace saveClickTrace(Long siteId, Map<String, String> params,
-                              String visitorId, String ip, String userAgent);
-
+public interface IClickTraceService extends IService<ClickTrace> {
     /**
      * 关联用户ID
      *
@@ -34,22 +18,6 @@ public interface IClickTraceService {
      */
     void bindUser(String traceId, Long userId);
 
-    /**
-     * 根据访客ID查询最近的追踪记录
-     *
-     * @param visitorId 访客ID
-     * @return 追踪记录
-     */
-    ClickTrace getLatestByVisitorId(String visitorId);
-
-    /**
-     * 根据用户ID查询最近的追踪记录
-     *
-     * @param userId 用户ID
-     * @return 追踪记录
-     */
-    ClickTrace getLatestByUserId(Long userId);
-
     /**
      * 标记为已转化
      *

+ 9 - 3
fs-service/src/main/java/com/fs/newAdv/service/ISiteService.java

@@ -1,12 +1,18 @@
 package com.fs.newAdv.service;
 
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.newAdv.domain.Site;
+
 /**
  * 站点管理服务
  *
  * @author zhangqin
- * @date 2025-11-06
  */
-public interface ISiteService {
-
+public interface ISiteService extends IService<Site> {
+    /**
+     * 创建站点
+     * @param site
+     */
+    void createSite(Site site);
 }
 

+ 2 - 120
fs-service/src/main/java/com/fs/newAdv/service/impl/ClickTraceServiceImpl.java

@@ -1,13 +1,9 @@
 package com.fs.newAdv.service.impl;
 
-import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-
-import com.fs.common.exception.base.BusinessException;
-import com.fs.common.utils.SnowflakeUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.newAdv.domain.ClickTrace;
-import com.fs.newAdv.domain.Site;
 import com.fs.newAdv.mapper.ClickTraceMapper;
 import com.fs.newAdv.mapper.SiteMapper;
 import com.fs.newAdv.service.IClickTraceService;
@@ -17,7 +13,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
-import java.util.Map;
 
 /**
  * 点击追踪服务实现类
@@ -27,7 +22,7 @@ import java.util.Map;
  */
 @Slf4j
 @Service
-public class ClickTraceServiceImpl implements IClickTraceService {
+public class ClickTraceServiceImpl extends ServiceImpl<ClickTraceMapper, ClickTrace> implements IClickTraceService {
 
     @Autowired
     private ClickTraceMapper clickTraceMapper;
@@ -35,99 +30,6 @@ public class ClickTraceServiceImpl implements IClickTraceService {
     @Autowired
     private SiteMapper siteMapper;
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public ClickTrace saveClickTrace(Long siteId, Map<String, String> params,
-                                     String visitorId, String ip, String userAgent) {
-
-        log.info("保存点击追踪记录:站点ID={},访客ID={}", siteId, visitorId);
-
-        // 解析站点信息
-        Site site = siteMapper.selectById(siteId);
-        if (site == null) {
-            throw new BusinessException("站点不存在:" + siteId);
-        }
-
-        ClickTrace trace = new ClickTrace();
-        trace.setId(SnowflakeUtil.nextId());
-        trace.setTraceId(SnowflakeUtil.nextIdStr());
-        trace.setSiteId(siteId);
-        trace.setAdvertiserId(site.getAdvertiserId());
-        trace.setAdvertiserName(site.getAdvertiserName());
-
-        // 提取不同平台的参数
-        extractPlatformParams(trace, params, site.getAdvertiserId());
-
-        // 访客信息
-        trace.setVisitorId(visitorId);
-        trace.setIp(ip);
-        trace.setUserAgent(userAgent);
-
-        // 保存原始参数
-        trace.setRawParams(JSONUtil.toJsonStr(params));
-
-        // 状态初始化
-        trace.setIsConverted(0);
-
-        // 时间
-        trace.setVisitTime(LocalDateTime.now());
-        trace.setCreateTime(LocalDateTime.now());
-        trace.setUpdateTime(LocalDateTime.now());
-
-        clickTraceMapper.insert(trace);
-
-        log.info("点击追踪记录保存成功:traceId={}", trace.getTraceId());
-        return trace;
-    }
-
-    /**
-     * 提取不同平台的参数
-     */
-    private void extractPlatformParams(ClickTrace trace,
-                                      Map<String, String> params,
-                                      Long advertiserId) {
-
-        // 根据广告商ID判断平台
-        // 1=百度,2=巨量引擎,3=新浪,4=广点通
-
-        if (advertiserId == 1L) {
-            // 百度
-            String clkId = params.get("clk_id");
-            String logId = params.get("logid");
-            trace.setClickId(clkId != null ? clkId : logId);
-            trace.setCampaignId(params.get("plan_id"));
-            trace.setCreativeId(params.get("unit_id"));
-            trace.setKeyword(params.get("keyword"));
-            trace.setSource("baidu");
-
-        } else if (advertiserId == 2L) {
-            // 巨量引擎
-            trace.setClickId(params.get("callback"));
-            trace.setCampaignId(params.get("campaign_id"));
-            trace.setCreativeId(params.get("creative_id"));
-            trace.setSource("oceanengine");
-
-        } else if (advertiserId == 3L) {
-            // 新浪
-            trace.setClickId(params.get("click_id"));
-            trace.setCampaignId(params.get("cid"));
-            trace.setSource("sina");
-
-        } else if (advertiserId == 4L) {
-            // 广点通
-            trace.setClickId(params.get("gdt_vid"));
-            trace.setCampaignId(params.get("campaign_id"));
-            trace.setSource("gdt");
-
-        } else {
-            // 通用处理
-            trace.setClickId(params.get("click_id"));
-            trace.setCampaignId(params.get("campaign_id"));
-            trace.setCreativeId(params.get("creative_id"));
-            trace.setSource(params.get("source"));
-        }
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void bindUser(String traceId, Long userId) {
@@ -147,26 +49,6 @@ public class ClickTraceServiceImpl implements IClickTraceService {
         }
     }
 
-    @Override
-    public ClickTrace getLatestByVisitorId(String visitorId) {
-        LambdaQueryWrapper<ClickTrace> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(ClickTrace::getVisitorId, visitorId);
-        wrapper.orderByDesc(ClickTrace::getVisitTime);
-        wrapper.last("LIMIT 1");
-
-        return clickTraceMapper.selectOne(wrapper);
-    }
-
-    @Override
-    public ClickTrace getLatestByUserId(Long userId) {
-        LambdaQueryWrapper<ClickTrace> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(ClickTrace::getUserId, userId);
-        wrapper.orderByDesc(ClickTrace::getVisitTime);
-        wrapper.last("LIMIT 1");
-
-        return clickTraceMapper.selectOne(wrapper);
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void markAsConverted(Long traceId) {

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

@@ -6,6 +6,7 @@ import com.fs.newAdv.mapper.SiteMapper;
 import com.fs.newAdv.service.ISiteService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 域名管理Service实现类
@@ -16,6 +17,12 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @Service
 public class SiteServiceImpl extends ServiceImpl<SiteMapper, Site> implements ISiteService {
-
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createSite(Site site) {
+        this.save(site);
+        site.setSiteUrl("http://" + site.getLaunchDomain() + "site/home.html/?tid=" + site.getId());
+        this.updateById(site);
+    }
 }