Przeglądaj źródła

coding:投流代码提交

zhangqin 1 miesiąc temu
rodzic
commit
18de8d1714
100 zmienionych plików z 3303 dodań i 718 usunięć
  1. 87 0
      fs-ad-new-api/pom.xml
  2. 14 0
      fs-ad-new-api/src/main/java/com/fs/FSServletInitializer.java
  3. 22 0
      fs-ad-new-api/src/main/java/com/fs/FsAdNewApiApplication.java
  4. 31 0
      fs-ad-new-api/src/main/java/com/fs/app/annotation/DistributedLock.java
  5. 79 0
      fs-ad-new-api/src/main/java/com/fs/app/aspect/DistributedLockAspect.java
  6. 57 0
      fs-ad-new-api/src/main/java/com/fs/app/config/AsyncConfig.java
  7. 119 0
      fs-ad-new-api/src/main/java/com/fs/app/config/MybatisPlusConfig.java
  8. 51 0
      fs-ad-new-api/src/main/java/com/fs/app/config/RedisConfig.java
  9. 25 0
      fs-ad-new-api/src/main/java/com/fs/app/config/RocketMqConfig.java
  10. 42 0
      fs-ad-new-api/src/main/java/com/fs/app/config/ScheduleConfig.java
  11. 77 0
      fs-ad-new-api/src/main/java/com/fs/app/config/ThreadPoolConfig.java
  12. 41 0
      fs-ad-new-api/src/main/java/com/fs/app/config/WebMvcConfig.java
  13. 48 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java
  14. 45 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/LeadController.java
  15. 139 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/RocketMqTestController.java
  16. 417 0
      fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java
  17. 67 0
      fs-ad-new-api/src/main/java/com/fs/app/dto/req/LeadSubmitRequest.java
  18. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/enums/AdvertiserTypeEnum.java
  19. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/enums/CallbackStatusEnum.java
  20. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/enums/TaskStatusEnum.java
  21. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/enums/TaskTypeEnum.java
  22. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEvent.java
  23. 4 3
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventListener.java
  24. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventPublisher.java
  25. 26 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeService.java
  26. 313 0
      fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java
  27. 7 8
      fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java
  28. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java
  29. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/BaiduAdapter.java
  30. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/IAdvertiserAdapter.java
  31. 3 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/adapter/OceanEngineAdapter.java
  32. 6 6
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/BaiduApiClient.java
  33. 3 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/client/OceanEngineApiClient.java
  34. 4 3
      fs-ad-new-api/src/main/java/com/fs/app/integration/factory/AdvertiserHandlerFactory.java
  35. 3 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/AbstractCallbackHandler.java
  36. 209 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/BaiduCallbackHandler.java
  37. 203 0
      fs-ad-new-api/src/main/java/com/fs/app/integration/handler/OceanEngineCallbackHandler.java
  38. 3 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/BaiduCallbackStrategy.java
  39. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/ICallbackStrategy.java
  40. 2 2
      fs-ad-new-api/src/main/java/com/fs/app/integration/strategy/OceanEngineCallbackStrategy.java
  41. 65 0
      fs-ad-new-api/src/main/java/com/fs/app/manager/ConversionManager.java
  42. 30 0
      fs-ad-new-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  43. 4 3
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionDLQConsumer.java
  44. 4 4
      fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer.java
  45. 1 1
      fs-ad-new-api/src/main/java/com/fs/app/mq/message/ConversionMessage.java
  46. 37 38
      fs-ad-new-api/src/main/java/com/fs/app/mq/producer/ConversionMessageProducer.java
  47. 89 0
      fs-ad-new-api/src/main/java/com/fs/app/task/ConversionRetryTask.java
  48. 126 0
      fs-ad-new-api/src/main/java/com/fs/app/task/DailyArchiveTask.java
  49. 115 0
      fs-ad-new-api/src/main/java/com/fs/app/task/DataSyncTask.java
  50. 82 0
      fs-ad-new-api/src/main/java/com/fs/app/task/MonthlyArchiveTask.java
  51. 69 0
      fs-ad-new-api/src/main/java/com/fs/app/task/RedisDataPersistTask.java
  52. 34 0
      fs-ad-new-api/src/main/java/com/fs/app/task/Task.java
  53. 72 0
      fs-ad-new-api/src/main/java/com/fs/app/task/TaskCompensationTask.java
  54. 109 0
      fs-ad-new-api/src/main/java/com/fs/app/task/WeeklyArchiveTask.java
  55. 1 0
      fs-ad-new-api/src/main/resources/META-INF/spring-devtools.properties
  56. 7 0
      fs-ad-new-api/src/main/resources/application.yml
  57. 2 0
      fs-ad-new-api/src/main/resources/banner.txt
  58. 37 0
      fs-ad-new-api/src/main/resources/i18n/messages.properties
  59. 93 0
      fs-ad-new-api/src/main/resources/logback.xml
  60. 15 0
      fs-ad-new-api/src/main/resources/mybatis/mybatis-config.xml
  61. 4 4
      fs-company/src/main/java/com/fs/company/controller/newAdv/AdvertiserController.java
  62. 1 1
      fs-company/src/main/java/com/fs/company/controller/newAdv/CallbackAccountController.java
  63. 43 65
      fs-company/src/main/java/com/fs/company/controller/newAdv/DomainController.java
  64. 5 22
      fs-company/src/main/java/com/fs/company/controller/newAdv/LandingPageTemplateController.java
  65. 1 1
      fs-company/src/main/java/com/fs/company/controller/newAdv/LeadSourceController.java
  66. 1 1
      fs-company/src/main/java/com/fs/company/controller/newAdv/PromotionAccountController.java
  67. 2 2
      fs-company/src/main/java/com/fs/company/controller/newAdv/SiteController.java
  68. 1 1
      fs-company/src/main/java/com/fs/company/controller/newAdv/TrackingLinkController.java
  69. 4 0
      fs-company/src/main/java/com/fs/framework/config/MyBatisConfig.java
  70. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/Advertiser.java
  71. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/AlertLog.java
  72. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/ApiCallLog.java
  73. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/CallbackAccount.java
  74. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/CallbackLog.java
  75. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/ClickTrace.java
  76. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/ConversionLog.java
  77. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/ConversionTarget.java
  78. 8 5
      fs-service/src/main/java/com/fs/newAdv/domain/DomainUrl.java
  79. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/LandingPageTemplate.java
  80. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/Lead.java
  81. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/LeadSource.java
  82. 11 3
      fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java
  83. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/ScheduleTaskLog.java
  84. 7 4
      fs-service/src/main/java/com/fs/newAdv/domain/Site.java
  85. 6 3
      fs-service/src/main/java/com/fs/newAdv/domain/SiteStatistics.java
  86. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsDaily.java
  87. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsMonthly.java
  88. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsWeekly.java
  89. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/SyncLog.java
  90. 1 1
      fs-service/src/main/java/com/fs/newAdv/domain/TrackingLink.java
  91. 0 93
      fs-service/src/main/java/com/fs/newAdv/entity/OperationLog.java
  92. 0 197
      fs-service/src/main/java/com/fs/newAdv/integration/handler/BaiduCallbackHandler.java
  93. 0 191
      fs-service/src/main/java/com/fs/newAdv/integration/handler/OceanEngineCallbackHandler.java
  94. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/AdvertiserMapper.java
  95. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/AlertLogMapper.java
  96. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/ApiCallLogMapper.java
  97. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/CallbackAccountMapper.java
  98. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/CallbackLogMapper.java
  99. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/ClickTraceMapper.java
  100. 1 1
      fs-service/src/main/java/com/fs/newAdv/mapper/ConversionLogMapper.java

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

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>fs</artifactId>
+        <groupId>com.fs</groupId>
+        <version>1.1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>fs-ad-new-api</artifactId>
+    <description>
+       新投流接口
+    </description>
+    <dependencies>
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-service</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.3</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 14 - 0
fs-ad-new-api/src/main/java/com/fs/FSServletInitializer.java

