Sfoglia il codice sorgente

add 添加修改企微客户等级

ct 16 ore fa
parent
commit
36dd0edb08

+ 58 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -1035,4 +1035,62 @@ public class QwExternalContactController extends BaseController
         return util.exportExcel(list, "销售APP下载统计");
     }
 
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:changeLevel')")
+    @Log(title = "批量修改用户等级", businessType = BusinessType.UPDATE)
+    @PostMapping("/changeLevel")
+    public R changeLevel(@RequestBody QwExternalContactUpdateLevelParam param)
+    {
+
+        if(param.isFilter()){
+            param.setUserIds(getList(0, param.getParam()));
+        }
+        if(param.getUserIds() == null || param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
+
+        // 获取所有用户ID
+        List<Long> allUserIds = param.getUserIds();
+        int batchSize = 500;
+        int totalSize = allUserIds.size();
+
+        // 记录处理结果
+        int successCount = 0;
+        List<Long> failedUserIds = new ArrayList<>();
+
+        // 分批处理
+        for (int i = 0; i < totalSize; i += batchSize) {
+            int end = Math.min(i + batchSize, totalSize);
+            List<Long> batchUserIds = allUserIds.subList(i, end);
+
+            try {
+                // 创建新的参数对象,只包含当前批次的数据
+                QwExternalContactUpdateLevelParam batchParam = new QwExternalContactUpdateLevelParam();
+                batchParam.setLevel(param.getLevel());
+                batchParam.setFilter(param.isFilter());
+                batchParam.setUserIds(batchUserIds);
+
+                // 调用异步方法
+                qwUserServiceAsyncHelper.batchUpdateExternalContactLevelById(batchParam);
+
+                successCount += batchUserIds.size();
+
+                // 如果不是最后一批,可以稍微延迟一下,避免压力过大
+                if (end < totalSize) {
+                    Thread.sleep(100); // 100毫秒延迟,可根据需要调整
+                }
+            } catch (Exception e) {
+                // 记录失败的批次
+                logger.error("批次处理失败,用户IDs: {}, 错误: {}", batchUserIds, e.getMessage());
+                failedUserIds.addAll(batchUserIds);
+            }
+        }
+
+        // 返回处理结果
+        if (failedUserIds.isEmpty()) {
+            return R.ok("成功处理 " + successCount + " 个用户");
+        } else {
+            return R.ok("部分成功: 成功 " + successCount + " 个, 失败 " + failedUserIds.size() + " 个");
+        }
+    }
+
 }

+ 161 - 0
fs-service/src/main/java/com/fs/company/config/QwHandleExectorConfig.java

