소스 검색

提取服务

chenkq 1 개월 전
부모
커밋
f4a79d8dcb

+ 5 - 0
java/storlead-api/src/main/resources/application-dev.yml

@@ -94,6 +94,11 @@ spring:
           username: root
           password: rCgRgLjH99Xvg5BN
           driver-class-name: com.mysql.jdbc.Driver
+        trade:
+            url: jdbc:mysql://mysql.test.storlead.com:39091/sp_trade_platform_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+            username: root
+            password: rCgRgLjH99Xvg5BN
+            driver-class-name: com.mysql.jdbc.Driver
         sales:
           driver-class-name: com.mysql.jdbc.Driver
           url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true

+ 5 - 0
java/storlead-api/src/main/resources/application-test.yml

@@ -93,6 +93,11 @@ spring:
           username: root
           password: rCgRgLjH99Xvg5BN
           driver-class-name: com.mysql.jdbc.Driver
+        trade:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_trade_platform_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+          driver-class-name: com.mysql.jdbc.Driver
         sales:
           driver-class-name: com.mysql.jdbc.Driver
           url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true

+ 4 - 0
java/storlead-api/src/main/resources/application.yml

@@ -88,6 +88,10 @@ storlead:
     # 子模块默认数据源(未标注 @DS 时生效;标注 @DS 则以注解为准)
     module-default-enabled: true
     module-defaults:
+      - packages:
+          - com.storlead.trade.service
+          - com.storlead.trade.mapper
+        datasource: trade
       - packages:
           - com.storlead.sales.service
           - com.storlead.sales.mapper

+ 5 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java