@@ -0,0 +1,14 @@
+package com.fs;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+
+public class FSServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(FsAdNewApiApplication.class);
+    }
+}

+ 22 - 0
fs-ad-new-api/src/main/java/com/fs/FsAdNewApiApplication.java

@@ -0,0 +1,22 @@
+package com.fs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 启动程序
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
+@EnableAsync
+@EnableScheduling
+public class FsAdNewApiApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(FsAdNewApiApplication.class, args);
+        System.out.println("========================================");
+        System.out.println("ad-new-api启动成功");
+        System.out.println("========================================");
+    }
+}

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

@@ -0,0 +1,31 @@
+package com.fs.app.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;
+}
+

+ 79 - 0
fs-ad-new-api/src/main/java/com/fs/app/aspect/DistributedLockAspect.java

@@ -0,0 +1,79 @@
+package com.fs.app.aspect;
+
+import cn.hutool.core.util.StrUtil;
+
+
+import com.fs.app.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.app.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/app/config/AsyncConfig.java

@@ -0,0 +1,57 @@
+package com.fs.app.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;
+    }
+}
+

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

@@ -0,0 +1,119 @@
+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);
+            }
+        };
+    }
+}
+

+ 51 - 0
fs-ad-new-api/src/main/java/com/fs/app/config/RedisConfig.java

@@ -0,0 +1,51 @@
+package com.fs.app.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * Redis配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Configuration
+public class RedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
+        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        // 使用enableDefaultTyping替代activateDefaultTyping(兼容Spring Boot 2.1.1的Jackson版本)
+        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        serializer.setObjectMapper(mapper);
+
+        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
+
+        // key采用String的序列化方式
+        template.setKeySerializer(stringRedisSerializer);
+        // hash的key也采用String的序列化方式
+        template.setHashKeySerializer(stringRedisSerializer);
+        // value序列化方式采用jackson
+        template.setValueSerializer(serializer);
+        // hash的value序列化方式采用jackson
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+}
+

+ 25 - 0
fs-ad-new-api/src/main/java/com/fs/app/config/RocketMqConfig.java

@@ -0,0 +1,25 @@
+package com.fs.app.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * RocketMQ配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-05
+ */
+@Slf4j
+@Configuration
+public class RocketMqConfig {
+
+    @PostConstruct
+    public void init() {
+        log.info("========================================");
+        log.info(" RocketMQ已启用 - 异步转化回传功能开启");
+        log.info("========================================");
+    }
+}
+

+ 42 - 0
fs-ad-new-api/src/main/java/com/fs/app/config/ScheduleConfig.java

@@ -0,0 +1,42 @@
+package com.fs.app.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+/**
+ * 定时任务配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Configuration
+public class ScheduleConfig implements SchedulingConfigurer {
+
+    @Override
+    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+
+        // 线程池大小
+        scheduler.setPoolSize(10);
+
+        // 线程名称前缀
+        scheduler.setThreadNamePrefix("schedule-task-");
+
+        // 等待所有任务完成后再关闭线程池
+        scheduler.setWaitForTasksToCompleteOnShutdown(true);
+
+        // 等待时间
+        scheduler.setAwaitTerminationSeconds(60);
+
+        scheduler.initialize();
+
+        taskRegistrar.setTaskScheduler(scheduler);
+
+        log.info("定时任务线程池初始化完成");
+    }
+}
+

+ 77 - 0
fs-ad-new-api/src/main/java/com/fs/app/config/ThreadPoolConfig.java

@@ -0,0 +1,77 @@
+package com.fs.app.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程池配置类
+ * 用于业务处理的通用线程池
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Configuration
+public class ThreadPoolConfig {
+
+    /**
+     * 通用业务线程池
+     */
+    @Bean(name = "businessExecutor")
+    public ExecutorService businessExecutor() {
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                // 核心线程数
+                10,
+                // 最大线程数
+                20,
+                // 线程存活时间
+                60L,
+                // 时间单位
+                TimeUnit.SECONDS,
+                // 阻塞队列
+                new LinkedBlockingQueue<>(500),
+                // 线程工厂
+                new ThreadFactory() {
+                    private int count = 0;
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        return new Thread(r, "business-thread-" + count++);
+                    }
+                },
+                // 拒绝策略
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+
+        log.info("业务线程池初始化完成");
+        return executor;
+    }
+
+    /**
+     * 数据同步线程池
+     */
+    @Bean(name = "syncExecutor")
+    public ExecutorService syncExecutor() {
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                5,
+                10,
+                60L,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(200),
+                new ThreadFactory() {
+                    private int count = 0;
+                    @Override
+                    public Thread newThread(Runnable r) {
+                        return new Thread(r, "sync-thread-" + count++);
+                    }
+                },
+                new ThreadPoolExecutor.CallerRunsPolicy()
+        );
+
+        log.info("数据同步线程池初始化完成");
+        return executor;
+    }
+}
+

+ 41 - 0
fs-ad-new-api/src/main/java/com/fs/app/config/WebMvcConfig.java

@@ -0,0 +1,41 @@
+package com.fs.app.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * WebMvc配置类
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Configuration
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    /**
+     * 跨域配置
+     */
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                // Spring Boot 2.1.x 使用 allowedOrigins
+                .allowedOrigins("*")
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+                .allowedHeaders("*")
+                .maxAge(3600);
+    }
+
+    /**
+     * 静态资源映射
+     * 将 landing.html 映射到根路径,绕过 context-path
+     * 使落地页可以通过 http://127.0.0.1:8080/landing.html 访问
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/landing.html")
+                .addResourceLocations("classpath:/static/");
+    }
+}
+

+ 48 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/LandingPageController.java

@@ -0,0 +1,48 @@
+package com.fs.app.controller;
+
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 落地页控制器
+ * 用于接收广告平台跳转时携带的参数
+ *
+ * @author zhangqin
+ * @date 2025-11-04
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/landing")
+public class LandingPageController {
+
+    @Autowired
+    private CallbackProcessingFacadeService callbackProcessingFacadeService;
+
+    /**
+     * 落地页访问
+     */
+    @GetMapping("/track")
+    public Result<Map<String, Object>> 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);
+    }
+
+
+}
+

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

@@ -0,0 +1,45 @@
+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);
+
+    }
+
+
+}
+

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

@@ -0,0 +1,139 @@
+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());
+        }
+    }
+}
+
+
+

+ 417 - 0
fs-ad-new-api/src/main/java/com/fs/app/controller/TrackingController.java

