Prechádzať zdrojové kódy

优化根据进粉天数打标签任务

cgp 3 dní pred
rodič
commit
72595fe35b

+ 8 - 156
fs-admin/src/main/java/com/fs/his/task/StoreTask.java

@@ -1,186 +1,38 @@
 package com.fs.his.task;
 
-
-
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.vo.FsStoreOrderScrmPhoneAndOrderNumVO;
-import com.fs.qw.domain.QuickTagTask;
-import com.fs.qw.domain.QuickTagTaskLog;
-import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.*;
-import com.fs.qwApi.domain.QwResult;
-import com.fs.qwApi.param.QwEditUserTagParam;
-import com.fs.qwApi.service.QwApiService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-
 import java.util.*;
-import java.util.stream.Collectors;
 
 /**
  * 商城小程序相关定时任务
- * */
+ */
 @Slf4j
 @Component("StoreTask")
 public class StoreTask {
-    
+
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
 
     @Autowired
     private FsCompanyCustomerMapper fsCompanyCustomerMapper;
 
-    @Autowired
-    private QuickTagTaskMapper quickTagTaskMapper;
-
-    @Autowired
-    private QuickTagTaskLogMapper quickTagTaskLogMapper;
-
-    @Autowired
-    private QwExternalContactMapper qwExternalContactMapper;
-
-    @Autowired
-    private QwApiService qwApiService;
-    
     /**
      * 同步客户信息表客户的有效下单数
-     * */
-    public void syncStoreCompanyUserOrderNum(){
-        log.info("同步客户信息表客户的有效下单数据");
-        //获取商城处方订单每个手机号的有效下单数列表
-        List<FsStoreOrderScrmPhoneAndOrderNumVO> fsStoreOrderScrmPhoneAndOrderNumVOS = fsStoreOrderScrmMapper.fetchPharmacyPrescribeOrderPhoneCountList();
-        if (CollectionUtils.isEmpty(fsStoreOrderScrmPhoneAndOrderNumVOS)){
-            return;
-        }
-        //根据手机号批量更新客户信息表客户有效下单数
-        int updateResult=fsCompanyCustomerMapper.updateCompanyUserBuyCountByUserPhone(fsStoreOrderScrmPhoneAndOrderNumVOS);
-    }
-
-    /**
-     * 简洁版自动打标签定时任务
      */
-    public void quickTagTask() {
-        log.info("========== 简洁版自动打标签任务开始 ==========");
-        // 查询所有任务(若需状态过滤,可添加条件)
-        QuickTagTask query = new QuickTagTask();
-        List<QuickTagTask> taskList = quickTagTaskMapper.selectQuickTagTaskList(query);
-        if (CollectionUtils.isEmpty(taskList)) {
-            log.info("无自动打标签任务,结束");
+    public void syncStoreCompanyUserOrderNum() {
+        log.info("同步客户信息表客户的有效下单数据");
+        List<FsStoreOrderScrmPhoneAndOrderNumVO> list = fsStoreOrderScrmMapper.fetchPharmacyPrescribeOrderPhoneCountList();
+        if (CollectionUtils.isEmpty(list)) {
             return;
         }
-
-        // 遍历每个任务,单个任务异常不影响其他任务
-        for (QuickTagTask task : taskList) {
-            try {
-                executeSingleTask(task);
-            } catch (Exception e) {
-                log.error("执行任务失败,任务ID: {}, 任务详情: {}", task.getId(), task, e);
-            }
-        }
-        log.info("========== 简洁版自动打标签任务结束 ==========");
+        fsCompanyCustomerMapper.updateCompanyUserBuyCountByUserPhone(list);
     }
 
-    /**
-     * 执行单个打标签任务
-     */
-    private void executeSingleTask(QuickTagTask task) {
-        log.info("开始执行任务 ID: {}, corpId: {}", task.getId(), task.getCorpId());
-
-        // 1. 校验必要参数
-        Integer executionDays = task.getExecutionDays();
-        if (executionDays == null || executionDays < 0) {
-            log.warn("任务 {} 执行天数为空或无效,跳过", task.getId());
-            return;
-        }
-        String tagIdStr = task.getTagId();
-        if (StringUtils.isEmpty(tagIdStr)) {
-            log.warn("任务 {} 未配置标签,跳过", task.getId());
-            return;
-        }
-        List<String> tagIds = Arrays.stream(tagIdStr.split(","))
-                .filter(StringUtils::isNotBlank)
-                .collect(Collectors.toList());
-        if (tagIds.isEmpty()) {
-            log.warn("任务 {} 标签列表为空,跳过", task.getId());
-            return;
-        }
-
-        // 2. 计算触发日期范围(查询过去某一天的客户)
-        Date zeroToday = DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH);
-        // 目标日期:今天减去 executionDays 天
-        Date targetDate = DateUtils.addDays(zeroToday, -executionDays);
-        // 查询范围:targetDate 00:00:00 ~ targetDate 23:59:59
-        Date startTime = targetDate;
-        Date endTime = DateUtils.addDays(targetDate, 1); // 加1天即为第二天的零点
-
-        // 3. 查询该时间段内新增的客户
-        List<QwExternalContact> contactList = qwExternalContactMapper.selectQwExternalContactListByCreateTime(startTime, endTime);
-        if (CollectionUtils.isEmpty(contactList)) {
-            log.info("任务 {} 在 {} 至 {} 期间无新增客户", task.getId(), startTime, endTime);
-            return;
-        }
-
-        log.info("任务 {} 命中 {} 个客户,开始逐个打标签", task.getId(), contactList.size());
-        int successCount = 0;
-        int failCount = 0;
-
-        // 4. 遍历客户逐个打标签(单个失败不影响其他)
-        for (QwExternalContact contact : contactList) {
-            boolean apiSuccess = false;
-            String errorMsg = null;
-            try {
-                // 4.1 调用企微API打标签
-                QwEditUserTagParam param = new QwEditUserTagParam();
-                param.setUserid(contact.getUserId());
-                param.setExternal_userid(contact.getExternalUserId());
-                param.setAdd_tag(tagIds);
-
-                QwResult result = qwApiService.editUserTag(param, task.getCorpId());
-                if (result != null && result.getErrcode() == 0) {
-                    apiSuccess = true;
-                    log.debug("客户 {} 打标签成功", contact.getExternalUserId());
-                } else {
-                    errorMsg = result != null ? result.getErrmsg() : "API返回空";
-                    log.warn("客户 {} 打标签失败: errcode={}, errmsg={}",
-                            contact.getExternalUserId(),
-                            result != null ? result.getErrcode() : "null",
-                            errorMsg);
-                }
-            } catch (Exception e) {
-                errorMsg = e.getMessage();
-                log.error("客户 {} 打标签发生异常", contact.getExternalUserId(), e);
-            }
-
-            // 4.2 记录日志(无论成败均记录,可扩展表字段存储状态和错误信息)
-            try {
-                QuickTagTaskLog logEntry = new QuickTagTaskLog();
-                logEntry.setAutoTagId(task.getId());
-                logEntry.setType(1); // 可定义为常量 TagLogType.ADD_FRIEND
-                logEntry.setQwUserid(contact.getQwUserId());
-                logEntry.setExternalUserId(contact.getExternalUserId());
-                logEntry.setAddTime(contact.getCreateTime()); // 记录客户原始添加时间
-                logEntry.setCompanyId(contact.getCompanyId());
-                logEntry.setCorpId(task.getCorpId());
-                logEntry.setStatus(apiSuccess ? 1 : 0);
-                logEntry.setErrorMsg(apiSuccess ? null : errorMsg);
-                quickTagTaskLogMapper.insertQuickTagTaskLog(logEntry);
-            } catch (Exception e) {
-                // 日志插入失败不应影响打标签业务,仅记录错误
-                log.error("插入打标签日志失败,客户: {}, 任务: {}", contact.getExternalUserId(), task.getId(), e);
-            }
-
-            if (apiSuccess) {
-                successCount++;
-            } else {
-                failCount++;
-            }
-        }
-
-        log.info("任务 {} 执行完成,成功: {}, 失败: {}", task.getId(), successCount, failCount);
-    }
-}
+}

