فهرست منبع

我的/看课记录 + 批量添加标签和移除标签

三七 12 ساعت پیش
والد
کامیت
289144cc32

+ 27 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -26,6 +26,7 @@ import com.fs.qw.param.*;
 import com.fs.qw.service.IQwExternalContactInfoService;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwTagService;
+import com.fs.qw.service.IQwWatchLogService;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.fs.qw.vo.QwFsUserVO;
 import com.github.pagehelper.PageHelper;
@@ -269,14 +270,24 @@ public class QwExternalContactController extends BaseController
     }
 
     @PreAuthorize("@ss.hasPermi('qw:externalContact:addTag')")
-    @Log(title = "添加标签", businessType = BusinessType.INSERT)
+    @Log(title = "添加标签", businessType = BusinessType.UPDATE)
     @PostMapping("/addTag")
     public R addTag(@RequestBody QwExternalContactAddTagParam Param) throws JSONException {
 
         return qwExternalContactService.addUserTag(Param);
     }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:addTag')")
+    @Log(title = "添加标签", businessType = BusinessType.UPDATE)
+    @PostMapping("/addTagByWatch")
+    public R addTagByWatch(@RequestBody QwExtContactAddTagByWatchParam Param) throws JSONException {
+
+        return qwExternalContactService.addTagByWatch(Param);
+
+    }
+
     @PreAuthorize("@ss.hasPermi('qw:externalContact:delTag')")
-    @Log(title = "移除标签", businessType = BusinessType.INSERT)
+    @Log(title = "移除标签", businessType = BusinessType.UPDATE)
     @PostMapping("/delTag")
     public R delTag(@RequestBody QwExternalContactAddTagParam Param)
     {
@@ -284,6 +295,20 @@ public class QwExternalContactController extends BaseController
         return qwExternalContactService.delUserTag(Param);
     }
 
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:delTag')")
+    @Log(title = "在看课记录这移除标签", businessType = BusinessType.UPDATE)
+    @PostMapping("/delTagByWatch")
+    public R delTagByWatch(@RequestBody QwExtContactAddTagByWatchParam Param)
+    {
+
+        return qwExternalContactService.delTagByWatch(Param);
+    }
+
+
+
+
+
+
     @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
     @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
     @PutMapping("/resignedTransfer")

+ 16 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -6,6 +6,7 @@ import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.sop.vo.QwRatingVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -403,4 +404,19 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      * @return  count
      */
     int countByMap(@Param("params") Map<String, Object> params);
+
+
+    @Select("<script>" +
+            "select qec.id,qec.user_id,qec.external_user_id,qec.corp_id,qec.tag_ids,qec.name from fs_course_watch_log qwl " +
+            "left join qw_external_contact qec on qwl.qw_external_contact_id=qec.id " +
+            "        where qwl.log_id in\n" +
+            "        <foreach collection=\"logIds\" item=\"id\" open=\"(\" separator=\",\" close=\")\">\n" +
+            "            #{id}\n" +
+            "        </foreach>" +
+            "GROUP BY " +
+            "    qec.user_id,\n" +
+            "    qec.external_user_id,\n" +
+            "    qec.corp_id"+
+            "</script>")
+    List<QwExternalContact> selectQwWatchLogFomExtContact(@Param("logIds") List<Long> logIds);
 }

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

@@ -40,6 +40,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Select("select * from qw_external_contact where name='待同步客户' AND create_time >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) and status !=4 ")
     public List<QwExternalContact> synchronizeQwExternalContactTask();
 
+    public List<QwExternalContact> selectQwExternalContactByIds(@Param("ids") List<Long> ids);
+
     @Select("SELECT id,stage_status,fs_user_id from qw_external_contact where id=#{id}")
     public QwExternalContact selectQwExternalContactByIdForStageStatus(@Param("id") Long id);
 
@@ -185,6 +187,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     public int batchUpdateQwExternalContact(List<QwExternalContact> qwExternalContact);
 
+    public int batchUpdateQwExternalContactByTags(List<QwExternalContact> qwExternalContact);
+
     @Update("update qw_external_contact set qw_user_id=#{qwUserId} , company_user_id =#{companyUserId} " +
             "where user_id=#{userId}  and corp_id=#{corpId} ")
     public int updateQwExternalContactByQwUserId(QwExternalContact qwExternalContact);

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

