Browse Source

Merge remote-tracking branch 'origin/master' into ScrmStore

yjwang 2 weeks ago
parent
commit
a90c3e411f
67 changed files with 2369 additions and 410 deletions
  1. 3 0
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 7 1
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  3. 22 3
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorArticleCateController.java
  4. 6 1
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorArticleController.java
  5. 1 1
      fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java
  6. 2 1
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  7. 1 1
      fs-company-app/src/main/java/com/fs/app/utils/JwtUtils.java
  8. 2 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  9. 59 8
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  10. 1 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java
  11. 2 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java
  12. 12 2
      fs-company/src/main/java/com/fs/company/controller/store/FsPackageController.java
  13. 3 2
      fs-company/src/main/java/com/fs/user/FsUserAdminController.java
  14. 13 4
      fs-doctor-app/src/main/java/com/fs/app/controller/DoctorArticleController.java
  15. 1 1
      fs-qw-task/src/main/java/com/fs/FsQwTaskApplication.java
  16. 37 26
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  17. 8 3
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  18. 5 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  19. 3 1
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  20. 2 2
      fs-service/src/main/java/com/fs/core/utils/OrderCodeUtils.java
  21. 2 1
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java
  22. 2 1
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  23. 20 8
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  24. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  25. 1 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  26. 2 0
      fs-service/src/main/java/com/fs/course/param/BatchVideoSvae.java
  27. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoFinishUParam.java
  28. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  29. 8 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseAnswerLogsServiceImpl.java
  30. 47 16
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  31. 79 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  32. 4 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  33. 14 3
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  34. 1 1
      fs-service/src/main/java/com/fs/his/mapper/FsCouponMapper.java
  35. 3 2
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  36. 6 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  37. 1 1
      fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java
  38. 3 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateNoteParam.java
  39. 323 0
      fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java
  40. 85 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserServiceAsyncHelper.java
  41. 22 4
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  42. 1286 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceAsyncHelperImpl.java
  43. 9 7
      fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java
  44. 14 1
      fs-service/src/main/java/com/fs/sop/domain/QwSopTempContent.java
  45. 7 0
      fs-service/src/main/java/com/fs/sop/domain/QwSopTempVoice.java
  46. 12 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempContentMapper.java
  47. 2 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java
  48. 2 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java
  49. 6 2
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java
  50. 51 39
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  51. 0 73
      fs-service/src/main/resources/application-config-druid-fcky.yml
  52. 1 1
      fs-service/src/main/resources/application-config-druid-hcl.yml
  53. 4 4
      fs-service/src/main/resources/application-config-druid-jnmy.yml
  54. 1 1
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  55. 1 1
      fs-service/src/main/resources/application-config-zkzh.yml
  56. 0 150
      fs-service/src/main/resources/application-druid-fcky.yml
  57. 3 1
      fs-service/src/main/resources/application-druid-myhk-test.yml
  58. 3 1
      fs-service/src/main/resources/application-druid-myhk.yml
  59. 29 9
      fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml
  60. 1 0
      fs-service/src/main/resources/mapper/course/FsCourseFinishTempMapper.xml
  61. 96 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  62. 3 1
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  63. 8 8
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml
  64. 4 4
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  65. 5 0
      fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml
  66. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/CourseProductController.java
  67. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/CourseProductOrderController.java

+ 3 - 0
fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -64,6 +64,9 @@ public class IndexStatisticsController {
     @GetMapping("/trafficLog")
     public R getTrafficLog(){
         TrafficLogDTO trafficLogDTO = redisCache.getCacheObject(DATA_OVERVIEW_TRAFFIC_LOG);
+        if(trafficLogDTO == null) {
+            return null;
+        }
         SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("redPacket.Traffic.config");
         String configValue = sysConfig.getConfigValue();
         trafficLogDTO.setTraffic(configValue);

+ 7 - 1
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -191,7 +191,13 @@ public class FsUserCourseVideoController extends BaseController
         // 设置项目ID
         FsUserCourse fsUserCourse = fsUserCourseService.selectFsUserCourseByCourseId(vo.getCourseId());
         vo.setProjectId(fsUserCourse.getProject());
-
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            vo.setUserId(userId);
+        }
         fsUserCourseVideoService.batchSaveVideo(vo);
         return R.ok();
     }

+ 22 - 3
fs-admin/src/main/java/com/fs/his/controller/FsDoctorArticleCateController.java

@@ -1,7 +1,9 @@
 package com.fs.his.controller;
 
+import java.util.Collection;
 import java.util.List;
 
+import com.fs.common.core.redis.RedisCache;
 import com.fs.his.vo.OptionsVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +36,8 @@ public class FsDoctorArticleCateController extends BaseController
 {
     @Autowired
     private IFsDoctorArticleCateService fsDoctorArticleCateService;
+    @Autowired
+    RedisCache redisCache;
 
     /**
      * 查询医生文章分类列表
@@ -78,7 +82,12 @@ public class FsDoctorArticleCateController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody FsDoctorArticleCate fsDoctorArticleCate)
     {
-        return toAjax(fsDoctorArticleCateService.insertFsDoctorArticleCate(fsDoctorArticleCate));
+        int i = fsDoctorArticleCateService.insertFsDoctorArticleCate(fsDoctorArticleCate);
+        Collection<String> keys = redisCache.keys("getDoctorArticleCateList*");
+        for (String key : keys) {
+            redisCache.deleteObject(key);
+        }
+        return toAjax(i);
     }
 
     /**
@@ -89,7 +98,12 @@ public class FsDoctorArticleCateController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody FsDoctorArticleCate fsDoctorArticleCate)
     {
-        return toAjax(fsDoctorArticleCateService.updateFsDoctorArticleCate(fsDoctorArticleCate));
+        int i = fsDoctorArticleCateService.updateFsDoctorArticleCate(fsDoctorArticleCate);
+        Collection<String> keys = redisCache.keys("getDoctorArticleCateList*");
+        for (String key : keys) {
+            redisCache.deleteObject(key);
+        }
+        return toAjax(i);
     }
 
     /**
@@ -100,7 +114,12 @@ public class FsDoctorArticleCateController extends BaseController
 	@DeleteMapping("/{cateIds}")
     public AjaxResult remove(@PathVariable Long[] cateIds)
     {
-        return toAjax(fsDoctorArticleCateService.deleteFsDoctorArticleCateByCateIds(cateIds));
+        int i = fsDoctorArticleCateService.deleteFsDoctorArticleCateByCateIds(cateIds);
+        Collection<String> keys = redisCache.keys("getDoctorArticleCateList*");
+        for (String key : keys) {
+            redisCache.deleteObject(key);
+        }
+        return toAjax(i);
     }
 
     @GetMapping("/allList")

+ 6 - 1
fs-admin/src/main/java/com/fs/his/controller/FsDoctorArticleController.java

@@ -114,6 +114,11 @@ public class FsDoctorArticleController extends BaseController
 	@DeleteMapping("/{articleIds}")
     public AjaxResult remove(@PathVariable Long[] articleIds)
     {
-        return toAjax(fsDoctorArticleService.deleteFsDoctorArticleByArticleIds(articleIds));
+        int i = fsDoctorArticleService.deleteFsDoctorArticleByArticleIds(articleIds);
+        Collection<String> keys = redisCache.keys("getDoctorArticleList*");
+        for (String key : keys) {
+            redisCache.deleteObject(key);
+        }
+        return toAjax(i);
     }
 }

+ 1 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwSopTempController.java

@@ -134,7 +134,7 @@ public class QwSopTempController extends BaseController
         if(qwSopTemp.getSendType() == 11){
             //筛选选课程数据
             if(ObjectUtils.isNotNull(qwSopTemp.getTimeList()) && !qwSopTemp.getTimeList().isEmpty()){
-                qwSopTemp.setTimeList(new ArrayList<>(qwSopTemp.getTimeList().stream().filter(t->ObjectUtils.isNotNull(t) && !t.isEmpty()).collect(Collectors.toSet())));
+                qwSopTemp.setTimeList(new ArrayList<>(qwSopTemp.getTimeList().stream().filter(t->ObjectUtils.isNotNull(t) && !t.isEmpty()).collect(Collectors.toList())));
             }
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }

+ 2 - 1
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -209,7 +209,8 @@ public class CompanyUserController extends AppBaseController {
         companyUser.setCreateTime(new Date());
         companyUser.setIsAudit(0);
         companyUser.setParentId(upCompanyUser.getUserId());
-        companyUser.setCompanyId(upCompanyUser.getCompanyId());
+//        companyUser.setCompanyId(upCompanyUser.getCompanyId());
+        companyUser.setCompanyId(company.getCompanyId());
 
         // 部门
         CompanyDept dept = companyDeptService.getDefaultCompanyDeptByCompanyId(upCompanyUser.getCompanyId());

+ 1 - 1
fs-company-app/src/main/java/com/fs/app/utils/JwtUtils.java

@@ -31,7 +31,7 @@ public class JwtUtils {
         Date nowDate = new Date();
         //过期时间
         Date expireDate = new Date(nowDate.getTime() + expire * 1000);
-        System.out.println("==============================="+secret);
+//        System.out.println("==============================="+secret);
         return Jwts.builder()
                 .setHeaderParam("typ", "JWT")
                 .setSubject(userId+"")

+ 2 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java

@@ -226,7 +226,7 @@ public class FsCourseWatchLogController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVOexport(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");
     }
@@ -242,7 +242,7 @@ public class FsCourseWatchLogController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyUserId( loginUser.getUser().getUserId());
-        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVOexport(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");
     }

+ 59 - 8
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -7,6 +7,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.PubFun;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -23,10 +24,7 @@ import com.fs.his.service.IFsUserService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwTag;
 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.service.*;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.fs.qw.vo.QwFsUserVO;
 import com.github.pagehelper.PageHelper;
@@ -40,10 +38,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
@@ -76,6 +71,10 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IQwExternalContactInfoService qwExternalContactInfoService;
 
+
+    @Autowired
+    private IQwUserServiceAsyncHelper qwUserServiceAsyncHelper;
+
     @Autowired
     private CompanyDeptServiceImpl companyDeptService;
 
@@ -575,5 +574,57 @@ public class QwExternalContactController extends BaseController
 
         return  qwExternalContactService.setCustomerCourseSopList(param);
     }
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:edit')")
+    @Log(title = "批量修改备注", businessType = BusinessType.UPDATE)
+    @PostMapping("/batchUpdateExternalContactNotes")
+    public R batchUpdateExternalContactNotes(@RequestBody QwExternalContactUpdateNoteParam param) throws JSONException {
+        if(param.isFilter()){
+            param.setUserIds(getList(param.getAddType(), param.getParam()));
+        }
+        if(param.getUserIds() == null || param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
+//        qwExternalContactService.batchUpdateExternalContactNotes(Param);
+        qwUserServiceAsyncHelper.batchUpdateExternalContactNotes(param);
+        return R.ok("正在批量修改备注中");
+    }
+
+
+
+    private List<Long> getList(Integer addType, QwExternalContactParam param){
+        if(addType == null){
+            return Collections.emptyList();
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if(addType == 0){
+            param.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
+        if(addType == 1){
+            if(param.getQwUserId()==null){
+                return Collections.emptyList();
+            }
+            param.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
+        if(addType == 2){
+            List<Long> combinedList = new ArrayList<>();
+            //本部门
+            Long deptId = loginUser.getUser().getDeptId();
+            if (deptId!=null){
+                combinedList.add(deptId);
+            }
+            //本部门的下级部门
+            List<Long> deptList = companyDeptService.selectCompanyDeptByParentId(deptId);
+            if (!deptList.isEmpty()){
+                combinedList.addAll(deptList);
+            }
+
+            param.setCuDeptIdList(combinedList);
+            param.setUserType(loginUser.getUser().getUserType());
+            param.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(param);
+        if(list == null || list.isEmpty()) return Collections.emptyList();
+        return PubFun.listToNewList(list, QwExternalContactVO::getId);
+    }
 
 }

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -141,7 +141,7 @@ public class QwSopTempController extends BaseController
         if(qwSopTemp.getSendType() == 11){
             //筛选选课程数据
             if(ObjectUtils.isNotNull(qwSopTemp.getTimeList()) && !qwSopTemp.getTimeList().isEmpty()){
-                qwSopTemp.setTimeList(new ArrayList<>(qwSopTemp.getTimeList().stream().filter(t->ObjectUtils.isNotNull(t) && !t.isEmpty()).collect(Collectors.toSet())));
+                qwSopTemp.setTimeList(new ArrayList<>(qwSopTemp.getTimeList().stream().filter(t->ObjectUtils.isNotNull(t) && !t.isEmpty()).collect(Collectors.toList())));
             }
             new Thread(() -> qwSopTempService.createSopTempRules(qwSopTemp)).start();
         }

+ 2 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java

@@ -123,6 +123,8 @@ public class QwUserVoiceLogController extends BaseController
     @GetMapping("/sellTotalExport")
     public AjaxResult sellTotalExport(QwUserVoiceLogTotalVo qwUserVoiceLog)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
         List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
         list.forEach(m-> {
             m.setQwUserName(m.getQwUser().getQwUserName());

+ 12 - 2
fs-company/src/main/java/com/fs/company/controller/store/FsPackageController.java

@@ -9,6 +9,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsPackage;
 import com.fs.his.param.FsPackageParam;
 import com.fs.his.service.IFsPackageService;
+import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.FsPackageExcelVO;
 import com.fs.his.vo.FsPackageListVO;
 import com.fs.his.vo.OptionsVO;
@@ -31,6 +32,9 @@ public class FsPackageController extends BaseController
     @Autowired
     private IFsPackageService fsPackageService;
 
+    @Autowired
+    RedisCacheUtil redisCacheUtil;
+
     /**
      * 查询套餐包列表
      */
@@ -71,7 +75,10 @@ public class FsPackageController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody FsPackage fsPackage)
     {
-        return toAjax(fsPackageService.insertFsPackage(fsPackage));
+        int i = fsPackageService.insertFsPackage(fsPackage);
+        redisCacheUtil.delRedisKey("getPackageList");
+        redisCacheUtil.delRedisKey("getPackageById");
+        return toAjax(i);
     }
 
     /**
@@ -82,7 +89,10 @@ public class FsPackageController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody FsPackage fsPackage)
     {
-        return toAjax(fsPackageService.updateFsPackage(fsPackage));
+        int i = fsPackageService.updateFsPackage(fsPackage);
+        redisCacheUtil.delRedisKey("getPackageList");
+        redisCacheUtil.delRedisKey("getPackageById");
+        return toAjax(i);
     }
 
     /**

+ 3 - 2
fs-company/src/main/java/com/fs/user/FsUserAdminController.java

@@ -58,12 +58,13 @@ public class FsUserAdminController extends BaseController {
         //startPage();
 
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        if (param.getIsMyFsUser()){
+       /* if (param.getIsMyFsUser()){
             param.setCompanyId(loginUser.getCompany().getCompanyId());
             param.setCompanyUserId(String.valueOf(loginUser.getUser().getUserId()));
         }else {
             param.setCompanyId(loginUser.getCompany().getCompanyId());
-        }
+        }*/
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
         if(param.getPhone()!=null && !"".equals(param.getPhone())){
             param.setPhone(PhoneUtil.encryptPhone(param.getPhone()));
         }

+ 13 - 4
fs-doctor-app/src/main/java/com/fs/app/controller/DoctorArticleController.java

@@ -32,10 +32,7 @@ import org.springframework.web.bind.annotation.*;
 import javax.servlet.http.HttpServletRequest;
 import java.math.BigDecimal;
 import java.text.ParseException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 