@@ -0,0 +1,161 @@
+package com.fs.company.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author MixLiu
+ * @date 2026/3/26 10:04
+ * @description qwHandle线程池配置,提供给@Async注解使用
+ */
+@Configuration
+@EnableAsync
+public class QwHandleExectorConfig {
+
+    /**
+     * 核心线程数
+     */
+    private static final int CORE_POOL_SIZE = 8;
+
+    /**
+     * 最大线程数
+     */
+    private static final int MAX_POOL_SIZE = 16;
+
+    /**
+     * 队列容量
+     */
+    private static final int QUEUE_CAPACITY = 5000;
+
+    /**
+     * 线程空闲时间(分钟)
+     */
+    private static final int KEEP_ALIVE_MINUTES = 30;
+
+    /**
+     * 线程名称前缀
+     */
+    private static final String THREAD_NAME_PREFIX = "qwHandle-";
+
+    /**
+     * 自定义任务队列,优先创建新线程而不是进入队列
+     * ThreadPoolExecutor的任务提交逻辑:
+     * 1. 线程数 < corePoolSize -> 创建新线程
+     * 2. 线程数 >= corePoolSize -> 尝试入队(调用offer方法)
+     * 3. offer返回false且线程数 < maxPoolSize -> 创建新线程
+     * 4. offer返回false且线程数 >= maxPoolSize -> 执行拒绝策略
+     *
+     * 通过重写offer方法,让其在未达最大线程数时返回false,从而优先创建新线程
+     */
+    private static class PriorityThreadQueue extends LinkedBlockingQueue<Runnable> {
+
+        private static final long serialVersionUID = 1L;
+
+        private transient volatile ThreadPoolExecutor executor;
+
+        public PriorityThreadQueue(int capacity) {
+            super(capacity);
+        }
+
+        public void setExecutor(ThreadPoolExecutor executor) {
+            this.executor = executor;
+        }
+
+        @Override
+        public boolean offer(Runnable o) {
+            ThreadPoolExecutor exec = this.executor;
+            if (exec != null) {
+                int poolSize = exec.getPoolSize();
+                int maxPoolSize = exec.getMaximumPoolSize();
+                // 如果线程数未达到最大值,返回false让线程池创建新线程
+                if (poolSize < maxPoolSize) {
+                    return false;
+                }
+            }
+            // 线程数已达最大值,任务入队
+            return super.offer(o);
+        }
+
+        /**
+         * 强制入队方法,供拒绝策略使用
+         */
+        public boolean forceOffer(Runnable o) {
+            return super.offer(o);
+        }
+    }
+
+    /**
+     * 自定义拒绝策略:调用者执行
+     */
+    private static class CallerRunsPolicy implements RejectedExecutionHandler {
+
+        private final PriorityThreadQueue queue;
+
+        public CallerRunsPolicy(PriorityThreadQueue queue) {
+            this.queue = queue;
+        }
+
+        @Override
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+            if (executor.isShutdown()) {
+                return;
+            }
+            // 尝试强制入队
+            if (!queue.forceOffer(r)) {
+                // 入队失败,由调用者线程执行
+                r.run();
+            }
+        }
+    }
+
+    /**
+     * 自定义线程工厂
+     */
+    private static class QwHandleThreadFactory implements ThreadFactory {
+
+        private final AtomicInteger threadNumber = new AtomicInteger(1);
+        private final String namePrefix;
+
+        QwHandleThreadFactory() {
+            namePrefix = THREAD_NAME_PREFIX;
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
+            t.setDaemon(false);
+            t.setPriority(Thread.NORM_PRIORITY);
+            return t;
+        }
+    }
+
+    /**
+     * 配置qwHandle线程池
+     */
+    @Bean(name = "qwHandleExec")
+    public ThreadPoolExecutor qwHandleExecutor() {
+        PriorityThreadQueue queue = new PriorityThreadQueue(QUEUE_CAPACITY);
+
+        ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                CORE_POOL_SIZE,
+                MAX_POOL_SIZE,
+                KEEP_ALIVE_MINUTES,
+                TimeUnit.MINUTES,
+                queue,
+                new QwHandleThreadFactory(),
+                new CallerRunsPolicy(queue)
+        );
+
+        // 允许核心线程超时
+        executor.allowCoreThreadTimeOut(true);
+
+        // 将队列与执行器关联
+        queue.setExecutor(executor);
+
+        return executor;
+    }
+}

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

@@ -679,4 +679,6 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "</foreach>" +
             "</script>")
     public int batchUpdateQwExternalContactMandatoryRegistration(@Param("map") List<QwMandatoryRegistrParam> batchList);
+
+    public int batchUpdateQwExternalLevelById(QwExternalContactUpdateLevelParam param);
 }

+ 16 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateLevelParam.java

@@ -0,0 +1,16 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QwExternalContactUpdateLevelParam {
+
+    private List<Long> userIds;
+    private Integer level;
+
+    private boolean filter;
+    private QwExternalContactParam param;
+
+}

+ 3 - 4
fs-service/src/main/java/com/fs/qw/service/IQwUserServiceAsyncHelper.java

@@ -1,10 +1,7 @@
 package com.fs.qw.service;
 
 import com.fs.company.domain.Company;