@@ -21,6 +21,11 @@ public class DSConstants {
      */
     public static final String DATASOURCE_SALES = "sales";
 
+    /**
+     * 营销/交易库(与 spring.datasource.dynamic.datasource.trade 的 key 一致)
+     */
+    public static final String DATASOURCE_TRADE = "trade";
+
     /**
      * 邮件库(与 spring.datasource.dynamic.datasource.mail 的 key 一致)
      */

+ 0 - 3
java/storlead-knowledge/storlead-knowledge-biz/src/main/java/com/storlead/knowledge/service/impl/ChatServiceImpl.java

@@ -11,8 +11,6 @@ import com.storlead.knowledge.service.AiMessageService;
 import com.storlead.knowledge.service.ChatService;
 import com.storlead.knowledge.utils.HttpService;
 import com.storlead.knowledge.utils.JacksonHolder;
-import lombok.extern.flogger.Flogger;
-import lombok.extern.log4j.Log4j2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -23,7 +21,6 @@ import javax.annotation.Resource;
  * 对话服务实现
  */
 @Service
-@Log4j2
 public class ChatServiceImpl implements ChatService {
 
     private static final Logger log = LoggerFactory.getLogger(ChatServiceImpl.class);

+ 12 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/config/TradeSchedulingConfig.java

@@ -0,0 +1,12 @@
+package com.storlead.trade.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 启用 Spring 定时任务(无效客户分析投递等)
+ */
+@Configuration
+@EnableScheduling
+public class TradeSchedulingConfig {
+}

+ 25 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/constant/CustomerAnalysisConstants.java

@@ -0,0 +1,25 @@
+package com.storlead.trade.constant;
+
+/**
+ * 客户 AI 分析相关常量
+ */
+public final class CustomerAnalysisConstants {
+
+    private CustomerAnalysisConstants() {
+    }
+
+    /**
+     * 待分析(由其他业务消费后回写)
+     */
+    public static final int ANALYSIS_STATUS_PENDING = -1;
+
+    /**
+     * 无效客户(垃圾客户)标记
+     */
+    public static final int GARBAGE_CUSTOMER_YES = 1;
+
+    /**
+     * 同一客户再次投递间隔天数
+     */
+    public static final int NEXT_ANALYSIS_INTERVAL_DAYS = 15;
+}

+ 3 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/CustomerEntity.java

@@ -97,4 +97,7 @@ public class CustomerEntity extends SysBaseField {
     private Date giveUpTime;
     @ApiModelProperty(value = "前拥有者")
     private Long formerOwnerBy;
+
+    @ApiModelProperty(value = "下次分析日期(按天)")
+    private Date nextAnalysisDate;
 }

+ 14 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/CustomerAnalysisDispatchService.java

@@ -0,0 +1,14 @@
+package com.storlead.trade.service;
+
+/**
+ * 无效客户分析投递:仅写入待分析记录,不做实际分析
+ */
+public interface CustomerAnalysisDispatchService {
+
+    /**
+     * 捞取到期的无效客户,插入 CustomerAnalysisResult(status=-1),并回写下次分析日期(+15天)
+     *
+     * @return 本次成功投递条数
+     */
+    int dispatchPendingInvalidCustomers();
+}

+ 104 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/CustomerAnalysisDispatchServiceImpl.java

@@ -0,0 +1,104 @@
+package com.storlead.trade.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.trade.constant.CustomerAnalysisConstants;
+import com.storlead.trade.entity.CustomerAnalysisResultEntity;
+import com.storlead.trade.entity.CustomerEntity;
+import com.storlead.trade.service.CustomerAnalysisDispatchService;
+import com.storlead.trade.service.CustomerAnalysisResultEntityService;
+import com.storlead.trade.service.CustomerEntityService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class CustomerAnalysisDispatchServiceImpl implements CustomerAnalysisDispatchService {
+
+    private static final DateTimeFormatter BATCH_NO_FMT = DateTimeFormatter.BASIC_ISO_DATE;
+
+    @Resource
+    private CustomerEntityService customerEntityService;
+
+    @Resource
+    private CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int dispatchPendingInvalidCustomers() {
+        LocalDate today = LocalDate.now();
+        String batchNo = today.format(BATCH_NO_FMT);
+        Date todayDate = java.sql.Date.valueOf(today);
+        Date nextAnalysisDate = java.sql.Date.valueOf(today.plusDays(CustomerAnalysisConstants.NEXT_ANALYSIS_INTERVAL_DAYS));
+
+        List<CustomerEntity> candidates = customerEntityService.list(new LambdaQueryWrapper<CustomerEntity>()
+                .eq(CustomerEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
+                .eq(CustomerEntity::getHasGarbageCustomer, CustomerAnalysisConstants.GARBAGE_CUSTOMER_YES)
+                .and(w -> w.isNull(CustomerEntity::getNextAnalysisDate)
+                        .or()
+                        .le(CustomerEntity::getNextAnalysisDate, todayDate)));
+
+        if (CollectionUtils.isEmpty(candidates)) {
+            log.info("无效客户分析投递:无待投递数据, batchNo={}", batchNo);
+            return 0;
+        }
+
+        Set<Long> alreadyDispatchedToday = loadDispatchedCustomerIds(batchNo);
+        List<CustomerAnalysisResultEntity> toInsert = new ArrayList<>();
+        List<CustomerEntity> toUpdate = new ArrayList<>();
+
+        for (CustomerEntity customer : candidates) {
+            if (customer.getId() == null || alreadyDispatchedToday.contains(customer.getId())) {
+                continue;
+            }
+            CustomerAnalysisResultEntity result = new CustomerAnalysisResultEntity();
+            result.setCustomerId(customer.getId());
+            result.setAnalysisBatchNo(batchNo);
+            result.setAnalysisStatus(CustomerAnalysisConstants.ANALYSIS_STATUS_PENDING);
+            result.setIsDelete(CommonConstant.DEL_FLAG_0);
+            toInsert.add(result);
+
+            CustomerEntity update = new CustomerEntity();
+            update.setId(customer.getId());
+            update.setNextAnalysisDate(nextAnalysisDate);
+            toUpdate.add(update);
+        }
+
+        if (CollectionUtils.isEmpty(toInsert)) {
+            log.info("无效客户分析投递:候选已全部在本批次投递过, batchNo={}, candidates={}", batchNo, candidates.size());
+            return 0;
+        }
+
+        customerAnalysisResultEntityService.saveBatch(toInsert);
+        customerEntityService.updateBatchById(toUpdate);
+        log.info("无效客户分析投递完成: batchNo={}, count={}, nextAnalysisDate={}", batchNo, toInsert.size(), nextAnalysisDate);
+        return toInsert.size();
+    }
+
+    private Set<Long> loadDispatchedCustomerIds(String batchNo) {
+        List<CustomerAnalysisResultEntity> exists = customerAnalysisResultEntityService.list(
+                new LambdaQueryWrapper<CustomerAnalysisResultEntity>()
+                        .select(CustomerAnalysisResultEntity::getCustomerId)
+                        .eq(CustomerAnalysisResultEntity::getAnalysisBatchNo, batchNo)
+                        .eq(CustomerAnalysisResultEntity::getIsDelete, CommonConstant.DEL_FLAG_0));
+        if (CollectionUtils.isEmpty(exists)) {
+            return new HashSet<>();
+        }
+        return exists.stream()
+                .map(CustomerAnalysisResultEntity::getCustomerId)
+                .filter(id -> id != null)
+                .collect(Collectors.toCollection(HashSet::new));
+    }
+}

+ 31 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/task/InvalidCustomerAnalysisDispatchTask.java

@@ -0,0 +1,31 @@
+package com.storlead.trade.task;
+
+import com.storlead.trade.service.CustomerAnalysisDispatchService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 每天 7 点:将到期的无效客户投递到 CustomerAnalysisResult(status=-1)
+ */
+@Slf4j
+@Component
+public class InvalidCustomerAnalysisDispatchTask {
+
+    @Resource
+    private CustomerAnalysisDispatchService customerAnalysisDispatchService;
+
+    @Scheduled(cron = "0 0 7 * * ?")
+    public void dispatch() {
+        long start = System.currentTimeMillis();
+        log.info("无效客户分析投递任务开始");
+        try {
+            int count = customerAnalysisDispatchService.dispatchPendingInvalidCustomers();
+            log.info("无效客户分析投递任务结束, count={}, cost={}ms", count, System.currentTimeMillis() - start);
+        } catch (Exception e) {
+            log.error("无效客户分析投递任务失败", e);
+        }
+    }
+}

+ 10 - 0
java/storlead-sasa/storlead-trade/src/main/resources/sql/customer_add_next_analysis_date.sql

@@ -0,0 +1,10 @@
+-- trade 库:客户表增加下次分析日期(按天);新建默认当天,存量可空(任务会按「空或已到期」捞取)
+ALTER TABLE `customer`
+  ADD COLUMN `next_analysis_date` date DEFAULT NULL COMMENT '下次分析日期' AFTER `former_owner_by`;
+
+-- 无效客户(垃圾客户)初始化为当天,便于首次被定时任务捞取
+UPDATE `customer`
+SET `next_analysis_date` = CURDATE()
+WHERE `is_delete` = 0
+  AND `has_garbage_customer` = 1
+  AND `next_analysis_date` IS NULL;