@@ -88,6 +85,10 @@ public class DoctorArticleController extends  AppBaseController {
         param.setDoctorId(Long.parseLong(getDoctorId()));
         param.setViews(0l);
         if(doctorArticleService.insertFsDoctorArticle(param)>0){
+            Collection<String> keys = redisCache.keys("getDoctorArticleList*");
+            for (String key : keys) {
+                redisCache.deleteObject(key);
+            }
             return R.ok();
         }
         else
@@ -101,6 +102,10 @@ public class DoctorArticleController extends  AppBaseController {
     public R editDoctorArticle(@Validated @RequestBody FsDoctorArticle param, HttpServletRequest request) throws ParseException {
 
         if(doctorArticleService.updateFsDoctorArticle(param)>0){
+            Collection<String> keys = redisCache.keys("getDoctorArticleList*");
+            for (String key : keys) {
+                redisCache.deleteObject(key);
+            }
             return R.ok();
         }
         else
@@ -115,6 +120,10 @@ public class DoctorArticleController extends  AppBaseController {
     public R delStoreNotice(  @RequestBody FsDoctorArticle param, HttpServletRequest request) throws ParseException {
 
         if(doctorArticleService.deleteFsDoctorArticleByArticleId(param.getArticleId())>0){
+            Collection<String> keys = redisCache.keys("getDoctorArticleList*");
+            for (String key : keys) {
+                redisCache.deleteObject(key);
+            }
             return R.ok();
         }
         else

+ 1 - 1
fs-qw-task/src/main/java/com/fs/FsQwTaskApplication.java

@@ -13,7 +13,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 @EnableTransactionManagement
 @EnableAsync
-//@EnableScheduling
+@EnableScheduling
 public class FsQwTaskApplication
 {
     public static void main(String[] args){

+ 37 - 26
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -459,16 +459,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 return;
             }
 
-//            String[] userKey = logVo.getUserId().split("\\|");
-//            if (userKey.length < 3) {
-//                log.error("用户 ID {} 格式不正确,跳过处理。", logVo.getUserId());
-//                return;
-//            }
-
-//            String qwUserId = userKey[0].trim();
-//            String companyUserId = userKey[1].trim();
-//            String companyId = userKey[2].trim();
-
 
             //获取企业微信员工的称呼//从redis里或者从库里取
             QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedis(logVo.getCorpId(),logVo.getQwUserId());
@@ -943,27 +933,21 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
 
-                    if (!miniMap.isEmpty() && sendMsgType==1) {
-                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
-                        if (integerListMap != null) {
 
-                            int effectiveGrade = (grade == null) ? 5 : grade;
-                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
-                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+                    //算主备小程序
+                    String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
 
-                            if (miniapps != null && !miniapps.isEmpty()) {
-                                CompanyMiniapp companyMiniapp = miniapps.get(0);
-                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
-                                    setting.setMiniprogramAppid(companyMiniapp.getAppId());
-                                }
-                            }
-                        }
-                    }else if (!StringUtil.strIsNullOrEmpty(miniAppId)){
-                        setting.setMiniprogramAppid(miniAppId);
-                    }else {
+                    if (StringUtil.strIsNullOrEmpty(finalAppId)) {
+                        finalAppId = miniAppId;
+                    }
+
+                    if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
+                        setting.setMiniprogramAppid(finalAppId);
+                    } else {
                         log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                     }
 
+
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
 
                     try {
@@ -993,6 +977,33 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         enqueueQwSopLogs(sopLogs);
     }
 
+    private String getAppIdFromMiniMap(Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
+                                       String companyId,
+                                       int sendMsgType,
+                                       Integer grade) {
+        if (miniMap.isEmpty() || sendMsgType != 1) {
+            return null;
+        }
+
+        Map<Integer, List<CompanyMiniapp>> gradeMap = miniMap.get(Long.valueOf(companyId));
+        if (gradeMap == null) {
+            return null;
+        }
+
+        int effectiveGrade = (grade == null) ? 5 : grade;
+        int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+        List<CompanyMiniapp> miniapps = gradeMap.get(listIndex);
+
+        if (miniapps == null || miniapps.isEmpty()) {
+            return null;
+        }
+
+        CompanyMiniapp companyMiniapp = miniapps.get(0);
+        return (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId()))
+                ? companyMiniapp.getAppId()
+                : null;
+    }
+
     /**
      * 深拷贝 Content 对象,避免使用 JSON 进行序列化和反序列化
      */

+ 8 - 3
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -45,6 +45,8 @@ import org.springframework.stereotype.Service;
 import com.fs.company.service.ICompanyService;
 import org.springframework.transaction.annotation.Transactional;
 
+import static com.fs.company.service.impl.CompanyMiniappServiceImpl.GET_MINI_APP_STR;
+
 /**
  * 企业Service业务层处理
  *
@@ -105,9 +107,12 @@ public class CompanyServiceImpl implements ICompanyService
      * @return 企业
      */
     @Override
-    public Company selectCompanyById(Long companyId)
-    {
-        return companyMapper.selectCompanyById(companyId);
+    public Company selectCompanyById(Long companyId){
+        Company company = companyMapper.selectCompanyById(companyId);
+        List<CompanyMiniapp> miniApp = companyMiniappService.getMiniAppListByCompanyList(Collections.singletonList(company.getCompanyId()));
+        company.setMiniAppMaster(GET_MINI_APP_STR.apply(0, miniApp));
+        company.setMiniAppServer(GET_MINI_APP_STR.apply(1, miniApp));
+        return company;
     }
 
     /**

+ 5 - 4
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -246,12 +246,13 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Transactional
     public int insertUser(CompanyUser user)
     {
+        logger.info("打印邀请用户信息------------------------------》:{}",user);
         // 新增用户信息
         int rows = companyUserMapper.insertCompanyUser(user);
-        // 新增用户岗位关联
-        insertUserPost(user);
-        // 新增用户与角色管理
-        insertUserRole(user);
+//        // 新增用户岗位关联
+//        insertUserPost(user);
+//        // 新增用户与角色管理
+//        insertUserRole(user);
         //新增用户需要修改密码
         redisCache.setCacheObject("newCompanyUser:" + user.getCompanyId() + ":" +user.getUserName(),user.getUserId());
         return rows;

+ 3 - 1
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -14,6 +14,7 @@ import com.fs.system.mapper.SysConfigMapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import jodd.util.StringUtil;
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
@@ -30,7 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
-
+@Slf4j
 @Configuration
 @ComponentScan("com.fs.system.mapper")
 public class WxMaConfiguration {
@@ -74,6 +75,7 @@ public class WxMaConfiguration {
         }
         wx.setConfigs(c);
         this.properties = wx;
+        log.info("配置加载完毕! 配置文件: {}",JSON.toJSONString(this.properties));
     }
 
     public static WxMaService getMaService(String appid) {

+ 2 - 2
fs-service/src/main/java/com/fs/core/utils/OrderCodeUtils.java

@@ -44,8 +44,8 @@ public class OrderCodeUtils {
 
     }
     public static String getOrderSn(){
-        //String url= FSConfig.getCommonApi()+ "/app/common/genOrderCode";
-        String url= "42.194.245.189:8010/app/common/genOrderCode";
+        String url= FSConfig.getCommonApi()+ "/app/common/genOrderCode";
+//        String url= "42.194.245.189:8010/app/common/genOrderCode";
         String json = HttpRequest.get(url)
                 .execute().body();
         OrderCodeVO vo= JSONUtil.toBean(json, OrderCodeVO.class);

+ 2 - 1
fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTemp.java

@@ -54,6 +54,7 @@ public class FsCourseFinishTemp extends BaseEntity
     /** 删除标志 */
     @Excel(name = "删除标志")
     private Long isDel;
-    @Excel(name = "删除标志")
+
+    @Excel(name = "全选销售标志")
     private Integer isAllCompanyUser;
 }

+ 2 - 1
fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java

@@ -62,7 +62,8 @@ public class FsCourseLink extends BaseEntity
 
     @ApiModelProperty(value = "营期课程id")
     private Long id;
-
+    // 识别编号
+    private String uNo;
 //    private String link_uuid;
 
 }

+ 20 - 8
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -115,18 +115,30 @@ public interface FsCourseTrafficLogMapper
     List<FsCourseTrafficLogListVO> selectTrafficNew(FsCourseTrafficLogParam param);
 
     @Select("<script>" +
-            "SELECT COALESCE(sum(internet_traffic), 0) FROM fs_course_traffic_log " +
-            "WHERE status =0" +
-            "<if test='createTime != null'>AND create_time &lt;= #{createTime}</if> " +
+            "SELECT log_id FROM fs_course_traffic_log " +
+            "WHERE status = 0 " +
+            "<if test='createTime != null'>" +
+            "  AND create_time &lt; DATE_ADD(#{createTime,jdbcType=TIMESTAMP}, INTERVAL 1 SECOND)" +
+            "</if>" +
+            " AND log_id > #{lastId} " +
+            " ORDER BY log_id ASC " +
+            " LIMIT #{batchSize}" +
             "</script>")
-    Long findRecordsNumBYD( @Param("createTime") Date createTime);
+    List<Long> findRecordsByIdsPaged(
+            @Param("createTime") Date createTime,
+            @Param("lastId") Long lastId,
+            @Param("batchSize") int batchSize);
+
 
     @Select("<script>" +
-            "SELECT log_id FROM fs_course_traffic_log " +
-            "WHERE status =0" +
-            "<if test='createTime != null'>AND create_time &lt;= #{createTime}</if> " +
+            "SELECT COALESCE(SUM(internet_traffic), 0) FROM fs_course_traffic_log " +
+            "WHERE log_id IN " +
+            "<foreach item='id' collection='ids' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
             "</script>")
-    List<Long> findRecordsNumByIds( @Param("createTime") Date createTime);
+    Long sumTrafficByIds(@Param("ids") List<Long> ids);
+
 
     @Update("<script>" +
             "UPDATE fs_course_traffic_log SET status = #{status} WHERE log_id IN " +

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

@@ -487,4 +487,6 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "    (SELECT @streak := 0, @prev_date := NULL) AS vars\n" +
             ") AS streak_data;")
     Long selectByWatchlxDay(@Param("userId") Long userId,@Param("projectId")  Long projectId);
+
+    List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(@Param("maps") FsCourseWatchLogListParam param);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java

@@ -70,7 +70,7 @@ public interface FsUserCourseVideoRedPackageMapper
             "VALUES (#{companyId}, #{videoId}, #{redPacketMoney}) " +
             "ON DUPLICATE KEY UPDATE red_packet_money = VALUES(red_packet_money);")
     void insertOrUpdateFsUserCourseVideoRedPackage(FsUserCourseVideoParam fsUserCourseVideo);
-
+    @Select("select * from fs_user_course_video_red_package where video_id =#{videoId} and company_id = #{companyId} and period_id = #{periodId}")
     FsUserCourseVideoRedPackage selectRedPacketByCompanyId(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);
 
     /**

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/BatchVideoSvae.java

@@ -11,4 +11,6 @@ public class BatchVideoSvae {
 
     // 项目ID
     private Long projectId;
+
+    private Long userId;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoFinishUParam.java

@@ -22,4 +22,6 @@ public class FsUserCourseVideoFinishUParam implements Serializable {
     private Integer linkType;
     private Integer isRoom;//是否群聊
     private Integer isOpen;//是否公开课
+    private Long periodId;
+    private Integer projectId;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -132,4 +132,6 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     FsCourseWatchLog getWatchCourseVideoIsOpen(Long userId, Long videoId);
 
     void scheduleBatchUpdateToDatabaseIsOpen();
+
+    List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseAnswerLogsServiceImpl.java

@@ -190,6 +190,14 @@ public class FsCourseAnswerLogsServiceImpl implements IFsCourseAnswerLogsService
 
     @Override
     public Long selectFsCourseAnswerLogsListVONewCount(FsCourseAnswerLogsParam param) {
+        if(StringUtils.isNotEmpty(param.getUserName())){
+            List<FsUser> fsUsers = fsUserService.selectFsUserListByJointUserNameKey(param.getUserName());
+            if(fsUsers.isEmpty()) {
+                return 0L;
+            }
+            Set<Long> userIds = fsUsers.stream().map(FsUser::getUserId).collect(Collectors.toSet());
+            param.setUserIds(userIds);
+        }
         return fsCourseAnswerLogsMapper.selectFsCourseAnswerLogsListVONewCount(param);
     }
 

+ 47 - 16
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -4,10 +4,12 @@ import java.text.SimpleDateFormat;
 import java.util.*;
 
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.company.cache.ICompanyCacheService;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.param.InternetTrafficParam;
@@ -23,6 +25,7 @@ import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.service.IFsCourseTrafficLogService;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 短链课程流量记录Service业务层处理
@@ -343,27 +346,55 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         }
     }
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void sumTrafficlog() {
+        // 初始化参数
+        int batchSize = 10000;
+        Long lastId = 0L;
+        long totalTraffic = 0L;
+        Date currentDate = DateUtils.addHours(new Date(),-3);
         SysConfig sysConfig = iSysConfigService.selectConfigByConfigKey("redPacket.Traffic.config");
-        Date date = new Date();
-        Long count = fsCourseTrafficLogMapper.findRecordsNumBYD(date);
-        List<Long> ids = fsCourseTrafficLogMapper.findRecordsNumByIds(date);
-        if (count<=0){
-            return;
+
+        // 分批处理
+        while (true) {
+            // 分页查询ID
+            List<Long> ids = fsCourseTrafficLogMapper.findRecordsByIdsPaged(
+                    currentDate, lastId, batchSize);
+            System.out.println(ids);
+
+            if (CollectionUtils.isEmpty(ids)) {
+                break;
+            }
+            // 统计本批次流量
+            Long batchTraffic = fsCourseTrafficLogMapper.sumTrafficByIds(ids);
+            // 批量更新状态
+            int updatedCount = fsCourseTrafficLogMapper.updateStatusByIds(ids, 2);
+
+            totalTraffic += batchTraffic;
+
         }
-        if (ObjectUtils.isEmpty(sysConfig)){
-            sysConfig = new SysConfig();
-            sysConfig.setConfigKey("redPacket.Traffic.config");
-            sysConfig.setConfigName("红包流量配置");
-            sysConfig.setConfigValue("-"+count);
-            iSysConfigService.insertConfig(sysConfig);
-        }else {
-            sysConfig.setConfigValue(String.valueOf((Long.parseLong(sysConfig.getConfigValue())-count)));
-            iSysConfigService.updateConfig(sysConfig);
+        log.info("数据跑完了,数据有:{}",totalTraffic);
+        if (totalTraffic > 0) {
+            updateConfig(sysConfig, totalTraffic);
+        }
+    }
+    private void updateConfig(SysConfig sysConfig, long trafficSum) {
+        try {
+            if (sysConfig == null) {
+                sysConfig = new SysConfig();
+                sysConfig.setConfigKey("redPacket.Traffic.config");
+                sysConfig.setConfigName("红包流量配置");
+                sysConfig.setConfigValue("-" + trafficSum);
+                iSysConfigService.insertConfig(sysConfig);
+            } else {
+                long newValue = Long.parseLong(sysConfig.getConfigValue()) - trafficSum;
+                sysConfig.setConfigValue(String.valueOf(newValue));
+                iSysConfigService.updateConfig(sysConfig);
+            }
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("配置值格式错误", e);
         }
-        fsCourseTrafficLogMapper.updateStatusByIds(ids,2);
     }
-
     private static String formatDuration(long millis) {
         long seconds = millis / 1000;
         long minutes = seconds / 60;

+ 79 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -12,6 +12,7 @@ import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DictUtils;
 import com.fs.company.cache.ICompanyCacheService;
 import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseFinishTemp;
@@ -29,6 +30,7 @@ import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
+import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactInfo;
@@ -91,6 +93,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     RedisCache redisCache;
     @Autowired
+    private IQwExternalContactCacheService qwExternalContactCacheService;
+    @Autowired
     private QwWatchLogMapper qwWatchLogMapper;
     @Autowired
     private FsUserCourseVideoMapper courseVideoMapper;
@@ -999,4 +1003,79 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 totalCost,
                 totalCost > 0 ? String.format("%.2f", logs.size() * 1000.0 / totalCost) : "∞");
     }
+
+
+
+    @Override
+    public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param) {
+
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogMapper.selectFsCourseWatchLogListVOexport(param);
+        for (FsCourseWatchLogListVO item : list) {
+            // 项目
+            if(ObjectUtils.isNotNull(item.getProject())) {
+                String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
+                if(StringUtils.isNotBlank(sysCourseProject)){
+                    item.setProjectName(sysCourseProject);
+                }
+            }
+            // 用户名
+            if(ObjectUtils.isNotNull(item.getUserId())) {
+                FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
+                if(ObjectUtils.isNotNull(fsUser)){
+                    item.setExternalUserName(String.format("%s_%d",fsUser.getNickname(),fsUser.getUserId()));
+                    item.setFsNickName(fsUser.getNickname());
+                    item.setFsAvatar(fsUser.getAvatar());
+                }
+            }
+            // 公司名
+            if(ObjectUtils.isNotNull(item.getCompanyId())){
+                Company company = companyCacheService.selectCompanyById(Long.valueOf(item.getCompanyId()));
+                if(ObjectUtils.isNotNull(company)){
+                    item.setCompanyName(String.format("%s_%d", company.getCompanyName(), company.getCompanyId()));
+                }
+            }
+
+            // 销售名
+            if(ObjectUtils.isNotNull(item.getCompanyUserId())){
+                CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
+                if(ObjectUtils.isNotNull(companyUser)){
+                    item.setCompanyUserName(String.format("%s_%d", companyUser.getNickName(), companyUser.getUserId()));
+                }
+            }
+
+            // 课程
+            if(ObjectUtils.isNotNull(item.getCourseId())){
+                FsUserCourse course = fsUserCourseCacheService.selectFsUserCourseByCourseId(item.getCourseId());
+                if(ObjectUtils.isNotNull(course)){
+                    item.setCourseName(course.getCourseName());
+                }
+            }
+            // 小节
+            if(ObjectUtils.isNotNull(item.getVideoId())){
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
+                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                    item.setVideoName(fsUserCourseVideo.getTitle());
+                }
+            }
+
+            // 企微用户名
+            if(ObjectUtils.isNotNull(item.getQwUserId())){
+                String qwUserName = qwUserCacheService.queryQwUserNameByUserId(item.getQwUserId());
+                if(StringUtils.isNotBlank(qwUserName)){
+                    item.setQwUserName(qwUserName);
+                }
+            }
+
+            // 企微外部联系人
+            if(ObjectUtils.isNotNull(item.getQwExternalContactId())){
+                String qwExternalContactName = qwExternalContactCacheService.selectQwExternalContactById(Long.valueOf(item.getQwExternalContactId()));
+                if(StringUtils.isNotBlank(qwExternalContactName)){
+                    item.setExternalUserName(qwExternalContactName);
+                }
+            }
+
+        }
+        return list;
+    }
+
 }

+ 4 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1594,6 +1594,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         if(watchCourseVideo != null){
             if(!watchCourseVideo.getCompanyUserId().equals(param.getCompanyUserId())) {
                 //提示
+                log.error("数据库存在销售信息:{},分享得销售信息:{}",watchCourseVideo.getCompanyUserId(),param.getCompanyUserId());
                 return ResponseResult.fail(504, "已看过其他销售分享的此课程,不能重复观看");
             }
 
@@ -1864,10 +1865,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
         // 从Redis中获取观看时长
         String redisKey = "h5wxuser:watch:duration:" + param.getUserId() + ":" + param.getVideoId() + ":" + param.getCompanyUserId();
-        log.info("看课redis缓存key:{}", redisKey);
+//        log.info("看课redis缓存key:{}", redisKey);
         try {
             String durationStr = redisCache.getCacheObject(redisKey);
-            log.info("看课记录:{}", durationStr);
+//            log.info("看课记录:{}", durationStr);
             long duration = durationStr != null ? Long.parseLong(durationStr) : 0L;
 
             // 更新Redis中的观看时长
@@ -1905,6 +1906,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         List<FsUserCourseVideo> collect = videoResourceList.stream().map(e -> {
             FsUserCourseVideo entity = new FsUserCourseVideo();
+            entity.setUserId(vo.getUserId());
             entity.setTitle(e.getResourceName());
             entity.setVideoUrl(e.getVideoUrl());
             entity.setThumbnail(e.getThumbnail());

+ 14 - 3
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -26,20 +26,27 @@ public class FsCourseWatchLogListVO extends BaseEntity
     private Long userId;
 
     /** 小程序昵称 */
-//    @Excel(name = "小程序昵称")
+    @Excel(name = "小程序昵称")
     private String fsNickName;
 
     @Excel(name = "客户昵称")
     private String externalUserName;
 
     /** 外部联系人头像 */
-    @Excel(name = "客户头像")
+//    @Excel(name = "客户头像")
     private String externalUserAvatar;
 
     /** 客户头像 */
-//    @Excel(name = "客户头像")
+    @Excel(name = "小程序头像")
     private String fsAvatar;
 
+    /**
+     * 项目
+     */
+    private Integer project;
+    @Excel(name = "项目名称")
+    private String projectName;
+
     @Excel(name = "课程名称")
     private String courseName;
 
@@ -106,4 +113,8 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date qecCreateTime;
 
+    private Long companyUserId;
+    private Long courseId;
+    private Long videoId;
+
 }

+ 1 - 1
fs-service/src/main/java/com/fs/his/mapper/FsCouponMapper.java

@@ -96,6 +96,6 @@ public interface FsCouponMapper
             " order by c.coupon_id desc "+
             "</script>"})
     List<FsCouponListUVO> selectFsCouponListUVO(@Param("maps")FsCouponListUParam param);
