Prechádzať zdrojové kódy

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

# Conflicts:
#	fs-service/src/main/resources/application-config-druid-cqtyt.yml
xgb 3 týždňov pred
rodič
commit
105ecdadc4
44 zmenil súbory, kde vykonal 1267 pridanie a 112 odobranie
  1. 3 1
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 7 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  3. 5 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  4. 1 0
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java
  5. 118 5
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempParentController.java
  6. 15 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  7. 5 0
      fs-ipad-task/pom.xml
  8. 4 0
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  9. 52 0
      fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java
  10. 39 6
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  11. 18 12
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  12. 12 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  13. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  14. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  15. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java
  16. 6 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  17. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsVideoResourceMapper.java
  18. 3 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java
  19. 41 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  20. 2 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  21. 3 3
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  22. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  23. 2 2
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  24. 21 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  25. 39 34
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  26. 2 0
      fs-service/src/main/java/com/fs/qw/cache/IQwUserCacheService.java
  27. 15 0
      fs-service/src/main/java/com/fs/qw/cache/impl/QwUserCacheServiceImpl.java
  28. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  29. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  30. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  31. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwWatchLogServiceImpl.java
  32. 89 0
      fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java
  33. 161 0
      fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java
  34. 29 0
      fs-service/src/main/java/com/fs/tag/service/FsTagUpdateService.java
  35. 457 0
      fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java
  36. 2 2
      fs-service/src/main/resources/application-dev.yml
  37. 3 1
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  38. 13 6
      fs-service/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml
  39. 14 0
      fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml
  40. 2 0
      fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml
  41. 3 0
      fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml
  42. 0 17
      fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java
  43. 2 10
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  44. 57 3
      fs-user-app/src/main/java/com/fs/app/param/FsUserEditParam.java

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

@@ -174,8 +174,10 @@ public class IndexStatisticsController {
         if(ObjectUtils.isNull(redPacketCompanyMoney)){
             redPacketCompanyMoney = BigDecimal.ZERO;
         }
-        consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
+        if (consumptionBalanceDataDTO != null){
+            consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
 
+        }
         return R.ok().put("data", consumptionBalanceDataDTO);
     }
 

+ 7 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -197,6 +197,8 @@ public class Task {
     private FsUserOperationLogMapper fsUserOperationLogMapper;
 
     public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
+    @Autowired
+    private IFsStorePaymentService fsStorePaymentService;
 
     /**
      * sop任务token消耗统计
@@ -1531,5 +1533,10 @@ public class Task {
         log.info("定时删除行为轨迹记录 {} 条", deleteCount);
     }
 
+    //同步支付状态
+    public void synchronizePayStatus(){
+        fsStorePaymentService.synchronizePayStatus();
+    }
+
 
 }

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

@@ -516,7 +516,11 @@ public class CompanyUserController extends BaseController {
                                         @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String,Object> params = new HashMap<>();
         params.put("nickName", name);
-
+        //查询多条数据传入公司
+        if (pageSize>=200){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            params.put("companyId", loginUser.getCompany().getCompanyId());
+        }
         PageHelper.startPage(pageNum, pageSize);
         List<OptionsVO> companyUserList = companyUserService.selectCompanyUserListByMap(params);
         return R.ok().put("data", new PageInfo<>(companyUserList));

+ 1 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempController.java

@@ -82,6 +82,7 @@ public class FsCourseFinishTempController extends BaseController
     {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         fsCourseFinishTemp.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTemp.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         return toAjax(fsCourseFinishTempService.insertFsCourseFinishTemp(fsCourseFinishTemp));
     }
 

+ 118 - 5
fs-company/src/main/java/com/fs/company/controller/course/FsCourseFinishTempParentController.java

@@ -1,7 +1,11 @@
 package com.fs.company.controller.course;
 
+import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
+import com.fs.company.service.impl.CompanyDeptServiceImpl;
+import com.fs.company.service.impl.CompanyUserServiceImpl;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -25,7 +29,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 完课模板Controller
- * 
+ *
  * @author 吴树波
  * @date 2025-05-22
  */
@@ -36,6 +40,12 @@ public class FsCourseFinishTempParentController extends BaseController
     @Autowired
     private IFsCourseFinishTempParentService fsCourseFinishTempParentService;
 
+    @Autowired
+    private CompanyDeptServiceImpl companyDeptService;
+
+    @Autowired
+    private CompanyUserServiceImpl companyUserService;
+
     /**
      * 查询完课模板列表
      */
@@ -50,6 +60,55 @@ public class FsCourseFinishTempParentController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询我创建的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myList')")
+    @GetMapping("/myList")
+    public TableDataInfo myList(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        startPage();
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询部门的创建的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:deptList')")
+    @GetMapping("/deptList")
+    public TableDataInfo deptList(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        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);
+        }
+
+        List<Long> userIds = companyUserService.selectCompanyQwUserByDept(deptList, loginUser.getUser().getUserType());
+        if (userIds.isEmpty()){
+            return getDataTable(new ArrayList<>());
+        }
+
+        fsCourseFinishTempParent.setUserIds(userIds);
+
+        startPage();
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        return getDataTable(list);
+    }
+
     /**
      * 导出完课模板列表
      */