@@ -1,6 +1,7 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
@@ -235,4 +236,5 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
     @Select("SELECT count(1) from qw_watch_log where fs_user_id=#{userId} and `day`=0")
     int selectQwWatchLogIsFirstByUserId(Long userId);
 
+
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/param/QwExtContactAddTagByWatchParam.java

@@ -0,0 +1,18 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+* 给看课记录上的用户打标签
+*/
+@Data
+public class QwExtContactAddTagByWatchParam {
+
+    /**
+    * 看课记录上的logId
+    */
+    List<Long> logIds;
+    List<String> tagIds;
+}

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

@@ -107,8 +107,10 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
     R transfer(TransferParam param);
 
     R addUserTag(QwExternalContactAddTagParam param) throws JSONException;
+    R addTagByWatch(QwExtContactAddTagByWatchParam param) throws JSONException;
 
     R delUserTag(QwExternalContactAddTagParam param);
+    R delTagByWatch(QwExtContactAddTagByWatchParam param);
 
     QwUser getQwUserByRedis(String corpId, String userID);
 

+ 531 - 79
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -72,6 +72,7 @@ import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -1189,142 +1190,593 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     @Override
     public R addUserTag(QwExternalContactAddTagParam param) throws JSONException {
-        int err = 0;
-        int suc = 0;
-        // 获取当前日期(只包含年月日)
+        // 获取当前日期和时间
         LocalDate currentDate = LocalDate.now();
-        // 获取当前系统时间 (HH:mm)
         LocalTime localTime = LocalTime.now();
 
-        List<Long> userIds = param.getUserIds();
-        for (Long userId : userIds) {
+        // 使用线程安全的计数器
+        AtomicInteger suc = new AtomicInteger(0);
+
+        List<String> failList = new CopyOnWriteArrayList<>(); // 记录失败客户的名字
 
-            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(userId);
-            QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
-            qwEditUserTagParam.setAdd_tag(param.getTagIds());
-            qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
-            qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+        // 创建线程池
+        int threadCount = Math.min(8, Runtime.getRuntime().availableProcessors() * 2);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+
+
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<QwExternalContact> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
 
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+
+            // 1. 批量查询所有用户数据
+            List<QwExternalContact> contacts;
             try {
+                contacts = qwExternalContactMapper.selectQwExternalContactByIds(param.getUserIds());
+                if (contacts == null || contacts.isEmpty()) {
+                    return R.error("成功:0,失败:" + param.getUserIds().size());
+                }
+            } catch (Exception e) {
+                return R.error("批量查询用户数据失败:"+e.getMessage());
+            }
 
-                QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, param.getCorpId());
-                if (qwResult.getErrcode() == 0) {
+            // 直接遍历contacts而不是userIds
+            for (QwExternalContact qwExternalContact : contacts) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                        qwEditUserTagParam.setAdd_tag(param.getTagIds());
+                        qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+                        qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+                        QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, param.getCorpId());
+                        if (qwResult.getErrcode() == 0) {
+                            // 处理标签
+                            String tagIds = qwExternalContact.getTagIds();
+                            Set<String> uniqueIds = new HashSet<>();
+
+                            if (tagIds != null && !tagIds.isEmpty()) {
+                                List<String> parsedTags = JSON.parseArray(tagIds, String.class);
+                                if (parsedTags != null && !parsedTags.isEmpty()) {
+                                    uniqueIds.addAll(parsedTags);
+                                }
+                            }
 
-                    //客户本身的标签集合+打的标签
-                    QwExternalContact qwExternal = new QwExternalContact();
-                    String tagIds = qwExternalContact.getTagIds();
+                            if (param.getTagIds() != null && !param.getTagIds().isEmpty()) {
+                                uniqueIds.addAll(param.getTagIds());
+                            }
+
+                            QwExternalContact qwExternal = new QwExternalContact();
+                            qwExternal.setTagIds(JSON.toJSONString(uniqueIds));
+                            qwExternal.setId(qwExternalContact.getId());
 
-                    // 将标签字符串解析为 List    //客户总标签
-                    List<String> tagIdsList = new ArrayList<>();
+                            List<String> tagIdsList = new ArrayList<>();
+                            if (qwExternal.getTagIds() != null && !qwExternal.getTagIds().isEmpty()) {
+                                tagIdsList = JSON.parseArray(qwExternal.getTagIds(), String.class);
+                            }
 
-                    Set<String> uniqueIds = new HashSet<>();
-                    if (tagIds != null && !tagIds.isEmpty()) {
-                        List<String> parsedTags = JSON.parseArray(tagIds, String.class);
-                        if (parsedTags != null && !parsedTags.isEmpty()) {
-                            uniqueIds.addAll(parsedTags);
+                            logger.info("客户添加标签addUserTag:" + qwExternalContact.getName() +
+                                    "|公司" + qwExternalContact.getCorpId() +
+                                    "|员工" + qwExternalContact.getUserId() +
+                                    "|总标签" + tagIdsList);
+
+                            // 插件sop处理
+                            processTagsAll(qwExternalContact, qwExternalContact.getCorpId(),
+                                    tagIdsList, currentDate, localTime);
+
+                            // 添加到批量更新列表
+                            batchUpdateList.add(qwExternal);
+                            suc.incrementAndGet();
+                        } else {
+                            failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + getErrorMsg(qwResult.getErrcode()) + ")\n");
                         }
+                    } catch (Exception e) {
+                        logger.error("客户添加标签失败,userId: " + qwExternalContact.getId() +
+                                ", externalUserId: " + qwExternalContact.getExternalUserId() +
+                                ", 错误信息: " + e.getMessage());
+                        failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + e.getMessage() + ")\n");
                     }