-import com.fs.qw.param.QwExternalContactAddTagParam;
-import com.fs.qw.param.QwExternalContactUpdateNoteParam;
-import com.fs.qw.param.ResignedTransferParam;
-import com.fs.qw.param.TransferParam;
+import com.fs.qw.param.*;
 import org.codehaus.jettison.json.JSONException;
 
 import java.io.IOException;
@@ -82,4 +79,6 @@ public interface IQwUserServiceAsyncHelper {
      * @throws Exception
      */
     void cogradientGroupChat(String corpId, List<String> qwUserIdList ) throws Exception;
+
+    void batchUpdateExternalContactLevelById(QwExternalContactUpdateLevelParam batchParam);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceAsyncHelperImpl.java

@@ -28,6 +28,7 @@ import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.params.DeleteQwSopParam;
 import com.fs.sop.params.QwSopAutoByTags;
 import com.fs.sop.params.SopUserLogsParamByDate;
+import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.watch.mapper.WatchCompanyUserMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -102,6 +103,9 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
     @Autowired
     private AsyncQwAiChatSopService asyncQwAiChatSopService;
 
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
     org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
 
     /**
@@ -1283,4 +1287,12 @@ public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
         }
         log.info("同步客户群列表成功");
     }
+
+    @Override
+    @Async("qwHandleExec")
+    public void batchUpdateExternalContactLevelById(QwExternalContactUpdateLevelParam param) {
+
+        qwExternalContactMapper.batchUpdateQwExternalLevelById(param);
+        iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByIsLevel(param);
+    }
 }

+ 21 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -4,6 +4,7 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
 import com.fs.sop.params.DeleteQwSopParam;
@@ -285,4 +286,24 @@ public interface SopUserLogsInfoMapper {
             "GROUP BY qsts.id, sul.start_time "+
             "</script>")
     List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(@Param("data") QwExtCourseSopWatchLog qwExternalContactId);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET " +
+            "    is_days_not_study = CASE " +
+            "        WHEN #{map.level} = 5 THEN 1 " +
+            "        WHEN #{map.level} = 9 THEN 0 " +
+            "        ELSE 0 " +
+            "    END, " +
+            "    grade = CASE " +
+            "        WHEN #{map.level} = 9 THEN 0 " +
+            "        ELSE #{map.level} " +
+            "    END " +
+            "WHERE external_id IN " +
+            "<foreach collection='map.userIds' item='item' open='(' separator=',' close=')'>" +
+            "    #{item} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByIsLevel(@Param("map") QwExternalContactUpdateLevelParam param);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -3,6 +3,7 @@ package com.fs.sop.service;
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
 import com.fs.sop.params.SendUserLogsInfoMsgParam;
@@ -84,4 +85,6 @@ public interface ISopUserLogsInfoService {
     List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId);
 
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId);
+
+    void batchUpdateSopUserLogsInfoByIsLevel(QwExternalContactUpdateLevelParam param);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -36,6 +36,7 @@ import com.fs.live.mapper.LiveWatchLogMapper;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
+import com.fs.qw.param.QwExternalContactUpdateLevelParam;
 import com.fs.qw.param.QwExternalContactVOTime;
 import com.fs.qw.param.QwTagSearchParam;
 import com.fs.qw.service.IQwCompanyService;
@@ -1532,6 +1533,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return watchLogVOList;
     }
 
+    @Override
+    public void batchUpdateSopUserLogsInfoByIsLevel(QwExternalContactUpdateLevelParam param) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsLevel(param);
+    }
+
     private R processQwSopLogsBySendMsg(SendUserLogsInfoMsgParam param,Integer draftStrategy) {
 
 

+ 17 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -849,4 +849,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and t1.corp_id = #{corpId}
             and t1.status = 0
      </select>
+
+    <update id="batchUpdateQwExternalLevelById" parameterType="map">
+        UPDATE qw_external_contact
+        SET level =
+        <choose>
+            <when test="level == 9">
+                NULL
+            </when>
+            <otherwise>
+                #{level}
+            </otherwise>
+        </choose>
+        WHERE id IN
+        <foreach collection="userIds" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+    </update>
 </mapper>