-    @Update("update fs_coupon set remain_number=(select ifnull(count(1),0) from fs_user_coupon uc where uc.coupon_id=#{couponId}) where coupon_id=#{couponId}")
+    @Update("update fs_coupon set remain_number=number-(select ifnull(count(1),0) from fs_user_coupon uc where uc.coupon_id=#{couponId}) where coupon_id=#{couponId}")
     int updateRemainCount(Long couponId);
 }

+ 3 - 2
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -80,8 +80,9 @@ public interface FsUserMapper
     public int deleteFsUserByUserIds(Long[] userIds);
     @Select({"<script> " +
                 "select f1.*,f2.nick_name tui_name,f2.phone tui_phone,cu.nick_name AS companyUserNickName,co.company_name FROM fs_user f1 LEFT JOIN fs_user f2 ON f1.tui_user_id =f2.user_id "+
-            " LEFT JOIN company_user cu ON cu.user_id = f1.company_user_id"+
-            " LEFT JOIN company co on co.company_id = f1.company_id"+
+            " LEFT JOIN fs_user_company_user cuc ON cuc.user_id = f1.user_id\n" +
+            " LEFT JOIN company_user cu ON cu.user_id = cuc.company_user_id\n" +
+            " LEFT JOIN company co ON co.company_id = cuc.company_id "+
             " where f1.is_del=0 "+
             "  <if test=\"nickName != null  and nickName != ''\"> and f1.nick_name like concat( #{nickName}, '%')</if>\n" +
             "            <if test=\"avatar != null  and avatar != ''\"> and f1.avatar = #{avatar}</if>\n" +

+ 6 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -550,7 +550,12 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         TransferBatchesRequest request = new TransferBatchesRequest();
         request.setAppid(config.getAppId());
 
-        String code = IdUtil.getSnowflake(0, 0).nextIdStr();
+        // todo 如果未配置负载均衡请还原原本的单号方式
+//        String code = IdUtil.getSnowflake(0, 0).nextIdStr();
+        String code =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(code)){
+            return R.error("红包单号生成失败,请重试");
+        }
         request.setOutBatchNo("fsCourse" + code);
         request.setBatchRemark("课堂答题奖励");
         request.setBatchName("课堂答题奖励");

+ 1 - 1
fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java

@@ -24,7 +24,7 @@ public class QwUserCacheServiceImpl implements IQwUserCacheService {
     @Override
     public String queryQwUserNameByUserId(String userId) {
         return QW_USER_CACHE.get(userId,e-> {
-            QwUser qwUser = qwUserService.selectQwUserByQwUserId(userId);
+            QwUser qwUser = qwUserService.selectQwUserById(Long.valueOf(userId));
             if(qwUser != null) {
                 return String.format("%s_%s",qwUser.getQwUserId(),qwUser.getQwUserName());
             }

+ 3 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateNoteParam.java

@@ -10,4 +10,7 @@ public class QwExternalContactUpdateNoteParam {
     private String notes;
     private int type;
     private int nameType;
+    private Integer addType;
+    private boolean filter;
+    private QwExternalContactParam param;
 }

+ 323 - 0
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java

@@ -0,0 +1,323 @@
+package com.fs.qw.service;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.utils.date.DateUtil;
+import com.fs.company.service.ICompanyMiniappService;
+import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsCourseLink;
+import com.fs.course.domain.FsCourseRealLink;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.FsCourseLinkMapper;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.fastGpt.domain.FastGptChatReplaceWords;
+import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.qw.vo.QwSopTempSetting;
+import com.fs.sop.domain.QwSopLogs;
+import com.fs.sop.domain.QwSopTempContent;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.QwSopTempContentMapper;
+import com.fs.sop.params.QwSopAutoByTags;
+import com.fs.sop.service.IQwSopTempVoiceService;
+import com.fs.sop.service.impl.SopUserLogsInfoServiceImpl;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+public class AsyncQwAiChatSopService {
+
+    private static final String miniappRealLink = "/pages_course/video.html?course=";
+
+    @Autowired
+    private QwSopMapper qwSopMapper;
+
+    @Autowired
+    private QwSopTempContentMapper qwSopTempContentMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private SopUserLogsInfoServiceImpl sopUserLogsInfoService;
+    @Autowired
+    private FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private QwSopLogsMapper qwSopLogsMapper;
+
+    @Autowired
+    private FsCourseLinkMapper fsCourseLinkMapper;
+
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
+
+
+    @Autowired
+    private IQwSopTempVoiceService sopTempVoiceService;
+
+    @Async("threadPoolTaskExecutor")
+    public void executeQwAiChatSop(QwSopAutoByTags qwSopAutoByTags, String userID,
+                                   QwUser qwUser, String externalUserID, String externalContactName,
+                                   Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
+
+        //新客对话任务
+        List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+        List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
+        List<QwSopLogs> sopLogsList = new ArrayList<>(Collections.emptyList());
+
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+
+        if (config == null) {
+            log.error("配置为空-新客对话创建失败");
+            return ;
+        }
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUser.getCorpId());
+
+        if (qwCompany == null ) {
+            log.error("企业微信主体未配置默认小程序-新客对话创建失败");
+            return ;
+        }
+
+        if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()){
+
+            qwSopAiRuleTimeVOS.forEach(item->{
+
+                List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
+
+                tempContentList.forEach(content->{
+
+                    QwSopLogs sopLogs = new QwSopLogs();
+                    sopLogs.setQwUserKey(qwUser.getId());
+                    sopLogs.setQwUserid(userID);
+                    sopLogs.setExternalUserId(externalUserID);
+                    sopLogs.setExternalId(externalId);
+                    sopLogs.setLogType(2);
+                    sopLogs.setSendStatus(3L);
+                    sopLogs.setCompanyId(qwUser.getCompanyId());
+                    sopLogs.setReceivingStatus(0L);
+                    sopLogs.setSopId(item.getId());
+                    sopLogs.setCorpId(qwUser.getCorpId());
+                    sopLogs.setFsUserId(fsUserId);
+                    sopLogs.setSort(99999999);
+                    sopLogs.setSendType(4);
+                    sopLogs.setExternalUserName(externalContactName);
+
+                    List<QwSopTempSetting.Content.Setting> settingList =new ArrayList<>();
+                    QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
+
+                    LocalDateTime dateTime = LocalDateTime.of(currentDate, localTime);
+                    LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
+                    String sendTime = DateUtil.formatLocalDateTime(expiryDateTime);
+                    sopLogs.setSendTime(sendTime);
+
+                    Date expirySendTime = Date.from(expiryDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+
+                    //过滤违禁词
+                    if ("1".equals(setting.getContentType())) {
+                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getValue(), setting::setValue, words); // 替换 value
+                    }
+                    //过滤违禁词
+                    if ("3".equals(setting.getContentType())) {
+                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkTitle(), setting::setLinkTitle, words); // 替换 linkTitle
+                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkDescribe(), setting::setLinkDescribe, words); // 替换 linkTitle
+                    }
+                    switch (setting.getContentType()) {
+                        //文字和短链一起
+                        case "1":
+                        case "3":
+
+                            if ("1".equals(setting.getContentType())) {
+                                setting.setValue(setting.getValue()
+                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
+                            }
+
+
+                            break;
+                        //小程序单独
+                        case "4":
+                            addWatchLogIfNeededByNewChat(item.getId(), content.getVideoId(), content.getCourseId(), fsUserId,qwUser.getId(), qwUser.getCompanyUserId(), qwUser.getCompanyId(),
+                                    externalId, sendTime, expirySendTime);
+
+                            String linkByMiniApp = createLinkByMiniAppByNewChat(setting.getExpiresDays(), qwUser.getCorpId(), expirySendTime, content.getCourseId(), content.getVideoId(),
+                                    qwUser.getId(), String.valueOf(qwUser.getCompanyUserId()),String.valueOf(qwUser.getCompanyId()), externalId, config);
+
+
+                            setting.setMiniprogramAppid(qwCompany.getMiniAppId());
+
+                            String miniprogramTitle = setting.getMiniprogramTitle();
+                            int maxLength = 17;
+                            setting.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength)+"..." : miniprogramTitle);
+                            setting.setMiniprogramPage(linkByMiniApp);
+                            break;
+                        case "7":
+
+                            createVoiceUrlByNewChat(setting, qwUser.getCompanyUserId());
+                            break;
+                        default:
+                            break;
+
+                    }
+
+                    settingList.add(setting);
+
+                    QwSopTempSetting.Content clonedContent=new QwSopTempSetting.Content();
+                    clonedContent.setContentType(setting.getContentType());
+                    clonedContent.setCourseId(Long.valueOf(content.getCourseId()));
+                    clonedContent.setVideoId(Long.valueOf(content.getVideoId()));
+                    clonedContent.setSetting(settingList);
+                    clonedContent.setType(content.getType());
+                    clonedContent.setCourseType(0);
+                    sopLogs.setContentJson(JSON.toJSONString(clonedContent));
+                    sopLogsList.add(sopLogs);
+                });
+
+
+            });
+
+            //批量插入 发送记录
+            if (!sopLogsList.isEmpty()) {
+                processAndInsertQwSopLogsBySendMsg(sopLogsList);
+            }
+        }
+
+    }
+
+    private void processAndInsertQwSopLogsBySendMsg(List<QwSopLogs> sopLogsList) {
+        // 定义批量插入的大小
+        int batchSize = 500;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < sopLogsList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, sopLogsList.size());
+            List<QwSopLogs> batchList = sopLogsList.subList(i, endIndex);  // 获取当前批次的子集
+
+            // 直接使用批次数据进行批量更新,不需要额外的 List
+            try {
+                qwSopLogsMapper.batchInsertQwSopLogsOneTouch(batchList);
+            } catch (Exception e) {
+                // 记录异常日志,方便后续排查问题
+                log.error("批量执行一键群发时发生异常,处理的批次起始索引为: " + i, e);
+            }
+        }
+    }
+    //插入观看记录
+    private Long addWatchLogIfNeededByNewChat(String sopId, Integer videoId, Integer courseId,
+                                     Long fsUserId, Long qwUserId, Long companyUserId,
+                                     Long companyId, Long externalId, String startTime, Date createTime) {
+
+        try {
+            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+            watchLog.setVideoId(Long.valueOf(videoId));
+            watchLog.setQwExternalContactId(externalId);
+            watchLog.setSendType(2);
+            watchLog.setQwUserId(qwUserId);
+            watchLog.setSopId(sopId);
+            watchLog.setDuration(0L);
+            watchLog.setCourseId(Long.valueOf(courseId));
+            watchLog.setCompanyUserId(companyUserId);
+            watchLog.setCompanyId(companyId);
+            watchLog.setCreateTime(createTime);
+            watchLog.setUpdateTime(createTime);
+            watchLog.setLogType(3);
+            watchLog.setUserId(fsUserId);
+            watchLog.setCampPeriodTime(sopUserLogsInfoService.convertStringToDate(startTime, "yyyy-MM-dd HH:mm:ss"));
+
+            //存看课记录
+            fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+            return watchLog.getLogId();
+        } catch (Exception e) {
+            log.error("一键群发失败-插入观看记录失败:" + e.getMessage());
+            return null;
+        }
+    }
+
+    private String createLinkByMiniAppByNewChat(Integer expiresDays, String corpId, Date sendTime,
+                                                Integer courseId, Integer videoId, Long qwUserId,
+                                       String companyUserId, String companyId, Long externalId, CourseConfig config) {
+
+        try {
+            FsCourseLink link = sopUserLogsInfoService.createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+                    companyUserId, companyId, externalId, 3, null);
+
+            FsCourseRealLink courseMap = new FsCourseRealLink();
+            BeanUtils.copyProperties(link, courseMap);
+
+            String courseJson = JSON.toJSONString(courseMap);
+            String realLinkFull = miniappRealLink + courseJson;
+            link.setRealLink(realLinkFull);
+
+            Date updateTime = createUpdateTimeByNewChat(expiresDays, sendTime, config);
+
+            link.setUpdateTime(updateTime);
+            //存短链-
+            fsCourseLinkMapper.insertFsCourseLink(link);
+            return link.getRealLink();
+        }catch (Exception e){
+            log.error("创建新客对话短链失败:{}|{}|{}|{}|{}", corpId, sendTime, courseId, videoId, qwUserId);
+            log.error("e",e);
+        }
+            return null;
+    }
+
+    public Date createUpdateTimeByNewChat(Integer expiresDays, Date sendTime, CourseConfig config) {
+
+
+        Integer expDays = (expiresDays == null || expiresDays == 0)
+                ? config.getVideoLinkExpireDate()
+                : expiresDays;
+
+//         使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expDays - 1);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+        return updateTime;
+    }
+
+    private void createVoiceUrlByNewChat(QwSopTempSetting.Content.Setting setting, Long companyUserId) {
+        QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, setting.getValue());
+        if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
+            setting.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
+            setting.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
+        } else if (qwSopTempVoice == null) {
+            qwSopTempVoice = new QwSopTempVoice();
+            qwSopTempVoice.setCompanyUserId(companyUserId);
+            qwSopTempVoice.setVoiceTxt(setting.getValue());
+            qwSopTempVoice.setRecordType(0);
+            sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
+        }
+    }
+
+}

+ 85 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserServiceAsyncHelper.java