@@ -0,0 +1,417 @@
+package com.fs.app.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.fs.newAdv.service.IClickTraceService;
+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 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")
+public class TrackingController {
+
+    @Autowired
+    private IClickTraceService clickTraceService;
+
+    /**
+     * 百度推广监测端点
+     * <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));
+
+        try {
+            // 1. 提取百度特有参数
+            String clickId = allParams.get("clkid");
+            if (StrUtil.isBlank(clickId)) {
+                clickId = allParams.get("logid"); // 百度也可能使用 logid
+            }
+
+            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", "BAIDU");
+
+            // 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);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    /**
+     * 巨量引擎监测端点
+     * <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请求
+     * @param response  HTTP响应
+     */
+    @GetMapping("/oceanengine")
+    public void trackOceanEngine(
+            @RequestParam Map<String, String> allParams,
+            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);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    /**
+     * 新浪扶翼监测端点
+     * <p>
+     * 监测链接示例:
+     * https://track.yourdomain.com/api/track/sina?click_id={click_id}&site_id=100&campaign_id={campaign_id}
+     * <p>
+     * 新浪宏参数说明:
+     * - {click_id}: 点击ID(必填)
+     * - {campaign_id}: 广告计划ID
+     * - {ad_id}: 广告ID
+     *
+     * @param allParams 所有URL参数
+     * @param request   HTTP请求
+     * @param response  HTTP响应
+     */
+    @GetMapping("/sina")
+    public void trackSina(
+            @RequestParam Map<String, String> allParams,
+            HttpServletRequest request,
+            HttpServletResponse response) {
+
+        log.info("【新浪】接收监测请求 | params={}, ip={}",
+                allParams, getClientIp(request));
+
+        try {
+            // 1. 提取新浪参数
+            String 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", "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);
+
+        } catch (Exception e) {
+            log.error("【新浪】监测请求处理失败 | params={}", allParams, e);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    /**
+     * 腾讯广点通监测端点
+     * <p>
+     * 监测链接示例:
+     * https://track.yourdomain.com/api/track/gdt?click_id={CLICK_ID}&site_id=100&ad_id={ADGROUPID}
+     * <p>
+     * 广点通宏参数说明:
+     * - {CLICK_ID}: 点击ID(必填)
+     * - {ADGROUPID}: 广告组ID
+     * - {CREATIVEID}: 创意ID
+     * - {CAMPAIGNID}: 推广计划ID
+     *
+     * @param allParams 所有URL参数
+     * @param request   HTTP请求
+     * @param response  HTTP响应
+     */
+    @GetMapping("/gdt")
+    public void trackGdt(
+            @RequestParam Map<String, String> allParams,
+            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);
+
+        } catch (Exception e) {
+            log.error("【广点通】监测请求处理失败 | params={}", allParams, e);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    /**
+     * 通用监测端点(兼容旧配置)
+     * <p>
+     * 如果广告平台已配置了统一的监测链接,仍可使用此端点
+     * 需要在参数中指定 source 参数
+     * <p>
+     * 示例:
+     * https://track.yourdomain.com/api/track/common?click_id=ABC123&site_id=100&source=baidu
+     *
+     * @param allParams 所有URL参数
+     * @param request   HTTP请求
+     * @param response  HTTP响应
+     */
+    @GetMapping("/common")
+    public void trackCommon(
+            @RequestParam Map<String, String> allParams,
+            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);
+
+        } catch (Exception e) {
+            log.error("【通用】监测请求处理失败 | params={}", allParams, e);
+            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;
+    }
+}

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

@@ -0,0 +1,67 @@
+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;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/enums/AdvertiserTypeEnum.java → fs-ad-new-api/src/main/java/com/fs/app/enums/AdvertiserTypeEnum.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.enums;
+package com.fs.app.enums;
 
 import lombok.Getter;
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/enums/CallbackStatusEnum.java → fs-ad-new-api/src/main/java/com/fs/app/enums/CallbackStatusEnum.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.enums;
+package com.fs.app.enums;
 
 import lombok.Getter;
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/enums/TaskStatusEnum.java → fs-ad-new-api/src/main/java/com/fs/app/enums/TaskStatusEnum.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.enums;
+package com.fs.app.enums;
 
 import lombok.Getter;
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/enums/TaskTypeEnum.java → fs-ad-new-api/src/main/java/com/fs/app/enums/TaskTypeEnum.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.enums;
+package com.fs.app.enums;
 
 import lombok.Getter;
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/event/ConversionEvent.java → fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEvent.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.event;
+package com.fs.app.event;
 
 import lombok.Getter;
 import org.springframework.context.ApplicationEvent;

+ 4 - 3
fs-service/src/main/java/com/fs/newAdv/event/ConversionEventListener.java → fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventListener.java

@@ -1,8 +1,9 @@
-package com.fs.newAdv.event;
+package com.fs.app.event;
 
 import cn.hutool.core.util.StrUtil;
-import com.fs.newAdv.mq.message.ConversionMessage;
-import com.fs.newAdv.mq.producer.ConversionMessageProducer;
+
+import com.fs.app.mq.message.ConversionMessage;
+import com.fs.app.mq.producer.ConversionMessageProducer;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.event.EventListener;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/event/ConversionEventPublisher.java → fs-ad-new-api/src/main/java/com/fs/app/event/ConversionEventPublisher.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.event;
+package com.fs.app.event;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

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

@@ -0,0 +1,26 @@
+package com.fs.app.facade;
+
+import com.fs.app.dto.req.LeadSubmitRequest;
+import com.fs.common.result.Result;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+public interface CallbackProcessingFacadeService {
+    /**
+     * 落地页访问接口
+     *
+     * @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);
+
+
+
+}

+ 313 - 0
fs-ad-new-api/src/main/java/com/fs/app/facade/CallbackProcessingFacadeServiceImpl.java

@@ -0,0 +1,313 @@
+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.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.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.Lead;
+import com.fs.newAdv.mapper.SiteStatisticsMapper;
+import com.fs.newAdv.service.IClickTraceService;
+import com.fs.newAdv.service.ILeadService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class CallbackProcessingFacadeServiceImpl implements CallbackProcessingFacadeService {
+    @Autowired
+    private IClickTraceService clickTraceService;
+
+    @Autowired
+    private BaiduApiClient baiduApiClient;
+
+    @Autowired
+    private AdvertiserHandlerFactory handlerFactory;
+    @Autowired
+    private ILeadService leadService;
+
+    @Autowired
+    private SiteStatisticsMapper statisticsMapper;
+
+    @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")
+            );
+
+            // 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 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, "提交失败,请稍后重试");
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    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);
+                // 不影响线索保存,只记录日志
+            }
+        } else {
+            log.warn("缺少点击ID或来源信息,跳过转化事件 | leadId={}, clickId={}, source={}",
+                    lead.getId(), lead.getClickId(), lead.getSource());
+        }
+
+        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;
+                    }
+                }
+            }
+        }
+
+        // 生成新的访客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");
+        }
+        if (StrUtil.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        // 处理多个IP的情况,取第一个
+        if (StrUtil.isNotBlank(ip) && ip.contains(",")) {
+            ip = ip.split(",")[0].trim();
+        }
+
+        return ip;
+    }
+
+
+    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 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
+     */
+    private Map<String, String> convertToStringMap(Map<String, Object> map) {
+        Map<String, String> result = new HashMap<>();
+        map.forEach((key, value) -> {
+            if (value != null) {
+                result.put(key, value.toString());
+            }
+        });
+        return result;
+    }
+}

+ 7 - 8
fs-service/src/main/java/com/fs/newAdv/service/impl/ConversionServiceImpl.java → fs-ad-new-api/src/main/java/com/fs/app/facade/ConversionServiceImpl.java