+ 237 - 0
fs-admin/src/main/java/com/fs/his/task/TagTask.java

@@ -0,0 +1,237 @@
+package com.fs.his.task;
+
+import com.fs.qw.domain.QuickTagTask;
+import com.fs.qw.domain.QuickTagTaskLog;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QuickTagTaskLogMapper;
+import com.fs.qw.mapper.QuickTagTaskMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qwApi.domain.QwResult;
+import com.fs.qwApi.param.QwEditUserTagParam;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+ * 简洁版进粉打标签相关定时任务
+ */
+@Slf4j
+@Component("TagTask")
+public class TagTask {
+
+    @Autowired
+    private QuickTagTaskMapper quickTagTaskMapper;
+
+    @Autowired
+    private QuickTagTaskLogMapper quickTagTaskLogMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private QwApiService qwApiService;
+
+    // 线程池配置
+    private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
+            10, 20, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadPoolExecutor.CallerRunsPolicy());
+
+    // 并行处理阈值:超过此数量才启用多线程
+    private static final int PARALLEL_THRESHOLD = 500;
+
+    // 最大并发数(控制API调用并发)
+    private static final int MAX_CONCURRENT = 10;
+
+    /**
+     * 简洁版自动打标签定时任务
+     */
+    public void quickAddTag() {
+        log.info("========== 简洁版自动打标签任务开始 ==========");
+        QuickTagTask query = new QuickTagTask();
+        List<QuickTagTask> taskList = quickTagTaskMapper.selectQuickTagTaskList(query);
+        if (CollectionUtils.isEmpty(taskList)) {
+            log.info("无自动打标签任务,结束");
+            return;
+        }
+
+        for (QuickTagTask task : taskList) {
+            try {
+                executeSingleTask(task);
+            } catch (Exception e) {
+                log.error("执行任务失败,任务ID: {}, 任务详情: {}", task.getId(), task, e);
+            }
+        }
+        log.info("========== 简洁版自动打标签任务结束 ==========");
+    }
+
+    /**
+     * 执行单个打标签任务
+     */
+    private void executeSingleTask(QuickTagTask task) {
+        log.info("开始执行任务 ID: {}, corpId: {}", task.getId(), task.getCorpId());
+
+        // 1. 校验必要参数
+        Integer executionDays = task.getExecutionDays();
+        if (executionDays == null || executionDays < 0) {
+            log.warn("任务 {} 执行天数为空或无效,跳过", task.getId());
+            return;
+        }
+        String tagIdStr = task.getTagId();
+        if (StringUtils.isEmpty(tagIdStr)) {
+            log.warn("任务 {} 未配置标签,跳过", task.getId());
+            return;
+        }
+        List<String> tagIds = Arrays.stream(tagIdStr.split(","))
+                .filter(StringUtils::isNotBlank)
+                .collect(Collectors.toList());
+        if (tagIds.isEmpty()) {
+            log.warn("任务 {} 标签列表为空,跳过", task.getId());
+            return;
+        }
+
+        // 2. 计算触发日期范围
+        Date zeroToday = DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH);
+        Date targetDate = DateUtils.addDays(zeroToday, -executionDays);
+        Date startTime = targetDate;
+        Date endTime = DateUtils.addDays(targetDate, 1);
+
+        // 3. 查询客户列表
+        List<QwExternalContact> contactList = qwExternalContactMapper.selectQwExternalContactListByCreateTime(
+                task.getCorpId(), startTime, endTime);
+        if (CollectionUtils.isEmpty(contactList)) {
+            log.info("任务 {} 在 {} 至 {} 期间无新增客户", task.getId(), startTime, endTime);
+            return;
+        }
+
+        log.info("任务 {} 命中 {} 个客户", task.getId(), contactList.size());
+
+        // 4. 根据数据量决定串行或并行处理
+        if (contactList.size() <= PARALLEL_THRESHOLD) {
+            processSequentially(task, tagIds, contactList);
+        } else {
+            processConcurrently(task, tagIds, contactList);
+        }
+    }
+
+    /**
+     * 串行处理
+     */
+    private void processSequentially(QuickTagTask task, List<String> tagIds, List<QwExternalContact> contactList) {
+        log.info("任务 {} 使用串行处理", task.getId());
+        int successCount = 0;
+        int failCount = 0;
+
+        for (QwExternalContact contact : contactList) {
+            boolean success = processSingleContact(task, tagIds, contact);
+            if (success) {
+                successCount++;
+            } else {
+                failCount++;
+            }
+        }
+
+        log.info("任务 {} 串行处理完成,成功: {}, 失败: {}", task.getId(), successCount, failCount);
+    }
+
+    /**
+     * 并行处理
+     */
+    private void processConcurrently(QuickTagTask task, List<String> tagIds, List<QwExternalContact> contactList) {
+        log.info("任务 {} 使用并行处理,并发数: {}", task.getId(), MAX_CONCURRENT);
+        Semaphore semaphore = new Semaphore(MAX_CONCURRENT);
+        AtomicInteger successCount = new AtomicInteger(0);
+        AtomicInteger failCount = new AtomicInteger(0);
+
+        List<CompletableFuture<Void>> futures = new ArrayList<>(contactList.size());
+
+        for (QwExternalContact contact : contactList) {
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    semaphore.acquire();
+                    boolean success = processSingleContact(task, tagIds, contact);
+                    if (success) {
+                        successCount.incrementAndGet();
+                    } else {
+                        failCount.incrementAndGet();
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    log.error("线程被中断,客户: {}", contact.getExternalUserId(), e);
+                } finally {
+                    semaphore.release();
+                }
+            }, EXECUTOR);
+            futures.add(future);
+        }
+
+        // 等待所有任务完成(设置超时)
+        try {
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+                    .get(30, TimeUnit.MINUTES);
+        } catch (Exception e) {
+            log.error("并行处理任务超时或异常", e);
+        }
+
+        log.info("任务 {} 并行处理完成,成功: {}, 失败: {}", task.getId(), successCount.get(), failCount.get());
+    }
+
+    /**
+     * 处理单个客户(打标签 + 记录日志)
+     *
+     * @return true-成功,false-失败
+     */
+    private boolean processSingleContact(QuickTagTask task, List<String> tagIds, QwExternalContact contact) {
+        boolean apiSuccess = false;
+        String errorMsg = null;
+
+        try {
+            QwEditUserTagParam param = new QwEditUserTagParam();
+            param.setUserid(contact.getUserId());
+            param.setExternal_userid(contact.getExternalUserId());
+            param.setAdd_tag(tagIds);
+
+            QwResult result = qwApiService.editUserTag(param, task.getCorpId());
+            if (result != null && result.getErrcode() == 0) {
+                apiSuccess = true;
+            } else {
+                errorMsg = result != null ? result.getErrmsg() : "API返回空";
+                log.warn("客户 {} 打标签失败: errcode={}, errmsg={}",
+                        contact.getExternalUserId(),
+                        result != null ? result.getErrcode() : "null",
+                        errorMsg);
+            }
+        } catch (Exception e) {
+            errorMsg = e.getMessage();
+            log.error("客户 {} 打标签发生异常", contact.getExternalUserId(), e);
+        }
+
+        // 记录日志
+        try {
+            QuickTagTaskLog logEntry = new QuickTagTaskLog();
+            logEntry.setAutoTagId(task.getId());
+            logEntry.setType(1);
+            logEntry.setQwUserid(contact.getQwUserId());
+            logEntry.setExternalUserId(contact.getExternalUserId());
+            logEntry.setAddTime(com.fs.common.utils.DateUtils.getNowDate());
+            logEntry.setCompanyId(contact.getCompanyId());
+            logEntry.setCorpId(task.getCorpId());
+            logEntry.setStatus(apiSuccess ? 1 : 0);
+            logEntry.setErrorMsg(apiSuccess ? null : errorMsg);
+            quickTagTaskLogMapper.insertQuickTagTaskLog(logEntry);
+        } catch (Exception e) {
+            log.error("插入打标签日志失败,客户: {}, 任务: {}", contact.getExternalUserId(), task.getId(), e);
+        }
+
+        return apiSuccess;
+    }
+}