@@ -0,0 +1,85 @@
+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 org.codehaus.jettison.json.JSONException;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface IQwUserServiceAsyncHelper {
+
+    /**
+     * 同步企微员工和部门信息
+     * @param corpId
+     * @param company
+     */
+    void syncQwUserAsync(String corpId, Company company);
+
+    /**
+     * 同步企微用户名称
+     * @param corpId
+     * @param company
+     */
+    void syncNameQwUserAsync(String corpId, Company company);
+
+    /**
+     * 批量修改备注
+     * @param param
+     */
+    void batchUpdateExternalContactNotes(QwExternalContactUpdateNoteParam param);
+
+    /**
+     * 添加标签
+     * @param param
+     * @throws JSONException
+     */
+    void addUserTag(QwExternalContactAddTagParam param) throws JSONException;
+
+    /**
+     * 删除标签
+     */
+    void delUserTag(QwExternalContactAddTagParam param);
+
+    /**
+     * 同步我的客户
+     * @param id
+     * @throws IOException
+     */
+    void syncMyQwExternalContact(Long id) throws IOException;
+
+    /**
+     * 同步新客
+     * @param id
+     */
+    void  syncAddMyQwExternalContact(Long id);
+
+    /**
+     * 企业微信客户
+     * @param param
+     */
+    void transfer(TransferParam param);
+
+    /**
+     * 同步待转
+     * @param corpId
+     */
+    void syncQwExternalContactUnassigned(String corpId);
+
+    /**
+     * 企业微信客户分配客户
+     * @param param
+     */
+    void resignedTransfer(ResignedTransferParam param);
+
+    /**
+     * 同步群聊
+     * @param corpId
+     * @param qwUserIdList
+     * @throws Exception
+     */
+    void cogradientGroupChat(String corpId, List<String> qwUserIdList ) throws Exception;
+}

+ 22 - 4
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -2584,7 +2584,8 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                 }
 
                 //有渠道活码
-                if (isWay && StringUtil.strIsNullOrEmpty(followUser.getRemark()) ) {
+                if (isWay && (StringUtil.strIsNullOrEmpty(followUser.getRemark()) || StringUtils.equals(followUser.getRemark().trim(),externalContact.getName().trim()))) {
+
                     if (wayId.getIsDescription() == 1) {
                         qwExternalContact.setDescription(wayId.getDescription());
                     }
@@ -2636,7 +2637,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                     }
                 }
                 else {
-                    if (StringUtil.strIsNullOrEmpty(followUser.getRemark()) && !StringUtil.strIsNullOrEmpty(tagRemark)) {
+                    if (!StringUtil.strIsNullOrEmpty(tagRemark) && (StringUtil.strIsNullOrEmpty(followUser.getRemark()) || StringUtils.equals(followUser.getRemark().trim(),externalContact.getName().trim()))) {
+                        // 逻辑代码
+
                         qwExternalContact.setRemark(tagRemark + "-" + externalContact.getName());
                         QwExternalContactRemarkParam param = new QwExternalContactRemarkParam();
                         param.setRemark(qwExternalContact.getRemark());
@@ -2657,8 +2660,6 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                     }
                 }
 
-
-
             }
         }
 
@@ -5607,4 +5608,21 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     public List<QwExternalContact> selectQwExternalContactByFsUserId(Long userId) {
         return qwExternalContactMapper.selectQwExternalContactByFsUserId(userId);
     }
+
+
+    public Boolean getSopAiChatByRedis(String qwUserId,String corpId,String externalUserId){
+
+        try {
+            String key =(String)redisCache.getCacheObject("qwNewChat:"+qwUserId+":"+corpId+":"+externalUserId);
+            if (!StringUtil.strIsNullOrEmpty(key)){
+                return true;
+            }else {
+                redisCache.setCacheObject("qwNewChat:"+qwUserId+":"+corpId+":"+externalUserId ,"1",1, TimeUnit.DAYS);
+                return false;
+            }
+        }catch (Exception e){
+            logger.info("获取getSopAiChatByRedis 异常"+e);
+        }
+        return false;
+    }
 }

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

@@ -0,0 +1,1286 @@
+package com.fs.qw.service.impl;
+
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONException;
+import com.fs.company.domain.Company;
+import com.fs.company.mapper.CompanyCompanyFsuserMapper;
+import com.fs.qw.domain.*;
+import com.fs.qw.mapper.*;
+import com.fs.qw.param.*;
+import com.fs.qw.service.AsyncQwAiChatSopService;
+import com.fs.qw.service.IQwDeptService;
+import com.fs.qw.service.IQwUserServiceAsyncHelper;
+import com.fs.qw.vo.QwSopRuleTimeVO;
+import com.fs.qwApi.Result.QwGroupChatListResult;
+import com.fs.qwApi.domain.*;
+import com.fs.qwApi.domain.inner.ExternalContact;
+import com.fs.qwApi.domain.inner.FollowUser;
+import com.fs.qwApi.domain.inner.QwCustomer;
+import com.fs.qwApi.domain.inner.Tag;
+import com.fs.qwApi.param.*;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.QwSopLogsMapper;
+import com.fs.sop.mapper.QwSopMapper;
+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.voice.utils.StringUtil;
+import com.fs.watch.mapper.WatchCompanyUserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class QwUserServiceAsyncHelperImpl implements IQwUserServiceAsyncHelper {
+
+    @Autowired
+    private IQwDeptService qwDeptService;
+
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
+
+    @Autowired
+    QwUserServiceImpl qwUserService;
+
+    @Autowired
+    QwApiService qwApiService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private QwSopMapper qwSopMapper;
+
+    @Autowired
+    QwExternalContactServiceImpl qwExternalContactService;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    QwAutoTagsLogsMapper qwAutoTagsLogsMapper;
+
+    @Autowired
+    QwAutoTagsMapper qwAutoTagsMapper;
+
+    @Autowired
+    private QwSopLogsMapper qwSopLogsMapper;
+
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+
+    @Autowired
+    private QwExternalContactTransferLogMapper qwExternalContactTransferLogMapper;
+
+    @Autowired
+    private QwGroupChatServiceImpl qwGroupChatService;
+
+    @Autowired
+    private CompanyCompanyFsuserMapper companyCompanyFsuserMapper;
+
+    @Autowired
+    private WatchCompanyUserMapper watchCompanyUserMapper;
+
+    @Autowired
+    private AsyncQwAiChatSopService asyncQwAiChatSopService;
+
+    org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
+
+    /**
+     * 同步企微用户
+     *
+     * @param corpId
+     * @param company
+     */
+    @Async
+    @Override
+    public void syncQwUserAsync(String corpId, Company company) {
+        try {
+            log.info("同步企微用户Async");
+            List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(company.getCompanyId());
+            log.info("strings:{}", strings);
+            log.info("corpId:{}", corpId);
+            for (String string : strings) {
+                if (string.equals(corpId)) {
+                    log.info("瞒住条件");
+                    qwUserService.syncQwUser(string);
+                    qwDeptService.insertOrUpdateQwDept(string);
+                }
+            }
+            log.info("同步完成");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("同步企微员工和部门,同步失败");
+        }
+    }
+
+    /**
+     * 同步企微用户名称
+     *
+     * @param corpId
+     * @param company
+     */
+    @Async
+    @Override
+    public void syncNameQwUserAsync(String corpId, Company company) {
+        try {
+            log.info("同步企微用户名称Async");
+            List<String> strings = qwCompanyMapper.selectQwCompanyCorpIdListByCompanyId(company.getCompanyId());
+            for (String string : strings) {
+                if (string.equals(corpId)) {
+                    qwUserService.syncQwUserName(string);
+                }
+            }
+            log.info("同步完成");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("同步企微用户名称,同步失败");
+        }
+
+    }
+
+    /**
+     * 批量修改备注
+     *
+     * @param param
+     */
+    @Override
+    @Async
+    public void batchUpdateExternalContactNotes(QwExternalContactUpdateNoteParam param) {
+        try {
+            log.info("批量修改备注Async");
+            List<String> failList = new CopyOnWriteArrayList<>(); // 记录失败客户的名字
+
+            AtomicInteger suc = new AtomicInteger();
+
+
+            List<QwExternalContact> contactList = qwExternalContactMapper.selectQwExternalContactByIds(param.getUserIds());
+
+            ExecutorService executorService = Executors.newFixedThreadPool(10); // 限制最大线程数
+            CountDownLatch latch = new CountDownLatch(contactList.size()); // 同步等待所有任务完成
+
+            AtomicReference<Integer> failReason = new AtomicReference<>(0);
+
+            contactList.forEach(item -> {
+                executorService.submit(() -> {
+                    try {
+                        String originalRemark = item.getRemark();
+                        String newRemark = "";
+
+                        if (StringUtil.strIsNullOrEmpty(originalRemark)) {
+                            originalRemark = "";
+                        }
+
+                        if (StringUtil.strIsNullOrEmpty(param.getNotes())) {
+                            newRemark = originalRemark;
+                        } else {
+
+                            String newNotes = "";
+
+                            switch (param.getNameType()) {
+                                case 1:
+                                    newNotes = item.getName() + "-" + param.getNotes();
+                                    break;
+                                case 2:
+                                    newNotes = param.getNotes() + "-" + item.getName();
+                                    break;
+                                case 3:
+                                    newNotes = param.getNotes();
+                                    break;
+                                default:
+                                    break;
+                            }
+
+                            switch (param.getType()) {
+                                case 1:
+                                    newRemark = (newNotes + originalRemark);
+                                    if (newRemark.length() > 20) {
+                                        newRemark = newRemark.substring(0, 20);
+                                    }
+                                    break;
+                                case 2:
+                                    int keepLength = 20 - newNotes.length();
+                                    if (originalRemark.length() > keepLength) {
+                                        originalRemark = originalRemark.substring(0, keepLength);
+                                    }
+                                    newRemark = originalRemark + param.getNotes();
+                                    break;
+                                case 3:
+                                    newRemark = newNotes;
+                                    break;
+                                default:
+                                    break;
+                            }
+
+                        }
+
+
+                        QwExternalContactRemarkParam remarkParam = new QwExternalContactRemarkParam();
+                        remarkParam.setRemark(newRemark);
+                        remarkParam.setUserid(item.getUserId());
+                        remarkParam.setExternal_userid(item.getExternalUserId());
+
+                        boolean success = false;
+                        for (int attempt = 1; attempt <= 3; attempt++) {
+                            try {
+                                QwExternalContactRemarkResult qwResult = qwApiService.externalcontactRemark(remarkParam, item.getCorpId());
+                                if (qwResult.getErrcode() == 0) {
+                                    item.setRemark(newRemark);
+                                    logger.info("成功添加备注:" + item.getName() + "|公司" + item.getCorpId() + "|员工" + item.getUserId() + "|备注" + param.getNotes() + "|类型" + param.getType());
+                                    suc.getAndIncrement();
+                                    qwExternalContactMapper.updateQwExternalContact(item);
+                                    success = true;
+                                    break;
+                                } else {
+                                    failReason.set(qwResult.getErrcode()); // 记录失败原因
+                                }
+                            } catch (Exception e) {
+                                logger.error("添加备注异常 [尝试第 " + attempt + " 次]:" + item.getName() + ",异常:" + e.getMessage(), e);
+                            }
+
+                            // 若不是最后一次尝试,则等待3秒再试
+                            if (attempt < 3) {
+                                try {
+                                    Thread.sleep(3000);
+                                } catch (InterruptedException e) {
+                                    logger.warn("线程等待被中断", e);
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (!success) {
+                            logger.error(" 加备注失败:" + item.getName() + "|" + item.getExternalUserId() + "|" + item.getUserId());
+
+                            failList.add("【" + item.getName() + "】" + "原因:(" + getErrorMsg(failReason.get()) + ")\n");
+                        }
+
+                    } finally {
+                        latch.countDown();
+                    }
+                });
+            });
+
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                logger.error("等待线程执行完成时被中断", e);
+            } finally {
+                executorService.shutdown();
+            }
+
+            String failNames = null;
+            if (!failList.isEmpty()) {
+                failNames = String.join(",", failList);
+            }
+            log.info("批量修改备注 成功:" + suc + ",失败:" + failNames);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("批量修改备注失败");
+        }
+    }
+
+    private String getErrorMsg(Integer code) {
+
+        String msg = "";
+        switch (code) {
+            case 40003:
+                msg = "无效的UserID(员工账号)";
+                break;
+            case 40096:
+                msg = "不合法的外部联系人userid";
+                break;
+            case 60020:
+                msg = "不安全的访问IP";
+                break;
+            case 84061:
+                msg = "不存在外部联系人的关系(客户【不存在】于员工的好友列表中)";
+                break;
+            default:
+                msg = code.toString();
+                break;
+
+        }
+        return msg;
+    }
+
+    /**
+     * 添加标签
+     *
+     * @param param
+     * @throws JSONException
+     */
+    @Override
+    @Async
+    public void addUserTag(QwExternalContactAddTagParam param) throws JSONException {
+        log.info("开始添加标签Async");
+        // 获取当前日期和时间
+        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);
+
+
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<QwExternalContact> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
+
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+
+            // 1. 批量查询所有用户数据
+            List<QwExternalContact> contacts = new ArrayList<>();
+            try {
+                contacts = qwExternalContactMapper.selectQwExternalContactByIds(param.getUserIds());
+                if (contacts == null || contacts.isEmpty()) {
+                    log.info("成功:0,失败:" + param.getUserIds().size());
+
+//                    return R.error("成功:0,失败:" + param.getUserIds().size());
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                log.info("批量查询用户数据失败:" + 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, 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);
+                                }
+                            }
+
+                            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());
+                }
+            }
+
+            // 关闭线程池
+            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();
+            }
+        }
+        try {
+            Thread.sleep(3000);
+        } catch (InterruptedException ex) {
+            log.info("线程被中断");
+        }
+        log.info("添加标签成功:" + suc.get() + ",添加标签失败:" + failList);
+
+    }
+
+    /**
+     * 整合客户的标签的逻辑 ,新增和移除 都要移除和添加到营期里
+     */
+    public void processTagsAll(QwExternalContact qwExternalContact, String corpId, List<String> tagArr, LocalDate currentDate, LocalTime localTime) {
+
+        List<String> cleanedTagList = tagArr.stream()
+                .map(String::trim)   // 去除前后空格
+                .filter(s -> !s.isEmpty()) // 过滤掉空字符串(如果有)
+                .distinct()           // 去重
+                .collect(Collectors.toList());
+
+        QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+        qwSopAutoByTags.setQwUserId(String.valueOf(qwExternalContact.getQwUserId()));
+        qwSopAutoByTags.setCorpId(corpId);
+        qwSopAutoByTags.setTagsIdsSelectList(cleanedTagList);
+        qwSopAutoByTags.setSendType(2);
+
+        // 定义日期和时间格式化器
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
+
+
+        try {
+            //查询匹配这个客服所有正在执行的 SOP任务 ps:仅仅是AI插件的
+            List<QwSopRuleTimeVO> runRuleTime = qwSopMapper.selectQwSopByQwUserId(qwSopAutoByTags);
+
+            logger.info("查出来的客服 任务长度:" + runRuleTime.size());
+            //如果剩下的总标签 啥也没有
+            // 查询匹配这些Tag的SOP任务
+            if (cleanedTagList.isEmpty()) {
+
+                //这个人标签空了,以防万一 将这个客服下的这个人的所有sop里的他(可能没有) 全刷一遍
+                if (!runRuleTime.isEmpty()) {
+                    runRuleTime.forEach(runSop -> {
+
+                        logger.info("cleanedTagList-空了,将这个客服下的这个人的所有sop里的他(可能没有) 全刷一遍 sopId:{}", runSop.getId());
+                        qwExternalContactService.deleteBySopIdToContactIdTools(runSop.getId(), qwExternalContact.getUserId(), qwExternalContact.getCorpId(), qwExternalContact.getId());
+
+                    });
+                }
+            } else {
+                List<QwSopRuleTimeVO> qwSopRuleTimeVOS = qwSopMapper.selectQwSopAutoByTagsByForeachNotAuto(qwSopAutoByTags);
+
+                if (!qwSopRuleTimeVOS.isEmpty()) {
+
+                    //匹配上剩下的标签看看符合不符合
+                    qwSopRuleTimeVOS.forEach(ruleTimeVO -> {
+
+                        // 将排除的字符串转成列表
+                        List<String> excludedTagsList = new ArrayList<>();
+                        if (ruleTimeVO.getExcludeTags() != null && !ruleTimeVO.getExcludeTags().isEmpty()) {
+                            excludedTagsList = Arrays.asList(ruleTimeVO.getExcludeTags().split(","));
+                        }
+
+
+                        // 检查 combinedTagsList 是否包含排除列表中的任意一个标签
+                        boolean containsExcludedTag = cleanedTagList.stream()
+                                .anyMatch(excludedTagsList::contains);
+
+                        //含任意一个排除标签
+                        if (containsExcludedTag) {
+                            logger.info("该客户已匹配到规则,但是被排除,规则id:" + ruleTimeVO.getId() + "|cleanedTagList" + cleanedTagList + "|excludedTagsList" + excludedTagsList);
+                            qwExternalContactService.deleteBySopIdToContactIdTools(ruleTimeVO.getId(), qwExternalContact.getUserId(), qwExternalContact.getCorpId(), qwExternalContact.getId());
+
+                        } else {
+                            //检查 有可能在营期里 有可能不在。在不动,不在
+                            // 从数据库中取到的开始时间(Date类型),转换为 LocalDate
+                            Date sopStartTime = ruleTimeVO.getStartTime();
+                            //开始时间
+                            LocalDate sopStartLocalDate = sopStartTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+
+                            // 自动sop的规则
+                            QwAutoSopTimeParam qwAutoSopTimeParam = JSON.parseObject(ruleTimeVO.getAutoSopTime(), QwAutoSopTimeParam.class);
+
+                            // 用于查询/或新增
+                            SopUserLogs userLogs = new SopUserLogs();
+                            userLogs.setSopId(ruleTimeVO.getId());
+                            userLogs.setSopTempId(ruleTimeVO.getTempId());
+                            userLogs.setCorpId(ruleTimeVO.getCorpId());
+                            userLogs.setStatus(1);
+
+                            // 用于今天的新增
+                            SopUserLogsParamByDate userLogsParamByDate = new SopUserLogsParamByDate();
+                            userLogsParamByDate.setSopId(ruleTimeVO.getId());
+                            userLogsParamByDate.setSopTempId(ruleTimeVO.getTempId());
+                            userLogsParamByDate.setCorpId(ruleTimeVO.getCorpId());
+                            userLogsParamByDate.setStatus(1);
+
+                            // 设定用户信息
+                            userLogs.setQwUserId(qwExternalContact.getUserId());
+                            userLogs.setUserId(qwExternalContact.getQwUserId() + "|" + qwExternalContact.getCompanyUserId() + "|" + qwExternalContact.getCompanyId());
+                            userLogsParamByDate.setQwUserId(qwExternalContact.getUserId());
+                            userLogsParamByDate.setUserId(qwExternalContact.getQwUserId() + "|" + qwExternalContact.getCompanyUserId() + "|" + qwExternalContact.getCompanyId());
+
+                            // 创建 SopUserLogsInfo
+                            SopUserLogsInfo logsInfo = new SopUserLogsInfo();
+                            logsInfo.setQwUserId(qwExternalContact.getUserId());
+                            logsInfo.setCorpId(corpId);
+                            logsInfo.setExternalContactId(qwExternalContact.getExternalUserId());
+                            logsInfo.setExternalId(qwExternalContact.getId());
+                            logsInfo.setFsUserId(qwExternalContact.getFsUserId());
+                            logsInfo.setExternalUserName(qwExternalContact.getName());
+                            logsInfo.setSopId(ruleTimeVO.getId());
+
+                            logger.warn("sop任务的时间设置Tags:{}|{}|{}|{}", ruleTimeVO.getAutoSopTime(), ruleTimeVO.getId(), ruleTimeVO.getTags(), ruleTimeVO.getExcludeTags());
+
+                            //检查营期(如果开启了新客自动创建sop才检查营期,进入营期)
+                            if (ruleTimeVO.getIsAutoSop() == 1) {
+                                qwExternalContactService.qwSopRuleTimeToolsCheck(sopStartLocalDate, currentDate, dateFormatter, userLogs, logsInfo, userLogsParamByDate, qwAutoSopTimeParam, timeFormatter, localTime);
+                            }
+
+                        }
+
+                    });
+
+                    List<QwSopRuleTimeVO> qwSopRuleTimeVOSRemove = qwSopMapper.selectQwSopAutoByTagsByForeachRemoveNotAuto(qwSopAutoByTags);
+                    if (!qwSopRuleTimeVOSRemove.isEmpty()) {
+                        qwSopRuleTimeVOSRemove.forEach(vosRemove -> {
+                            logger.info("删除这个客户在这个客服的其他无关的sop任务" + vosRemove.getId() + "标签:" + tagArr + "id:" + qwExternalContact.getQwUserId());
+                            qwExternalContactService.deleteBySopIdToContactIdTools(vosRemove.getId(), qwExternalContact.getUserId(), qwExternalContact.getCorpId(), qwExternalContact.getId());
+
+                        });
+                    }
+
+//                    if (!runRuleTime.isEmpty() && !qwSopRuleTimeVOS.isEmpty()){
+//
+//                        //剔除掉 客服的其他任务里的,这个人的信息ps:可能没有
+//                        Set<String> qwSopRuleTimeVOIds = qwSopRuleTimeVOS.stream()
+//                                .map(QwSopRuleTimeVO::getId)
+//                                .filter(Objects::nonNull)       // 过滤掉 null
+//                                .map(String::trim)              // 去除前后空格
+//                                .collect(Collectors.toSet());
+//
+//                        if (!qwSopRuleTimeVOIds.isEmpty()){
+//
+//                            //在 qwSopRuleTimeVOS 中过滤掉包含在 runRuleTimeIds 中的 id
+//                            List<QwSopRuleTimeVO> notOverlapList = runRuleTime.stream()
+//                                    .filter(item -> item.getId() != null &&!qwSopRuleTimeVOIds.contains(item.getId().trim()))
+//                                    .collect(Collectors.toList());
+//
+//                            notOverlapList.forEach(notSop->{
+//                                logger.info("删除这个客户在这个客服的其他不符合的sop"+notSop.getId()+"标签:"+tagArr+"id:"+qwExternalContact.getQwUserId()+"排除的标签:"+qwSopRuleTimeVOIds);
+//
+//                                deleteBySopIdToContactIdTools(notSop.getId(),qwExternalContact.getUserId(),qwExternalContact.getCorpId(),qwExternalContact.getId());
+//
+//                            });
+//                        }
+//                    }
+                } else {
+                    //没匹配上任意一个(即这个剩下的标签匹配不上任意的sop),但是客服有任务-以防万一-删除这个客户在这个客服的sop
+                    if (!runRuleTime.isEmpty()) {
+                        runRuleTime.forEach(runSop -> {
+                            logger.info("没匹配上任意一个(即这个剩下的标签匹配不上任意的sop),但是客服有任务-以防万一-删除这个客户在这个客服的sop" + runSop.getId() + "标签:" + tagArr + "id:" + qwExternalContact.getQwUserId());
+                            qwExternalContactService.deleteBySopIdToContactIdTools(runSop.getId(), qwExternalContact.getUserId(), qwExternalContact.getCorpId(), qwExternalContact.getId());
+
+                        });
+                    }
+                }
+
+            }
+
+        } catch (Exception e) {
+            logger.error("改动了客户的标签的异常:qwExternalContact" + qwExternalContact + "|corpId" + corpId + "|cleanedTagList" + cleanedTagList);
+        }
+
+    }
+
+
+    /**
+     * 删除标签
+     *
+     * @param param
+     */
+    @Override
+    @Async
+    public void delUserTag(QwExternalContactAddTagParam param) {
+        try {
+            log.info("删除标签Async");
+            // 获取当前日期(只包含年月日)
+            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) {
+                QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(userId);
+                QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                qwEditUserTagParam.setRemove_tag(param.getTagIds());
+                qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+                qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+                try {
+                    QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, param.getCorpId());
+                    log.info("qw:" + qwResult);
+                    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(userId);
+                            qwExternal.setTagIds(JSON.toJSONString(ids));
+                            qwExternalContactMapper.updateQwExternalContact(qwExternal);
+                            logger.info("客户移除标签delUserTag:" + qwExternalContact.getName() + "|公司" + qwExternalContact.getCorpId() + "|员工" + qwExternalContact.getUserId() + "|总标签" + ids);
+                            //检查sop
+                            processTagsAll(qwExternalContact, param.getCorpId(), ids, currentDate, localTime);
+                            //新客对话
+//                        processTagsAllByAiChat(qwExternalContact,param.getCorpId(),ids);
+                        }
+                        suc++;
+                    } else {
+                        err++;
+                    }
+                } catch (Exception e) {
+                    logger.error("移除标签失败:" + qwEditUserTagParam + e.getMessage());
+                }
+            }
+            log.info("成功:" + suc + ",失败:" + err);
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("删除标签失败");
+        }
+    }
+
+    /**
+     * 同步我的客户
+     *
+     * @param id
+     */
+    @Override
+    @Async
+    public void syncMyQwExternalContact(Long id) {
+        try {
+            log.info("同步我的客户Async");
+            QwUser qwUser = qwUserMapper.selectQwUserById(id);
+            //对比同步客户信息
+            qwExternalContactService.getContactListResult(qwUser);
+            qwExternalContactService.syncMyQwExternalContactRecursion(qwUser, null);
+            log.info("同步我的企业微信客户成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("同步我的企业微信客户失败");
+        }
+    }
+
+    /**
+     * 同步新客
+     *
+     * @param id
+     */
+    @Override
+    @Async
+    public void syncAddMyQwExternalContact(Long id) {
+
+        try {
+            log.info("同步新客Async");
+            QwUser qwUser = qwUserMapper.selectQwUserById(id);
+            String userID = qwUser.getQwUserId();
+
+            QwExternalContactListResult contactListResult = qwApiService.getExternalcontactList(qwUser.getQwUserId(), qwUser.getCorpId());
+            if (contactListResult.getErrcode() == 0) {
+                //企微获取的id
+                List<String> externalUserid = contactListResult.getExternal_userid();
+                //库里的
+                List<String> qwExternalContactList = qwExternalContactMapper.selectQwExternalContactListAllNoDel(qwUser.getQwUserId(), qwUser.getCorpId());
+
+                List<String> addList = externalUserid.stream()
+                        .filter(externalUserId -> !qwExternalContactList.contains(externalUserId))
+                        .collect(Collectors.toList());
+
+
+                String qwUserId = qwUser.getQwUserId();
+                String corpId = qwUser.getCorpId();
+                Long companyId = qwUser.getCompanyId();
+                for (String ext : addList) {
+                    String externalUserID = ext;
+                    QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(ext, qwUser.getQwUserId(), qwUser.getCorpId());
+
+                    if (qwExternalContact == null) {
+                        QwExternalContactResult followUsers = qwApiService.getExternalcontact(ext, qwUser.getCorpId());
+                        ExternalContact externalContact = followUsers.getExternal_contact();
+                        List<FollowUser> followUser = followUsers.getFollow_user();
+                        for (FollowUser followInfo : followUser) {
+                            if (followInfo.getUserid().equals(qwUserId)) {
+                                LocalDate currentDate = LocalDate.now();
+                                // 获取当前系统时间 (HH:mm)
+                                LocalTime localTime = LocalTime.now();
+                                qwExternalContact = new QwExternalContact();
+                                qwExternalContact.setUserId(qwUserId); // 设置属于用户ID
+                                qwExternalContact.setExternalUserId(ext); // 设置外部联系人ID
+                                qwExternalContact.setCorpId(corpId); // 设置企业ID
+                                qwExternalContact.setCompanyId(companyId); // 设置公司ID
+                                qwExternalContact.setCompanyUserId(qwUser.getCompanyUserId());
+                                qwExternalContact.setQwUserId(qwUser.getId());
+                                qwExternalContact.setName(externalContact.getName()); // 设置名称
+                                qwExternalContact.setAvatar(externalContact.getAvatar()); // 设置头像
+                                qwExternalContact.setType(externalContact.getType()); // 设置外部联系人类型(1微信用户,2企业微信用户)
+                                qwExternalContact.setGender(externalContact.getGender()); // 设置性别 (0-未知, 1-男性, 2-女性)
+                                qwExternalContact.setUnionid(externalContact.getUnionid());
+                                qwExternalContact.setDescription(followInfo.getDescription()); // 设置描述信息
+                                qwExternalContact.setRemark(followInfo.getRemark());
+                                List<Tag> tags = followInfo.getTags();
+                                List<String> tagIds = tags.stream()
+                                        .map(Tag::getTag_id)
+                                        .collect(Collectors.toList());
+                                Set<String> combinedTagsSet = new HashSet<>(tagIds);
+                                qwExternalContact.setTagIds(JSON.toJSONString(tagIds)); // 设置标签ID
+                                qwExternalContact.setRemarkMobiles(JSON.toJSONString(followInfo.getRemark_mobiles())); // 设置备注电话号码
+                                qwExternalContact.setState(followInfo.getState());
+                                if (followInfo.getState() != null && !followInfo.getState().isEmpty()) {
+                                    String s = "way:" + corpId + ":";
+                                    if (followInfo.getState().contains(s)) {
+                                        String wayId = followInfo.getState().substring(followInfo.getState().indexOf(s) + s.length());
+                                        qwExternalContact.setWayId(Long.parseLong(wayId));
+                                    }
+                                }
+                                qwExternalContact.setCreateTime(new Date());
+                                qwExternalContact.setRemarkCorpName(followInfo.getRemark_corp_name()); // 设置备注企业名称
+                                qwExternalContact.setAddWay(followInfo.getAdd_way()); // 设置来源
+                                qwExternalContact.setOperUserid(followInfo.getOper_userid()); // 设置oper用户ID
+                                qwExternalContactMapper.insertQwExternalContact(qwExternalContact);
+                                qwExternalContactService.SyncAddSendWelcome(qwExternalContact, qwUser, qwUser.getCorpId());
+                                //发送好友欢迎语
+//
+
+                                QwAutoTags qwAutoTags = qwAutoTagsMapper.selectQwAutoTagsByIdJSON(corpId, qwUser.getId());
+
+                                QwExternalContact contact = qwExternalContact;
+                                log.info("数据:{}", contact.getId());
+
+                                if (qwAutoTags != null) {
+                                    QwAutoTagsLogs qwAutoTagsLogs = new QwAutoTagsLogs();
+                                    qwAutoTagsLogs.setAutoTagId(qwAutoTags.getId());
+                                    qwAutoTagsLogs.setType(3L);
+                                    qwAutoTagsLogs.setQwUserid(qwUser.getId());
+                                    qwAutoTagsLogs.setExternalUserId(ext);
+
+                                    List<QwAutoTagsRulesTags> qwAutoTagsRulesTagsList = JSON.parseArray(qwAutoTags.getRulesTags(), QwAutoTagsRulesTags.class);
+                                    boolean isMatch = false;
+                                    //存分时段里符合条件的标签
+                                    Set<String> combinedTagsItem = new HashSet<>();
+                                    // 获取今天的星期数
+                                    DayOfWeek today = LocalDate.now().getDayOfWeek();
+                                    int todayIndex = today.getValue(); // 1: Monday, 2: Tuesday, ..., 7: Sunday
+                                    // 获取当前时间
+                                    LocalTime now = LocalTime.now();
+                                    // 遍历所有规则
+                                    String tagRemark = null;
+                                    for (QwAutoTagsRulesTags rulesTags : qwAutoTagsRulesTagsList) {
+
+                                        List<String> tagsItem = rulesTags.getTags();
+                                        List<Integer> week = rulesTags.getWeek();
+                                        String startTime = rulesTags.getStartTime();
+                                        String endTime = rulesTags.getEndTime();
+                                        String remarks = rulesTags.getRemarks();
+
+                                        // 检查今天是否在 week 集合中
+                                        boolean isTodayInWeek = week.contains(todayIndex);
+                                        // 转换时间字符串为 LocalTime
+                                        LocalTime start = LocalTime.parse(startTime);
+                                        LocalTime end = LocalTime.parse(endTime.equals("24:00") ? "23:59:59" : endTime);
+                                        // 检查当前时间是否在 startTime 和 endTime 之间
+                                        boolean isNowInTimeRange = !now.isBefore(start) && !now.isAfter(end);
+                                        // 如果当前时间和日期匹配规则,将 tagsItem 添加到 combinedTagsSet 中
+                                        if (isTodayInWeek && isNowInTimeRange) {
+
+                                            combinedTagsSet.addAll(tagsItem);
+                                            combinedTagsItem.addAll(tagsItem);
+                                            isMatch = true;
+                                            //如果备注不为空
+                                            if (rulesTags.getIsDay() != null && rulesTags.getIsDay() == 1) {
+                                                String DayDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
+                                                tagRemark = DayDate.substring(1);
+                                            }
+                                            //如果备注不为空
+                                            if (!StringUtil.strIsNullOrEmpty(remarks)) {
+                                                tagRemark = (tagRemark == null ? "" : (tagRemark + "-")) + remarks;
+                                            }
+                                        }
+                                    }
+                                    QwExternalContact qu = new QwExternalContact();
+                                    if (tagRemark != null) {
+                                        qu.setRemark(tagRemark + "-" + externalContact.getName());
+                                        QwExternalContactRemarkParam param = new QwExternalContactRemarkParam();
+                                        param.setRemark(qu.getRemark());
+                                        param.setUserid(userID);
+                                        param.setExternal_userid(externalUserID);
+                                        QwExternalContactRemarkResult remarkResult = qwApiService.externalcontactRemark(param, qwExternalContact.getCorpId());
+                                    }
+
+                                    // 如果有匹配的规则,使用 combinedTagsList
+                                    if (isMatch) {
+                                        QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+                                        qwEditUserTagParam.setUserid(userID);
+                                        qwEditUserTagParam.setExternal_userid(externalUserID);
+                                        //添加标签的日志记录
+
+                                        List<String> combinedTags = new ArrayList<>(combinedTagsItem);
+                                        qwAutoTagsLogs.setEffectiveRules(JSON.toJSONString(combinedTags));
+                                        qwAutoTagsLogs.setAddTime(new Date());
+                                        qwAutoTagsLogs.setCompanyId(qwUser.getCompanyId());
+                                        qwAutoTagsLogs.setCorpId(qwUser.getCorpId());
+                                        //总标签 转换回列表
+                                        List<String> combinedTagsList = new ArrayList<>(combinedTagsSet);
+                                        // 设置标签
+                                        log.info("标签");
+                                        log.info("数据:{}", combinedTagsList);
+                                        qwEditUserTagParam.setAdd_tag(combinedTagsList);
+
+                                        // 企微加标签
+                                        QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, corpId);
+                                        if (qwResult.getErrcode() == 0) {
+                                            qwExternalContact.setTagIds(JSON.toJSONString(combinedTagsList));
+                                            qwAutoTagsLogsMapper.insertOrUpdateQwAutoTagsLogs(qwAutoTagsLogs);
+                                            if (!combinedTagsSet.isEmpty()) {
+                                                //分时段里符合的标签-再用来创建自动SOP
+                                                QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+                                                qwSopAutoByTags.setQwUserId(String.valueOf(qwUser.getId()));
+                                                qwSopAutoByTags.setCorpId(corpId);
+                                                qwSopAutoByTags.setTagsIdsSelectList(combinedTagsList);
+                                                qwSopAutoByTags.setSendType(2);
+                                                List<QwSopRuleTimeVO> qwSopRuleTimeVOS = qwSopMapper.selectQwSopAutoByTagsByForeach(qwSopAutoByTags);
+                                                logger.info("分时段里符合的标签-自动SOP条件:" + qwSopAutoByTags + "|" + qwUser.getQwUserId() + "|" + ext + "|");
+
+                                                if (qwSopRuleTimeVOS != null && !qwSopRuleTimeVOS.isEmpty()) {
+                                                    //SOP规则
+                                                    qwExternalContactService.qwSopRuleTimeTools(qwSopRuleTimeVOS, qwUserId, qwUser, corpId, ext, externalContact.getName(), contact, currentDate, localTime, combinedTagsList);
+                                                }
+                                                //aiSop任务
+                                                Boolean sopAiChatByRedis = qwExternalContactService.getSopAiChatByRedis(userID, corpId, externalUserID);
+                                                if (!sopAiChatByRedis){
+                                                    asyncQwAiChatSopService.executeQwAiChatSop(qwSopAutoByTags,userID,qwUser,externalUserID
+                                                            ,externalContact.getName(),contact.getId(),contact.getFsUserId(),currentDate,localTime);
+                                                }
+//                                            List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+//                                            if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()) {
+//                                                qwAiSopRuleTimeTools(qwSopAiRuleTimeVOS, userID, qwUser, corpId, externalUserID, externalContact.getName(), contact);
+//                                            }
+                                            }
+                                        } else if (qwResult.getErrcode() == 45035) {
+                                            //加入补偿机制
+                                            qwExternalContactService.insertQwExternalErrRetryTool(corpId, JSON.toJSONString(qwEditUserTagParam), 1, qwResult.getErrmsg());
+
+                                        }
+
+
+                                        qu.setId(contact.getId());
+                                        qu.setTagIds(JSON.toJSONString(combinedTagsList));
+
+                                        qwExternalContactMapper.updateQwExternalContact(qu);
+
+
+                                    } else {
+                                        // 转换回列表
+                                        List<String> combinedTagsList = new ArrayList<>(combinedTagsSet);
+
+                                        logger.info("不符合分时段条件2--看全部标签是否有符合的:" + combinedTagsList + "|" + externalUserID + "|" + userID + "|" + corpId);
+
+                                        try {
+                                            if (!combinedTagsSet.isEmpty()) {
+                                                //分时段里符合的标签-再用来创建自动SOP
+                                                QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+                                                qwSopAutoByTags.setQwUserId(String.valueOf(qwUser.getId()));
+                                                qwSopAutoByTags.setCorpId(corpId);
+                                                qwSopAutoByTags.setTagsIdsSelectList(combinedTagsList);
+                                                qwSopAutoByTags.setSendType(2);
+                                                List<QwSopRuleTimeVO> qwSopRuleTimeVOS = qwSopMapper.selectQwSopAutoByTagsByForeach(qwSopAutoByTags);
+                                                if (qwSopRuleTimeVOS != null && !qwSopRuleTimeVOS.isEmpty()) {
+                                                    //SOP规则
+                                                    qwExternalContactService.qwSopRuleTimeTools(qwSopRuleTimeVOS, userID, qwUser, corpId, externalUserID, externalContact.getName(), contact, currentDate, localTime, combinedTagsList);
+                                                }
+                                                //aiSop任务
+                                                Boolean sopAiChatByRedis = qwExternalContactService.getSopAiChatByRedis(userID, corpId, externalUserID);
+                                                if (!sopAiChatByRedis){
+                                                    asyncQwAiChatSopService.executeQwAiChatSop(qwSopAutoByTags,userID,qwUser,externalUserID
+                                                            ,externalContact.getName(),contact.getId(),contact.getFsUserId(),currentDate,localTime);
+                                                }
+//                                            List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+//                                            if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()) {
+//                                                qwAiSopRuleTimeTools(qwSopAiRuleTimeVOS, userID, qwUser, corpId, externalUserID, externalContact.getName(), contact);
+//                                            }
+                                            }
+                                        } catch (Exception e) {
+
+                                        }
+
+                                    }
+
+                                } else {
+                                    try {
+                                        // 转换回列表
+                                        List<String> combinedTagsList = new ArrayList<>(combinedTagsSet);
+
+                                        logger.error("不符合分时段条件2--看全部标签是否有符合的:" + combinedTagsList + "|" + externalUserID + "|" + userID + "|" + corpId);
+
+                                        try {
+                                            if (!combinedTagsSet.isEmpty()) {
+                                                //分时段里符合的标签-再用来创建自动SOP
+                                                QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+                                                qwSopAutoByTags.setQwUserId(String.valueOf(qwUser.getId()));
+                                                qwSopAutoByTags.setCorpId(corpId);
+                                                qwSopAutoByTags.setTagsIdsSelectList(combinedTagsList);
+                                                qwSopAutoByTags.setSendType(2);
+                                                List<QwSopRuleTimeVO> qwSopRuleTimeVOS = qwSopMapper.selectQwSopAutoByTagsByForeach(qwSopAutoByTags);
+                                                if (qwSopRuleTimeVOS != null && !qwSopRuleTimeVOS.isEmpty()) {
+                                                    //SOP规则
+                                                    qwExternalContactService.qwSopRuleTimeTools(qwSopRuleTimeVOS, userID, qwUser, corpId, externalUserID, externalContact.getName(), contact, currentDate, localTime, combinedTagsList);
+                                                }
+                                                //aiSop任务
+                                                Boolean sopAiChatByRedis = qwExternalContactService.getSopAiChatByRedis(userID, corpId, externalUserID);
+                                                if (!sopAiChatByRedis){
+                                                    asyncQwAiChatSopService.executeQwAiChatSop(qwSopAutoByTags,userID,qwUser,externalUserID
+                                                            ,externalContact.getName(),contact.getId(),contact.getFsUserId(),currentDate,localTime);
+                                                }
+//                                            List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+//                                            if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()) {
+//                                                qwAiSopRuleTimeTools(qwSopAiRuleTimeVOS, userID, qwUser, corpId, externalUserID, externalContact.getName(), contact);
+//                                            }
+                                            }
+                                        } catch (Exception e) {
+                                            logger.error("用自带标签入营期出错" + combinedTagsList + "|" + externalUserID + "|" + userID + "|" + corpId);
+                                        }
+
+                                    } catch (Exception e) {
+                                        logger.error("打标签出错" + e.getMessage());
+                                    }
+
+                                }
+
+
+                            }
+                        }
+
+
+                    }
+
+                }
+
+                //库里有 企微没有的
+                List<String> notInExternalUseridList = qwExternalContactList.stream()
+                        .filter(externalUserId -> !externalUserid.contains(externalUserId))
+                        .collect(Collectors.toList());
+
+
+                if (!notInExternalUseridList.isEmpty()) {
+                    DeleteQwSopParam apiParam = new DeleteQwSopParam();
+                    apiParam.setQwUserId(qwUser.getQwUserId());
+                    apiParam.setCorpId(qwUser.getCorpId());
+                    apiParam.setExternalUserIds(notInExternalUseridList);
+
+                    // 批量删除 qw_external_contact 表中已经删除的客户的营期以及发送记录
+                    sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactIdList(apiParam);
+                    sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactIdByChatList(apiParam);
+                    //批量删除发送记录
+                    qwSopLogsMapper.deleteQwSopLogsByExternalUserIdList(apiParam);
+                    //批量更新状态
+                    batchUpdateQwExternalContact(notInExternalUseridList, qwUser.getQwUserId(), qwUser.getCorpId());
+                }
+            }
+            log.info("新客同步成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("新客同步失败");
+        }
+    }
+
+
+    private void batchUpdateQwExternalContact(List<String> notInExternalUseridList, String qwUserId, String corpId) {
+        // 定义批量插入的大小
+        int batchSize = 500;
+
+        // 循环处理外部用户 ID,每次处理批量大小的子集
+        for (int i = 0; i < notInExternalUseridList.size(); i += batchSize) {
+
+            int endIndex = Math.min(i + batchSize, notInExternalUseridList.size());
+            List<String> batchList = notInExternalUseridList.subList(i, endIndex);  // 获取当前批次的子集
+
+            // 直接使用批次数据进行批量更新,不需要额外的 List
+            try {
+                qwExternalContactMapper.batchUpdateQwExternalContactStatus(batchList, qwUserId, corpId);
+            } catch (Exception e) {
+                // 记录异常日志,方便后续排查问题
+                logger.error("批量更新数据时发生异常,处理的批次起始索引为: " + i, e);
+            }
+        }
+    }
+
+
+    /**
+     * 企业微信客户
+     *
+     * @param param
+     */
+    @Override
+    @Async
+    public void transfer(TransferParam param) {
+        try {
+            log.info("开始分配客户Async");
+            QwUser qwUser = qwUserMapper.selectQwUserById(param.getUserId());
+            Integer err = 0;
+            Integer suc = 0;
+            if (qwUser != null) {
+                List<Long> ids = param.getIds();
+                for (Long id : ids) {
+                    QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(id);
+                    if (qwExternalContact == null || qwExternalContact.getUserId().equals(qwUser.getQwUserId())) {
+                        err++;
+                    } else {
+                        QwTransferCustomerParam qwTransferCustomerParam = new QwTransferCustomerParam();
+                        String content = param.getContent();
+                        if (content == null || content.isEmpty()) {
+                            content = "您好,您的服务已升级,后续将由我的同事接替我的工作,继续为您服务。";
+                        }
+                        qwTransferCustomerParam.setTransfer_success_msg(content);
+                        qwTransferCustomerParam.setHandover_userid(qwExternalContact.getUserId());
+                        qwTransferCustomerParam.setTakeover_userid(qwUser.getQwUserId());
+                        qwTransferCustomerParam.setExternal_userid(Arrays.asList(qwExternalContact.getExternalUserId()));
+                        QwTransferCustomerResult qwTransferCustomerResult = qwApiService.transferCustomer(qwTransferCustomerParam, param.getCorpId());
+
+                        if (qwTransferCustomerResult.getErrcode() == 0) {
+                            List<QwCustomer> customer = qwTransferCustomerResult.getCustomer();
+                            if (customer != null && customer.size() > 0) {
+                                for (QwCustomer qwCustomer : customer) {
+                                    if (qwCustomer.getErrcode() == 0) {
+                                        QwExternalContactTransferLog qwExternalContactTransferLog = new QwExternalContactTransferLog();
+                                        // qwExternalContactTransferLog.setCompanyId(param.getCorpId());
+                                        qwExternalContactTransferLog.setExternalContactId(id);
+                                        qwExternalContactTransferLog.setCompanyUserId(qwExternalContact.getCompanyUserId());
+                                        qwExternalContactTransferLog.setCreateTime(new Date());
+                                        qwExternalContactTransferLog.setStatus(2);
+                                        qwExternalContactTransferLog.setQwUserId(qwUser.getId());
+                                        qwExternalContactTransferLog.setHandoverUserId(qwExternalContact.getUserId());
+                                        qwExternalContactTransferLog.setTakeoverUserId(qwUser.getQwUserId());
+                                        qwExternalContactTransferLog.setCorpId(param.getCorpId());
+                                        qwExternalContactTransferLog.setExternalUserId(qwCustomer.getExternal_userid());
+                                        qwExternalContactTransferLog.setHandoverQwUserId(qwExternalContact.getQwUserId());
+                                        qwExternalContactTransferLogMapper.insertQwExternalContactTransferLog(qwExternalContactTransferLog);
+                                        QwExternalContact qwExternal = new QwExternalContact();
+                                        qwExternal.setStatus(2);
+                                        qwExternal.setTransferStatus(2);
+                                        qwExternal.setId(qwExternalContact.getId());
+                                        qwExternalContactMapper.updateQwExternalContact(qwExternal);
+                                        suc++;
+                                    } else {
+                                        err++;
+                                    }
+                                }
+                            } else {
+                                err++;
+                            }
+                        } else {
+                            logger.error("在职转接失败-" + qwTransferCustomerResult.getErrmsg() + "|" + param.getCorpId());
+                            err++;
+                        }
+                    }
+                }
+            }
+            log.info("在职转接 分配客户成功");
+            log.info("接替成功:" + suc + ",接替失败:" + err);
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("在职转接 分配客户失败");
+        }
+    }
+
+
+    @Override
+    @Async
+    public void syncQwExternalContactUnassigned(String corpId) {
+        try {
+            log.info("同步待转接Async");
+
+            QwUnassignedListParam qwUnassignedListParam = new QwUnassignedListParam();
+            qwUnassignedListParam.setPage_size(1000);
+            QwUnassignedListResult unassignedList = qwApiService.getUnassignedList(qwUnassignedListParam, corpId);
+
+            if (unassignedList.getErrcode() == 0) {
+                List<QwUnassignedListResult.InfoEntry> info = unassignedList.getInfo();
+                for (QwUnassignedListResult.InfoEntry infoEntry : info) {
+
+                    QwExternalContact qwExternalContact = new QwExternalContact();
+                    qwExternalContact.setExternalUserId(infoEntry.getExternal_userid());
+                    qwExternalContact.setUserId(infoEntry.getHandover_userid());
+                    qwExternalContact.setStatus(0);
+                    List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectQwExternalContactList(qwExternalContact);
+                    if (qwExternalContacts != null && qwExternalContacts.size() > 0) {
+                        for (QwExternalContact externalContact : qwExternalContacts) {
+                            QwExternalContact q = new QwExternalContact();
+                            q.setId(externalContact.getId());
+                            q.setStatus(1);
+                            qwExternalContactMapper.updateQwExternalContact(q);
+                        }
+                    }
+                }
+            }
+            log.info("同步待转接成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("同步待转接失败");
+        }
+
+    }
+
+    @Override
+    @Async
+    public void resignedTransfer(ResignedTransferParam param) {
+        try {
+
+            log.info("离职继承分配客户Async");
+
+            QwUser qwUser = qwUserMapper.selectQwUserById(param.getUserId());
+            Integer err = 0;
+            Integer suc = 0;
+            if (qwUser != null) {
+                List<Long> ids = param.getIds();
+                for (Long id : ids) {
+                    QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(id);
+
+                    if (qwExternalContact == null || qwExternalContact.getUserId().equals(qwUser.getQwUserId())) {
+                        err++;
+                    } else {
+                        QwTransferCustomerResignedParam qwTransferCustomerParam = new QwTransferCustomerResignedParam();
+                        qwTransferCustomerParam.setHandover_userid(qwExternalContact.getUserId());
+                        qwTransferCustomerParam.setTakeover_userid(qwUser.getQwUserId());
+                        qwTransferCustomerParam.setExternal_userid(Arrays.asList(qwExternalContact.getExternalUserId()));
+                        QwTransferCustomerResignedResult qwTransferCustomerResignedParam = qwApiService.resignedTransferCustomer(qwTransferCustomerParam, param.getCorpId());
+
+                        if (qwTransferCustomerResignedParam.getErrcode() == 0) {
+                            List<QwCustomer> customer = qwTransferCustomerResignedParam.getCustomer();
+                            if (customer != null && customer.size() > 0) {
+                                for (QwCustomer qwCustomer : customer) {
+                                    if (qwCustomer.getErrcode() == 0) {
+                                        QwExternalContactTransferLog qwExternalContactTransferLog = new QwExternalContactTransferLog();
+                                        // qwExternalContactTransferLog.setCompanyId(param.getCorpId());
+                                        qwExternalContactTransferLog.setExternalContactId(id);
+                                        qwExternalContactTransferLog.setCompanyUserId(qwExternalContact.getCompanyUserId());
+                                        qwExternalContactTransferLog.setCreateTime(new Date());
+                                        qwExternalContactTransferLog.setStatus(2);
+                                        qwExternalContactTransferLog.setQwUserId(qwUser.getId());
+                                        qwExternalContactTransferLog.setHandoverUserId(qwExternalContact.getUserId());
+                                        qwExternalContactTransferLog.setTakeoverUserId(qwUser.getQwUserId());
+                                        qwExternalContactTransferLog.setCorpId(param.getCorpId());
+                                        qwExternalContactTransferLog.setExternalUserId(qwCustomer.getExternal_userid());
+                                        qwExternalContactTransferLogMapper.insertQwExternalContactTransferLog(qwExternalContactTransferLog);
+                                        QwExternalContact qwExternal = new QwExternalContact();
+                                        qwExternal.setStatus(2);
+                                        qwExternal.setTransferStatus(2);
+                                        qwExternal.setId(qwExternalContact.getId());
+                                        qwExternalContactMapper.updateQwExternalContact(qwExternal);
+                                        //查询是否绑定公司码
+                                        /*CompanyCompanyFsuser companyCompanyFsuser = companyCompanyFsuserMapper.getInfoByUserId(String.valueOf(qwExternalContact.getFsUserId()));
+                                        if (companyCompanyFsuser!=null){
+                                            //修改腕表客服
+                                            watchCompanyUserMapper.updateByCompanyUserId(qwUser.getCompanyUserId(),companyCompanyFsuser.getCompanyUserId());
+                                            //换绑公司码
+                                            companyCompanyFsuser.setCompanyUserId(qwUser.getCompanyUserId());
+                                            companyCompanyFsuser.setQwContactId(qwExternalContact.getId());
+                                            companyCompanyFsuserMapper.updateCompanyCompanyUser(companyCompanyFsuser);
+                                        }*/
+                                        suc++;
+                                    } else {
+                                        err++;
+                                    }
+                                }
+                            } else {
+                                err++;
+                            }
+                        } else {
+                            err++;
+                        }
+                    }
+                }
+            }
+            log.info("接替成功:" + suc + ",接替失败:" + err);
+            log.info("离职继承分配客户成功");
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("离职继承分配客户失败");
+        }
+    }
+
+
+    @Override
+    @Async
+    public void cogradientGroupChat(String corpId, List<String> qwUserIdList) throws Exception {
+
+//        过滤群主的id集合
+        ArrayList<String> ownerFilterList = new ArrayList<>(qwUserIdList);
+        try {
+            QwGroupChatListResult qwGroupChatListResult = qwApiService.groupChatList(ownerFilterList, null, corpId);
+            qwGroupChatService.insertGroupChatList(qwGroupChatListResult, corpId, ownerFilterList);
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.info("同步客户群列表失败");
+        }
+        log.info("同步客户群列表成功");
+    }
+}

+ 9 - 7
fs-service/src/main/java/com/fs/qw/vo/QwUserVoiceLogTotalVo.java

@@ -14,6 +14,9 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
     /** id */
     private Long id;
 
+    @Excel(name = "客服名称")
+    private String companyUserName;
+
     /** 外部联系人id */
     //@Excel(name = "外部联系人id")
     private Long extId;
@@ -28,6 +31,9 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
     @Excel(name = "企微用户名称")
     private String qwUserName;
 
+    @Excel(name = "企微主体名称")
+    private String corpName;
+
     /** 时长秒 */
     @Excel(name = "时长秒")
     private Long duration;
@@ -44,6 +50,7 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
     //@Excel(name = "企微id")
     private String corpId;
 
+
     /** 公司id */
     //@Excel(name = "公司id")
     private Long companyId;
@@ -51,13 +58,10 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
     //@Excel(name = "公司名称")
     private String companyName;
 
-    /** 销售用户id */
-    //@Excel(name = "销售用户id")
+    /** 客服用户id */
+    //@Excel(name = "客服用户id")
     private Long companyUserId;
 
-    //@Excel(name = "销售用户名称")
-    private String companyUserName;
-
     //接通数量
     @Excel(name = "接通数量")
     private Long connectCount;
@@ -73,8 +77,6 @@ public class QwUserVoiceLogTotalVo extends BaseEntity {
 
     private QwUser qwUser;
 
-    private String corpName;
-
     public String getCorpName() {
         return corpName;
     }

+ 14 - 1
fs-service/src/main/java/com/fs/sop/domain/QwSopTempContent.java

@@ -1,6 +1,7 @@
 package com.fs.sop.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
@@ -44,5 +45,17 @@ public class QwSopTempContent{
     @Excel(name = "链接过期时间")
     private String expiresDays;
 
-
+    @TableField(exist = false)
+    private Integer type;
+
+    /**
+     *  课程
+     */
+    @TableField(exist = false)
+    private Integer courseId;
+    /**
+     *  课节
+     */
+    @TableField(exist = false)
+    private Integer videoId;
 }

+ 7 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTempVoice.java

@@ -55,6 +55,13 @@ public class QwSopTempVoice{
     /** 语音文件路径 */
     @Excel(name = "秒")
     private LocalDateTime createTime;
+    private LocalDateTime updateTime;
 
+    //@Excel(name = "是否录制完成")
+    private Integer recordType;
+
+    private Long qwUserId;
+
+    private String voicePrintUrl;
 
 }

+ 12 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempContentMapper.java

@@ -90,4 +90,16 @@ public interface QwSopTempContentMapper extends BaseMapper<QwSopTempContent>{
             "\tt.company_id = 170 \n" +
             "\tAND c.content_type = 3")
     List<QwSopTempContentVO> updateSiFenTemp();
+
+    @Select("select   tc.id,\n" +
+            "  tc.content_type,\n" +
+            "  tc.content,\n" +
+            "  tr.course_id,\n" +
+            "  tr.video_id," +
+            "  tr.content_type as type " +
+            "FROM\n" +
+            "  qw_sop_temp_content tc " +
+            "left join qw_sop_temp_rules tr on  tc.rules_id=tr.id " +
+            "where tc.temp_id=#{tempId}")
+    List<QwSopTempContent> selectQwSopTempContentByTempIdAndRules(@Param("tempId") String tempId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempVoiceMapper.java

@@ -77,4 +77,6 @@ public interface QwSopTempVoiceMapper extends BaseMapper<QwSopTempVoice>{
     List<QwSopTempVoice> getVoiceByText(@Param("companyUserId") Long companyUserId, @Param("textList") List<String> textList);
 
     List<QwSopTempVoice> selectAllByUserIds(@Param("userIdList") List<Long> userIdList);
+
+    QwSopTempVoice selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(@Param("companyUserId") Long companyUserId,@Param("voiceTxt") String voiceTxt);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopTempVoiceService.java

@@ -77,4 +77,6 @@ public interface IQwSopTempVoiceService extends IService<QwSopTempVoice>{
     void synchronousTemp(List<QwSopTempContent> collect, QwSopTempDay day);
 
     List<QwSopTempVoice> listByTempIdAndCompanyId(String tempId, List<Long> longs);
+
+    QwSopTempVoice selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(Long companyUserId, String voiceTxt);
 }

+ 6 - 2
fs-service/src/main/java/com/fs/sop/service/impl/QwSopTempVoiceServiceImpl.java

@@ -47,7 +47,7 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
     private final QwSopMapper qwSopMapper;
     private final QwSopTempDayMapper qwSopTempDayMapper;
     private final ICompanyUserService companyUserService;
-
+    private final QwSopTempVoiceMapper qwSopTempVoiceMapper;
     /**
      * 查询模板对应的销售语音文件
      *
@@ -275,5 +275,9 @@ public class QwSopTempVoiceServiceImpl extends ServiceImpl<QwSopTempVoiceMapper,
         log.info("语音生成成功,salesId: {}", salesId);
         return voice;
     }
-
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public QwSopTempVoice selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(Long companyUserId, String voiceTxt) {
+        return qwSopTempVoiceMapper.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId,voiceTxt);
+    }
 }

+ 51 - 39
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -597,7 +597,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //小程序单独
                             case "4":
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
-                                        String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
+                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
 
                                 if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
                                     log.error("配置中无小程序id,采用默认的");
@@ -696,7 +696,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //小程序单独
                             case "4":
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
-                                        String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
+                                        qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config);
 
                                 if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
                                     log.error("配置中无小程序id,采用默认的");
@@ -804,7 +804,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),param.getStartTime(),createTime );
 
                                 String sortLink = generateShortLink(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                        qwUserId, companyUserId, companyId, finalDomainName,item.getExternalId(),config);
+                                        Long.valueOf(qwUserId), companyUserId, companyId, finalDomainName,item.getExternalId(),config);
 
                                 if (StringUtils.isNotEmpty(sortLink)) {
                                     if ("3".equals(st.getContentType())) {
@@ -837,12 +837,13 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),item.getStartTime(),createTime );
 
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                    qwUserId, companyUserId, companyId, item.getExternalId(), config);
+                                    Long.valueOf(qwUserId), companyUserId, companyId, item.getExternalId(), config);
+
+                            String miniAppId = null;
 
                             if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
                                 Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
                                 if (integerListMap != null) {
-
                                     int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
                                     int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
                                     List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
@@ -850,12 +851,18 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     if (miniapps != null && !miniapps.isEmpty()) {
                                         CompanyMiniapp companyMiniapp = miniapps.get(0);
                                         if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
-                                            st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                            miniAppId = companyMiniapp.getAppId();
                                         }
                                     }
                                 }
-                            } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
-                                st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                            }
+
+                            if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                miniAppId = qwCompany.getMiniAppId();
+                            }
+
+                            if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                st.setMiniprogramAppid(miniAppId);
                             } else {
                                 log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                             }
@@ -869,7 +876,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),item.getStartTime(),createTime );
 
                             QwCreateLinkByAppVO linkByApp = createLinkByApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                    qwUserId, companyUserId, companyId, item.getExternalId(), config,qwUser.getQwUserName(),contact.getFsUserId());
+                                    Long.valueOf(qwUserId), companyUserId, companyId, item.getExternalId(), config,qwUser.getQwUserName(),contact.getFsUserId());
                             st.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
                             st.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
 
@@ -1170,7 +1177,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 companyId, item.getExternalId(),param.getStartTime(),dataTime );
 
                         String sortLink = generateShortLink(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
-                                String.valueOf(qwUser.getId()), companyUserId, companyId, domainName,item.getExternalId(),config);
+                                qwUser.getId(), companyUserId, companyId, domainName,item.getExternalId(),config);
 
                         if (StringUtils.isNotEmpty(sortLink)) {
                             if ("3".equals(st.getContentType())) {
@@ -1205,20 +1212,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             item.getExternalId(),item.getStartTime(),dataTime );
 
                     String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
-                            String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
-
-//                    if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())){
-//
-//                        log.error("配置中无小程序id,采用默认的");
-//
-//                        st.setMiniprogramAppid("wxc84c6f789ba7f176");
-//                    }else {
-//                        st.setMiniprogramAppid(config.getMiniprogramAppid());
-//                    }
+                            qwUser.getId(), companyUserId, companyId, item.getExternalId(), config);
+
+
+                    String miniAppId = null;
+
                     if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
                         Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
                         if (integerListMap != null) {
-
                             int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
                             int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
                             List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
@@ -1226,16 +1227,23 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             if (miniapps != null && !miniapps.isEmpty()) {
                                 CompanyMiniapp companyMiniapp = miniapps.get(0);
                                 if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
-                                    st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                    miniAppId = companyMiniapp.getAppId();
                                 }
                             }
                         }
-                    } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
-                        st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                    }
+
+                    if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                        miniAppId = qwCompany.getMiniAppId();
+                    }
+
+                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                        st.setMiniprogramAppid(miniAppId);
                     } else {
-                        log.error("企业未配置小程序-" + param.getCorpId());
+                        log.error("企业未配置小程序" +  param.getCorpId());
                     }
 
+
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
                 default:
@@ -1261,7 +1269,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         }
     }
     // 提取通用替换逻辑的方法
-    private void replaceContent(String contentType, String content, Consumer<String> setter, List<FastGptChatReplaceWords> words) {
+    public void replaceContent(String contentType, String content, Consumer<String> setter, List<FastGptChatReplaceWords> words) {
         if (contentType != null && content != null && !content.isEmpty()) {
             for (FastGptChatReplaceWords word : words) {
                 if (content.contains(word.getContent())) {
@@ -1271,6 +1279,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             }
         }
     }
+
     private void processAndInsertQwSopLogsBySendMsg(List<QwSopLogs> sopLogsList ) {
         // 定义批量插入的大小
         int batchSize = 500;
@@ -1323,11 +1332,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     }
 
     private String generateShortLink(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
-                                     Integer courseId, Integer videoId, String qwUserId,
+                                     Integer courseId, Integer videoId, Long qwUserId,
                                      String companyUserId, String companyId,String domainName, Long externalId,CourseConfig config) {
 
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
-                companyUserId, companyId, externalId,0);
+                companyUserId, companyId, externalId,0,null);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link,courseMap);
@@ -1349,11 +1358,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     }
 
     private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
-                                     Integer courseId, Integer videoId, String qwUserId,
+                                     Integer courseId, Integer videoId, Long qwUserId,
                                      String companyUserId, String companyId, Long externalId,CourseConfig config) {
 
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
-                                                companyUserId, companyId, externalId,3);
+                                                companyUserId, companyId, externalId,3,null);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link,courseMap);
@@ -1371,12 +1380,12 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     }
 
     private QwCreateLinkByAppVO createLinkByApp(QwSopCourseFinishTempSetting.Setting setting, String corpId,
-                                                Date sendTime, Integer courseId, Integer videoId, String qwUserId,
+                                                Date sendTime, Integer courseId, Integer videoId, Long qwUserId,
                                                 String companyUserId, String companyId, Long externalId,
                                                 CourseConfig config,String qwUserName,Long fsUserId){
 
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
-                companyUserId, companyId, externalId,4);
+                companyUserId, companyId, externalId,4,null);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link,courseMap);
@@ -1398,7 +1407,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         byAppVO.setAppMsgLink(appMsgLink);
 
             //异步生成app链接记录
-        asyncSopTestService.createFsCourseSopAppLink(link.getLink(),sendTime,updateTime,companyId,companyUserId,qwUserId,
+        asyncSopTestService.createFsCourseSopAppLink(link.getLink(),sendTime,updateTime,companyId,companyUserId,String.valueOf(qwUserId),
                 qwUserName,corpId,courseId,setting.getLinkTitle(),setting.getLinkImageUrl(),videoId,
                 setting.getLinkDescribe(),appMsgLink,externalId);
 
@@ -1435,29 +1444,32 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return updateTime;
     }
 
-    public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Integer courseId,Integer videoId, String qwUserId,
-                                           String companyUserId, String companyId,Long externalId,Integer type){
+    public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Integer courseId, Integer videoId, Long qwUserId,
+                                           String companyUserId, String companyId, Long externalId, Integer type, String chatId) {
         // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(Long.parseLong(companyId));
-        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setQwUserId(qwUserId);
         link.setCompanyUserId(Long.parseLong(companyUserId));
         link.setVideoId(videoId.longValue());
         link.setCorpId(corpId);
         link.setCourseId(courseId.longValue());
+        link.setChatId(chatId);
         link.setQwExternalId(externalId);
         link.setLinkType(type); //小程序
-
+        link.setUNo(UUID.randomUUID().toString());
         String randomString = generateRandomStringWithLock();
-        if (StringUtil.strIsNullOrEmpty(randomString)){
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
             link.setLink(UUID.randomUUID().toString().replace("-", ""));
-        }else {
+        } else {
             link.setLink(randomString);
         }
+
         link.setCreateTime(sendTime);
 
         return link;
     }
+
     /**
      * 时间字符串转Date时间
      * @param dateString

+ 0 - 73
fs-service/src/main/resources/application-config-druid-fcky.yml

@@ -1,73 +0,0 @@
-baidu:
-  token: 12313231232
-  back-domain: https://www.xxxx.com
-#配置
-logging:
-  level:
-    org.springframework.web: INFO
-    com.github.binarywang.demo.wx.cp: DEBUG
-    me.chanjar.weixin: DEBUG
-wx:
-  miniapp:
-    configs:
-  cp:
-    corpId:
-    appConfigs:
-  pay:
-    appId:  #微信公众号或者小程序等的appid
-    mchId:  #微信支付商户号
-    mchKey:  #微信支付商户密钥
-    subAppId:  #服务商模式下的子商户公众账号ID
-    subMchId:  #服务商模式下的子商户号
-    keyPath:  # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
-    notifyUrl:
-  mp:
-    useRedis: false
-    redisConfig:
-      host: 127.0.0.1
-      port: 6379
-      timeout: 2000
-    configs:
-      - appId: wxea1da2b708ab3c2f # 第一个公众号的appid  //公众号名称:爱尚佳园
-        secret: 981c60b03292f572039402d7ae09f91f # 公众号的appsecret
-        token: PPKOdAlCoMO # 接口配置里的Token值
-        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
-aifabu:  #爱链接
-  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
-watch:
-  watchUrl: watch.ylrzcloud.com/prod-api
-  #  account: tcloud
-  #  password: mdf-m2h_6yw2$hq
-  account1: ccif #866655060138751
-  password1: cp-t5or_6xw7$mt
-  account2: tcloud #rt500台
-  password2: mdf-m2h_6yw2$hq
-  account3: whr
-  password3: v9xsKuqn_$d2y
-
-fs :
-  commonApi: http://10.206.0.16:8010
-  h5CommonApi: http://127.0.0.1:7771
-nuonuo:
-  key: 10924508
-  secret: A2EB20764D304D16
-
-# 存储捅配置
-tencent_cloud_config:
-  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
-  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
-  bucket: fcky-1323137866
-  app_id: 1323137866
-  region: ap-chongqing
-  proxy: fcky
-cloud_host:
-  company_name: 蜂巢快药
-headerImg:
-  imgUrl: https://fc-1361520560.cos.ap-beijing.myqcloud.com/fs/20250624/490729259f354c338ad6fc0fbbfe0e53.jpg
-ipad:
-  ipadUrl: http://ipad.cdwjyyh.com
-wx_miniapp_temp:
-  pay_order_temp_id:
-  inquiry_temp_id:
-
-

+ 1 - 1
fs-service/src/main/resources/application-config-druid-hcl.yml

@@ -85,7 +85,7 @@ cloud_host:
   company_name: 恒春来
 #看课授权时显示的头像
 headerImg:
-  imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250808/1754640068227.png
+  imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250815/1755228988455.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:

+ 4 - 4
fs-service/src/main/resources/application-config-druid-jnmy.yml

@@ -37,10 +37,10 @@ wx:
       port: 6379
       timeout: 2000
     configs:
-      - appId:  # 第一个公众号的appid
-        secret:  # 公众号的appsecret
-        token:  # 接口配置里的Token值
-        aesKey:  # 接口配置里的EncodingAESKey值
+      - appId: wx6ee517a8d8743f88  # 第一个公众号的appid
+        secret: 1fac75465a61f9259a0fe19795d9e80d # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
 aifabu:  #爱链接
   appKey: 7b471be905ab17e00f3b858c6710dd117601d008
 watch:

+ 1 - 1
fs-service/src/main/resources/application-config-druid-jzzx.yml

@@ -87,7 +87,7 @@ headerImg:
   imgUrl: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
 ipad:
   ipadUrl: http://ipad.jiuzhouzaixian.com
-  aiApi: http://154.8.194.176:3000/api
+  aiApi: http://1.95.196.10:3000/api
 wx_miniapp_temp:
   pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek
   inquiry_temp_id: 9POPYeqhI48LOPvq-Rfoklze7H-9SlunJKh10Qt4_2I

+ 1 - 1
fs-service/src/main/resources/application-config-zkzh.yml

@@ -132,7 +132,7 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs:
-  commonApi: http://172.27.0.7:8010
+  commonApi: http://172.21.76.167:8010
 nuonuo:
   key: 10924508
   secret: A2EB20764D304D16

+ 0 - 150
fs-service/src/main/resources/application-druid-fcky.yml

@@ -1,150 +0,0 @@
-# 数据源配置
-spring:
-    profiles:
-        include: config-druid-fcky,common
-    # redis 配置
-    redis:
-        # 地址
-        host: 10.206.0.14
-        # 端口,默认为6379
-        port: 6379
-        # 数据库索引
-        database: 0
-        # 密码
-        password: Ylrz_1q2w3e4r5t6y
-        # 连接超时时间
-        timeout: 20s
-        lettuce:
-            pool:
-                # 连接池中的最小空闲连接
-                min-idle: 0
-                # 连接池中的最大空闲连接
-                max-idle: 8
-                # 连接池的最大数据库连接数
-                max-active: 8
-                # #连接池最大阻塞等待时间(使用负值表示没有限制)
-                max-wait: -1ms
-    datasource:
-        #        clickhouse:
-        #            type: com.alibaba.druid.pool.DruidDataSource
-        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
-        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
-        #            username: rt_2024
-        #            password: Yzx_19860213
-        #            initialSize: 10
-        #            maxActive: 100
-        #            minIdle: 10
-        #            maxWait: 6000
-        mysql:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://10.206.0.3:3306/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Ylrz_1q2w3e4r5t6y
-                # 从库数据源
-                slave:
-                    # 从数据源开关/默认关闭
-                    enabled: false
-                    url:
-                    username:
-                    password:
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-        sop:
-            type: com.alibaba.druid.pool.DruidDataSource
-            driverClassName: com.mysql.cj.jdbc.Driver
-            druid:
-                # 主库数据源
-                master:
-                    url: jdbc:mysql://10.206.0.3:3306/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
-                    username: root
-                    password: Ylrz_1q2w3e4r5t6y
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
-                        enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
-rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
-    producer:
-        group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-    consumer:
-        group: test-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey

+ 3 - 1
fs-service/src/main/resources/application-druid-myhk-test.yml

@@ -150,4 +150,6 @@ rocketmq:
         group: common-group
         access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
         secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
-
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 3 - 1
fs-service/src/main/resources/application-druid-myhk.yml

@@ -150,4 +150,6 @@ rocketmq:
         group: common-group
         access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
         secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
-
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 29 - 9
fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml

@@ -152,29 +152,49 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectFsCourseAnswerLogsListVONewCount" resultType="java.lang.Long">
-        select count(1) from fs_course_answer_logs cal
-        left join fs_user_course uc on cal.course_id=uc.course_id
+        SELECT
+        count(1)
+        FROM
+        fs_course_answer_logs cal
+        INNER JOIN (
+        SELECT
+        cal_inner.log_id
+        FROM
+        fs_course_answer_logs cal_inner
+        LEFT JOIN fs_user_course uc_inner ON cal_inner.course_id = uc_inner.course_id
         <where>
             <if test="courseId != null">
-                cal.course_id = #{courseId}
+                cal_inner.course_id = #{courseId}
+            </if>
+            <if test="videoId != null and videoId != '' ">
+                AND cal_inner.video_id = #{videoId}
             </if>
             <if test="companyUserId != null">
-                AND cal.company_user_id = #{companyUserId}
+                AND cal_inner.company_user_id = #{companyUserId}
             </if>
             <if test="companyId != null">
-                AND cal.company_id = #{companyId}
+                AND cal_inner.company_id = #{companyId}
             </if>
             <if test="isRight != null">
-                AND is_right = #{isRight}
+                AND cal_inner.is_right = #{isRight}
             </if>
             <if test="project != null">
-                AND uc.project = #{project}
+                AND uc_inner.project = #{project}
             </if>
             <if test="sTime != null and eTime != null">
-                AND cal.create_time BETWEEN #{sTime} AND #{eTime}
+                AND cal_inner.create_time BETWEEN #{sTime} AND #{eTime}
+            </if>
+            <if test="userIds != null and userIds.size() > 0">
+                and cal_inner.user_id in
+                <foreach collection="userIds"  open="(" close=")" separator="," item="userId" index="index">
+                    #{userId}
+                </foreach>
+            </if>
+            <if test="watchLogId != null">
+                AND cal_inner.watch_log_id = #{watchLogId}
             </if>
-
         </where>
+        ) AS paged_ids ON cal.log_id = paged_ids.log_id
     </select>
 
 </mapper>

+ 1 - 0
fs-service/src/main/resources/mapper/course/FsCourseFinishTempMapper.xml

@@ -91,6 +91,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="videoId != null">video_id = #{videoId},</if>
             <if test="companyUserIds != null">company_user_ids = #{companyUserIds},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="isAllCompanyUser != null">is_all_company_user = #{isAllCompanyUser},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
         </trim>
         where id = #{id}

+ 96 - 0
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -726,4 +726,100 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
+
+    <select id="selectFsCourseWatchLogListVOexport" resultType="com.fs.course.vo.FsCourseWatchLogListVO">
+        SELECT
+        l.log_id,
+        l.project AS project,
+        l.user_id,
+        l.log_type,
+        SEC_TO_TIME(l.duration) AS duration,
+        l.camp_period_time,
+        l.finish_time,
+        l.send_type,
+        l.create_time,
+        l.update_time,
+        l.last_heartbeat_time,
+        l.company_id,
+        l.company_user_id,
+        l.course_id,
+        l.video_id,
+        l.qw_user_id,
+        l.qw_external_contact_id,
+        qec.create_time as qec_create_time
+        FROM
+        fs_course_watch_log l LEFT JOIN qw_external_contact qec on l.qw_external_contact_id = qec.id
+        left join fs_user u on u.user_id = l.user_id
+        left join company_user cu on cu.user_id = l.company_user_id
+        <where>
+            <if test ='maps.sendType !=null'>
+                and l.send_type = #{maps.sendType}
+            </if>
+            <if test ='maps.userId !=null'>
+                and l.user_id = #{maps.userId}
+            </if>
+            <if test ='maps.qwExternalContactId !=null'>
+                and l.qw_external_contact_id = #{maps.qwExternalContactId}
+            </if>
+            <if test ='maps.qwUserId !=null'>
+                and l.qw_user_id = #{maps.qwUserId}
+            </if>
+            <if test ='maps.courseId !=null'>
+                and l.course_id = #{maps.courseId}
+            </if>
+            <if test ='maps.videoId !=null'>
+                and l.video_id = #{maps.videoId}
+            </if>
+            <if test ='maps.logType !=null'>
+                and l.log_type = #{maps.logType}
+            </if>
+            <if test ='maps.companyId !=null'>
+                and l.company_id = #{maps.companyId}
+            </if>
+            <if test ='maps.companyUserId !=null'>
+                and l.company_user_id = #{maps.companyUserId}
+            </if>
+            <if test ='maps.companyUserName !=null and maps.companyUserName!=""'>
+                and cu.nick_name  like concat('%', #{maps.companyUserName}, '%')
+            </if>
+            <if test ='maps.nickName !=null and maps.nickName!=""'>
+                and u.nick_name  like concat('%', #{maps.nickName}, '%')
+            </if>
+            <if test ='maps.externalUserName !=null and maps.externalUserName!=""'>
+                and qec.name  like concat('%', #{maps.externalUserName}, '%')
+            </if>
+            <if test= 'maps.qecSTime != null '>
+                and DATE(qec.create_time) &gt;= DATE(#{maps.qecSTime})
+            </if>
+            <if test='maps.qecETime != null '>
+                and DATE(qec.create_time) &lt;= DATE(#{maps.qecETime})
+            </if>
+            <if test= 'maps.sTime != null '>
+                and DATE(l.create_time) &gt;= DATE(#{maps.sTime})
+            </if>
+            <if test='maps.eTime != null '>
+                and DATE(l.create_time) &lt;= DATE(#{maps.eTime})
+            </if>
+            <if test= 'maps.scheduleStartTime != null '>
+                and DATE(l.camp_period_time) &gt;= DATE(#{maps.scheduleStartTime})
+            </if>
+            <if test='maps.scheduleEndTime != null '>
+                and DATE(l.camp_period_time) &lt;= DATE(#{maps.scheduleEndTime})
+            </if>
+            <if test= 'maps.upSTime != null '>
+                and DATE(l.update_time) &gt;= DATE(#{maps.upSTime})
+            </if>
+            <if test='maps.upETime != null '>
+                and DATE(l.update_time) &lt;= DATE(#{maps.upETime})
+            </if>
+            <if test="maps.sopIds != null and maps.sopIds.size() > 0">
+                and l.sop_id in
+                <foreach item="sopId" index="index" collection="maps.sopIds" open="(" separator="," close=")">
+                    #{sopId}
+                </foreach>
+            </if>
+        </where>
+        order by l.finish_time desc,l.update_time desc,l.create_time desc
+    </select>
+
 </mapper>

+ 3 - 1
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -163,6 +163,7 @@
         file_size,
         file_key,
         is_transcode,
+        user_id,
         project_id
         )
         values
@@ -184,6 +185,7 @@
             #{item.fileSize},
             #{item.fileKey},
             #{item.isTranscode},
+            #{item.userId},
             #{item.projectId}
             )
         </foreach>
@@ -363,7 +365,7 @@
         select *  from fs_user_course_video
         where video_id=#{videoId} and is_del = 0
         <if test="userId != null">
-            user_id = #{userId}
+           and user_id = #{userId}
         </if>
     </select>
 

+ 8 - 8
fs-service/src/main/resources/mapper/course/FsUserCourseVideoRedPackageMapper.xml

@@ -145,12 +145,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
-    <select id="selectRedPacketByCompanyId" resultType="com.fs.course.domain.FsUserCourseVideoRedPackage">
-        select * from fs_user_course_video_red_package
-        <where>
-            <if test="videoId != null "> and video_id =#{videoId}</if>
-            <if test="companyId != null "> and company_id = #{companyId}</if>
-            <if test="periodId != null "> and period_id = #{periodId}</if>
-        </where>
-    </select>
+<!--    <select id="selectRedPacketByCompanyId" resultType="com.fs.course.domain.FsUserCourseVideoRedPackage">-->
+<!--        select * from fs_user_course_video_red_package-->
+<!--        <where>-->
+<!--            <if test="videoId != null "> and video_id =#{videoId}</if>-->
+<!--            <if test="companyId != null "> and company_id = #{companyId}</if>-->
+<!--            <if test="periodId != null "> and period_id = #{periodId}</if>-->
+<!--        </where>-->
+<!--    </select>-->
 </mapper>

+ 4 - 4
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -321,7 +321,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             order by
             <choose>
                 <when test = "continueMissCourseSort == 0">
-                    fs_user_company_user.create_time desc
+                    fs_user.create_time desc
                 </when>
                 <when test = "continueMissCourseSort == 1">
                     fs_user.nick_name asc
@@ -477,7 +477,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND u.user_id LIKE CONCAT("%",#{maps.userId},"%")
             </if >
             <if test = "maps.nickname != null and  maps.nickname !='' " >
-                AND u.nickname LIKE CONCAT("%",#{maps.nickname},"%")
+                AND u.nick_name LIKE CONCAT("%",#{maps.nickname},"%")
             </if >
             <if test = "maps.phone != null   and  maps.phone !='' " >
                 AND u.phone LIKE CONCAT("%",#{maps.phone},"%")
@@ -1652,9 +1652,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         LEFT JOIN company_user ON company_user.user_id = ucu.company_user_id
         LEFT JOIN company on company.company_id = company_user.company_id
         <where>
-            1 = 1 and u.nickname is not null
+            1 = 1 and u.nick_name is not null
             <if test = "maps.nickname != null and  maps.nickname !='' " >
-                AND u.nickname LIKE CONCAT("%",#{maps.nickname},"%")
+                AND u.nick_name LIKE CONCAT("%",#{maps.nickname},"%")
             </if >
             <if test = "maps.userId != null and  maps.userId !='' " >
                 AND u.user_id = #{maps.userId}

+ 5 - 0
fs-service/src/main/resources/mapper/sop/QwSopTempVoiceMapper.xml

@@ -119,4 +119,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <delete id="remove">
         delete from qw_sop_temp_voice a where a.temp_id = #{tempId} and a.day_id =#{dayId}
     </delete>
+
+    <select id="selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt" resultType="com.fs.sop.domain.QwSopTempVoice">
+        select qw_user_id qwUserId,voice_url voiceUrl,voice_txt voiceTxt,user_voice_url userVoiceUrl,company_user_id companyUserId,
+               record_type recordType,duration from qw_sop_temp_voice where company_user_id = #{companyUserId} and voice_txt = #{voiceTxt}
+    </select>
 </mapper>

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/CourseProductController.java

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 @Api("拍商品订单接口")
 @RestController
-@RequestMapping(value="/app/courseProduct")
+@RequestMapping(value="/store/app/courseProduct")
 public class CourseProductController extends AppBaseController{
 
     @Autowired

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/CourseProductOrderController.java

@@ -23,7 +23,7 @@ import java.util.List;
 
 @Api("拍商品订单接口")
 @RestController
-@RequestMapping(value="/app/courseProductOrder")
+@RequestMapping(value="/store/app/courseProductOrder")
 public class CourseProductOrderController extends AppBaseController {
 
     @Autowired