@@ -1,23 +1,22 @@
-package com.fs.newAdv.service.impl;
+package com.fs.app.facade;
 
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.entity.ConversionLog;
-import com.fs.newAdv.entity.ConversionTarget;
-import com.fs.newAdv.entity.PromotionAccount;
-import com.fs.newAdv.entity.Site;
-import com.fs.newAdv.integration.client.BaiduApiClient;
-import com.fs.newAdv.integration.client.OceanEngineApiClient;
+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.IClickTraceService;
-import com.fs.newAdv.service.IConversionService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/service/IConversionService.java → fs-ad-new-api/src/main/java/com/fs/app/facade/IConversionService.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.service;
+package com.fs.app.facade;
 
 /**
  * 转化回传服务接口

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

@@ -1,8 +1,8 @@
-package com.fs.newAdv.integration.adapter;
+package com.fs.app.integration.adapter;
 
 import cn.hutool.core.map.MapUtil;
 
-import com.fs.newAdv.enums.AdvertiserTypeEnum;
+import com.fs.app.enums.AdvertiserTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 

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

@@ -1,4 +1,4 @@
-package com.fs.newAdv.integration.adapter;
+package com.fs.app.integration.adapter;
 
 import java.util.Map;
 

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

@@ -1,7 +1,8 @@
-package com.fs.newAdv.integration.adapter;
+package com.fs.app.integration.adapter;
 
 import cn.hutool.core.map.MapUtil;
-import com.fs.newAdv.enums.AdvertiserTypeEnum;
+
+import com.fs.app.enums.AdvertiserTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 

+ 6 - 6
fs-service/src/main/java/com/fs/newAdv/integration/client/BaiduApiClient.java → fs-ad-new-api/src/main/java/com/fs/app/integration/client/BaiduApiClient.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.integration.client;
+package com.fs.app.integration.client;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpRequest;
@@ -8,7 +8,7 @@ import cn.hutool.json.JSONUtil;
 import com.fs.common.constant.SystemConstant;
 import com.fs.common.exception.ThirdPartyException;
 import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.entity.ApiCallLog;
+import com.fs.newAdv.domain.ApiCallLog;
 import com.fs.newAdv.mapper.ApiCallLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,7 +48,7 @@ public class BaiduApiClient {
     /**
      * 回传转化数据到百度
      *
-     * @param accessToken    访问令牌
+     * @param accessToken 访问令牌
      * @param conversionData 转化数据
      * @return 是否成功
      */
@@ -160,7 +160,7 @@ public class BaiduApiClient {
     /**
      * 获取访问令牌(百度OAuth2.0)
      *
-     * @param apiKey    API Key
+     * @param apiKey API Key
      * @param secretKey Secret Key
      * @return 访问令牌
      */
@@ -199,8 +199,8 @@ public class BaiduApiClient {
      * 查询账户报告数据
      *
      * @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) {

+ 3 - 2
fs-service/src/main/java/com/fs/newAdv/integration/client/OceanEngineApiClient.java → fs-ad-new-api/src/main/java/com/fs/app/integration/client/OceanEngineApiClient.java

@@ -1,14 +1,15 @@
-package com.fs.newAdv.integration.client;
+package com.fs.app.integration.client;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
+
 import com.fs.common.constant.SystemConstant;
 import com.fs.common.exception.ThirdPartyException;
 import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.entity.ApiCallLog;
+import com.fs.newAdv.domain.ApiCallLog;
 import com.fs.newAdv.mapper.ApiCallLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

+ 4 - 3
fs-service/src/main/java/com/fs/newAdv/integration/factory/AdvertiserHandlerFactory.java → fs-ad-new-api/src/main/java/com/fs/app/integration/factory/AdvertiserHandlerFactory.java

@@ -1,8 +1,9 @@
-package com.fs.newAdv.integration.factory;
+package com.fs.app.integration.factory;
 
+
+import com.fs.app.integration.adapter.IAdvertiserAdapter;
+import com.fs.app.integration.strategy.ICallbackStrategy;
 import com.fs.common.exception.base.BusinessException;
-import com.fs.newAdv.integration.adapter.IAdvertiserAdapter;
-import com.fs.newAdv.integration.strategy.ICallbackStrategy;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;

+ 3 - 2
fs-service/src/main/java/com/fs/newAdv/integration/handler/AbstractCallbackHandler.java → fs-ad-new-api/src/main/java/com/fs/app/integration/handler/AbstractCallbackHandler.java

@@ -1,8 +1,9 @@
-package com.fs.newAdv.integration.handler;
+package com.fs.app.integration.handler;
 
 import cn.hutool.json.JSONUtil;
+
 import com.fs.common.utils.SnowflakeUtil;
-import com.fs.newAdv.entity.CallbackLog;
+import com.fs.newAdv.domain.CallbackLog;
 import com.fs.newAdv.mapper.CallbackLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

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

@@ -0,0 +1,209 @@
+/*
+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();
+    }
+}
+*/

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

@@ -0,0 +1,203 @@
+/*
+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();
+    }
+}
+
+*/

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

@@ -1,8 +1,9 @@
-package com.fs.newAdv.integration.strategy;
+package com.fs.app.integration.strategy;
 
 import cn.hutool.core.map.MapUtil;
+
+import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.common.utils.SignatureUtil;
-import com.fs.newAdv.enums.AdvertiserTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 

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

@@ -1,4 +1,4 @@
-package com.fs.newAdv.integration.strategy;
+package com.fs.app.integration.strategy;
 
 import java.util.Map;
 

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

@@ -1,8 +1,8 @@
-package com.fs.newAdv.integration.strategy;
+package com.fs.app.integration.strategy;
 
 import cn.hutool.core.map.MapUtil;
+import com.fs.app.enums.AdvertiserTypeEnum;
 import com.fs.common.utils.SignatureUtil;
-import com.fs.newAdv.enums.AdvertiserTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 

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

@@ -0,0 +1,65 @@
+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("批量处理转化");
+    }
+}

+ 30 - 0
fs-ad-new-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -0,0 +1,30 @@
+package com.fs.app.mq;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.vo.AdUploadVo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+@RocketMQMessageListener(topic = "ad-upload", consumerGroup = "${rocketmq.consumer.group}")
+public class RocketMQConsumerService implements RocketMQListener<String> {
+
+    private final IAdHtmlClickLogService  adHtmlClickLogService;
+
+    @Override
+    public void onMessage(String message) {
+        AdUploadVo adUploadVo = JSON.parseObject(message, AdUploadVo.class);
+        if(adUploadVo == null  || StringUtils.isEmpty(adUploadVo.getState()) || "null".equals(adUploadVo.getState())){
+            return;
+        }
+        adHtmlClickLogService.upload(adUploadVo.getState(), adUploadVo.getType());
+    }
+}

+ 4 - 3
fs-service/src/main/java/com/fs/newAdv/mq/consumer/ConversionDLQConsumer.java → fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionDLQConsumer.java

@@ -1,8 +1,9 @@
-package com.fs.newAdv.mq.consumer;
+package com.fs.app.mq.consumer;
 
-import com.fs.newAdv.entity.ConversionLog;
+
+import com.fs.app.mq.message.ConversionMessage;
+import com.fs.newAdv.domain.ConversionLog;
 import com.fs.newAdv.mapper.ConversionLogMapper;
-import com.fs.newAdv.mq.message.ConversionMessage;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
 import org.apache.rocketmq.spring.core.RocketMQListener;