+ 17 - 20
fs-company/src/main/java/com/fs/company/controller/qw/QuickTagTaskController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import com.fs.common.exception.CustomException;
 import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
@@ -10,6 +11,8 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
 import java.util.List;
 
 /**
@@ -44,16 +47,8 @@ public class QuickTagTaskController extends BaseController {
      * 新增任务
      */
     @PostMapping
-    public AjaxResult add(@RequestBody QuickTagTask quickTagTask) {
-        //获取当前登录销售
-        Long companyUserId;
-        try {
-            LoginUser loginUser = SecurityUtils.getLoginUser();
-            CompanyUser companyUser = loginUser.getUser();
-            companyUserId=companyUser.getUserId();
-        }catch (Exception e) {
-            return AjaxResult.error("登录信息已过期,请重新登录");
-        }
+    public AjaxResult add(@Valid @RequestBody QuickTagTask quickTagTask) {
+        Long companyUserId = getCurrentUserId();
         quickTagTask.setCreateBy(companyUserId);
         return toAjax(quickTagTaskService.insertQuickTagTask(quickTagTask));
     }
@@ -62,16 +57,8 @@ public class QuickTagTaskController extends BaseController {
      * 修改任务
      */
     @PutMapping
-    public AjaxResult edit(@RequestBody QuickTagTask quickTagTask) {
-        //获取当前登录销售
-        Long companyUserId;
-        try {
-            LoginUser loginUser = SecurityUtils.getLoginUser();
-            CompanyUser companyUser = loginUser.getUser();
-            companyUserId=companyUser.getUserId();
-        }catch (Exception e) {
-            return AjaxResult.error("登录信息已过期,请重新登录");
-        }
+    public AjaxResult edit(@Valid @RequestBody QuickTagTask quickTagTask) {
+        Long companyUserId = getCurrentUserId();
         quickTagTask.setUpdateBy(companyUserId);
         return toAjax(quickTagTaskService.updateQuickTagTask(quickTagTask));
     }
@@ -83,4 +70,14 @@ public class QuickTagTaskController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(quickTagTaskService.deleteQuickTagTaskByIds(ids));
     }
+
+    // 提取公共方法获取当前用户ID,减少重复代码
+    private Long getCurrentUserId() {
+        try {
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+            return loginUser.getUser().getUserId();
+        } catch (Exception e) {
+            throw new CustomException("登录信息已过期,请重新登录");
+        }
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/domain/QuickTagTask.java

@@ -4,6 +4,8 @@ import lombok.Data;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 /**
@@ -17,12 +19,15 @@ public class QuickTagTask {
     /** 主键ID */
     private Long id;
 
+    @NotBlank(message = "公司主体ID不能为空")
     /** 公司主体ID */
     private String corpId;
 
+    @NotNull(message = "触发打标签不能为空")
     /** 进粉后触发打标签天数 */
     private Integer executionDays;
 
+    @NotBlank(message = "标签ID不能为空")
     /** 配置的标签ID(逗号分隔) */
     private String tagId;
 

+ 2 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -576,7 +576,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     QwExternalContact queryQwUserIdIsAddContact(@Param("qwUserId") Long qwUserId, @Param("phone") String phone, @Param("addWay") int addWay);
 
     /**
-     *  根据创建日期范围查询企微客户列表
+     *  根据企业主体id+创建日期范围查询企微客户列表
      * */
-    List<QwExternalContact> selectQwExternalContactListByCreateTime(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
+    List<QwExternalContact> selectQwExternalContactListByCreateTime(@Param("corpId") String corpId, @Param("startTime") Date startTime, @Param("endTime") Date endTime);
 }

+ 2 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -811,7 +811,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectQwExternalContactListByCreateTime"  resultType="QwExternalContact">
         SELECT *
         FROM qw_external_contact
-        where create_time &gt;= #{startTime}
+        where corp_id = #{corpId}
+          AND create_time &gt;= #{startTime}
           AND create_time &lt; #{endTime}
     </select>