@@ -58,15 +117,67 @@ public class FsCourseFinishTempParentController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsCourseFinishTempParent fsCourseFinishTempParent)
     {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+    /**
+     * 导出我的完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myExport')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/myExport")
+    public AjaxResult myExport(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
         ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
         return util.exportExcel(list, "完课模板数据");
     }
 
+    /**
+     * 导出部门完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:myExport')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/deptExport")
+    public AjaxResult deptExport(FsCourseFinishTempParent fsCourseFinishTempParent)
+    {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+
+        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);
+        }
+
+        List<Long> userIds = companyUserService.selectCompanyQwUserByDept(deptList, loginUser.getUser().getUserType());
+        if (userIds.isEmpty()){
+            return AjaxResult.error();
+        }
+
+        fsCourseFinishTempParent.setUserIds(userIds);
+        List<FsCourseFinishTempParent> list = fsCourseFinishTempParentService.selectFsCourseFinishTempParentList(fsCourseFinishTempParent);
+        ExcelUtil<FsCourseFinishTempParent> util = new ExcelUtil<FsCourseFinishTempParent>(FsCourseFinishTempParent.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+
     /**
      * 获取完课模板详细信息
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:query') || @ss.hasPermi('course:courseFinishTempParent:myQuery') || @ss.hasPermi('course:courseFinishTempParent:deptQuery')")
     @GetMapping(value = "/{id}")
     public AjaxResult getInfo(@PathVariable("id") Long id)
     {
@@ -76,20 +187,22 @@ public class FsCourseFinishTempParentController extends BaseController
     /**
      * 新增完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:add') || @ss.hasPermi('course:courseFinishTempParent:myAdd') || @ss.hasPermi('course:courseFinishTempParent:deptAdd')")
     @Log(title = "完课模板", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent){
 
         LoginUser loginUser = SecurityUtils.getLoginUser();
         fsCourseFinishTempParent.setCompanyId(loginUser.getCompany().getCompanyId());
+        fsCourseFinishTempParent.setCreateTime(new Date());
+        fsCourseFinishTempParent.setCreateBy(String.valueOf(loginUser.getUser().getUserId()));
         return toAjax(fsCourseFinishTempParentService.insertFsCourseFinishTempParent(fsCourseFinishTempParent));
     }
 
     /**
      * 修改完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:edit') || @ss.hasPermi('course:courseFinishTempParent:myEdit') || @ss.hasPermi('course:courseFinishTempParent:deptEdit')")
     @Log(title = "完课模板", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsCourseFinishTempParent fsCourseFinishTempParent)
@@ -102,7 +215,7 @@ public class FsCourseFinishTempParentController extends BaseController
     /**
      * 删除完课模板
      */
-    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove')")
+    @PreAuthorize("@ss.hasPermi('course:courseFinishTempParent:remove') || @ss.hasPermi('course:courseFinishTempParent:myRemove') || @ss.hasPermi('course:courseFinishTempParent:deptRemove')")
     @Log(title = "完课模板", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

+ 15 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -136,6 +136,21 @@ public class QwUserController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询我的企微员工列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:user:myStaffList')")
+    @GetMapping("/myStaffList")
+    public TableDataInfo myStaffList(QwUserListParam qwUser) {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        qwUser.setCompanyUserId(loginUser.getUser().getUserId());
+        List<QwUserVO> list = qwUserService.selectQwUserListStaffVO(qwUser);
+        return getDataTable(list);
+    }
+
+
     /**
      * 导出企微员工列表
      * @param qwUser

+ 5 - 0
fs-ipad-task/pom.xml

@@ -105,6 +105,11 @@
             <groupId>com.fs</groupId>
             <artifactId>fs-service</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 4 - 0
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -90,12 +90,14 @@ public class SendMsg {
         if (qwUserList.isEmpty()) {
             List<QwIpadServer> serverList = qwIpadServerMapper.selectList(new QueryWrapper<QwIpadServer>().eq("group_no", groupNo));
             if (serverList.isEmpty()) {
+                log.info("没找到可用的服务器 {} ",serverList);
                 return new ArrayList<>();
             }
             List<Long> serverIds = PubFun.listToNewList(serverList, QwIpadServer::getId);
             List<QwUser> qwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().eq("send_msg_type", 1).eq("server_status", 1).eq("ipad_status", 1).in("server_id", serverIds));
             qwUserList.addAll(qwUsers);
         }
+        log.info("getQwUserList {}",JSON.toJSONString(qwUserList));
         return qwUserList;
     }
 
@@ -169,6 +171,7 @@ public class SendMsg {
         // 获取当前企微待发送记录
         List<QwSopLogs> qwSopLogList = qwSopLogsMapper.selectByQwUserId(qwUser.getId());
         if (qwSopLogList.isEmpty()) {
+            log.info("获取当前企微待发送记录为空");
             return;
         }
         // 获取企微用户
@@ -178,6 +181,7 @@ public class SendMsg {
         long end1 = System.currentTimeMillis();
         // 判断这个企微是否需要发送
         if (!sendServer.isSend(user, parentVo)) {
+            log.info("当前这个企微不需要发送 数据{}",user);
             return;
         }
         log.info("销售:{}, 消息:{}, 耗时: {}, 时间:{}", user.getQwUserName(), qwSopLogList.size(), end1 - start1, qwMap.get(qwUser.getId()));

+ 52 - 0
fs-ipad-task/src/test/java/com/fs/app/task/SendMsgTest.java

@@ -0,0 +1,52 @@
+package com.fs.app.task;
+
+import com.fs.FsIpadTaskApplication;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.tag.service.FsTagUpdateService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = FsIpadTaskApplication.class)
+@RequiredArgsConstructor
+@Slf4j
+public class SendMsgTest {
+    @Autowired
+    private SendMsg sendMsg;
+
+    @Autowired
+    private FsTagUpdateService fsTagUpdateService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Test
+    public void sendMsg2() {
+        sendMsg.sendMsg2();
+    }
+
+    @Test
+    public void testLogWrite(){
+        List<Long> testLogIds = Arrays.asList(177L,180L,182L,183L,184L,185L);
+
+        for(Long logId : testLogIds){
+            FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
+            fsTagUpdateService.onCourseWatchFinishedBatch(Collections.singletonList(fsCourseWatchLog));
+        }
+    }
+
+    @Test
+    public void handleData(){
+        fsTagUpdateService.handleData();
+    }
+}

+ 39 - 6
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -26,6 +26,8 @@ import com.google.gson.JsonParser;
 import com.tencent.wework.Finance;
 import lombok.extern.slf4j.Slf4j;
 import org.json.JSONObject;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
@@ -73,6 +75,9 @@ public class QwDataCallbackService {
     @Autowired
     IQwAutoTagsService qwAutoTagsService;
 
+    @Autowired
+    private RedissonClient redissonClient;
+
     @Autowired
     IQwAutoTagsLogsService qwAutoTagsLogsService;
 
@@ -202,13 +207,41 @@ public class QwDataCallbackService {
                             if(WelcomeCodeList.getLength() > 0) {
                                 WelcomeCode = WelcomeCodeList.item(0).getTextContent();
                             }
-
-                            String qwApiExternal=redisCache.getCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent());
-                            if (StringUtil.strIsNullOrEmpty(qwApiExternal)){
-                                redisCache.setCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent() ,"1",10, TimeUnit.MINUTES);
-                                qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
-
+                            String userId = root.getElementsByTagName("UserID").item(0).getTextContent();
+                            String externalUserId = root.getElementsByTagName("ExternalUserID").item(0).getTextContent();
+                            String cacheKey = "qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId;
+                            String lockKey = "lock:qwApiExternal:" + userId + ":" + corpId + ":" + externalUserId; // 锁Key(Hash类型,加前缀lock:)
+
+                            // 2. 获取 Redisson 分布式锁
+                            RLock lock = redissonClient.getLock(lockKey);
+                            boolean isLocked = false;
+                            try {
+                                // 3. 尝试加锁:最多等待 5 秒,锁自动释放时间 15 分钟
+                                isLocked = lock.tryLock(5, 15, TimeUnit.MINUTES);
+                                if (isLocked) {
+                                    // 4. 加锁成功后,再次检查缓存(避免多线程竞争时重复执行业务)
+                                    String qwApiExternal = redisCache.getCacheObject(cacheKey);
+                                    if (StringUtil.strIsNullOrEmpty(qwApiExternal)) {
+                                        try {
+                                            // 5. 新增用户
+                                            qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
+                                            // 6. 业务逻辑执行成功后,写入 Redis 缓存(有效期 10 分钟)
+                                            redisCache.setCacheObject(cacheKey, "1", 10, TimeUnit.MINUTES);
+                                        } catch (Exception e) {
+                                            // 7. 业务逻辑失败时,删除缓存
+                                            redisCache.deleteObject(cacheKey);
+                                        }
+                                    }
+                                }
+                            } catch (InterruptedException e) {
+                                logger.error("中断异常");
+                            } finally {
+                                // 4. 确保锁最终被释放(只有加锁成功的线程才需要释放)
+                                if (isLocked && lock.isHeldByCurrentThread()) {
+                                    lock.unlock();
+                                }
                             }
+
                             break;
                         case "edit_external_contact":
                             qwExternalContactService.updateQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),corpId);

+ 18 - 12
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -714,23 +714,29 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             ruleTimeVO.setSendType(6);
             ruleTimeVO.setType(2);
-            if (content.getIndex() == 0) {
+            if (groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()) {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
                         null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
-            } else {
-                if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
-                    groupChat.getChatUserList().forEach(user -> {
-                        ruleTimeVO.setSendType(2);
-                        ruleTimeVO.setRemark("客户群催课");
-                        QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
-                        handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
-                                null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
-                    });
-                }
             }
+//            if (content.getIndex() == 0) {
+//                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
+//                handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
+//                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
+//                        null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
+//            } else {
+//                if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
+//                    groupChat.getChatUserList().forEach(user -> {
+//                        ruleTimeVO.setSendType(2);
+//                        ruleTimeVO.setRemark("客户群催课");
+//                        QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
+//                        handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
+//                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
+//                                null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
+//                    });
+//                }
+//            }
         } else {
             // 处理每个 externalContactId
             sopUserLogsInfos.forEach(contactId -> {

+ 12 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -284,4 +284,16 @@ public interface CompanyUserMapper
 
 
     List<String> selectCompanyUserNameByIdsList(@Param("companyUserIDs")List<Long> companyUserID);
+
+    @Select("<script>" +
+            "SELECT user_id FROM company_user WHERE 1=1 " +
+            "<if test=\"companyUserIDs != null and companyUserIDs.size() > 0 and userType != '00'\">" +
+            "   AND dept_id IN " +
+            "   <foreach collection='companyUserIDs' item='item' open='(' separator=',' close=')'>" +
+            "       #{item}" +
+            "   </foreach>" +
+            "</if>" +
+            "</script>")
+    List<Long> selectCompanyQwUserByDept(@Param("companyUserIDs") List<Long> companyUserIDs, @Param("userType") String userType);
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java

@@ -244,4 +244,6 @@ public interface ICompanyUserService {
      * @param batchUserRolesVO 批量修改角色参数
      */
     R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO);
+
+    List<Long> selectCompanyQwUserByDept(List<Long> deptList,String userType);
 }

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

@@ -1061,4 +1061,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         }
         return R.ok("修改成功");
     }