+ 4 - 4
fs-service/src/main/java/com/fs/newAdv/mq/consumer/ConversionMessageConsumer.java → fs-ad-new-api/src/main/java/com/fs/app/mq/consumer/ConversionMessageConsumer.java

@@ -1,10 +1,10 @@
-package com.fs.newAdv.mq.consumer;
+package com.fs.app.mq.consumer;
 
 import cn.hutool.core.util.StrUtil;
-import com.fs.newAdv.entity.ConversionLog;
+import com.fs.app.mq.message.ConversionMessage;
+import com.fs.newAdv.domain.ConversionLog;
 import com.fs.newAdv.mapper.ConversionLogMapper;
-import com.fs.newAdv.mq.message.ConversionMessage;
-import com.fs.newAdv.service.IConversionService;
+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;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/mq/message/ConversionMessage.java → fs-ad-new-api/src/main/java/com/fs/app/mq/message/ConversionMessage.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.mq.message;
+package com.fs.app.mq.message;
 
 import lombok.Data;
 

+ 37 - 38
fs-service/src/main/java/com/fs/newAdv/mq/producer/ConversionMessageProducer.java → fs-ad-new-api/src/main/java/com/fs/app/mq/producer/ConversionMessageProducer.java

@@ -1,8 +1,7 @@
-package com.fs.newAdv.mq.producer;
+package com.fs.app.mq.producer;
 
 import cn.hutool.core.util.StrUtil;
-
-import com.fs.newAdv.mq.message.ConversionMessage;
+import com.fs.app.mq.message.ConversionMessage;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.client.producer.SendCallback;
 import org.apache.rocketmq.client.producer.SendResult;
@@ -60,35 +59,35 @@ public class ConversionMessageProducer {
 
             // 3. 异步发送消息
             rocketMQTemplate.asyncSend(
-                "event-feedback",
-                message,
-                new SendCallback() {
-                    @Override
-                    public void onSuccess(SendResult sendResult) {
-                        log.info("转化消息发送成功 | clickId={}, eventType={}, msgId={}, queueId={}",
-                                 message.getClickId(),
-                                 message.getEventType(),
-                                 sendResult.getMsgId(),
-                                 sendResult.getMessageQueue().getQueueId());
-                    }
-
-                    @Override
-                    public void onException(Throwable e) {
-                        log.error("转化消息发送失败 | clickId={}, eventType={}, error={}",
-                                  message.getClickId(),
-                                  message.getEventType(),
-                                  e.getMessage(),
-                                  e);
-                        // TODO: 记录到数据库,后续补偿
+                    "event-feedback",
+                    message,
+                    new SendCallback() {
+                        @Override
+                        public void onSuccess(SendResult sendResult) {
+                            log.info("转化消息发送成功 | clickId={}, eventType={}, msgId={}, queueId={}",
+                                    message.getClickId(),
+                                    message.getEventType(),
+                                    sendResult.getMsgId(),
+                                    sendResult.getMessageQueue().getQueueId());
+                        }
+
+                        @Override
+                        public void onException(Throwable e) {
+                            log.error("转化消息发送失败 | clickId={}, eventType={}, error={}",
+                                    message.getClickId(),
+                                    message.getEventType(),
+                                    e.getMessage(),
+                                    e);
+                            // TODO: 记录到数据库,后续补偿
+                        }
                     }
-                }
             );
 
         } catch (Exception e) {
             log.error("发送转化消息异常 | clickId={}, eventType={}",
-                      message.getClickId(),
-                      message.getEventType(),
-                      e);
+                    message.getClickId(),
+                    message.getEventType(),
+                    e);
         }
     }
 
@@ -110,22 +109,22 @@ public class ConversionMessageProducer {
 
             // 2. 同步发送消息
             SendResult sendResult = rocketMQTemplate.syncSend(
-                "event-feedback",
-                message
+                    "event-feedback",
+                    message
             );
 
             log.info("转化消息同步发送成功 | clickId={}, eventType={}, msgId={}",
-                     message.getClickId(),
-                     message.getEventType(),
-                     sendResult.getMsgId());
+                    message.getClickId(),
+                    message.getEventType(),
+                    sendResult.getMsgId());
 
             return true;
 
         } catch (Exception e) {
             log.error("转化消息同步发送失败 | clickId={}, eventType={}",
-                      message.getClickId(),
-                      message.getEventType(),
-                      e);
+                    message.getClickId(),
+                    message.getEventType(),
+                    e);
             return false;
         }
     }
@@ -139,9 +138,9 @@ public class ConversionMessageProducer {
      */
     private String buildMessageId(ConversionMessage message) {
         return String.format("%s_%s_%d",
-                             message.getClickId(),
-                             message.getEventType(),
-                             System.currentTimeMillis());
+                message.getClickId(),
+                message.getEventType(),
+                System.currentTimeMillis());
     }
 }
 

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

@@ -0,0 +1,89 @@
+package com.fs.app.task;
+
+
+import com.fs.common.constant.SystemConstant;
+import com.fs.newAdv.domain.ConversionLog;
+import com.fs.newAdv.mapper.ConversionLogMapper;
+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;
+
+/**
+ * 转化回传重试定时任务
+ * 每10分钟执行一次,重试失败的转化回传
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Component
+public class ConversionRetryTask {
+
+    @Autowired
+    private ConversionLogMapper conversionLogMapper;
+
+    /**
+     * 转化回传重试任务
+     * cron: 每10分钟执行
+     */
+    @Scheduled(cron = "0 */10 * * * ?")
+    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);
+                    failCount++;
+                }
+            }
+
+            log.info("========== 转化回传重试任务完成,成功:{},失败:{} ==========", successCount, failCount);
+
+        } catch (Exception e) {
+            log.error("转化回传重试任务执行失败", e);
+        }
+    }
+
+    /**
+     * 重试转化回传
+     */
+    private boolean retryConversion(ConversionLog conversionLog) {
+        log.info("重试转化回传,ID:{},重试次数:{}", conversionLog.getId(), conversionLog.getRetryCount());
+
+        // TODO: 调用第三方平台API进行回传
+        // 使用工厂模式获取对应的回传处理器
+
+        // 更新重试次数
+        conversionLog.setRetryCount(conversionLog.getRetryCount() + 1);
+
+        // 如果重试次数超过最大值,标记为失败
+        if (conversionLog.getRetryCount() >= SystemConstant.MAX_RETRY_COUNT) {
+            conversionLog.setCallbackStatus(2); // 失败
+            log.warn("转化回传重试次数已达上限,ID:{}", conversionLog.getId());
+        }
+
+        conversionLogMapper.updateById(conversionLog);
+
+        return false;
+    }
+}
+

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

@@ -0,0 +1,126 @@
+package com.fs.app.task;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+
+import com.fs.app.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);
+    }
+}
+

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