+                }));
+            }
+
 
-                    // 将 param 中的标签 ID 加入集合
-                    if (param.getTagIds() != null  && !param.getTagIds().isEmpty()) {
-                        uniqueIds.addAll(param.getTagIds());
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    logger.error("任务执行异常: " + e.getMessage());
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            // 批量更新数据库
+            if (!batchUpdateList.isEmpty()) {
+                try {
+                    // 分批处理,避免单次批量过大
+                    int batchSize = 500; // 根据数据库性能调整
+                    for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, batchUpdateList.size());
+                        List<QwExternalContact> subList = batchUpdateList.subList(i, end);
+                        qwExternalContactMapper.batchUpdateQwExternalContactByTags(subList);
                     }
+                } catch (Exception e) {
+                    logger.error("批量更新失败: " + e.getMessage());
+                }
+            }
 
-                    // 将唯一标签集合设置回 qwExternal
-                    qwExternal.setTagIds(JSON.toJSONString(uniqueIds));
+            // 关闭线程池
+            executor.shutdown();
 
+        }catch (Exception e){
+            failList.add("处理过程中发生异常:"+e.getMessage());
+        }finally {
+            // 7. 确保线程池关闭
+            try {
+                // 先尝试正常关闭
+                executor.shutdown();
+                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    // 超时后强制关闭
+                    executor.shutdownNow();
+                    logger.warn("线程池强制关闭");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                executor.shutdownNow();
+            }
+        }
 
-                    if (qwExternal.getTagIds() != null && !qwExternal.getTagIds().isEmpty()) {
-                        tagIdsList = JSON.parseArray(qwExternal.getTagIds(), String.class);
-                    }
+        return R.ok("成功:" + suc.get() + ",失败:" + failList);
+    }
+
+
+    private String getErrorMsg(Integer code){
+
+        String msg="";
+        switch (code){
+            case 40003:
+                msg="无效的UserID(员工账号)";
+                break;
+            case 40096:
+                msg="不合法的外部联系人userid";
+                break;
+            case 45033:
+                msg="接口并发调用超过限制(**调用过于频繁请稍后再试**)";
+                break;
+            case 45035:
+                msg="当前客户有其他修改操作,请稍后重试(**比如其他地方修改客户信息**)";
+                break;
+            case 60020:
+                msg="不安全的访问IP";
+                break;
+            case 84061:
+                msg="不存在外部联系人的关系(**客户【不存在】于员工的好友列表中**)";
+                break;
+            default:
+                msg=code.toString();
+                break;
+
+        }
+        return msg;
+    }
+
+    @Override
+    public R addTagByWatch(QwExtContactAddTagByWatchParam param) throws org.codehaus.jettison.json.JSONException {
 
-                    qwExternal.setId(userId);
 
-                    logger.info("客户添加标签addUserTag:"+qwExternalContact.getName()+"|公司"+qwExternalContact.getCorpId()+"|员工"+qwExternalContact.getUserId()+"|总标签"+tagIdsList);
 
-                    //插件sop
-                    processTagsAll(qwExternalContact,qwExternalContact.getCorpId(),tagIdsList,currentDate,localTime);
+        // 获取当前日期和时间
+        LocalDate currentDate = LocalDate.now();
+        LocalTime localTime = LocalTime.now();
+
+        // 使用线程安全的计数器
+        AtomicInteger suc = new AtomicInteger(0);
 
-                    //新客对话
-//                    processTagsAllByAiChat(qwExternalContact,param.getCorpId(),tagIdsList);
+        List<String> failList = new CopyOnWriteArrayList<>(); // 记录失败客户的名字
 
-                    suc++;
-                    qwExternalContactMapper.updateQwExternalContact(qwExternal);
+        // 创建线程池
+        int threadCount = Math.min(8, Runtime.getRuntime().availableProcessors() * 2);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+
+
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<QwExternalContact> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
+
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+
+            // 1. 批量查询所有用户数据
+            List<QwExternalContact> contacts;
+            try {
+                 contacts = fsCourseWatchLogMapper.selectQwWatchLogFomExtContact(param.getLogIds());
+                if (contacts == null || contacts.isEmpty()) {
+                    return R.error("成功:0,失败:" + param.getLogIds().size());
                 }
-                else {
-                    err++;
+            } catch (Exception e) {
+                return R.error("批量查询用户数据失败:"+e.getMessage());
+            }
+
+            // 直接遍历contacts而不是userIds
+            for (QwExternalContact qwExternalContact : contacts) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                        qwEditUserTagParam.setAdd_tag(param.getTagIds());
+                        qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+                        qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+                        QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, qwExternalContact.getCorpId());
+                        if (qwResult.getErrcode() == 0) {
+                            // 处理标签
+                            String tagIds = qwExternalContact.getTagIds();
+                            Set<String> uniqueIds = new HashSet<>();
+
+                            if (tagIds != null && !tagIds.isEmpty()) {
+                                List<String> parsedTags = JSON.parseArray(tagIds, String.class);
+                                if (parsedTags != null && !parsedTags.isEmpty()) {
+                                    uniqueIds.addAll(parsedTags);
+                                }
+                            }
+
+                            if (param.getTagIds() != null && !param.getTagIds().isEmpty()) {
+                                uniqueIds.addAll(param.getTagIds());
+                            }
+
+                            QwExternalContact qwExternal = new QwExternalContact();
+                            qwExternal.setTagIds(JSON.toJSONString(uniqueIds));
+                            qwExternal.setId(qwExternalContact.getId());
+
+                            List<String> tagIdsList = new ArrayList<>();
+                            if (qwExternal.getTagIds() != null && !qwExternal.getTagIds().isEmpty()) {
+                                tagIdsList = JSON.parseArray(qwExternal.getTagIds(), String.class);
+                            }
+
+                            logger.info("看课处客户添加标签addUserTag:" + qwExternalContact.getName() +
+                                    "|公司" + qwExternalContact.getCorpId() +
+                                    "|员工" + qwExternalContact.getUserId() +
+                                    "|总标签" + tagIdsList);
+
+                            // 插件sop处理
+                            processTagsAll(qwExternalContact, qwExternalContact.getCorpId(),
+                                    tagIdsList, currentDate, localTime);
+
+                            // 添加到批量更新列表
+                            batchUpdateList.add(qwExternal);
+                            suc.incrementAndGet();
+                        } else {
+                            failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + getErrorMsg(qwResult.getErrcode()) + ")\n");
+                        }
+                    } catch (Exception e) {
+                        logger.error("看课处客户添加标签失败,userId: " + qwExternalContact.getId() +
+                                ", externalUserId: " + qwExternalContact.getExternalUserId() +
+                                ", 错误信息: " + e.getMessage());
+                        failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + e.getMessage() + ")\n");
+                    }
+                }));
+            }
+
+
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    logger.error("任务执行异常: " + e.getMessage());
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            // 批量更新数据库
+            if (!batchUpdateList.isEmpty()) {
+                try {
+                    // 分批处理,避免单次批量过大
+                    int batchSize = 500; // 根据数据库性能调整
+                    for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, batchUpdateList.size());
+                        List<QwExternalContact> subList = batchUpdateList.subList(i, end);
+                        qwExternalContactMapper.batchUpdateQwExternalContactByTags(subList);
+                    }
+                } catch (Exception e) {
+                    logger.error("批量更新失败: " + e.getMessage());
                 }
-            }catch (Exception e){
-                logger.error("客户添加标签失败:"+qwEditUserTagParam+e.getMessage());
             }
 
+            // 关闭线程池
+            executor.shutdown();
 
+        }catch (Exception e){
+            failList.add("处理过程中发生异常:"+e.getMessage());
+        }finally {
+            // 7. 确保线程池关闭
+            try {
+                // 先尝试正常关闭
+                executor.shutdown();
+                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    // 超时后强制关闭
+                    executor.shutdownNow();
+                    logger.warn("线程池强制关闭");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                executor.shutdownNow();
+            }
         }
 
-        return R.ok("成功:" + suc + ",失败:" + err);
+        return R.ok("成功:" + suc.get() + ",失败:" + failList);
+
     }
 
 
+
     @Override
     public R delUserTag(QwExternalContactAddTagParam param) {
-
-        // 获取当前日期(只包含年月日)
+        // 获取当前日期和时间
         LocalDate currentDate = LocalDate.now();
-        // 获取当前系统时间 (HH:mm)
         LocalTime localTime = LocalTime.now();
 
-        int err = 0;
-        int suc = 0;
-        List<Long> userIds = param.getUserIds();
-        for (Long userId : userIds) {
+        // 使用线程安全的计数器
+        AtomicInteger suc = new AtomicInteger(0);
+
+        List<String> failList = new CopyOnWriteArrayList<>(); // 记录失败客户的名字
 
-            QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(userId);
+        // 创建线程池
+        int threadCount = Math.min(8, Runtime.getRuntime().availableProcessors() * 2);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
 
-            QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
-            qwEditUserTagParam.setRemove_tag(param.getTagIds());
-            qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
-            qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
 
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<QwExternalContact> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
+
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+
+            // 1. 批量查询所有用户数据
+            List<QwExternalContact> contacts;
             try {
-                QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, param.getCorpId());
-                if (qwResult.getErrcode() == 0) {
-
-                    String tagIds = qwExternalContact.getTagIds();
-                    if (tagIds != null && tagIds != "") {
-                        List<String> ids = JSON.parseArray(tagIds, String.class);
-                        for (String tagId : param.getTagIds()) {
-                            ids.removeIf(str -> str.equals(tagId));
+                contacts = qwExternalContactMapper.selectQwExternalContactByIds(param.getUserIds());
+                if (contacts == null || contacts.isEmpty()) {
+                    return R.error("成功:0,失败:" + param.getUserIds().size());
+                }
+            } catch (Exception e) {
+                return R.error("批量查询用户数据失败:"+e.getMessage());
+            }
+
+            // 直接遍历contacts而不是userIds
+            for (QwExternalContact qwExternalContact : contacts) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                        qwEditUserTagParam.setRemove_tag(param.getTagIds());
+                        qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+                        qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+                        QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, param.getCorpId());
+                        if (qwResult.getErrcode() == 0) {
+
+                            String tagIds = qwExternalContact.getTagIds();
+                            if (tagIds != null && tagIds != "") {
+                                List<String> ids = JSON.parseArray(tagIds, String.class);
+                                for (String tagId : param.getTagIds()) {
+                                    ids.removeIf(str -> str.equals(tagId));
+                                }
+
+                                QwExternalContact qwExternal = new QwExternalContact();
+                                qwExternal.setId(qwExternalContact.getId());
+                                qwExternal.setTagIds(JSON.toJSONString(ids));
+
+
+                                logger.info("客户移除标签delUserTag:"+qwExternalContact.getName()+"|公司"+qwExternalContact.getCorpId()+"|员工"+qwExternalContact.getUserId()+"|总标签"+ids);
+
+                                //检查sop
+                                processTagsAll(qwExternalContact,param.getCorpId(),ids,currentDate,localTime);
+
+                                // 添加到批量更新列表
+                                batchUpdateList.add(qwExternal);
+                            }
+
+                            suc.incrementAndGet();
+                        } else {
+                            failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + getErrorMsg(qwResult.getErrcode()) + ")\n");
                         }
+                    } catch (Exception e) {
+                        logger.error("客户移除标签delUserTag失败,userId: " + qwExternalContact.getId() +
+                                ", externalUserId: " + qwExternalContact.getExternalUserId() +
+                                ", 错误信息: " + e.getMessage());
+                        failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + e.getMessage() + ")\n");
+                    }
+                }));
+            }
+
+
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    logger.error("任务执行异常: " + e.getMessage());
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            // 批量更新数据库
+            if (!batchUpdateList.isEmpty()) {
+                try {
+                    // 分批处理,避免单次批量过大
+                    int batchSize = 500; // 根据数据库性能调整
+                    for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, batchUpdateList.size());
+                        List<QwExternalContact> subList = batchUpdateList.subList(i, end);
+                        qwExternalContactMapper.batchUpdateQwExternalContactByTags(subList);
+                    }
+                } catch (Exception e) {
+                    logger.error("批量更新失败: " + e.getMessage());
+                }
+            }
+
+            // 关闭线程池
+            executor.shutdown();
+
+        }catch (Exception e){
+            failList.add("处理过程中发生异常:"+e.getMessage());
+        }finally {
+
+            // 7. 确保线程池关闭
+            try {
+                // 先尝试正常关闭
+                executor.shutdown();
+                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    // 超时后强制关闭
+                    executor.shutdownNow();
+                    logger.warn("线程池强制关闭");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                executor.shutdownNow();
+            }
+        }
+
+
+        return R.ok("成功:" + suc.get() + ",失败:" + failList);
+    }
+
+    @Override
+    public R delTagByWatch(QwExtContactAddTagByWatchParam param) {
+        // 获取当前日期和时间
+        LocalDate currentDate = LocalDate.now();
+        LocalTime localTime = LocalTime.now();
+
+        // 使用线程安全的计数器
+        AtomicInteger suc = new AtomicInteger(0);
+
+        List<String> failList = new CopyOnWriteArrayList<>(); // 记录失败客户的名字
+
+        // 创建线程池
+        int threadCount = Math.min(8, Runtime.getRuntime().availableProcessors() * 2);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
 
-                        QwExternalContact qwExternal = new QwExternalContact();
-                        qwExternal.setId(userId);
-                        qwExternal.setTagIds(JSON.toJSONString(ids));
 
-                        qwExternalContactMapper.updateQwExternalContact(qwExternal);
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<QwExternalContact> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
+
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+
+            // 1. 批量查询所有用户数据
+            List<QwExternalContact> contacts;
+            try {
+                contacts = fsCourseWatchLogMapper.selectQwWatchLogFomExtContact(param.getLogIds());
+                if (contacts == null || contacts.isEmpty()) {
+                    return R.error("成功:0,失败:" + param.getLogIds().size());
+                }
+            } catch (Exception e) {
+                return R.error("批量查询用户数据失败:"+e.getMessage());
+            }
+
+            // 直接遍历contacts而不是userIds
+            for (QwExternalContact qwExternalContact : contacts) {
+                futures.add(executor.submit(() -> {
+                    try {
+                        QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                        qwEditUserTagParam.setRemove_tag(param.getTagIds());
+                        qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+                        qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+                        QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, qwExternalContact.getCorpId());
+                        if (qwResult.getErrcode() == 0) {
+
+                            String tagIds = qwExternalContact.getTagIds();
+                            if (tagIds != null && tagIds != "") {
+                                List<String> ids = JSON.parseArray(tagIds, String.class);
+                                for (String tagId : param.getTagIds()) {
+                                    ids.removeIf(str -> str.equals(tagId));
+                                }
 
-                        logger.info("客户移除标签delUserTag:"+qwExternalContact.getName()+"|公司"+qwExternalContact.getCorpId()+"|员工"+qwExternalContact.getUserId()+"|总标签"+ids);
+                                QwExternalContact qwExternal = new QwExternalContact();
+                                qwExternal.setId(qwExternalContact.getId());
+                                qwExternal.setTagIds(JSON.toJSONString(ids));
 
-                        //检查sop
-                        processTagsAll(qwExternalContact,param.getCorpId(),ids,currentDate,localTime);
 
-                        //新客对话
-//                        processTagsAllByAiChat(qwExternalContact,param.getCorpId(),ids);
+                                logger.info("看课处客户移除标签delUserTag:"+qwExternalContact.getName()+"|公司"+qwExternalContact.getCorpId()+"|员工"+qwExternalContact.getUserId()+"|总标签"+ids);
+
+                                //检查sop
+                                processTagsAll(qwExternalContact,qwExternalContact.getCorpId(),ids,currentDate,localTime);
+
+                                // 添加到批量更新列表
+                                batchUpdateList.add(qwExternal);
+                            }
+
+                            suc.incrementAndGet();
+                        } else {
+                            failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + getErrorMsg(qwResult.getErrcode()) + ")\n");
+                        }
+                    } catch (Exception e) {
+                        logger.error("看课处客户移除标签delUserTag失败,userId: " + qwExternalContact.getId() +
+                                ", externalUserId: " + qwExternalContact.getExternalUserId() +
+                                ", 错误信息: " + e.getMessage());
+                        failList.add("【"+qwExternalContact.getName()+"】" + "原因:(" + e.getMessage() + ")\n");
                     }
-                    suc++;
+                }));
+            }
 
-                } else {
-                    err++;
+
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    logger.error("任务执行异常: " + e.getMessage());
+                    Thread.currentThread().interrupt();
                 }
+            }
 
-            }catch (Exception e){
-                logger.error("移除标签失败:"+qwEditUserTagParam+e.getMessage());
+            // 批量更新数据库
+            if (!batchUpdateList.isEmpty()) {
+                try {
+                    // 分批处理,避免单次批量过大
+                    int batchSize = 500; // 根据数据库性能调整
+                    for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, batchUpdateList.size());
+                        List<QwExternalContact> subList = batchUpdateList.subList(i, end);
+                        qwExternalContactMapper.batchUpdateQwExternalContactByTags(subList);
+                    }
+                } catch (Exception e) {
+                    logger.error("批量更新失败: " + e.getMessage());
+                }
             }
 
+            // 关闭线程池
+            executor.shutdown();
 
+        }catch (Exception e){
+            failList.add("处理过程中发生异常:"+e.getMessage());
+        }finally {
+
+            // 7. 确保线程池关闭
+            try {
+                // 先尝试正常关闭
+                executor.shutdown();
+                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    // 超时后强制关闭
+                    executor.shutdownNow();
+                    logger.warn("线程池强制关闭");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                executor.shutdownNow();
+            }
         }
 
 
-        return R.ok("成功:" + suc + ",失败:" + err);
+        return R.ok("成功:" + suc.get() + ",失败:" + failList);
     }
 
     public void insertQwExternalByMq(Long ExtId){

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

@@ -85,6 +85,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         group by a.id
     </select>
 
+    <select id="selectQwExternalContactByIds" resultType="com.fs.qw.domain.QwExternalContact">
+        select * from qw_external_contact
+        where id in
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+
     <select id="selectQwExternalContactListVOByIds" resultType="com.fs.qw.param.QwExternalContactVOTime">
             select id,tag_ids,remark,create_time,fs_user_id,avatar from qw_external_contact
           where id in
@@ -165,6 +173,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+
+    <update id="batchUpdateQwExternalContactByTags" parameterType="map">
+        UPDATE qw_external_contact
+        SET
+        tag_ids = CASE id
+        <foreach collection="list" item="item">
+            WHEN #{item.id} THEN #{item.tagIds}
+        </foreach>
+        ELSE tag_ids
+        END
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
     <update id="batchUpdateQwExternalContactStatus" parameterType="map">
         UPDATE qw_external_contact
         SET status = 4