+
+    @Override
+    public List<Long> selectCompanyQwUserByDept(List<Long> deptList,String userType) {
+        return companyUserMapper.selectCompanyQwUserByDept(deptList,userType);
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseFinishTempParent.java

@@ -8,6 +8,8 @@ import lombok.Data;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.EqualsAndHashCode;
 
+import java.util.List;
+
 /**
  * 完课模板对象 fs_course_finish_temp_parent
  *
@@ -45,4 +47,7 @@ public class FsCourseFinishTempParent extends BaseEntity{
     private String companyUserIds;
     @TableField(exist = false)
     private Integer isAllCompanyUser;
+
+    @TableField(exist = false)
+    private List<Long> userIds;
 }

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

@@ -10,6 +10,7 @@ import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -145,7 +146,7 @@ public interface FsUserCourseVideoMapper
     Long selectFsUserCourseVideoByCourseSort(@Param("courseId")Long courseId, @Param("courseSort")Long courseSort);
 
 
-    @Select("select video_id dict_value, title dict_label , thumbnail  dict_img_url from fs_user_course_video where course_id=#{id} and is_del = 0 order by course_sort")
+    @Select("select video_id dict_value, title dict_label  from fs_user_course_video where course_id=#{id} and is_del = 0 order by course_sort")
     List<OptionsVO> selectFsUserCourseVodeAllList(Long id);
 
     @Select({"<script> " +
@@ -255,4 +256,8 @@ public interface FsUserCourseVideoMapper
     List<FsUserCourseVideoAppletVO> getFsUserCourseVideoAppletVOListByIds(@Param("videoIds") List<Long> videoIds);
 
     FsUserCourseVO selectFsUserCourseVideoVoByVideoIdAndCourdeId(@Param("videoId") Long videoId,@Param("courseId") Long courseId);
+
+    @Select("select video_id,is_first,course_sort from fs_user_course_video")
+    @MapKey("videoId")
+    Map<Long, FsUserCourseVideo> selectAllMap();
 }

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

@@ -27,4 +27,6 @@ public interface FsVideoResourceMapper extends BaseMapper<FsVideoResource> {
 
     @Select("select * from fs_video_resource where file_key = #{fileKey} limit 1")
     FsVideoResource selectByFileKey(String fileKey);
+
+    List<FsVideoResource> selectByIds(@Param("ids") long[] ids);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempParentServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import java.util.Date;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -79,6 +80,8 @@ public class FsCourseFinishTempParentServiceImpl extends ServiceImpl<FsCourseFin
             temp.setCompanyId(fsCourseFinishTempParent.getCompanyId());
             temp.setCourseId(e.getCourseId());
             temp.setVideoId(e.getVideoId());
+            temp.setCreateBy(fsCourseFinishTempParent.getCreateBy());
+            temp.setCreateTime(new Date());
             temp.setCompanyUserIds(fsCourseFinishTempParent.getCompanyUserIds());
             temp.setIsAllCompanyUser(fsCourseFinishTempParent.getIsAllCompanyUser());
             temp.setParentId(fsCourseFinishTempParent.getId());

+ 41 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -48,10 +48,13 @@ import com.fs.sop.mapper.SopUserLogsMapper;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.tag.service.FsTagUpdateService;
 import com.hc.openapi.tool.util.StringUtils;
+import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -89,7 +92,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
     @Autowired
-    RedisCache redisCache;
+    private RedisCache redisCache;
     @Autowired
     private IQwExternalContactCacheService qwExternalContactCacheService;
     @Autowired
@@ -130,6 +133,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
 
+    @Autowired
+    private FsTagUpdateService fsTagUpdateService;
+
     /**
      * 查询短链课程看课记录
      *
@@ -367,6 +373,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             String[] parts = key.split(":");
@@ -405,12 +412,17 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
             logs.add(watchLog);
         }
         batchUpdateFsUserCourseWatchLog(logs,100);
+
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
     public Long getFsUserVideoDuration(Long videoId){
         //将视频时长也存到redis
@@ -439,6 +451,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         Collection<String> keys = redisCache.keys("h5wxuser:watch:heartbeat:*");
         LocalDateTime now = LocalDateTime.now();
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             String[] parts = key.split(":");
@@ -463,11 +476,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 redisCache.deleteObject(key);
             }else {
                 watchLog.setLogType(1);
+                watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
         batchUpdateFsUserCourseWatchLog(logs,100);
-
+        if(CollectionUtils.isNotEmpty(watchingLogs)){
+            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+        }
     }
 
     @Override
@@ -490,6 +506,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             Long videoId=null;
@@ -538,6 +555,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
@@ -545,6 +564,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
 
         batchUpdateFsCourseWatchLogIsOpen(logs,100);
+
+        // 完课打标签
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
 
     public void batchUpdateFsCourseWatchLogIsOpen(List<FsCourseWatchLog> logs, int batchSize) {
@@ -766,6 +790,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
 
         List<FsCourseWatchLog> logs = new ArrayList<>();
+        List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
             Long qwUserId=null;
@@ -824,6 +849,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
                     redisCache.deleteObject(key);
+
+                    finishedLogs.add(watchLog);
                 }
             }
             //集合中增加
@@ -831,6 +858,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         }
 
         batchUpdateFsCourseWatchLog(logs,100);
+
+        // 完课打标签
+        if(CollectionUtils.isNotEmpty(finishedLogs)){
+            fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
+        }
     }
 
     @Override
@@ -840,6 +872,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         Collection<String> keys = redisCache.keys("h5user:watch:heartbeat:*");
         LocalDateTime now = LocalDateTime.now();
         List<FsCourseWatchLog> logs = new ArrayList<>();
+
+        List<FsCourseWatchLog> watchingLogs = new ArrayList<>();
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             //取key中数据
@@ -874,10 +908,15 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 redisCache.deleteObject(key);
             }else {
                 watchLog.setLogType(1);
+                watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
         batchUpdateFsCourseWatchLog(logs,100);
+
+        if(CollectionUtils.isNotEmpty(watchingLogs)){
+            fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
+        }
     }
 
     @Override

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

@@ -2471,10 +2471,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     @Override
     public void batchSaveVideo(BatchVideoSvae vo) {
-        List<FsVideoResource> videoResourceList = fsVideoResourceMapper.selectBatchIds(vo.getIds());
-        videoResourceList = videoResourceList.stream()
-                .sorted(Comparator.comparing(FsVideoResource::getSort).thenComparing(FsVideoResource::getId))
-                .collect(Collectors.toList());
+        long[] idArray = vo.getIds().stream().mapToLong(Long::longValue).toArray();
+        List<FsVideoResource> videoResourceList = fsVideoResourceMapper.selectByIds(idArray);
         FsUserCourseVideo param = new FsUserCourseVideo();
         param.setCourseId(vo.getCourseId());
         List<FsUserCourseVideo> videoList = selectFsUserCourseVideoList(param);

+ 3 - 3
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -459,9 +459,9 @@ public class AiHookServiceImpl implements AiHookService {
                 return R.ok();
             }
 
-            //对用户处理的内容做处理
-            String maskedContent = processContent(qwContent);
-            String contentEmj = replaceWxEmo(maskedContent);
+            //对用户处理的内容做处理,去除手机号替换
+            //String maskedContent = processContent(qwContent);
+            String contentEmj = replaceWxEmo(qwContent);
             if(!contentEmj.contains("表情包")){
                 if(!contentEmj.isEmpty()){
                     addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java

@@ -126,4 +126,6 @@ public interface IFsStorePaymentService
     R getWxaCodeByPayment(FsStorePaymentGetWxaCodeParam param);
 
     String payConfirm(String payCode,String tradeNo,String bankTransactionId,String bankSerialNo);
+
+    void synchronizePayStatus();
 }

+ 2 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java

@@ -388,7 +388,7 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         return 1;
     }
 
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public int refundMoney(FsStoreAfterSales fsStoreAfterSales) {
         FsStoreAfterSales order = fsStoreAfterSalesMapper.selectFsStoreAfterSalesById(fsStoreAfterSales.getId());
@@ -473,7 +473,7 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         if (payments != null && payments.size() > 0) {
             FsStorePayment payment = payments.get(0);
             if (reMoney.compareTo(payment.getPayMoney()) > 0) {
-                return 0; //退款金额不能大于实际支付金额
+                throw new CustomException("退款金额不能大于实际支付金额"); //退款金额不能大于实际支付金额
             }
             String json = configService.selectConfigByKey("his.pay");
             if (payment.getPayMode().equals("wx")) {

+ 21 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -7,6 +7,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
@@ -1536,6 +1537,26 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return "SUCCESS";
     }
 
+    @Override
+    public void synchronizePayStatus() {
+        FsStorePayment queryParam = new FsStorePayment();
+        queryParam.setStatus(0);//未支付
+        queryParam.setBeginTime(DateUtils.addDateDays(-1));
+        queryParam.setEndTime(DateUtils.getDate());
+        List<FsStorePayment> list = selectFsStorePaymentList(queryParam);
+        if (list != null && !list.isEmpty()) {
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (FsStorePayment fsStorePayment : list) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    updateFsStorePaymentByDecryptForm(fsStorePayment.getPaymentId());
+                    logger.info("定时任务:同步支付状态,payment_id:{}",fsStorePayment.getPaymentId());
+                });
+                futures.add(future);
+            }
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+        }
+    }
+
     @Override
     public R paymentByWxaCode(FsStorePaymentPayParam param) {
         FsUser user = userMapper.selectFsUserById(param.getUserId());

+ 39 - 34
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -1728,43 +1728,44 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if (order != null && !order.getPaid().equals(0)) {
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                 return "";
-            } else if (type.equals(2)) {
-                //货到付款
-                order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
-                if (!order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())) {
-                    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-                    return "";
-                }
             }
-            //写入公司佣金 当有归属公司时只进行公司分佣
-            if (order.getCompanyId() != null && order.getCompanyId() > 0) {
-                companyService.addCompanyTuiMoney(order);
-                //没有归属公司对个人分佣
-            } else if (order.getIsPackage() != 1 && order.getTuiUserId() != null && order.getTuiUserId() > 0) {
-                //处理佣金 套餐不分佣金
-                FsStoreOrderItemScrm orderItemMap = new FsStoreOrderItemScrm();
-                orderItemMap.setOrderId(order.getId());
-                List<FsStoreOrderItemScrm> items = storeOrderItemService.selectFsStoreOrderItemList(orderItemMap);
-                userService.addTuiMoney(order, items);
-            }
-            //增加用户购买次数
-            userService.incPayCount(order.getUserId());
-            //增加状态
-            orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
-                    OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
-            FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
-            storeOrder.setId(order.getId());
-            storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());
-            storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
-            storeOrder.setPayTime(new Date());
-            fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
-            // 添加订单审核
-            if (storeOrder.getCompanyId() != null) {
-                addOrderAudit(order);
+        }else if (type.equals(2)) {
+            //货到付款
+            order = fsStoreOrderMapper.selectFsStoreOrderById(orderId);
+            if (!order.getStatus().equals(OrderInfoEnum.STATUS_0.getValue())) {
+                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+                return "";
             }
         }
+        //写入公司佣金 当有归属公司时只进行公司分佣
+        if (order.getCompanyId() != null && order.getCompanyId() > 0) {
+            companyService.addCompanyTuiMoney(order);
+            //没有归属公司对个人分佣
+        } else if (order.getIsPackage() != 1 && order.getTuiUserId() != null && order.getTuiUserId() > 0) {
+            //处理佣金 套餐不分佣金
+            FsStoreOrderItemScrm orderItemMap = new FsStoreOrderItemScrm();
+            orderItemMap.setOrderId(order.getId());
+            List<FsStoreOrderItemScrm> items = storeOrderItemService.selectFsStoreOrderItemList(orderItemMap);
+            userService.addTuiMoney(order, items);
+        }
+        //增加用户购买次数
+        userService.incPayCount(order.getUserId());
+        //增加状态
+        orderStatusService.create(order.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),
+                OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());
+        FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
+        storeOrder.setId(order.getId());
+        storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());
+        storeOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
+        storeOrder.setPayTime(new Date());
+        fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+        // 添加订单审核
+        if (storeOrder.getCompanyId() != null) {
+            addOrderAudit(order);
+        }
 
-        return "SUCCESS";
+
+    return "SUCCESS";
 
 
         //非处方直接提交OMS
@@ -3876,6 +3877,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     order.setPayType("2");
                     BigDecimal payMoney=order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate())).divide(new BigDecimal(100));
                     payMoney=new BigDecimal(payMoney.setScale(0, BigDecimal.ROUND_HALF_UP).longValue());
+                    // 如果小程序需要支付的金额小于0.01元,不能走物流代收,让走货到付款 xgb
+                    if (payMoney.compareTo(new BigDecimal("0.01")) < 0) {
+                        return R.error("物流代收计算支付金额为0,不允许选择物流代收");
+                    }
                     order.setPayDelivery(order.getPayPrice().subtract(payMoney));
                     order.setPayMoney(payMoney);
                 }
@@ -4371,7 +4376,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                     order.setPayDelivery(BigDecimal.ZERO);
                 }
                 else if(param.getPayType().equals(2)){
-
+                    // 物流代收
                     order.setPayType("2");
                     BigDecimal payMoney=order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate())).divide(new BigDecimal(100));
                     payMoney=new BigDecimal(payMoney.setScale(0, BigDecimal.ROUND_HALF_UP).longValue());

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

@@ -2,4 +2,6 @@ package com.fs.qw.cache;
 
 public interface IQwUserCacheService {
     String queryQwUserNameByUserId(String userId);
+    String queryCorpIdByQwUserId(Long qwUserId);
+
 }

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

@@ -2,6 +2,7 @@ package com.fs.qw.cache.impl;
 
 import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserService;
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
@@ -14,6 +15,9 @@ import java.util.concurrent.TimeUnit;
 public class QwUserCacheServiceImpl implements IQwUserCacheService {
     @Autowired
     private IQwUserService qwUserService;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
     /**
      * 企微用户名昵称缓存类
      */
@@ -21,6 +25,12 @@ public class QwUserCacheServiceImpl implements IQwUserCacheService {
             .maximumSize(5000)
             .expireAfterWrite(12, TimeUnit.HOURS)
             .build();
+
+    private static final Cache<Long, String> QW_USER_ID_CACHE = Caffeine.newBuilder()
+            .maximumSize(5000)
+            .expireAfterWrite(5, TimeUnit.HOURS)
+            .build();
+
     @Override
     public String queryQwUserNameByUserId(String userId) {
         return QW_USER_CACHE.get(userId,e-> {
@@ -31,4 +41,9 @@ public class QwUserCacheServiceImpl implements IQwUserCacheService {
             return "-";
         });
     }
+
+    @Override
+    public String queryCorpIdByQwUserId(Long qwUserId) {
+        return QW_USER_ID_CACHE.get(qwUserId,e-> qwUserMapper.selectCorpIdById(qwUserId));
+    }
 }

+ 1 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java

@@ -83,4 +83,5 @@ public interface QwTagGroupMapper
 
     List<QwTagGroupListVO> selectQwTagGroups(QwTagGroup qwTagGroup);
 
+    QwTagGroup selectQwTagGroupByName(@Param("tagGroup") String tagGroup, @Param("corpId") String corpId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -19,6 +19,7 @@ import org.apache.ibatis.annotations.Update;
 import org.springframework.stereotype.Repository;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 企微用户Mapper接口
@@ -448,5 +449,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
 
+    @Select("select corp_id from qw_user where id=#{id} limit 1")
+    String selectCorpIdById(@Param("id") Long id);
     List<Long> selectDeptByParentId(@Param("deptId")Long deptId,@Param("corpId") String corpId);
 }

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

@@ -142,7 +142,7 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "ORDER BY\n" +
             "    DATE(qec.create_time) "+
             "</script>"})