@@ -0,0 +1,115 @@
+package com.fs.app.task;
+
+import cn.hutool.core.date.DateUtil;
+import com.fs.app.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;
+import com.fs.newAdv.mapper.PromotionAccountMapper;
+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;
+import java.util.List;
+
+/**
+ * 数据同步定时任务
+ * 每2小时执行一次,拉取各平台账户数据
+ *
+ * @author zhangqin
+ * @date 2025-11-03
+ */
+@Slf4j
+@Component
+public class DataSyncTask {
+
+    @Autowired
+    private PromotionAccountMapper promotionAccountMapper;
+
+    @Autowired
+    private ScheduleTaskLogMapper scheduleTaskLogMapper;
+
+    /**
+     * 数据同步任务
+     * 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);
+
+        ScheduleTaskLog taskLog = createTaskLog(batchNo);
+
+        try {
+            // 查询开启API读取的推广账号
+            List<PromotionAccount> accounts = promotionAccountMapper.selectApiEnabledAccounts();
+            log.info("查询到 {} 个需要同步的账户", accounts.size());
+
+            int count = 0;
+            for (PromotionAccount account : accounts) {
+                try {
+                    syncAccountData(account);
+                    count++;
+                } catch (Exception e) {
+                    log.error("同步账户 {} 数据失败", account.getAccountName(), 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 syncAccountData(PromotionAccount account) {
+        log.info("开始同步账户:{}", account.getAccountName());
+
+        // 根据广告商类型调用对应的API
+        // 使用工厂模式获取对应的API客户端
+        // 实际业务逻辑在Service层实现
+
+        log.info("账户 {} 同步完成", account.getAccountName());
+    }
+
+    /**
+     * 创建任务日志
+     */
+    private ScheduleTaskLog createTaskLog(String batchNo) {
+        ScheduleTaskLog log = new ScheduleTaskLog();
+        log.setId(SnowflakeUtil.nextId());
+        log.setTaskType(TaskTypeEnum.DATA_SYNC.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);
+    }
+}
+

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

@@ -0,0 +1,82 @@
+package com.fs.app.task;
+
+import cn.hutool.core.date.DateUtil;
+
+import com.fs.app.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);
+    }
+}
+

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

@@ -0,0 +1,69 @@
+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,更新到数据库
+    }
+}
+

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

@@ -0,0 +1,34 @@
+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())));
+    }
+}

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

@@ -0,0 +1,72 @@
+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());
+    }
+}
+

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

@@ -0,0 +1,109 @@
+package com.fs.app.task;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.app.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);
+    }
+}
+

+ 1 - 0
fs-ad-new-api/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson.*.jar

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

@@ -0,0 +1,7 @@
+# 开发环境配置
+server:
+  port: 8011
+# Spring配置
+spring:
+  profiles:
+    active: dev

+ 2 - 0
fs-ad-new-api/src/main/resources/banner.txt

@@ -0,0 +1,2 @@
+Application Version: ${fs.version}
+Spring Boot Version: ${spring-boot.version}

+ 37 - 0
fs-ad-new-api/src/main/resources/i18n/messages.properties

@@ -0,0 +1,37 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 93 - 0
fs-ad-new-api/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-ai-websocket/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 15 - 0
fs-ad-new-api/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+	
+	<settings>
+		<setting name="cacheEnabled"             value="true" />  <!-- 全局映射器启用缓存 -->
+		<setting name="useGeneratedKeys"         value="true" />  <!-- 允许 JDBC 支持自动生成主键 -->
+		<setting name="defaultExecutorType"      value="REUSE" /> <!-- 配置默认的执行器 -->
+		<setting name="logImpl"                  value="SLF4J" /> <!-- 指定 MyBatis 所用日志的具体实现 -->
+		 <setting name="mapUnderscoreToCamelCase" value="true"/>
+	</settings>
+	
+</configuration>

+ 4 - 4
fs-company/src/main/java/com/fs/company/controller/newAdv/AdvertiserController.java