-    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAnd(FsCourseWatchLogListParam param);
+    List<QwWatchLogStatisticsListVO> selectQwExtCountByDayAndOther(FsCourseWatchLogListParam param);
     @Select("select \n" +
             "COUNT(CASE WHEN day = 0 and status in (1,2) THEN 1 END) AS firstOnline,\n" +
             "COUNT(CASE WHEN day = 0 and status=2 THEN 1 END) AS firstOver,\n" +

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

@@ -155,7 +155,7 @@ public class QwWatchLogServiceImpl extends ServiceImpl<QwWatchLogMapper, QwWatch
         if (param.getCompanyUserId()!=null){
             param.setIds(companyUserMapper.selectQwUserIdsByCompany(param.getCompanyUserId()));
         }
-        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAnd(param);
+        List<QwWatchLogStatisticsListVO> vos = qwWatchLogMapper.selectQwExtCountByDayAndOther(param);
         for (QwWatchLogStatisticsListVO vo : vos) {
             Long id = vo.getId();
             Date createTime = vo.getCreateTime();

+ 89 - 0
fs-service/src/main/java/com/fs/tag/domain/FsTagUpdateQueue.java

@@ -0,0 +1,89 @@
+package com.fs.tag.domain;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 标签更新对列表
+ */
+@Data
+public class FsTagUpdateQueue {
+    /** id */
+    private Long id;
+
+    /** 看课日志id */
+    private Long courseLogId;
+
+    /** 是否是先导课*/
+    private Integer isFirst;
+
+    /**
+     * 课程排序
+     */
+    private Long sort;
+
+    /** 课程ID */
+    private Long courseId;
+
+    /** 标签id */
+    private String tagId;
+
+    /** 标签名称 */
+    private String tagName;
+
+    /** 操作类型(0 ADD 1 REMOVE) 默认0 */
+    private Integer operationType;
+
+    /** 视频ID */
+    private Long videoId;
+
+    /** 0未处理 1处理中 2成功 3失败 */
+    private Integer status;
+
+    /** 重试次数 */
+    private Integer retryCount;
+
+    /** 企微主体id */
+    private String corpId;
+
+    /** 企微user_id */
+    private Long qwUserId;
+
+    /** 企微外部联系人id */
+    private Long qwExternalContactId;
+    /**
+     * 记录类型
+     * 0 正在看课
+     * 1 已完课
+     */
+    private Integer logType;
+
+    /** 失败原因 */
+    private String failMsg;
+
+    /** 请求参数 */
+    private String payload;
+
+    /** 返回结果 */
+    private String response;
+
+    /** 创建时间 */
+    private LocalDateTime createTime;
+
+    /** 更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 更新人 */
+    private Long updateBy;
+
+    /** 创建人 */
+    private Long createBy;
+
+    /**
+     * 下次执行时间
+     */
+    private LocalDateTime nextExecuteTime;
+
+}

+ 161 - 0
fs-service/src/main/java/com/fs/tag/mapper/FsTagUpdateQueueMapper.java

@@ -0,0 +1,161 @@
+package com.fs.tag.mapper;
+
+import com.fs.tag.domain.FsTagUpdateQueue;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * 标签更新对列表 Mapper
+ */
+@Mapper
+public interface FsTagUpdateQueueMapper {
+
+    @Select("select * from fs_tag_update_queue where retry_count < 3 and status in (0,3) and (next_execute_time < now() or next_execute_time is null) limit 500")
+    List<FsTagUpdateQueue> selectPending();
+
+    @Select("<script>" +
+            "SELECT * FROM fs_tag_update_queue " +
+            "<where>" +
+            "<if test='id != null'> AND id = #{id} </if>" +
+            "<if test='courseLogId != null'> AND course_log_id = #{courseLogId} </if>" +
+            "<if test='courseId != null'> AND course_id = #{courseId} </if>" +
+            "<if test='tagId != null'> AND tag_id = #{tagId} </if>" +
+            "<if test='tagName != null'> AND tag_name = #{tagName} </if>" +
+            "<if test='operationType != null'> AND operation_type = #{operationType} </if>" +
+            "<if test='videoId != null'> AND video_id = #{videoId} </if>" +
+            "<if test='status != null'> AND status = #{status} </if>" +
+            "<if test='retryCount != null'> AND retry_count = #{retryCount} </if>" +
+            "<if test='corpId != null'> AND corp_id = #{corpId} </if>" +
+            "<if test='qwUserId != null'> AND qw_user_id = #{qwUserId} </if>" +
+            "</where>" +
+            "</script>")
+    List<FsTagUpdateQueue> selectByConditions(FsTagUpdateQueue condition);
+
+    @Insert("<script>" +
+            "INSERT INTO fs_tag_update_queue " +
+            "(course_log_id, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, corp_id, qw_user_id, fail_msg, payload, response, create_time, update_time, update_by, create_by,log_type) " +
+            "VALUES " +
+            "<trim prefix='(' suffix=')' suffixOverrides=','>" +
+            "<if test='courseLogId != null'>course_log_id,</if>" +
+            "<if test='courseId != null'>course_id,</if>" +
+            "<if test='tagId != null'>tag_id,</if>" +
+            "<if test='tagName != null'>tag_name,</if>" +
+            "<if test='operationType != null'>operation_type,</if>" +
+            "<if test='videoId != null'>video_id,</if>" +
+            "<if test='status != null'>status,</if>" +
+            "<if test='retryCount != null'>retry_count,</if>" +
+            "<if test='corpId != null'>corp_id,</if>" +
+            "<if test='qwUserId != null'>qw_user_id,</if>" +
+            "<if test='qwExternalContactId != null'>qw_external_contact_id,</if>" +
+            "<if test='failMsg != null'>fail_msg,</if>" +
+            "<if test='payload != null'>payload,</if>" +
+            "<if test='response != null'>response,</if>" +
+            "<if test='createTime != null'>create_time,</if>" +
+            "<if test='updateTime != null'>update_time,</if>" +
+            "<if test='updateBy != null'>update_by,</if>" +
+            "<if test='createBy != null'>create_by,</if>" +
+            "<if test='logType != null'>log_type,</if>" +
+            "</trim>" +
+            "<trim prefix='VALUES (' suffix=')' suffixOverrides=','>" +
+            "<if test='courseLogId != null'>#{courseLogId},</if>" +
+            "<if test='courseId != null'>#{courseId},</if>" +
+            "<if test='tagId != null'>#{tagId},</if>" +
+            "<if test='tagName != null'>#{tagName},</if>" +
+            "<if test='operationType != null'>#{operationType},</if>" +
+            "<if test='videoId != null'>#{videoId},</if>" +
+            "<if test='status != null'>#{status},</if>" +
+            "<if test='retryCount != null'>#{retryCount},</if>" +
+            "<if test='corpId != null'>#{corpId},</if>" +
+            "<if test='qwExternalContactId != null'>#{qwExternalContactId},</if>" +
+            "<if test='qwUserId != null'>#{qwUserId},</if>" +
+            "<if test='failMsg != null'>#{failMsg},</if>" +
+            "<if test='payload != null'>#{payload},</if>" +
+            "<if test='response != null'>#{response},</if>" +
+            "<if test='createTime != null'>#{createTime},</if>" +
+            "<if test='updateTime != null'>#{updateTime},</if>" +
+            "<if test='updateBy != null'>#{updateBy},</if>" +
+            "<if test='createBy != null'>#{createBy},</if>" +
+            "<if test='log_type != null'>#{logType},</if>" +
+            "</trim>" +
+            "</script>")
+    @Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")
+    int insertSelective(FsTagUpdateQueue record);
+
+
+
+    @Insert("<script>" +
+            "INSERT IGNORE INTO fs_tag_update_queue (" +
+            "course_log_id, is_first, course_id, tag_id, tag_name, operation_type, video_id, status, retry_count, " +
+            "corp_id, qw_user_id, qw_external_contact_id, fail_msg, payload, response, create_time, update_time, update_by, create_by, log_type" +
+            ") VALUES " +
+            "<foreach collection='list' item='item' separator=','>" +
+            "(" +
+            "#{item.courseLogId}, #{item.isFirst}, #{item.courseId}, #{item.tagId}, #{item.tagName}, #{item.operationType}, #{item.videoId}, #{item.status}, #{item.retryCount}, " +
+            "#{item.corpId}, #{item.qwUserId}, #{item.qwExternalContactId}, #{item.failMsg}, #{item.payload}, #{item.response}, #{item.createTime}, #{item.updateTime}, #{item.updateBy}, #{item.createBy}, #{item.logType}" +
+            ")" +
+            "</foreach>" +
+            "</script>")
+    int batchInsert(@Param("list") List<FsTagUpdateQueue> list);
+
+
+    @Update("<script>" +
+            "UPDATE fs_tag_update_queue " +
+            "<set>" +
+            "<if test='courseLogId != null'>course_log_id = #{courseLogId},</if>" +
+            "<if test='courseId != null'>course_id = #{courseId},</if>" +
+            "<if test='tagId != null'>tag_id = #{tagId},</if>" +
+            "<if test='tagName != null'>tag_name = #{tagName},</if>" +
+            "<if test='operationType != null'>operation_type = #{operationType},</if>" +
+            "<if test='videoId != null'>video_id = #{videoId},</if>" +
+            "<if test='status != null'>status = #{status},</if>" +
+            "<if test='retryCount != null'>retry_count = #{retryCount},</if>" +
+            "<if test='corpId != null'>corp_id = #{corpId},</if>" +
+            "<if test='qwUserId != null'>qw_user_id = #{qwUserId},</if>" +
+            "<if test='failMsg != null'>fail_msg = #{failMsg},</if>" +
+            "<if test='payload != null'>payload = #{payload},</if>" +
+            "<if test='response != null'>response = #{response},</if>" +
+            "<if test='createTime != null'>create_time = #{createTime},</if>" +
+            "<if test='updateTime != null'>update_time = #{updateTime},</if>" +
+            "<if test='updateBy != null'>update_by = #{updateBy},</if>" +
+            "<if test='createBy != null'>create_by = #{createBy},</if>" +
+            "<if test='logType != null'>log_type = #{logType},</if>" +
+            "</set> " +
+            "WHERE id = #{id}" +
+            "</script>")
+    int updateSelective(FsTagUpdateQueue record);
+
+
+
+    @Update("<script>" +
+            "<foreach collection='list' item='item' separator=';'>" +
+            "UPDATE fs_tag_update_queue" +
+            "<set>" +
+            "<if test='item.courseLogId != null'>course_log_id = #{item.courseLogId},</if>" +
+            "<if test='item.isFirst != null'>is_first = #{item.isFirst},</if>" +
+            "<if test='item.courseId != null'>course_id = #{item.courseId},</if>" +
+            "<if test='item.tagId != null'>tag_id = #{item.tagId},</if>" +
+            "<if test='item.tagName != null'>tag_name = #{item.tagName},</if>" +
+            "<if test='item.operationType != null'>operation_type = #{item.operationType},</if>" +
+            "<if test='item.videoId != null'>video_id = #{item.videoId},</if>" +
+            "<if test='item.status != null'>status = #{item.status},</if>" +
+            "<if test='item.retryCount != null'>retry_count = #{item.retryCount},</if>" +
+            "<if test='item.corpId != null'>corp_id = #{item.corpId},</if>" +
+            "<if test='item.qwUserId != null'>qw_user_id = #{item.qwUserId},</if>" +
+            "<if test='item.qwExternalContactId != null'>qw_external_contact_id = #{item.qwExternalContactId},</if>" +
+            "<if test='item.failMsg != null'>fail_msg = #{item.failMsg},</if>" +
+            "<if test='item.payload != null'>payload = #{item.payload},</if>" +
+            "<if test='item.response != null'>response = #{item.response},</if>" +
+            "<if test='item.createTime != null'>create_time = #{item.createTime},</if>" +
+            "<if test='item.updateTime != null'>update_time = #{item.updateTime},</if>" +
+            "<if test='item.updateBy != null'>update_by = #{item.updateBy},</if>" +
+            "<if test='item.createBy != null'>create_by = #{item.createBy},</if>" +
+            "<if test='item.logType != null'>log_type = #{item.logType},</if>" +
+            "<if test='item.nextExecuteTime != null'>next_execute_time = #{item.nextExecuteTime},</if>" +
+            "</set>" +
+            " WHERE id = #{item.id}" +
+            "</foreach>" +
+            "</script>")
+    int batchUpdateSelective(@Param("list") List<FsTagUpdateQueue> list);
+
+}

+ 29 - 0
fs-service/src/main/java/com/fs/tag/service/FsTagUpdateService.java

@@ -0,0 +1,29 @@
+package com.fs.tag.service;
+
+import com.fs.course.domain.FsCourseWatchLog;
+
+import java.util.List;
+
+/**
+ * 完课打备注服务类
+ */
+public interface FsTagUpdateService {
+
+    /**
+     * 正课正在看课
+     * @param logs 日志
+     */
+    void onCourseWatchingBatch(List<FsCourseWatchLog> logs);
+
+    /**
+     * 正课完成看课
+     * @param logs 日志
+     */
+    void onCourseWatchFinishedBatch(List<FsCourseWatchLog> logs);
+
+
+    /**
+     * 处理数据
+     */
+    void handleData();
+}

+ 457 - 0
fs-service/src/main/java/com/fs/tag/service/impl/FsTagUpdateServiceImpl.java

@@ -0,0 +1,457 @@
+package com.fs.tag.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.mapper.FsUserCourseMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.qw.cache.IQwUserCacheService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwTag;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwTagGroupMapper;
+import com.fs.qw.mapper.QwTagMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.vo.QwTagGroupAddParam;
+import com.fs.qw.vo.QwTagVO;
+import com.fs.qwApi.domain.QwAddTagResult;
+import com.fs.qwApi.domain.QwResult;
+import com.fs.qwApi.domain.inner.InTag;
+import com.fs.qwApi.domain.inner.TagData;
+import com.fs.qwApi.param.QwAddTagParam;
+import com.fs.qwApi.param.QwEditUserTagParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.tag.domain.FsTagUpdateQueue;
+import com.fs.tag.mapper.FsTagUpdateQueueMapper;
+import com.fs.tag.service.FsTagUpdateService;
+import com.google.common.util.concurrent.RateLimiter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.http.util.Asserts;
+import org.checkerframework.checker.signature.qual.PolySignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.PostConstruct;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Slf4j
+@Service("fsTagUpdateService")
+public class FsTagUpdateServiceImpl implements FsTagUpdateService {
+
+
+    @Autowired
+    private FsTagUpdateQueueMapper fsTagUpdateQueueMapper;
+    @Autowired
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    private IQwUserCacheService qwUserCacheService;
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private QwTagMapper qwTagMapper;
+
+    @Autowired
+    private QwTagGroupMapper qwTagGroupMapper;
+
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
+    @Autowired
+    private FsUserCourseMapper fsUserCourseMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Value("${tag.thread.num:5}")
+    private Integer TAG_THREAD_NUM;
+
+    @Value("${tag.rate.limit:30}")
+    private Integer RATE_LIMIT_NUM;
+    /**
+     * 标签组最大数量
+     */
+    private static final Integer TAG_MAX_NUM = 100;
+
+    /**
+     * 接口限流
+     */
+    private RateLimiter rateLimiter;
+
+    /**
+     * 看课自动打标签开关
+     */
+    @Value("${qw.enableAutoTag:0}")
+    private Integer enableAutoTag;
+
+    @PostConstruct
+    public void init(){
+        this.rateLimiter = RateLimiter.create(RATE_LIMIT_NUM);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void onCourseWatchingBatch(List<FsCourseWatchLog> logs) {
+
+        if(ObjectUtil.equal(enableAutoTag,0)){
+            return;
+        }
+        Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
+        // 用户(这里用户用的是企微外部联系人ID)+videoId+status 唯一
+
+        // 先导课看课记录
+        List<FsTagUpdateQueue> batchData = new ArrayList<>();
+        for (FsCourseWatchLog item : logs) {
+            FsTagUpdateQueue task = new FsTagUpdateQueue();
+            task.setCourseId(item.getCourseId());
+            task.setVideoId(item.getVideoId());
+            task.setCourseLogId(item.getLogId());
+            task.setTagId(null);
+            task.setTagName(null);
+
+            task.setLogType(0);
+            task.setOperationType(0);
+            task.setStatus(0);
+            task.setRetryCount(0);
+            task.setQwExternalContactId(item.getQwExternalContactId());
+            task.setQwUserId(item.getQwUserId());
+
+            String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
+            if(StringUtils.isNotNull(corpId)){
+                task.setCorpId(corpId);
+            }
+
+            FsUserCourseVideo fsUserCourseVideo = courseVideoMap.get(task.getVideoId());
+
+            if(ObjectUtils.isNull(fsUserCourseVideo)) {
+                String errorMsg = String.format("该条记录 %d 找不到对应的课堂视频", task.getVideoId());
+                log.error(errorMsg);
+                task.setStatus(3);
+                task.setRetryCount(3);
+                task.setFailMsg(errorMsg);
+                batchData.add(task);
+                continue;
+            }
+            if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
+                task.setIsFirst(1);
+            } else {
+                task.setIsFirst(0);
+            }
+            task.setSort(fsUserCourseVideo.getCourseSort());
+            batchData.add(task);
+        }
+
+
+        fsTagUpdateQueueMapper.batchInsert(batchData);
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    public void onCourseWatchFinishedBatch(List<FsCourseWatchLog> logs) {
+        if(ObjectUtil.equal(enableAutoTag,0)){
+            return;
+        }
+        Map<Long, FsUserCourseVideo> courseVideoMap = fsUserCourseVideoMapper.selectAllMap();
+
+        // 先导课看课记录
+        List<FsTagUpdateQueue> batchData = new ArrayList<>();
+        for (FsCourseWatchLog item : logs) {
+            FsTagUpdateQueue task = new FsTagUpdateQueue();
+            task.setCourseId(item.getCourseId());
+            task.setVideoId(item.getVideoId());
+            task.setCourseLogId(item.getLogId());
+            task.setTagId(null);
+            task.setTagName(null);
+            task.setOperationType(0);
+            task.setStatus(0);
+            task.setRetryCount(0);
+            task.setQwExternalContactId(item.getQwExternalContactId());
+            task.setQwUserId(item.getQwUserId());
+            task.setLogType(1);
+
+            String corpId = qwUserCacheService.queryCorpIdByQwUserId(item.getQwUserId());
+            if(StringUtils.isNotNull(corpId)){
+                task.setCorpId(corpId);
+            }
+
+            FsUserCourseVideo fsUserCourseVideo = courseVideoMap.get(task.getVideoId());
+            if(ObjectUtils.isNull(fsUserCourseVideo)) {
+                String errorMsg = String.format("该条记录 %d 找不到对应的课堂视频", task.getVideoId());
+                log.error(errorMsg);
+                task.setStatus(3);
+                task.setRetryCount(3);
+                task.setFailMsg(errorMsg);
+                batchData.add(task);
+                continue;
+            }
+            if(ObjectUtil.equal(fsUserCourseVideo.getIsFirst(),1)) {
+                task.setIsFirst(1);
+            } else {
+                task.setIsFirst(0);
+            }
+            task.setSort(fsUserCourseVideo.getCourseSort());
+            batchData.add(task);
+        }
+
+
+        fsTagUpdateQueueMapper.batchInsert(batchData);
+    }
+
+    @Override
+    public void handleData() {
+        List<FsTagUpdateQueue> tasks = fsTagUpdateQueueMapper.selectPending();
+        if(CollectionUtils.isEmpty(tasks)){
+            log.info("找不到可处理的任务,已跳过!");
+            return;
+        }
+        ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
+        ExecutorService executor = Executors.newFixedThreadPool(TAG_THREAD_NUM);
+        CountDownLatch latch = new CountDownLatch(tasks.size());
+
+        for (FsTagUpdateQueue task : tasks) {
+            executor.submit(() -> {
+                String lockKey = task.getCourseId() + "_" + task.getVideoId();
+                ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
+                lock.lock();
+                try {
+                    processSingleTask(task);
+                } finally {
+                    lock.unlock();
+                    latch.countDown();
+                }
+            });
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        try {
+            executor.shutdown();
+            if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
+                executor.shutdownNow();
+                // 再次等待确保已经关闭
+                if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                    log.warn("线程池未能在预期时间内关闭");
+                }
+            }
+        } catch (InterruptedException e) {
+            executor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+
+        if(CollectionUtils.isNotEmpty(tasks)){
+            fsTagUpdateQueueMapper.batchUpdateSelective(tasks);
+        }
+
+    }
+
+    private void processSingleTask(FsTagUpdateQueue fsTagUpdateQueue) {
+        try {
+            String courseName = getCourseName(fsTagUpdateQueue.getCourseId());
+            String tagGroupName = String.format("%s-看课标签组(自动创建)",courseName);
+            String tagName01 = String.format("%d上", fsTagUpdateQueue.getVideoId());
+            String tagName02 = String.format("%d完", fsTagUpdateQueue.getVideoId());
+            String corpId = fsTagUpdateQueue.getCorpId();
+
+            // 是否存在该标签
+            QwTagVO qwTagVO = qwTagMapper.selectQwTagByName(tagName01, corpId);
+            if(ObjectUtil.isNull(qwTagVO)){
+                // 是否存在标签组
+                QwTagGroup qwTagGroup = qwTagGroupMapper.selectQwTagGroupByName(tagGroupName, corpId);
+                // 创建标签组 & 标签
+                if(ObjectUtil.isNull(qwTagGroup)) {
+                    log.info("当前标签组不存在 建标签组 & 标签");
+                    QwTagGroupAddParam qwTagGroupAddParam = buildQwTagGroupAndTags(tagGroupName, fsTagUpdateQueue);
+                    qwTagGroupService.insertQwTagGroupParam(qwTagGroupAddParam);
+
+                } else {
+                    // 这个标签组满了吗?
+                    List<QwTagVO> qwTags = getQwTags(qwTagGroup);
+                    // 如果满了,找下一个标签
+                    if(CollectionUtils.isNotEmpty(qwTags) && qwTags.size() >= TAG_MAX_NUM){
+                        log.info("标签组1 {}满了,查找标签组2",tagGroupName);
+                        tagGroupName = String.format("%s-看课标签组2(自动创建)",courseName);
+                        qwTagGroup = qwTagGroupMapper.selectQwTagGroupByName(tagGroupName, corpId);
+                        // 判断是否满了
+                        if(ObjectUtil.isNotNull(qwTagGroup)) {
+                            List<QwTagVO> qwTags1 = getQwTags(qwTagGroup);
+                            if(CollectionUtils.isNotEmpty(qwTags1) && qwTags1.size() >= TAG_MAX_NUM){
+                                log.info("标签组2 {} 满了,查找标签组3",courseName);
+                                tagGroupName = String.format("%s-看课标签组3(自动创建)",courseName);
+                                qwTagGroup = qwTagGroupMapper.selectQwTagGroupByName(tagGroupName, corpId);
+                                List<QwTagVO> qwTags2 = getQwTags(qwTagGroup);
+                                if(CollectionUtils.isNotEmpty(qwTags2) && qwTags2.size() >= TAG_MAX_NUM){
+                                    log.error("最大支持3个标签组! tagGroupName={}",tagGroupName);
+                                    throw new UnsupportedOperationException("最大支持分成3个组!");
+                                }
+                            }
+                        }
+                    }
+                    // 创建标签
+                    log.info("当前标签 {}、{} 在标签组 {}不存在,正在创建标签",tagName01,tagName02,qwTagGroup);
+                    createNotExistLabel(fsTagUpdateQueue, qwTagGroup, tagName01, tagName02);
+                }
+            }
+
+            QwTagVO qwTagVO1 = qwTagMapper.selectQwTagByName(tagName01, corpId);
+            QwTagVO qwTagVO2 = qwTagMapper.selectQwTagByName(tagName02, corpId);
+
+            if(ObjectUtil.isNull(qwTagVO1)){
+                throw new IllegalArgumentException(String.format("标签 %s-%s 未添加成功!",tagName01,corpId));
+            }
+            if(ObjectUtil.isNull(qwTagVO2)){
+                throw new IllegalArgumentException(String.format("标签 %s-%s 未添加成功!",tagName02,corpId));
+            }
+
+
+            // 调用企微API更新标签
+            QwEditUserTagParam qwEditUserTagParam = new QwEditUserTagParam();
+            QwExternalContact qwExternalContact = qwExternalContactMapper
+                    .selectQwExternalContactById(fsTagUpdateQueue.getQwExternalContactId());
+            if(qwExternalContact == null) {
+                throw new IllegalArgumentException(String.format("企微外部联系人 %s 未找到!", fsTagUpdateQueue.getQwExternalContactId()));
+            }
+            qwEditUserTagParam.setUserid(qwExternalContact.getUserId());
+            qwEditUserTagParam.setExternal_userid(qwExternalContact.getExternalUserId());
+
+            rateLimiter.acquire();
+
+            // 如果是看课中
+            if(ObjectUtil.equal(fsTagUpdateQueue.getLogType(),0)){
+                qwEditUserTagParam.setAdd_tag(Collections.singletonList(qwTagVO1.getTagId()));
+            } else {
+                // 已完课
+                qwEditUserTagParam.setAdd_tag(Collections.singletonList(qwTagVO2.getTagId()));
+            }
+
+            QwResult qwResult = qwApiService.editUserTag(qwEditUserTagParam, fsTagUpdateQueue.getCorpId());
+            fsTagUpdateQueue.setPayload(JSON.toJSONString(qwEditUserTagParam));
+            fsTagUpdateQueue.setResponse(JSON.toJSONString(qwResult));
+            fsTagUpdateQueue.setTagName(tagGroupName);
+            fsTagUpdateQueue.setTagId(qwTagVO1.getGroupId());
+            // 打标签成功
+            if(ObjectUtil.equal(qwResult.getErrcode(),0)) {
+                fsTagUpdateQueue.setStatus(2);
+                fsTagUpdateQueue.setRetryCount(0);
+            } else {
+                throw new RuntimeException(String.format("打标签失败 原因: %s", JSON.toJSONString(qwResult)));
+            }
+        } catch (Exception e){
+            fsTagUpdateQueue.setStatus(3);
+            fsTagUpdateQueue.setRetryCount(fsTagUpdateQueue.getRetryCount()+1);
+            fsTagUpdateQueue.setFailMsg(ExceptionUtils.getFullStackTrace(e));
+            fsTagUpdateQueue.setNextExecuteTime(LocalDateTime.now().plusHours(1));
+        }
+    }
+
+    private void createNotExistLabel(FsTagUpdateQueue fsTagUpdateQueue, QwTagGroup qwTagGroup, String tagName01, String tagName02) {
+        QwAddTagParam qwAddTagParam = buildQwTagGroupAndTags(qwTagGroup, tagName01, tagName02);
+        QwAddTagResult qwAddTagResult = qwApiService.addTag(qwAddTagParam, fsTagUpdateQueue.getCorpId());
+        if(ObjectUtil.equal(qwAddTagResult.getErrcode(),0)){
+            List<InTag> addTag = qwAddTagResult.getTag_group().getTag();
+            if (CollectionUtils.isNotEmpty(addTag)) {
+                for (InTag inTag : addTag) {
+                    QwTag addQwTag = new QwTag();
+                    addQwTag.setGroupId(qwTagGroup.getGroupId());
+                    addQwTag.setTagId(inTag.getId());
+                    addQwTag.setOrder(inTag.getOrder());
+                    addQwTag.setName(inTag.getName());
+                    addQwTag.setCorpId(qwTagGroup.getCorpId());
+                    addQwTag.setCreateTime(new Date(inTag.getCreate_time()));
+                    addQwTag.setTagFrom(1);
+                    qwTagMapper.insertQwTag(addQwTag);
+                }
+            }
+            log.info("创建标签完成 {}",JSON.toJSON(qwAddTagResult));
+        } else {
+            throw new RuntimeException(String.format("创建标签失败!%s",JSON.toJSONString(qwAddTagResult)));
+        }
+    }
+
+    private List<QwTagVO> getQwTags(QwTagGroup qwTagGroup) {
+        QwTag qwTag = new QwTag();
+        qwTag.setGroupId(qwTagGroup.getGroupId());
+        qwTag.setCompanyId(qwTagGroup.getCompanyId());
+        List<QwTagVO> qwTags = qwTagMapper.selectQwTagListVO(qwTag);
+        return qwTags;
+    }
+
+    private QwAddTagParam buildQwTagGroupAndTags(QwTagGroup qwTagGroup, String tagName01, String tagName02) {
+        QwAddTagParam qwTagGroupAddParam = new QwAddTagParam();
+        qwTagGroupAddParam.setGroup_name(qwTagGroup.getName());
+        qwTagGroupAddParam.setOrder(qwTagGroup.getOrder());
+        qwTagGroupAddParam.setStrategy_id(1);
+        qwTagGroupAddParam.setGroup_id(qwTagGroup.getGroupId());
+        List<TagData> tags = new ArrayList<>();
+
+        TagData qwTag1 = new TagData();
+        qwTag1.setName(tagName01);
+        qwTag1.setOrder(0);
+        tags.add(qwTag1);
+
+        TagData qwTag2 = new TagData();
+        qwTag2.setName(tagName02);
+        qwTag2.setOrder(0);
+        tags.add(qwTag2);
+
+        qwTagGroupAddParam.setTag(tags);
+
+        return qwTagGroupAddParam;
+    }
+
+
+    private QwTagGroupAddParam buildQwTagGroupAndTags(String tagGroupName, FsTagUpdateQueue fsTagUpdateQueue) {
+        QwTagGroupAddParam qwTagGroupAddParam = new QwTagGroupAddParam();
+        qwTagGroupAddParam.setCorpId(fsTagUpdateQueue.getCorpId());
+        qwTagGroupAddParam.setName(tagGroupName);
+        qwTagGroupAddParam.setOrder(0);
+
+
+        List<QwTag> tags = new ArrayList<>();
+        QwTag qwTag = new QwTag();
+        qwTag.setName(String.format("%d上",fsTagUpdateQueue.getVideoId()));
+        qwTag.setTagFrom(1);
+        qwTag.setOrder(0);
+        tags.add(qwTag);
+
+        QwTag qwTagFinish = new QwTag();
+        qwTagFinish.setName(String.format("%d完",fsTagUpdateQueue.getVideoId()));
+        qwTagFinish.setTagFrom(1);
+        qwTagFinish.setOrder(0);
+        tags.add(qwTagFinish);
+
+        qwTagGroupAddParam.setTag(tags);
+        return qwTagGroupAddParam;
+    }
+
+    /**
+     * 获取课程名称
+     * @param courseId 课程id
+     * @return 课程名称
+     */
+    private String getCourseName(Long courseId){
+        FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(courseId);
+        if(ObjectUtil.isNotNull(course)){
+            return course.getCourseName();
+        }
+        return null;
+    }
+}

+ 2 - 2
fs-service/src/main/resources/application-dev.yml

@@ -43,7 +43,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
                     username: Rtroot
                     password: Rtroot
                 # 初始连接数
@@ -176,7 +176,7 @@ cloud_host:
 headerImg:
     imgUrl: https://hzyy.obs.cn-north-4.myhuaweicloud.com/fs/20250616/1750067609692.png
 ipad:
-    ipadUrl: http://139.159.133.223:8667
+    ipadUrl: http://admin.test.ylrztop.com/ipad
     aiApi: http://1.95.196.10:3000/api
 wx_miniapp_temp:
     pay_order_temp_id:

+ 3 - 1
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -431,7 +431,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <sql id="selectUserVo">
-        select u.user_id,u.company_id,u.qw_user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time,u.id_card, u.remark,u.user_type,u.open_id,u.qr_code_weixin,u.qr_code_wecom,u.jpush_id,u.domain,u.is_audit,u.address_id,
+        select u.user_id,u.company_id,u.qw_user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar,
+               u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by,
+               u.create_time,u.id_card, u.remark,u.user_type,u.open_id,u.qr_code_weixin,u.qr_code_wecom,u.jpush_id,u.domain,u.is_audit,u.address_id,
                d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
                r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,
                u.is_need_register_member, u.is_allowed_all_register,u.doctor_id

+ 13 - 6
fs-service/src/main/resources/mapper/course/FsCourseFinishTempParentMapper.xml

@@ -3,7 +3,7 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.course.mapper.FsCourseFinishTempParentMapper">
-    
+
     <resultMap type="FsCourseFinishTempParent" id="FsCourseFinishTempParentResult">
         <result property="id"    column="id"    />
         <result property="name"    column="name"    />
@@ -22,18 +22,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <select id="selectFsCourseFinishTempParentList" parameterType="FsCourseFinishTempParent" resultMap="FsCourseFinishTempParentResult">
         <include refid="selectFsCourseFinishTempParentVo"/>
-        <where>  
+        <where>
             <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
             <if test="courseId != null "> and course_id = #{courseId}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="createBy != null "> and create_by = #{createBy}</if>
+            <if test="userIds != null and !userIds.isEmpty() ">
+                AND create_by IN
+                 <foreach collection='userIds' item='item' open='(' separator=',' close=')'>
+                 #{item}
+                </foreach>
+             </if>
         </where>
     </select>
-    
+
     <select id="selectFsCourseFinishTempParentById" parameterType="Long" resultMap="FsCourseFinishTempParentResult">
         <include refid="selectFsCourseFinishTempParentVo"/>
         where id = #{id}
     </select>
-        
+
     <insert id="insertFsCourseFinishTempParent" parameterType="FsCourseFinishTempParent" useGeneratedKeys="true" keyProperty="id">
         insert into fs_course_finish_temp_parent
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -75,9 +82,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteFsCourseFinishTempParentByIds" parameterType="String">
-        delete from fs_course_finish_temp_parent where id in 
+        delete from fs_course_finish_temp_parent where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
-</mapper>
+</mapper>

+ 14 - 0
fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml

@@ -26,4 +26,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
         order by rr.sort,rr.id desc
     </select>
+
+    <select id="selectByIds" parameterType="String" resultType="com.fs.course.domain.FsVideoResource">
+        SELECT *
+        FROM fs_video_resource
+        WHERE id IN
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+        ORDER BY FIELD(id,
+        <foreach collection="ids" item="id" separator=",">
+            #{id}
+        </foreach>
+        )
+    </select>
 </mapper>

+ 2 - 0
fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml

@@ -58,6 +58,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="storeId != null "> and store_id = #{storeId}</if>
             <if test="businessCode != null "> and business_code = #{businessCode}</if>
             <if test="appId != null and appId !=''"> and app_id = #{appId}</if>
+            <if test="beginTime != null and beginTime !=''"> and create_time >= #{beginTime}</if>
+            <if test="endTime != null and endTime !=''"> and create_time &lt; #{endTime}</if>
         </where>
     </select>
 

+ 3 - 0
fs-service/src/main/resources/mapper/qw/QwTagGroupMapper.xml

@@ -108,4 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      </where>
         )
     </select>
+    <select id="selectQwTagGroupByName" resultType="com.fs.qw.domain.QwTagGroup">
+        select * from qw_tag_group where name=#{tagGroup} and corp_id=#{corpId} limit 1
+    </select>
 </mapper>

+ 0 - 17
fs-service/src/test/java/com/fs/his/service/impl/FsUserServiceImplTest.java

@@ -1,17 +0,0 @@
-package com.fs.his.service.impl;
-
-
-import com.fs.tulin.service.ITulinInfoSyncLogService;
-
-import javax.annotation.Resource;
-
-class FsUserServiceImplTest {
-
-    @Resource
-    private ITulinInfoSyncLogService tulinInfoSyncLogService;
-
-
-    void selectFsUserPageListNew() {
-        tulinInfoSyncLogService.syncInfo();
-    }
-}

+ 2 - 10
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -4,7 +4,6 @@ package com.fs.app.controller;
 import cn.hutool.core.lang.Validator;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.app.annotation.Login;
-import com.fs.app.param.FriendsSearchParam;
 import com.fs.app.param.FsDoctorRegisterParam;
 import com.fs.app.param.FsUserEditParam;
 import com.fs.common.core.domain.R;
@@ -12,10 +11,8 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.sign.Md5Utils;
-import com.fs.company.domain.CompanyUser;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.his.domain.FsDoctor;
-import com.fs.his.domain.FsPackage;
 import com.fs.his.domain.FsUser;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
@@ -26,8 +23,6 @@ import com.fs.his.service.IFsPackageService;
 import com.fs.his.service.IFsUserCouponService;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.PhoneUtil;
-import com.fs.his.utils.qrcode.QRCodeUtils;
-import com.fs.his.vo.FsDoctorListUVO;
 import com.fs.his.vo.FsUserCouponCountUVO;
 import com.fs.his.vo.FsUserCouponListUVO;
 import com.fs.his.vo.UserVo;
@@ -36,8 +31,6 @@ import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
-import com.google.zxing.WriterException;
-import io.jsonwebtoken.lang.Assert;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -45,7 +38,6 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.parameters.P;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -62,7 +54,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static com.fs.common.utils.SecurityUtils.getUserId;
 import static com.fs.his.utils.PhoneUtil.decryptPhoneMk;
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
 
@@ -219,7 +210,8 @@ public class UserController extends  AppBaseController {
         FsUser user=new FsUser();
         user.setUserId(Long.parseLong(getUserId()));
         user.setAvatar(param.getAvatar());
-        user.setNickName(param.getNickname());
+        // 前端传了nickname 和 nickName 但是只有nickName是正确的
+        user.setNickName(StringUtils.isNotEmpty(param.getNickName())?param.getNickName():param.getNickname());
         if (ObjectUtils.isNotEmpty(param.getIsWeixinAuth())){
             user.setIsWeixinAuth(param.getIsWeixinAuth());
         }

+ 57 - 3
fs-user-app/src/main/java/com/fs/app/param/FsUserEditParam.java

@@ -9,17 +9,71 @@ import lombok.Setter;
 import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
-@Data
+
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class FsUserEditParam implements Serializable {
-
     @NotNull(message = "用户昵称不能为空!")
-    @JsonAlias({"nickname", "nickName"})
+    @JsonAlias("nickname")
     private String nickname;
 
     //    @NotBlank(message = "用户头像不能为空!")
     private String avatar;
 
+    @JsonAlias("nickName")
+    private String nickName;
+
     private Long userId;
     private Integer isWeixinAuth;
+
+
+    public @NotNull(message = "用户昵称不能为空!") String getNickname() {
+        return nickname;
+    }
+
+    public void setNickname(@NotNull(message = "用户昵称不能为空!") String nickname) {
+        this.nickname = nickname;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getNickName() {
+        return nickName;
+    }
+
+    public void setNickName(String nickName) {
+        this.nickName = nickName;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Integer getIsWeixinAuth() {
+        return isWeixinAuth;
+    }
+
+    public void setIsWeixinAuth(Integer isWeixinAuth) {
+        this.isWeixinAuth = isWeixinAuth;
+    }
+
+    @Override
+    public String toString() {
+        return "FsUserEditParam{" +
+                "nickname='" + nickname + '\'' +
+                ", avatar='" + avatar + '\'' +
+                ", nickName='" + nickName + '\'' +
+                ", userId=" + userId +
+                ", isWeixinAuth=" + isWeixinAuth +
+                '}';
+    }
 }