@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.Advertiser;
+import com.fs.newAdv.domain.Advertiser;
 import com.fs.newAdv.service.IAdvertiserService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,13 +34,13 @@ public class AdvertiserController {
      */
     @GetMapping("/page")
     public Result<IPage<Advertiser>> page(
-            @RequestParam(defaultValue = "1") Long current,
-            @RequestParam(defaultValue = "10") Long size,
+            @RequestParam(defaultValue = "1") Long pageNum,
+            @RequestParam(defaultValue = "10") Long pageSize,
             @RequestParam(required = false) String advertiserName,
             @RequestParam(required = false) Integer type,
             @RequestParam(required = false) Integer enabled) {
 
-        Page<Advertiser> page = new Page<>(current, size);
+        Page<Advertiser> page = new Page<>(pageNum, pageSize);
         IPage<Advertiser> result = advertiserService.page(page, advertiserName, type, enabled);
         return Result.success(result);
     }

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

@@ -3,7 +3,7 @@ package com.fs.company.controller.newAdv;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.CallbackAccount;
+import com.fs.newAdv.domain.CallbackAccount;
 import com.fs.newAdv.service.ICallbackAccountService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

+ 43 - 65
fs-company/src/main/java/com/fs/company/controller/newAdv/DomainController.java

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.Domain;
+import com.fs.newAdv.domain.DomainUrl;
 import com.fs.newAdv.service.IDomainService;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
@@ -35,59 +35,37 @@ public class DomainController {
     /**
      * 分页查询域名列表
      *
-     * @param page    当前页
-     * @param size    每页大小
      * @param name    域名名称(模糊查询)
      * @param domain  域名地址(模糊查询)
      * @param status  状态
      * @return 域名列表
      */
     @GetMapping("/page")
-    public Result<IPage<Domain>> page(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
+    public Result<IPage<DomainUrl>> page(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
             @RequestParam(required = false) String name,
             @RequestParam(required = false) String domain,
             @RequestParam(required = false) Integer status) {
-
-        log.info("分页查询域名列表 | page={}, size={}, name={}, domain={}, status={}",
-                 page, size, name, domain, status);
-
-        Page<Domain> pageParam = new Page<>(page, size);
-        LambdaQueryWrapper<Domain> queryWrapper = new LambdaQueryWrapper<>();
+        Page<DomainUrl> pageParam = new Page<>(pageNum, pageSize);
+        LambdaQueryWrapper<DomainUrl> queryWrapper = new LambdaQueryWrapper<>();
 
         if (StrUtil.isNotBlank(name)) {
-            queryWrapper.like(Domain::getName, name);
+            queryWrapper.like(DomainUrl::getName, name);
         }
         if (StrUtil.isNotBlank(domain)) {
-            queryWrapper.like(Domain::getDomain, domain);
+            queryWrapper.like(DomainUrl::getDomain, domain);
         }
         if (status != null) {
-            queryWrapper.eq(Domain::getStatus, status);
+            queryWrapper.eq(DomainUrl::getStatus, status);
         }
 
-        queryWrapper.orderByDesc(Domain::getCreateTime);
+        queryWrapper.orderByDesc(DomainUrl::getCreateTime);
 
-        IPage<Domain> result = domainService.page(pageParam, queryWrapper);
+        IPage<DomainUrl> result = domainService.page(pageParam, queryWrapper);
         return Result.success(result);
     }
 
-    /**
-     * 查询所有启用的域名
-     *
-     * @return 域名列表
-     */
-    @GetMapping("/list/enabled")
-    public Result<List<Domain>> listEnabled() {
-        log.info("查询所有启用的域名");
-
-        LambdaQueryWrapper<Domain> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(Domain::getStatus, 1)
-                   .orderByDesc(Domain::getCreateTime);
-
-        List<Domain> list = domainService.list(queryWrapper);
-        return Result.success(list);
-    }
 
     /**
      * 根据ID查询域名详情
@@ -96,15 +74,15 @@ public class DomainController {
      * @return 域名详情
      */
     @GetMapping("/{id}")
-    public Result<Domain> getById(@PathVariable Long id) {
+    public Result<DomainUrl> getById(@PathVariable Long id) {
         log.info("查询域名详情 | id={}", id);
 
-        Domain domain = domainService.getById(id);
-        if (domain == null) {
+        DomainUrl domainUrl = domainService.getById(id);
+        if (domainUrl == null) {
             return Result.error("域名不存在");
         }
 
-        return Result.success(domain);
+        return Result.success(domainUrl);
     }
 
     /**
@@ -114,30 +92,30 @@ public class DomainController {
      * @return 操作结果
      */
     @PostMapping
-    public Result<Domain> create(@RequestBody @Validated DomainCreateRequest request) {
+    public Result<DomainUrl> create(@RequestBody @Validated DomainCreateRequest request) {
         log.info("新增域名 | name={}, domain={}", request.getName(), request.getDomain());
 
         // 检查域名是否已存在
-        LambdaQueryWrapper<Domain> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(Domain::getDomain, request.getDomain());
+        LambdaQueryWrapper<DomainUrl> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DomainUrl::getDomain, request.getDomain());
         long count = domainService.count(queryWrapper);
         if (count > 0) {
             return Result.error("域名已存在");
         }
 
-        Domain domain = new Domain();
-        domain.setName(request.getName());
-        domain.setDomain(request.getDomain());
-        domain.setStatus(request.getStatus() != null ? request.getStatus() : 1);
-        domain.setRemark(request.getRemark());
+        DomainUrl domainUrl = new DomainUrl();
+        domainUrl.setName(request.getName());
+        domainUrl.setDomain(request.getDomain());
+        domainUrl.setStatus(request.getStatus() != null ? request.getStatus() : 1);
+        domainUrl.setRemark(request.getRemark());
 
-        boolean success = domainService.save(domain);
+        boolean success = domainService.save(domainUrl);
         if (!success) {
             return Result.error("新增域名失败");
         }
 
-        log.info("新增域名成功 | id={}", domain.getId());
-        return Result.success(domain);
+        log.info("新增域名成功 | id={}", domainUrl.getId());
+        return Result.success(domainUrl);
     }
 
     /**
@@ -152,28 +130,28 @@ public class DomainController {
                                   @RequestBody @Validated DomainUpdateRequest request) {
         log.info("更新域名 | id={}, name={}, domain={}", id, request.getName(), request.getDomain());
 
-        Domain domain = domainService.getById(id);
-        if (domain == null) {
+        DomainUrl domainUrl = domainService.getById(id);
+        if (domainUrl == null) {
             return Result.error("域名不存在");
         }
 
         // 如果修改了域名地址,检查新域名是否已被其他记录使用
-        if (StrUtil.isNotBlank(request.getDomain()) && !request.getDomain().equals(domain.getDomain())) {
-            LambdaQueryWrapper<Domain> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(Domain::getDomain, request.getDomain())
-                       .ne(Domain::getId, id);
+        if (StrUtil.isNotBlank(request.getDomain()) && !request.getDomain().equals(domainUrl.getDomain())) {
+            LambdaQueryWrapper<DomainUrl> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(DomainUrl::getDomain, request.getDomain())
+                       .ne(DomainUrl::getId, id);
             long count = domainService.count(queryWrapper);
             if (count > 0) {
                 return Result.error("域名已存在");
             }
         }
 
-        domain.setName(request.getName());
-        domain.setDomain(request.getDomain());
-        domain.setStatus(request.getStatus());
-        domain.setRemark(request.getRemark());
+        domainUrl.setName(request.getName());
+        domainUrl.setDomain(request.getDomain());
+        domainUrl.setStatus(request.getStatus());
+        domainUrl.setRemark(request.getRemark());
 
-        boolean success = domainService.updateById(domain);
+        boolean success = domainService.updateById(domainUrl);
         if (!success) {
             return Result.error("更新域名失败");
         }
@@ -192,8 +170,8 @@ public class DomainController {
     public Result<String> delete(@PathVariable Long id) {
         log.info("删除域名 | id={}", id);
 
-        Domain domain = domainService.getById(id);
-        if (domain == null) {
+        DomainUrl domainUrl = domainService.getById(id);
+        if (domainUrl == null) {
             return Result.error("域名不存在");
         }
 
@@ -240,13 +218,13 @@ public class DomainController {
     public Result<String> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
         log.info("修改域名状态 | id={}, status={}", id, status);
 
-        Domain domain = domainService.getById(id);
-        if (domain == null) {
+        DomainUrl domainUrl = domainService.getById(id);
+        if (domainUrl == null) {
             return Result.error("域名不存在");
         }
 
-        domain.setStatus(status);
-        boolean success = domainService.updateById(domain);
+        domainUrl.setStatus(status);
+        boolean success = domainService.updateById(domainUrl);
         if (!success) {
             return Result.error("修改状态失败");
         }

+ 5 - 22
fs-company/src/main/java/com/fs/company/controller/newAdv/LandingPageTemplateController.java

@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.LandingPageTemplate;
+import com.fs.newAdv.domain.LandingPageTemplate;
 import com.fs.newAdv.service.ILandingPageTemplateService;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
@@ -46,17 +46,17 @@ public class LandingPageTemplateController {
      */
     @GetMapping("/page")
     public Result<IPage<LandingPageTemplate>> page(
-            @RequestParam(defaultValue = "1") Integer page,
-            @RequestParam(defaultValue = "10") Integer size,
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
             @RequestParam(required = false) String templateName,
             @RequestParam(required = false) String templateType,
             @RequestParam(required = false) Long domainId,
             @RequestParam(required = false) Integer status) {
 
         log.info("分页查询模板列表 | page={}, size={}, templateName={}, templateType={}, domainId={}, status={}",
-                 page, size, templateName, templateType, domainId, status);
+                pageNum, pageSize, templateName, templateType, domainId, status);
 
-        Page<LandingPageTemplate> pageParam = new Page<>(page, size);
+        Page<LandingPageTemplate> pageParam = new Page<>(pageNum, pageSize);
         LambdaQueryWrapper<LandingPageTemplate> queryWrapper = new LambdaQueryWrapper<>();
 
         if (StrUtil.isNotBlank(templateName)) {
@@ -78,23 +78,6 @@ public class LandingPageTemplateController {
         return Result.success(result);
     }
 
-    /**
-     * 查询所有启用的模板
-     *
-     * @return 模板列表
-     */
-    @GetMapping("/list/enabled")
-    public Result<List<LandingPageTemplate>> listEnabled() {
-        log.info("查询所有启用的模板");
-
-        LambdaQueryWrapper<LandingPageTemplate> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(LandingPageTemplate::getStatus, 1)
-                   .orderByDesc(LandingPageTemplate::getCreateTime);
-
-        List<LandingPageTemplate> list = templateService.list(queryWrapper);
-        return Result.success(list);
-    }
-
     /**
      * 根据ID查询模板详情
      *

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

@@ -3,7 +3,7 @@ package com.fs.company.controller.newAdv;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.LeadSource;
+import com.fs.newAdv.domain.LeadSource;
 import com.fs.newAdv.service.ILeadSourceService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

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

@@ -3,7 +3,7 @@ package com.fs.company.controller.newAdv;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.PromotionAccount;
+import com.fs.newAdv.domain.PromotionAccount;
 import com.fs.newAdv.service.IPromotionAccountService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

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

@@ -2,8 +2,8 @@ package com.fs.company.controller.newAdv;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.Site;
-import com.fs.newAdv.entity.SiteStatistics;
+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 lombok.extern.slf4j.Slf4j;

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

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fs.common.result.Result;
-import com.fs.newAdv.entity.TrackingLink;
+import com.fs.newAdv.domain.TrackingLink;
 import com.fs.newAdv.mapper.TrackingLinkMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;

+ 4 - 0
fs-company/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -1,5 +1,6 @@
 package com.fs.framework.config;
 
+import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import com.fs.common.utils.StringUtils;
 import org.apache.ibatis.io.VFS;
@@ -145,6 +146,9 @@ public class MyBatisConfig
         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();
     }
 }

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/Advertiser.java → fs-service/src/main/java/com/fs/newAdv/domain/Advertiser.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -62,13 +63,15 @@ public class Advertiser implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/AlertLog.java → fs-service/src/main/java/com/fs/newAdv/domain/AlertLog.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

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

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/CallbackAccount.java → fs-service/src/main/java/com/fs/newAdv/domain/CallbackAccount.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -47,13 +48,15 @@ public class CallbackAccount implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/CallbackLog.java → fs-service/src/main/java/com/fs/newAdv/domain/CallbackLog.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/ClickTrace.java → fs-service/src/main/java/com/fs/newAdv/domain/ClickTrace.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/ConversionLog.java → fs-service/src/main/java/com/fs/newAdv/domain/ConversionLog.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/ConversionTarget.java → fs-service/src/main/java/com/fs/newAdv/domain/ConversionTarget.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -48,13 +49,15 @@ public class ConversionTarget implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 }
 

+ 8 - 5
fs-service/src/main/java/com/fs/newAdv/entity/Domain.java → fs-service/src/main/java/com/fs/newAdv/domain/DomainUrl.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -13,8 +14,8 @@ import java.time.LocalDateTime;
  * @date 2025-11-06
  */
 @Data
-@TableName("adv_domain")
-public class Domain implements Serializable {
+@TableName("adv_domain_url")
+public class DomainUrl implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
@@ -47,13 +48,15 @@ public class Domain implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/LandingPageTemplate.java → fs-service/src/main/java/com/fs/newAdv/domain/LandingPageTemplate.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -57,13 +58,15 @@ public class LandingPageTemplate implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/Lead.java → fs-service/src/main/java/com/fs/newAdv/domain/Lead.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -107,13 +108,15 @@ public class Lead implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 }
 

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/LeadSource.java → fs-service/src/main/java/com/fs/newAdv/domain/LeadSource.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -72,13 +73,15 @@ public class LeadSource implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 11 - 3
fs-service/src/main/java/com/fs/newAdv/entity/PromotionAccount.java → fs-service/src/main/java/com/fs/newAdv/domain/PromotionAccount.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -94,16 +95,23 @@ public class PromotionAccount implements Serializable {
      */
     private String callbackUrl;
 
+    /**
+     * 应用授权链接
+     */
+    private String authUrl;
+
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/ScheduleTaskLog.java → fs-service/src/main/java/com/fs/newAdv/domain/ScheduleTaskLog.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -77,13 +78,15 @@ public class ScheduleTaskLog implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 }
 

+ 7 - 4
fs-service/src/main/java/com/fs/newAdv/entity/Site.java → fs-service/src/main/java/com/fs/newAdv/domain/Site.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -77,7 +78,7 @@ public class Site implements Serializable {
     /**
      * 投放页面ID
      */
-    private Long launchPageId;
+    private String launchPageId;
 
     /**
      * 分配方式
@@ -137,13 +138,15 @@ public class Site implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 
     /**

+ 6 - 3
fs-service/src/main/java/com/fs/newAdv/entity/SiteStatistics.java → fs-service/src/main/java/com/fs/newAdv/domain/SiteStatistics.java

@@ -1,6 +1,7 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -133,13 +134,15 @@ public class SiteStatistics implements Serializable {
     /**
      * 创建时间
      */
-    @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;
 }
 

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/SiteStatisticsDaily.java → fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsDaily.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/SiteStatisticsMonthly.java → fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsMonthly.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/SiteStatisticsWeekly.java → fs-service/src/main/java/com/fs/newAdv/domain/SiteStatisticsWeekly.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/SyncLog.java → fs-service/src/main/java/com/fs/newAdv/domain/SyncLog.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 1 - 1
fs-service/src/main/java/com/fs/newAdv/entity/TrackingLink.java → fs-service/src/main/java/com/fs/newAdv/domain/TrackingLink.java

@@ -1,4 +1,4 @@
-package com.fs.newAdv.entity;
+package com.fs.newAdv.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;

+ 0 - 93
fs-service/src/main/java/com/fs/newAdv/entity/OperationLog.java

@@ -1,93 +0,0 @@
-package com.fs.newAdv.entity;
-
-import com.baomidou.mybatisplus.annotation.*;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
-/**
- * 系统操作日志表
- *
- * @author zhangqin
- * @date 2025-11-03
- */
-@Data
-@TableName("adv_operation_log")
-public class OperationLog implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 主键
-     */
-    @TableId(value = "id", type = IdType.AUTO)
-    private Long id;
-
-    /**
-     * 模块名称
-     */
-    private String moduleName;
-
-    /**
-     * 操作类型(增/删/改/查)
-     */
-    private String operationType;
-
-    /**
-     * 操作描述
-     */
-    private String operationDesc;
-
-    /**
-     * 请求方法
-     */
-    private String requestMethod;
-
-    /**
-     * 请求URL
-     */
-    private String requestUrl;
-
-    /**
-     * 请求参数
-     */
-    private String requestParams;
-
-    /**
-     * 响应结果
-     */
-    private String responseResult;
-
-    /**
-     * 操作人ID
-     */
-    private Long operatorId;
-
-    /**
-     * 操作人姓名
-     */
-    private String operatorName;
-
-    /**
-     * IP地址
-     */
-    private String ipAddress;
-
-    /**
-     * 用户代理
-     */
-    private String userAgent;
-
-    /**
-     * 耗时(ms)
-     */
-    private Long costTime;
-
-    /**
-     * 创建时间
-     */
-    @TableField(fill = FieldFill.INSERT)
-    private LocalDateTime createTime;
-}
-

+ 0 - 197
fs-service/src/main/java/com/fs/newAdv/integration/handler/BaiduCallbackHandler.java

@@ -1,197 +0,0 @@
-//package com.fs.newAdv.integration.handler;
-//
-//import cn.hutool.core.map.MapUtil;
-//import cn.hutool.core.util.StrUtil;
-//import com.fs.newAdv.enums.AdvertiserTypeEnum;
-//import com.fs.newAdv.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
-// */
-//@Deprecated
-//@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 - 191
fs-service/src/main/java/com/fs/newAdv/integration/handler/OceanEngineCallbackHandler.java

@@ -1,191 +0,0 @@
-//package com.fs.newAdv.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();
-//    }
-//}
-//

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.Advertiser;
+import com.fs.newAdv.domain.Advertiser;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.AlertLog;
+import com.fs.newAdv.domain.AlertLog;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.ApiCallLog;
+import com.fs.newAdv.domain.ApiCallLog;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.CallbackAccount;
+import com.fs.newAdv.domain.CallbackAccount;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.CallbackLog;
+import com.fs.newAdv.domain.CallbackLog;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.ClickTrace;
+import com.fs.newAdv.domain.ClickTrace;
 import org.apache.ibatis.annotations.Mapper;
 
 /**

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

@@ -1,7 +1,7 @@
 package com.fs.newAdv.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.newAdv.entity.ConversionLog;
+import com.fs.newAdv.domain.ConversionLog;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Select;
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików