瀏覽代碼

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java into openIm

# Conflicts:
#	fs-admin/src/main/java/com/fs/his/task/Task.java
#	fs-company/src/main/resources/application.yml
#	fs-doctor-app/src/main/resources/application.yml
#	fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java
#	fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
#	fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
#	fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java
#	fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
#	fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
#	fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
#	fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
#	fs-service/src/main/resources/application-druid-hcl.yml
#	fs-service/src/main/resources/application-druid-hdt.yml
#	fs-service/src/main/resources/application-druid-nmgyt.yml
#	fs-service/src/main/resources/application-druid-sxjz.yml
#	fs-user-app/src/main/resources/application.yml
15376779826 2 天之前
父節點
當前提交
787d176f56
共有 100 個文件被更改,包括 3395 次插入595 次删除
  1. 11 6
      README.md
  2. 27 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java
  3. 41 0
      fs-admin/src/main/java/com/fs/course/controller/CourseRedPacketStatisticsController.java
  4. 17 6
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  5. 21 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  6. 19 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  7. 14 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  8. 37 0
      fs-admin/src/main/java/com/fs/course/task/RedPacketLogsTask.java
  9. 26 4
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  10. 20 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  11. 9 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  12. 24 8
      fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java
  13. 71 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java
  14. 41 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java
  15. 6 2
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  16. 208 262
      fs-admin/src/main/java/com/fs/his/task/Task.java
  17. 50 0
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactTransferCompanyAuditController.java
  18. 154 0
      fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java
  19. 1 1
      fs-admin/src/main/java/com/fs/qw/controller/QwSopController.java
  20. 34 0
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  21. 1 1
      fs-admin/src/main/resources/application.yml
  22. 25 0
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  23. 10 0
      fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java
  24. 2 0
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  25. 6 4
      fs-company/src/main/java/com/fs/company/controller/company/CompanyController.java
  26. 43 9
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  27. 64 6
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  28. 167 3
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  29. 56 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferCompanyAuditController.java
  30. 86 4
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopController.java
  31. 16 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  32. 29 0
      fs-company/src/main/java/com/fs/company/utils/QwStatusEnum.java
  33. 11 20
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java
  34. 2 2
      fs-company/src/main/resources/application.yml
  35. 2 2
      fs-doctor-app/src/main/resources/application.yml
  36. 69 20
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  37. 29 1
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  38. 42 5
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  39. 11 0
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  40. 10 0
      fs-qw-task/src/main/java/com/fs/app/taskService/SyncQwExternalContactService.java
  41. 271 35
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java
  42. 277 4
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  43. 78 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SyncQwExternalContactServiceImpl.java
  44. 8 6
      fs-qw-voice/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  45. 5 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java
  46. 11 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java
  47. 165 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwSopController.java
  48. 5 1
      fs-qwhook/src/main/java/com/fs/app/controller/QwSopController.java
  49. 11 2
      fs-qwhook/src/main/java/com/fs/app/controller/QwUserController.java
  50. 1 1
      fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  51. 5 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  52. 21 8
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserRoleMapper.java
  53. 8 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  54. 6 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  55. 42 94
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  56. 59 4
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  57. 21 0
      fs-service/src/main/java/com/fs/company/vo/BatchUserRolesVO.java
  58. 2 1
      fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java
  59. 4 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  60. 5 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  61. 83 0
      fs-service/src/main/java/com/fs/course/domain/FsBlackTalent.java
  62. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsUserTalent.java
  63. 36 0
      fs-service/src/main/java/com/fs/course/dto/CourseRedPacketStatisticsDTO.java
  64. 76 0
      fs-service/src/main/java/com/fs/course/mapper/FsBlackTalentMapper.java
  65. 5 3
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  66. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  67. 6 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  68. 5 2
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  69. 4 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  70. 13 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java
  71. 11 5
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  72. 11 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentFollowMapper.java
  73. 5 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentMapper.java
  74. 25 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java
  75. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoTagsMapper.java
  76. 38 0
      fs-service/src/main/java/com/fs/course/param/CourseRedPacketStatisticsParam.java
  77. 14 0
      fs-service/src/main/java/com/fs/course/param/FsBlackTalentAuditParam.java
  78. 9 1
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  79. 10 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  80. 3 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java
  81. 10 0
      fs-service/src/main/java/com/fs/course/param/FsUserTalentFansParam.java
  82. 11 0
      fs-service/src/main/java/com/fs/course/service/CourseRedPacketStatisticsService.java
  83. 77 0
      fs-service/src/main/java/com/fs/course/service/IFsBlackTalentService.java
  84. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java
  85. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  86. 0 7
      fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java
  87. 4 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java
  88. 5 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java
  89. 1 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  90. 6 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  91. 11 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentFollowService.java
  92. 10 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentService.java
  93. 12 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java
  94. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoTagsService.java
  95. 28 0
      fs-service/src/main/java/com/fs/course/service/impl/CourseRedPacketStatisticsServiceImpl.java
  96. 126 0
      fs-service/src/main/java/com/fs/course/service/impl/FsBlackTalentServiceImpl.java
  97. 102 16
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java
  98. 5 6
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  99. 71 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  100. 0 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

+ 11 - 6
README.md

@@ -3,21 +3,26 @@
 #### 介绍
 问诊平台
 
-#### 软件架构
-软件架构说明
+#### 软件模块说明
+
+| 模块名称         | 模块描述         | 对应前端项目                  |
+|--------------|--------------|-------------------------|
+| fs-admin     | 总后台服务        | ylrz_his_scrm_adminUI   |
+| fs-company   | 销售端          | ylrz_his_scrm_companyUI |
+| fs-user-app  | 微信小程序端       | 对应某个微信小程序(前端蒲瑶清楚)       |
+| fs-framework | 主要依赖包,核心包    | /                       |
+| fs-service   | 所有的链接配置文件都在里面 | /                       |
 
 
 #### 安装教程
 
 1.  xxxx
 2.  xxxx
-3.  xxxx
 
 #### 使用说明
 
-1.  xxxx
-2.  xxxx
-3.  xxxx
+1.  注意调整Memory的大小,以及堆内存大小
+2.  对于maven仓库缺少的jar包引用,需要拷贝现有的文件(拷贝后依然出现错误,直接删除错误包下的_remote.repositories文件)。
 
 #### 参与贡献
 

+ 27 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java

@@ -19,9 +19,14 @@ import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.service.ICrmCustomerVisitService;
 import com.fs.crm.vo.CrmCustomerStatisticsVO;
 import com.fs.crm.vo.CrmCustomerVisitStatisticsVO;
+import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
+import com.fs.his.dto.FsStoreOrderAmountStatsQueryDto;
 import com.fs.his.service.IFsStoreAfterSalesService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.vo.FsStoreOrderAmountScrmStatsVo;
+import com.fs.his.vo.FsStoreOrderAmountStatsVo;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -67,6 +72,10 @@ public class CompanyStatisticsController extends BaseController
     private ICrmCustomerService crmCustomerService;
     @Autowired
     private ICrmCustomerVisitService crmCustomerVisitService;
+
+    //app商城订单接口Service
+    @Autowired
+    private IFsStoreOrderScrmService fsStoreOrderScrmService;
     @GetMapping("/storeOrder")
     public R storeOrder(FsStoreStatisticsParam param)
     {
@@ -724,4 +733,22 @@ public class CompanyStatisticsController extends BaseController
         return util.exportExcel(qwIpadTotalVos, "visit");
     }
 
+    /**
+     * 获取互联网医院订单统计数据
+     * */
+    @GetMapping("/hisOrderCountStats")
+    public AjaxResult getHisOrderCount(FsStoreOrderAmountStatsQueryDto statsQueryDto){
+        FsStoreOrderAmountStatsVo fsStoreOrderAmountStatsVo = storeOrderService.selectFsStoreOrderAmountStats(statsQueryDto);
+        return AjaxResult.success(fsStoreOrderAmountStatsVo);
+    }
+
+    /**
+     * 获取App商城订单统计数据
+     * */
+    @GetMapping("/appOrderCountStats")
+    public AjaxResult getAppOrderCount(FsStoreOrderAmountScrmStatsQueryDto statsQueryDto){
+        FsStoreOrderAmountScrmStatsVo scrmStatsVo = fsStoreOrderScrmService.selectFsStoreOrderAmountScrmStats(statsQueryDto);
+        return AjaxResult.success(scrmStatsVo);
+    }
+
 }

+ 41 - 0
fs-admin/src/main/java/com/fs/course/controller/CourseRedPacketStatisticsController.java

@@ -0,0 +1,41 @@
+package com.fs.course.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+import com.fs.course.service.CourseRedPacketStatisticsService;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiModelProperty;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@RestController
+@RequestMapping("/course/courseRedPacketStatistics")
+public class CourseRedPacketStatisticsController extends BaseController {
+
+    @Autowired
+    private CourseRedPacketStatisticsService courseRedPacketStatisticsService;
+
+    @ApiModelProperty("看客红包发送统计")
+    @GetMapping("/list")
+    public R list(CourseRedPacketStatisticsParam param) {
+
+        startPage();
+        // 看客红包发送统计
+        List<CourseRedPacketStatisticsDTO> list = courseRedPacketStatisticsService.statistics(param);
+
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
+}

+ 17 - 6
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java

@@ -5,8 +5,11 @@ import java.util.List;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.param.FsCourseOverParam;
 import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
@@ -100,13 +103,10 @@ public class FsCourseWatchLogController extends BaseController
         if (param.getSTime()==null||param.getETime()==null){
             throw new CustomException("必须选择开始时间和结束时间!");
         }
+        startPage();
         List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONew(param);
-        TableDataInfo rspData = new TableDataInfo();
-        rspData.setCode(HttpStatus.SUCCESS);
-        rspData.setMsg("查询成功");
-        rspData.setRows(list);
-        rspData.setTotal(fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVONewCount(param));
-        return rspData;
+
+        return getDataTable(list);
     }
 
     /**
@@ -164,4 +164,15 @@ public class FsCourseWatchLogController extends BaseController
     {
         return toAjax(fsCourseWatchLogService.deleteFsCourseWatchLogByLogIds(logIds));
     }
+
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
 }

+ 21 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -20,6 +20,7 @@ import com.fs.course.param.BatchVideoSvae;
 import com.fs.course.param.CourseVideoUpdates;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.vo.FsUserCourseVideoChooseVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.vo.OptionsVO;
 import com.fs.system.service.ISysConfigService;
@@ -222,4 +223,24 @@ public class FsUserCourseVideoController extends BaseController
         List<OptionsVO> periodList = fsUserCourseVideoService.selectVideoListByMap(params);
         return R.ok().put("data", new PageInfo<>(periodList));
     }
+
+    @GetMapping("/getChooseCourseVideoList")
+    public R getChooseCourseVideoList(@RequestParam(required = false) Long courseId,
+                                      @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                      @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        Map<String,Object> params = new HashMap<>();
+        params.put("courseId", courseId);
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            params.put("userId", userId);
+        }
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCourseVideoChooseVO> list = fsUserCourseVideoService.getChooseCourseVideoListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
 }

+ 19 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java

@@ -180,8 +180,11 @@ public class FsUserVideoController extends BaseController
         return toAjax(fsUserVideoService.updateFsUserVideoIsShow(videoIds,0));
     }
 
-    private static final String VIDEO_UPLOAD_DIR = "C:\\fs\\uploadPath\\userVideo\\video";  // 上传目录
-    private static final String FRAME_OUTPUT_DIR = "C:\\fs\\uploadPath\\userVideo\\frame";  // 输出帧的目录
+//    private static final String VIDEO_UPLOAD_DIR = "C:\\fs\\uploadPath\\userVideo\\video";  // 上传目录
+//    private static final String FRAME_OUTPUT_DIR = "C:\\fs\\uploadPath\\userVideo\\frame";  // 输出帧的目录
+    // 改为使用系统临时目录或相对路径
+    private static final String VIDEO_UPLOAD_DIR = System.getProperty("java.io.tmpdir") + File.separator + "fs_upload" + File.separator + "userVideo" + File.separator + "video";
+    private static final String FRAME_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator + "fs_upload" + File.separator + "userVideo" + File.separator + "frame";
 
 
     /**
@@ -198,16 +201,19 @@ public class FsUserVideoController extends BaseController
 
         // 保存上传的视频文件
         String videoFileName = System.currentTimeMillis() + "_" + UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
+        createDir(VIDEO_UPLOAD_DIR);
         File videoFile = new File(VIDEO_UPLOAD_DIR, videoFileName);
         try {
             file.transferTo(videoFile);
         } catch (IOException e) {
+            e.printStackTrace();
             // 记录错误日志
             return R.error("获取封面失败");
         }
 
         // 提取视频第一帧
         String frameFileName = FilenameUtils.removeExtension(videoFileName) + "_frame.jpg";
+        createDir(FRAME_OUTPUT_DIR);
         File frameFile = new File(FRAME_OUTPUT_DIR, frameFileName);
         try {
             extractFirstFrame(videoFile.getAbsolutePath(), frameFile.getAbsolutePath());
@@ -268,6 +274,17 @@ public class FsUserVideoController extends BaseController
         }
     }
 
+    private void createDir(String path){
+        File videoUploadDir = new File(path);
+        if (!videoUploadDir.exists()) {
+            boolean created = videoUploadDir.mkdirs();
+            if (!created) {
+                log.error("创建视频上传目录失败: {}", path);
+            }
+            log.info("创建视频上传目录: {}", path);
+        }
+    }
+
     @PostMapping("/updateUrl")
     public R updateUrl()
     {

+ 14 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -6,12 +6,15 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.param.FsCourseOverParam;
 import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
 import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
@@ -150,4 +153,15 @@ public class QwFsCourseWatchLogController extends BaseController
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectListBytrainingCampId(param);
         return getDataTable(list);
     }
+
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
 }

+ 37 - 0
fs-admin/src/main/java/com/fs/course/task/RedPacketLogsTask.java

@@ -0,0 +1,37 @@
+package com.fs.course.task;
+
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.course.service.IFsCourseRedPacketLogService;
+import com.fs.course.service.impl.FsCourseRedPacketLogServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * @description: 微信红包转账回调部分没有接收到,主动自己去查询
+ * @author: Xgb
+ * @createDate: 2025/10/15
+ * @version: 1.0
+ */
+@Component("redPacketLogsTask")
+public class RedPacketLogsTask {
+
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    /**
+     * @Description: 查询微信红包转账结果 每10分钟查询上一个10分钟区间的红包转账结果
+     * (定时调取失败 可以启动fs-qw-task 中的 CommonController queryRedPacketResult 手动调取) 仅给内部人员使用
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2025/10/15 16:32
+     */
+    public void queryRedPacketResult() {
+
+        // 查询RedPacketLog表,
+        fsCourseRedPacketLogService.queryRedPacketResult(null, null);
+    }
+
+}

+ 26 - 4
fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java

@@ -1,17 +1,19 @@
 package com.fs.his.controller;
 
 import java.util.Base64;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.sign.Md5Utils;
 import com.fs.his.param.*;
 import com.fs.his.utils.RedisCacheUtil;
-import com.fs.his.vo.FsDoctorListVO;
-import com.fs.his.vo.FsDoctorVO;
-import com.fs.his.vo.OptionsVO;
-import com.fs.his.vo.UserVo;
+import com.fs.his.vo.*;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -269,4 +271,24 @@ public class FsDoctorController extends BaseController
         return toAjax(fsDoctorService.updateFsDoctor(doc));
     }
 
+    @GetMapping("/getChooseDoctorList")
+    public R getChooseDoctorList(@RequestParam(required = false) String doctorName,
+                                 @RequestParam(required = false) Long hospitalId,
+                                 @RequestParam(required = false) Long deptId,
+                                 @RequestParam(required = false) String position,
+                                 @RequestParam(required = false) String mobile,
+                                 @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                 @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("doctorName", doctorName);
+        params.put("hospitalId", hospitalId);
+        params.put("deptId", deptId);
+        params.put("position", position);
+        params.put("mobile", mobile);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsDoctorChooseVO> list = fsDoctorService.getChooseDoctorListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

+ 20 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java

@@ -3,20 +3,26 @@ package com.fs.his.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.utils.RedisCacheUtil;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 积分商品Controller
@@ -127,4 +133,18 @@ public class FsIntegralGoodsController extends BaseController
         redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.deleteFsIntegralGoodsByGoodsIds(goodsIds));
     }
+
+    @GetMapping("/getChooseIntegralGoodsList")
+    public R getChooseIntegralGoodsList(@RequestParam(required = false) String goodsName,
+                                        @RequestParam(required = false) Integer goodsType,
+                                        @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                        @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("goodsName", goodsName);
+        params.put("goodsType", goodsType);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsIntegralGoodsChooseVO> list = fsIntegralGoodsService.getChooseIntegralGoodsListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
 }

+ 9 - 17
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
+import java.util.Map;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
 import static com.fs.his.utils.PhoneUtil.decryptPhone;
@@ -66,23 +67,7 @@ public class FsIntegralOrderController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsIntegralOrder fsIntegralOrder)
     {
-        List<FsIntegralOrder> list = fsIntegralOrderService.selectFsIntegralOrderList(fsIntegralOrder);
-        for (FsIntegralOrder vo : list) {
-            //商品名称以及原价赋值
-            String itemJson = vo.getItemJson();
-            if(StringUtils.isNotBlank(itemJson)){
-                JSONObject jsonObject = JSONObject.parseObject(itemJson);
-                vo.setGoodsName(jsonObject.getString("goodsName"));
-                vo.setOtPrice(jsonObject.getBigDecimal("otPrice"));
-            }
-            if (vo.getUserPhone()!=null&&!vo.getUserPhone().equals("")){
-                if(vo.getUserPhone().chars().allMatch(Character::isDigit)){continue;}
-//                vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
-                vo.setUserPhone(PhoneUtil.decryptPhone(vo.getUserPhone()));
-            }
-        }
-        ExcelUtil<FsIntegralOrder> util = new ExcelUtil<FsIntegralOrder>(FsIntegralOrder.class);
-        return util.exportExcel(list, "积分商品订单数据");
+        return fsIntegralOrderService.export(fsIntegralOrder);
     }
     /**
      * 发货
@@ -186,4 +171,11 @@ public class FsIntegralOrderController extends BaseController
     {
         return toAjax(fsIntegralOrderService.deleteFsIntegralOrderByOrderIds(orderIds));
     }
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:cancel')")
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @PostMapping("/cancelOrder")
+    public AjaxResult cancelOrder(@RequestBody Map<String, String> requestBody){
+        String orderCode = requestBody.get("orderCode");
+        return toAjax(fsIntegralOrderService.cancelOrder(orderCode));
+    }
 }

+ 24 - 8
fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java

@@ -1,7 +1,9 @@
 package com.fs.his.controller;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
@@ -12,20 +14,16 @@ import com.fs.his.param.FsPackageParam;
 import com.fs.his.param.FsStoreProductPackageModifyParam;
 import com.fs.his.service.IFsFollowTempService;
 import com.fs.his.utils.RedisCacheUtil;
+import com.fs.his.vo.FsPackageChooseVO;
 import com.fs.his.vo.FsPackageExcelVO;
 import com.fs.his.vo.FsPackageListVO;
 import com.fs.his.vo.OptionsVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -191,4 +189,22 @@ public class FsPackageController extends BaseController
         return toAjax(fsPackageService.updatePackagesStatus(param.getPackageIds(),param.getStatus()));
     }
 
+    @GetMapping("/getChoosePackageList")
+    public R getChoosePackageList(@RequestParam(required = false) String packageName,
+                                  @RequestParam(required = false) String secondName,
+                                  @RequestParam(required = false) Integer packageType,
+                                  @RequestParam(required = false) Integer packageSubType,
+                                  @RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                                  @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Map<String,Object> params = new HashMap<>();
+        params.put("packageName", packageName);
+        params.put("secondName", secondName);
+        params.put("packageType", packageType);
+        params.put("packageSubType", packageSubType);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsPackageChooseVO> list = fsPackageService.getChoosePackageListByMap(params);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

+ 71 - 0
fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java

@@ -0,0 +1,71 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.dto.FsPromotionalActiveDTO;
+import com.fs.his.service.IFsPromotionalActiveService;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 宣传活动控制类
+ */
+@RestController
+@RequestMapping("/his/promotionActive")
+@AllArgsConstructor
+public class FsPromotionalActiveController extends BaseController {
+
+    private final IFsPromotionalActiveService fsPromotionalActiveService;
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsPromotionalActive active) {
+        startPage();
+        List<FsPromotionalActiveVO> list = fsPromotionalActiveService.selectPromotionalActiveVOList(active);
+        return getDataTable(list);
+    }
+
+    @GetMapping(value = "/{activeId}")
+    public AjaxResult getInfo(@PathVariable Long activeId) {
+        return AjaxResult.success(fsPromotionalActiveService.selectPromotionalActiveVOById(activeId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:add')")
+    @Log(title = "宣传活动", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Valid @RequestBody FsPromotionalActiveDTO param) {
+        fsPromotionalActiveService.addPromotionalActive(param);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:edit')")
+    @Log(title = "宣传活动", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Valid @RequestBody FsPromotionalActiveDTO param) {
+        fsPromotionalActiveService.editPromotionalActive(param);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActive:remove')")
+    @Log(title = "宣传活动", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{activeIds}")
+    public AjaxResult remove(@PathVariable Long[] activeIds) {
+        fsPromotionalActiveService.logicalRemove(activeIds);
+        return AjaxResult.success();
+    }
+
+    @GetMapping("/getPromotionalActiveOption")
+    public R getPromotionalActiveOption() {
+        return R.ok().put("list", fsPromotionalActiveService.getPromotionalActiveOption());
+    }
+}

+ 41 - 0
fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java

@@ -0,0 +1,41 @@
+package com.fs.his.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.his.service.IFsPromotionalActiveLogService;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+import lombok.AllArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/his/promotionActiveLog")
+@AllArgsConstructor
+public class FsPromotionalActiveLogController extends BaseController {
+
+    private final IFsPromotionalActiveLogService promotionalActiveLogService;
+
+    @PreAuthorize("@ss.hasPermi('his:promotionActiveLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String name,
+                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startTime,
+                              @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endTime) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("name", name);
+        params.put("startTime", startTime);
+        params.put("endTime", endTime);
+
+        startPage();
+        List<FsPromotionalActiveStatVO> list = promotionalActiveLogService.getPromotionalActiveLogStatByMap(params);
+        return getDataTable(list);
+    }
+}

+ 6 - 2
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -954,8 +954,12 @@ public class FsStoreOrderController extends BaseController
     @GetMapping("/getErpAccount")
     public R getErpAccount()
     {
-        List<DFConfigVo> erpAccounts = fsStoreOrderService.getErpAccount();
-        List<String> list = erpAccounts.stream().map(DFConfigVo::getLoginAccount).collect(Collectors.toList());
+        List<String> list = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医","康年堂")){
+            List<DFConfigVo> erpAccounts = fsStoreOrderService.getErpAccount();
+            list = erpAccounts.stream().map(DFConfigVo::getLoginAccount).collect(Collectors.toList());
+        }
+
         return R.ok().put("data", list);
     }
 }

+ 208 - 262
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -15,7 +15,6 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.QwIpadTotalVo;
 import com.fs.company.vo.RedPacketMoneyVO;
-import com.fs.course.dto.BatchSendCourseAllDTO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.ITencentCloudCosService;
@@ -29,9 +28,13 @@ import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
 import com.fs.fastGpt.domain.FastgptEventLogTotal;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
+import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
+import com.fs.fastgptApi.util.AudioUtils;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
@@ -47,13 +50,13 @@ import com.fs.his.service.*;
 import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsSubOrderResultVO;
-import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
-import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.google.gson.Gson;
@@ -62,9 +65,10 @@ import org.apache.commons.lang3.StringUtils;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
@@ -90,7 +94,7 @@ public class Task {
     private IFsFollowService fsFollowService;
     @Autowired
     private IFsStoreAfterSalesService fsStoreAfterSalesService;
-//    @Autowired
+    //    @Autowired
 //    IErpOrderService erpOrderService;
     @Autowired
     FsIntegralOrderMapper integralOrderMapper;
@@ -131,7 +135,7 @@ public class Task {
     FsPackageOrderServiceImpl packageOrderService;
     @Autowired
     private IFsStoreOrderLogsService fsStoreOrderLogsService;
-    org.slf4j.Logger logger= LoggerFactory.getLogger(getClass());
+    org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
     @Autowired
     IFsDoctorService doctorService;
     @Autowired
@@ -163,10 +167,6 @@ public class Task {
     private IQwCompanyService qwCompanyService;
     @Autowired
     private IQwUserService qwUserService;
-    @Autowired
-    private OpenIMService openIMService;
-    @Autowired
-    public RedisTemplate redisTemplate;
 
     @Autowired
     private ICompanyUserService userService;
@@ -174,6 +174,51 @@ public class Task {
     @Autowired
     private IFastgptEventLogTotalService fastgptEventLogTotalService;
 
+    @Autowired
+    private FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    @Autowired
+    private IQwSopTempVoiceService qwSopTempVoiceService;
+
+    public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
+
+    /**
+     * 一键生成语音定时任务
+     */
+    public void ConsumerSopTempVoice() {
+        try {
+            Long newCompanyUserId = redisCache.popVoiceKey(SOP_TEMP_VOICE_KEY);
+            if (newCompanyUserId != null) {
+                List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + newCompanyUserId);
+                if (sopTempVoices != null && !sopTempVoices.isEmpty()) {
+                    try {
+                        for (QwSopTempVoice qwSopTempVoice : sopTempVoices) {
+                            try {
+                                AudioVO audioVO = new AudioVO();
+                                List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+                                audioVO = AudioUtils.createUserUrlAndUrl(homos, qwSopTempVoice.getCompanyUserId(), qwSopTempVoice.getVoiceTxt());
+                                if (audioVO != null && audioVO.getWavUrl() != null && audioVO.getUrl() != null) {
+                                    qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                                    qwSopTempVoice.setUserVoiceUrl(audioVO.getWavUrl());
+                                    qwSopTempVoice.setDuration(audioVO.getDuration());
+                                    qwSopTempVoice.setRecordType(1);
+                                    qwSopTempVoiceService.updateQwSopTempVoice(qwSopTempVoice);
+                                }
+                            } catch (Exception e) {
+
+                            }
+                        }
+                    } finally {
+                        redisCache.deleteObject(SOP_TEMP_VOICE_KEY + ":" + newCompanyUserId);
+                    }
+                }
+            } else {
+                log.info("没有需要生成的语音");
+            }
+        } catch (Exception e) {
+            log.error("生成语音定时任务执行异常", e);
+        }
+    }
+
     //统计ai事件埋点
     public void eventLogTotals() {
         // 判断是否是凌晨 00:00 - 00:59
@@ -238,7 +283,7 @@ public class Task {
                         FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
                         eventLogTotal.setId(info.getId());
                         eventLogTotal.setCount(newCount);
-                        if(!processedKeys.contains(uniqueKey)) {
+                        if (!processedKeys.contains(uniqueKey)) {
                             toUpdateList.add(eventLogTotal);
                             // 标记为已处理
                             processedKeys.add(uniqueKey);
@@ -246,7 +291,7 @@ public class Task {
                     }
                 } else {
                     total.setStatTime(dateTime);
-                    if(!processedKeys.contains(uniqueKey)) {
+                    if (!processedKeys.contains(uniqueKey)) {
                         toInsertList.add(total);
                         // 标记为已处理
                         processedKeys.add(uniqueKey);
@@ -349,7 +394,7 @@ public class Task {
                         FastgptEventLogTotal eventLogTotalNew = new FastgptEventLogTotal();
                         eventLogTotalNew.setId(info.getId());
                         eventLogTotalNew.setCount(totalCount);
-                        if(!processedKeys.contains(uniqueKey)){
+                        if (!processedKeys.contains(uniqueKey)) {
                             toUpdateList.add(eventLogTotalNew);
                             // 标记为已处理
                             processedKeys.add(uniqueKey);
@@ -366,7 +411,7 @@ public class Task {
                     eventLogTotal.setQwUserId(tokenLog.getQwUserId());
                     eventLogTotal.setStatTime(dateTime);
 
-                    if(!processedKeys.contains(uniqueKey)) {
+                    if (!processedKeys.contains(uniqueKey)) {
                         toInsertList.add(eventLogTotal);
                         // 标记为已处理
                         processedKeys.add(uniqueKey);
@@ -392,23 +437,23 @@ public class Task {
 
 
     //定时查询ipad主机使用情况,建议每天凌晨1点执行一次
-    public void totalIpadTask(){
+    public void totalIpadTask() {
         String dateTime = DateUtils.addDateDays(-1); // 昨天
         List<QwIpadTotalVo> qwIpadTotalVos = userService.selectCompanyByIpadStatusCount();
-        if(qwIpadTotalVos != null && !qwIpadTotalVos.isEmpty()){
+        if (qwIpadTotalVos != null && !qwIpadTotalVos.isEmpty()) {
             qwIpadTotalVos.forEach(qwIpadTotalVo ->
                     qwIpadTotalVo.setStatTime(dateTime)
             );
             int a = userService.insertQwIpadTotal(qwIpadTotalVos);
-            if(a == 0){
+            if (a == 0) {
                 log.error("插入ipad主机失败");
             }
-        }else{
+        } else {
             log.error("查询没有数据");
         }
     }
 
-    public void addQwUserName(){
+    public void addQwUserName() {
         QwCompany qwCompany = new QwCompany();
         List<QwCompany> companyList = qwCompanyService.selectQwCompanyList(qwCompany);
         for (QwCompany company : companyList) {
@@ -416,50 +461,56 @@ public class Task {
         }
     }
 
-    public void videoTranscode() throws Exception
-    {
+    public void videoTranscode() throws Exception {
 
         tencentCloudCosService.videoTranscode();
     }
 
-    public void updateUrl() throws Exception
-    {
+    public void updateUrl() throws Exception {
 
         tencentCloudCosService.updateUrl();
     }
-    public void addPrescribeImg() throws Exception
-    {
-       List<Long> ids= fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();
+
+    public void addPrescribeImg() throws Exception {
+        List<Long> ids = fsPrescribeService.selectFsPrescribeByPrescribeIdByOrderType();
         for (Long id : ids) {
             System.out.println(id);
             fsPrescribeService.PrescribeStoreImg(id);
         }
     }
 
-    public void addQwWatchLog() throws Exception
-    {
+    public void addQwWatchLog() throws Exception {
+        LocalDateTime now = LocalDateTime.now().withSecond(0);
         fsCourseWatchLogService.addCourseWatchLogDay();
     }
 
 
-    public void transferLog() throws Exception
-    {
+    public void addQwWatchLogMinute(Integer minute) throws Exception{
+        log.info("定时更新看课111");
+        if(minute == null) minute = 30;
+        LocalDateTime now = LocalDateTime.now().withSecond(0);
+        LocalDateTime start = now.minusMinutes(minute);
+        LocalDateTime end = now.minusSeconds(1);
+        fsCourseWatchLogService.addCourseWatchLogDayMinute(start, end);
+    }
+
+    public void transferLog() throws Exception {
         qwExternalContactTransferLogService.updateQwExternalContactTransferLogByStatus();
 
 
     }
 
-    public void isArtificial() throws Exception
-    {
+    public void isArtificial() throws Exception {
         fastGptChatSessionMapper.updateFastGptChatSessionByIsReply();
 
     }
-    public void expirationQwAppCountWay(){
+
+    public void expirationQwAppCountWay() {
         qwAppContactWayService.expirationQwAppCountWay();
 
     }
-    public void sendOrderMsg() throws Exception
-    {
+
+    public void sendOrderMsg() throws Exception {
         List<FsStoreOrder> fsStoreOrders = fsStoreOrderMapper.selectStoreOrderIdByFollow();
         for (FsStoreOrder fsStoreOrder : fsStoreOrders) {
 
@@ -467,128 +518,100 @@ public class Task {
         }
 
     }
-    public void redPacketSubMoney() throws Exception
-    {
+
+    public void redPacketSubMoney() throws Exception {
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogByCompany();
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
-            companyService.subtractCompanyMoney(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId());
+            companyService.subtractCompanyMoney(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId());
         }
     }
 
 
-    public void redPacketAddMoney() throws Exception
-    {
+    public void redPacketAddMoney() throws Exception {
         List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseAddRedPacketLogByCompany();
         for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
-            companyService.addRedPacketCompanyMoney(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId());
+            companyService.addRedPacketCompanyMoney(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId());
         }
     }
 
-    public void updateCompanyUserStatus()
-    {
+    public void updateCompanyUserStatus() {
         CompanyUser user = new CompanyUser();
         user.setStatus("0");
         user.setDelFlag("0");
         List<CompanyUser> companyUsers = companyUserMapper.selectCompanyUserList(user);
         for (CompanyUser companyUser : companyUsers) {
-            if(SecurityUtils.matchesPassword("123456", companyUser.getPassword())){
+            if (SecurityUtils.matchesPassword("123456", companyUser.getPassword())) {
                 companyUser.setStatus("1");
                 companyUserMapper.updateCompanyUser(companyUser);
-                logger.info("密码为123456 停用账号:"+companyUser.getUserId()+":"+companyUser.getNickName());
+                logger.info("密码为123456 停用账号:" + companyUser.getUserId() + ":" + companyUser.getNickName());
             }
         }
     }
-    public void couponStatus()
-    {
+
+    public void couponStatus() {
         fsUserCouponMapper.updateFsUserCouponStatusByLimtTime();
     }
 
     //每10秒执行一次
-    public void auditPrescribe()
-    {
+    public void auditPrescribe() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.inquiryConfig");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        boolean isAudit = (boolean)config.get("isAutoPrescribeAudit");
-        if (isAudit){
+        boolean isAudit = (boolean) config.get("isAutoPrescribeAudit");
+        if (isAudit) {
             fsPrescribeService.auditPrescribe();
         }
     }
 
-    public void deliveryOp()
-    {
+    public void deliveryOp() {
         IErpOrderService erpOrderService = getErpService();
         List<FsStoreOrder> orders = null;
-        if (erpOrderService == gyOrderService){
+        if (erpOrderService == gyOrderService) {
             orders = fsStoreOrderMapper.selectOmsOrderdeliveryOp();
-        } else if (erpOrderService == wdtOrderService || erpOrderService == dfOrderService || erpOrderService == jSTOrderService){
+        } else if (erpOrderService == wdtOrderService || erpOrderService == dfOrderService || erpOrderService == jSTOrderService) {
             orders = fsStoreOrderMapper.selectWdtOmsOrderdeliveryOp();
         }
-        for(FsStoreOrder order:orders){
-            ErpOrderQueryRequert request=new ErpOrderQueryRequert();
-            request.setCode(order.getExtendOrderId());
-            if (erpOrderService != null){
-                ErpOrderQueryResponse response=erpOrderService.getOrder(request);
-                if (erpOrderService != dfOrderService){
-                    if(response.getOrders()!=null&&response.getOrders().size()>0){
-                        for(ErpOrderQuery orderQuery : response.getOrders()){
-                            if(orderQuery.getDeliverys()!=null&&orderQuery.getDeliverys().size()>0){
-                                for(ErpDeliverys delivery:orderQuery.getDeliverys()){
-                                    if(delivery.getDelivery()&& StringUtils.isNotEmpty(delivery.getMail_no())){
-                                        //更新商订单状态 删除REDIS
-                                        fsStoreOrderService.deliveryOrder(order.getOrderCode(),delivery.getMail_no(),delivery.getExpress_code(),delivery.getExpress_name());
-                                        redisCache.deleteObject("delivery"+":"+order.getExtendOrderId());
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
 
-    public void deliveryOpScrm()
-    {
-        IErpOrderService erpOrderService = getErpService();
-        List<FsStoreOrderScrm> orders = null;
-        if (erpOrderService == gyOrderService){
-            orders = fsStoreOrderMapper.selectOmsOrderdeliveryOpScrm();
-        } else if (erpOrderService == wdtOrderService || erpOrderService == dfOrderService || erpOrderService == jSTOrderService){
-            orders = fsStoreOrderMapper.selectWdtOmsOrderdeliveryOpScrm();
-        }
-        for(FsStoreOrderScrm order:orders){
-            ErpOrderQueryRequert request=new ErpOrderQueryRequert();
+
+        for (FsStoreOrder order : orders) {
+
+            ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+
             request.setCode(order.getExtendOrderId());
-            if (erpOrderService != null){
-                ErpOrderQueryResponse response=erpOrderService.getScrmOrder(request);
-                if (erpOrderService != dfOrderService){
-                    if(response.getOrders()!=null&&response.getOrders().size()>0){
-                        for(ErpOrderQuery orderQuery : response.getOrders()){
-                            if(orderQuery.getDeliverys()!=null&&orderQuery.getDeliverys().size()>0){
-                                for(ErpDeliverys delivery:orderQuery.getDeliverys()){
-                                    if(delivery.getDelivery()&& StringUtils.isNotEmpty(delivery.getMail_no())){
+            if (erpOrderService != null) {
+                ErpOrderQueryResponse response = erpOrderService.getOrder(request);
+                if (erpOrderService != dfOrderService) {
+                    if (response.getOrders() != null && response.getOrders().size() > 0) {
+                        for (ErpOrderQuery orderQuery : response.getOrders()) {
+                            if (orderQuery.getDeliverys() != null && orderQuery.getDeliverys().size() > 0) {
+                                for (ErpDeliverys delivery : orderQuery.getDeliverys()) {
+                                    if (delivery.getDelivery() && StringUtils.isNotEmpty(delivery.getMail_no())) {
                                         //更新商订单状态 删除REDIS
-                                        fsStoreOrderService.deliveryOrderScrm(order.getOrderCode(),delivery.getMail_no(),delivery.getExpress_code(),delivery.getExpress_name());
-                                        redisCache.deleteObject("delivery"+":"+order.getExtendOrderId());
+                                        fsStoreOrderService.deliveryOrder(order.getOrderCode(), delivery.getMail_no(), delivery.getExpress_code(), delivery.getExpress_name());
+                                        redisCache.deleteObject("delivery" + ":" + order.getExtendOrderId());
                                     }
                                 }
+
                             }
                         }
                     }
                 }
             }
+
         }
+
+
     }
-    public void getOrderDeliveryStatus()
-    {
+
+
+    public void getOrderDeliveryStatus() {
         IErpOrderService erpOrderService = getErpService();
         List<FsStoreOrder> orders = null;
-        if (erpOrderService !=null && erpOrderService == dfOrderService){
+        if (erpOrderService != null && erpOrderService == dfOrderService) {
             orders = fsStoreOrderMapper.selectShippedOrder();
-            if(orders!=null&& !orders.isEmpty()){
+            if (orders != null && !orders.isEmpty()) {
                 List<CompletableFuture<Void>> futures = new ArrayList<>();
-                for(FsStoreOrder order:orders){
+                for (FsStoreOrder order : orders) {
                     CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                         erpOrderService.getOrderDeliveryStatus(order);
                     });
@@ -599,15 +622,14 @@ public class Task {
         }
     }
 
-    public void CreateOmsAndHis()
-    {
+    public void CreateOmsAndHis() {
         List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
-        logger.info("推送订单id====>{}",omsList);
+        logger.info("推送订单id====>{}", omsList);
         for (Long l : omsList) {
             try {
                 fsStoreOrderService.createOmsOrder(l);
             } catch (Exception e) {
-                logger.error("推送订单异常:",e);
+                logger.error("推送订单异常:", e);
             }
         }
 //        List<Long> tuiOrderList = fsStoreOrderMapper.selectFsStoreOrderNoTuiOrder();
@@ -620,31 +642,30 @@ public class Task {
 //        }
     }
 
-    public void createFollow()
-    {
+    public void createFollow() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectStoreOrderIdByFollow();
         for (FsStoreOrder order : orders) {
             try {
 
                 fsStoreOrderService.addFsFollowByStoreOrder(order);
-            }catch (Exception e){
-                logger.info("创建随访错误:"+order);
+            } catch (Exception e) {
+                logger.info("创建随访错误:" + order);
             }
         }
     }
+
     @Autowired
     IFsStoreSubOrderService fsStoreSubOrderService;
 
 
-    public void puSubStoreOrder()
-    {
+    public void puSubStoreOrder() {
         List<Long> longs = fsStoreSubOrderService.selectFsStoreSubOrderByNoPush();
-        int i=0;
+        int i = 0;
         for (Long aLong : longs) {
             FsSubOrderResultVO fsSubOrderResultVO = fsStoreSubOrderService.TuiFsStoreSubOrderByStoreOrder(aLong);
-            if (fsSubOrderResultVO!=null&&fsSubOrderResultVO.getCode()!=null&&fsSubOrderResultVO.getCode()==1){
+            if (fsSubOrderResultVO != null && fsSubOrderResultVO.getCode() != null && fsSubOrderResultVO.getCode() == 1) {
                 i++;
-                if (i>65){
+                if (i > 65) {
                     return;
                 }
             }
@@ -652,14 +673,13 @@ public class Task {
         }
     }
 
-    public void refundOp()
-    {
-        List<FsStoreAfterSales> list=fsStoreAfterSalesService.selectFsStoreAfterSalesByDoAudit();
-        if(list!=null){
-            for(FsStoreAfterSales afterSales:list){
+    public void refundOp() {
+        List<FsStoreAfterSales> list = fsStoreAfterSalesService.selectFsStoreAfterSalesByDoAudit();
+        if (list != null) {
+            for (FsStoreAfterSales afterSales : list) {
                 try {
                     fsStoreAfterSalesService.auditing(afterSales);
-                }catch (Exception e){
+                } catch (Exception e) {
 
                 }
 
@@ -667,16 +687,17 @@ public class Task {
         }
 
     }
+
     public void isAfterSales() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        Integer storeAfterSalesDay = (Integer)config.get("storeAfterSalesDay");
+        Integer storeAfterSalesDay = (Integer) config.get("storeAfterSalesDay");
         List<FsStoreOrder> fsStoreOrders = fsStoreOrderMapper.selectFsStoreOrderNoIsAfterSales(storeAfterSalesDay);
         for (FsStoreOrder fsStoreOrder : fsStoreOrders) {
             try {
                 fsStoreOrderService.addIntegralAndShareByStoreOrder(fsStoreOrder);
-            }catch (Exception e) {
+            } catch (Exception e) {
                 logger.info("分账错误: " + fsStoreOrder.getOrderCode());
             }
         }
@@ -684,42 +705,41 @@ public class Task {
     }
 
 
-    public void integralOrderStatus(){
+    public void integralOrderStatus() {
         integralOrderMapper.updatePackageOrderStatusByDeliveryTime();
     }
 
-    public void packageStatus()
-    {
+    public void packageStatus() {
         fsPackageOrderMapper.updatePackageOrderStatusByFinishTime();
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         String configValue = sysConfig.getConfigValue();
         Map<String, Object> config = (Map<String, Object>) JSON.parse(configValue);
-        Integer unPayTime = (Integer)config.get("unPayTime");
+        Integer unPayTime = (Integer) config.get("unPayTime");
         fsPackageOrderMapper.updatePackageOrderStatusByStatus(unPayTime);
     }
 
-    public void endFollow(){
+    public void endFollow() {
         fsStoreOrderService.endFollow();
     }
 
 
-    public void erDelivery(){
+    public void erDelivery() {
         fsStoreOrderService.endDeliveryOrder();
     }
 
 
-
     @Autowired
     private ICompanyService companyService;
-    public void addCompanyMoney(){
+
+    public void addCompanyMoney() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectOrderIds();
-        for (FsStoreOrder order : orders){
+        for (FsStoreOrder order : orders) {
             CompanyMoneyLogs moneyLog1 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(), 5);
-            CompanyMoneyLogs moneyLog2 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(),3);
-            if (moneyLog1==null){
+            CompanyMoneyLogs moneyLog2 = moneyLogsMapper.selectCompanyMoneyLogsByOrderId(order.getOrderId(), 3);
+            if (moneyLog1 == null) {
                 companyService.subtractCompanyMoney(order);
             }
-            if (moneyLog2==null){
+            if (moneyLog2 == null) {
                 companyService.addCompanyMoney(order);
                 FsStoreOrder orderMap = new FsStoreOrder();
                 orderMap.setOrderId(order.getOrderId());
@@ -730,28 +750,26 @@ public class Task {
     }
 
     //每天执行一次
-    public void syncExpress()
-    {
-        List<Long> ids =fsStoreOrderMapper.selectSyncExpressIds();
+    public void syncExpress() {
+        List<Long> ids = fsStoreOrderMapper.selectSyncExpressIds();
         for (Long id : ids) {
             fsStoreOrderService.syncExpress(id);
         }
 
     }
 
-    public void refundCompanyMoney(){
+    public void refundCompanyMoney() {
         List<FsStoreOrder> list = fsStoreOrderMapper.selectOrders();
-        for (FsStoreOrder order : list ){
+        for (FsStoreOrder order : list) {
             companyService.refundCompanyMoney(order);
         }
     }
 
-    public void subIntegral()
-    {
+    public void subIntegral() {
         fsUserIntegralLogsService.subFsUserIntegralLogsByOrder5();
     }
 
-    public void finishInquiry(){
+    public void finishInquiry() {
         List<FsInquiryOrder> orders = inquiryOrderMapper.selectFsInquiryOrderByFinish();
         for (FsInquiryOrder order : orders) {
             // 订单已超过48小时,执行关闭操作
@@ -760,20 +778,20 @@ public class Task {
             param.setDoctorId(order.getDoctorId());
             try {
                 iFsInquiryOrderService.autoFinishOrder(param);
-            }catch (Exception e){
-                logger.info("订单已超过48小时关闭异常"+param);
+            } catch (Exception e) {
+                logger.info("订单已超过48小时关闭异常" + param);
             }
 
         }
     }
 
 
-    public void finishStoreOrderByXN(){
+    public void finishStoreOrderByXN() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectFinishStoreOrderByXN();
-        if (orders!=null&&orders.size()>0){
+        if (orders != null && orders.size() > 0) {
             for (FsStoreOrder o : orders) {
                 FsStoreOrder order = fsStoreOrderMapper.selectFsStoreOrderByOrderId(o.getOrderId());
-                if (order.getStatus()!= 2) {
+                if (order.getStatus() != 2) {
                     continue;
                 }
                 FsStoreOrder o1 = new FsStoreOrder();
@@ -784,20 +802,20 @@ public class Task {
                 int i = fsStoreOrderMapper.updateFsStoreOrder(o1);
                 fsStoreOrderLogsService.create(order.getOrderId(), FsStoreOrderLogEnum.FINISH_ORDER.getValue(),
                         FsStoreOrderLogEnum.FINISH_ORDER.getDesc());
-                if (order.getCompanyId()!=null&&order.getTuiMoneyStatus()==0&&order.getPayType()==1){
+                if (order.getCompanyId() != null && order.getTuiMoneyStatus() == 0 && order.getPayType() == 1) {
                     companyService.addCompanyMoney(order);
                 }
             }
         }
     }
 
-    public void inquirySendSms(){
+    public void inquirySendSms() {
         List<FsInquiryOrder> orders = inquiryOrderMapper.selectFsInquiryOrderBySendSms();
-        for (FsInquiryOrder order : orders){
-            FsInquiryOrderPatientDTO patientDTO = JSON.parseObject(order.getPatientJson(),FsInquiryOrderPatientDTO.class);
-            if (patientDTO!=null&&patientDTO.getPatientName()!=null){
+        for (FsInquiryOrder order : orders) {
+            FsInquiryOrderPatientDTO patientDTO = JSON.parseObject(order.getPatientJson(), FsInquiryOrderPatientDTO.class);
+            if (patientDTO != null && patientDTO.getPatientName() != null) {
                 FsUser fsUser = fsUserMapper.selectFsUserByUserId(order.getUserId());
-                if (fsUser!=null&&fsUser.getPhone()!=null){
+                if (fsUser != null && fsUser.getPhone() != null) {
                     smsService.sendUserSms(fsUser.getPhone(), patientDTO.getPatientName(), "2");
                     order.setIsSendSms(1);
                     inquiryOrderMapper.updateFsInquiryOrder(order);
@@ -808,9 +826,9 @@ public class Task {
     }
 
     //处理30天问题件
-    public void clearProblemOrder(){
+    public void clearProblemOrder() {
         List<FsStoreOrder> orders = fsStoreOrderMapper.selectFsStoreOrderByProblemOrder();
-        for (FsStoreOrder order : orders){
+        for (FsStoreOrder order : orders) {
             FsStoreOrder map = new FsStoreOrder();
             map.setStatus(4);
             map.setOrderId(order.getOrderId());
@@ -821,14 +839,14 @@ public class Task {
 
 
     //30天无通话记录回收坐席
-    public void recoverCompanyCaller(){
+    public void recoverCompanyCaller() {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.store");
         StoreConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), StoreConfig.class);
         Integer days = fsPayConfig.getStoreCall();
         List<CompanyVoiceCaller> list = companyVoiceCallerMapper.selectCompanyVoiceCallerByRecover(days);
-        for (CompanyVoiceCaller caller : list){
-            Long count = companyVoiceLogsMapper.selectCompanyVoiceLogsCountByCallerNo(caller.getCallerNo(),caller.getBindTime());
-            if (count==0){
+        for (CompanyVoiceCaller caller : list) {
+            Long count = companyVoiceLogsMapper.selectCompanyVoiceLogsCountByCallerNo(caller.getCallerNo(), caller.getBindTime());
+            if (count == 0) {
                 caller.setCompanyId(0l);
                 caller.setCompanyUserId(0l);
                 caller.setMobile("");
@@ -841,32 +859,27 @@ public class Task {
 
 
     public void tb() {
-        packageOrderService.payConfirm("", "1780763211956486144", "1075999515888117190", "14", 1,null,null);
+        packageOrderService.payConfirm("", "1780763211956486144", "1075999515888117190", "14", 1, null, null);
     }
 
 
-
-
-
-
-
-    public void addSend(){
-       String userId="4048905872";
-       String doctorId="147";
-        String storeOrderId="470920";
-        String followId="1062986";
+    public void addSend() {
+        String userId = "4048905872";
+        String doctorId = "147";
+        String storeOrderId = "470920";
+        String followId = "1062986";
         //发送给用户
-        MsgDTO msgDTO=new MsgDTO();
-        MsgCustomDTO customDTO=new MsgCustomDTO();
+        MsgDTO msgDTO = new MsgDTO();
+        MsgCustomDTO customDTO = new MsgCustomDTO();
         customDTO.setType("startDrugReport");
         customDTO.setImType(2);
         customDTO.setOrderId(storeOrderId);
         customDTO.setFollowId(followId);
         msgDTO.setCloudCustomData(JSONUtil.toJsonStr(customDTO));
-        msgDTO.setFrom_Account("U-"+userId);
-        msgDTO.setTo_Account("D-"+doctorId);
-        List<MsgDataDTO> msgs=new ArrayList<>();
-        MsgDataDTO msg=new MsgDataDTO();
+        msgDTO.setFrom_Account("U-" + userId);
+        msgDTO.setTo_Account("D-" + doctorId);
+        List<MsgDataDTO> msgs = new ArrayList<>();
+        MsgDataDTO msg = new MsgDataDTO();
         msg.setMsgType("TIMTextElem");
         msg.setMsgContent(new MsgDataFormatDTO("您好"));
         msgs.add(msg);
@@ -1145,7 +1158,7 @@ public class Task {
 //                iFsPackageOrderService.updateFsPackageOrder(order);
 //                num++;
 //            }
-////
+    /// /
 //        }
 //
 //        logger.info("所有订单同步完成:"+num);
@@ -1221,7 +1234,7 @@ public class Task {
 //}
 
 
-        //同步流水
+    //同步流水
 //    public void task(){
 //        CompanyMoneyLogs item = moneyLogsMapper.selectCompanyMoneyLogsById(248884L);
 //        String logsId=item.getLogsId().toString();
@@ -1324,27 +1337,27 @@ public class Task {
     private IErpOrderService getErpService() {
         FsSysConfig sysConfig = configUtil.getSysConfig();
         Integer erpOpen = sysConfig.getErpOpen();
-        if (erpOpen != null && erpOpen == 1){
+        if (erpOpen != null && erpOpen == 1) {
             //判断erp类型
             Integer erpType = sysConfig.getErpType();
-            if (erpType != null){
+            if (erpType != null) {
                 IErpOrderService erpOrderService = null;
-                if (erpType == 1){
+                if (erpType == 1) {
                     //管易
-                    erpOrderService =  gyOrderService;
-                } else if (erpType == 2){
+                    erpOrderService = gyOrderService;
+                } else if (erpType == 2) {
                     //旺店通
-                    erpOrderService =  wdtOrderService;
-                } else if (erpType == 3){
+                    erpOrderService = wdtOrderService;
+                } else if (erpType == 3) {
                     //
-                    erpOrderService =  hzOMSErpOrderService;
-                } else if (erpType == 4){
+                    erpOrderService = hzOMSErpOrderService;
+                } else if (erpType == 4) {
                     //代服
-                    erpOrderService =  dfOrderService;
-                }else if(erpType == 5){
-                    erpOrderService=jSTOrderService;
-                }else if(erpType == 6){
-                    erpOrderService=k9OrderService;
+                    erpOrderService = dfOrderService;
+                } else if (erpType == 5) {
+                    erpOrderService = jSTOrderService;
+                } else if (erpType == 6) {
+                    erpOrderService = k9OrderService;
                 }
                 return erpOrderService;
 
@@ -1353,71 +1366,4 @@ public class Task {
         }
         return null;
     }
-
-
-    /**
-     * 定时任务-im会员定时发课,每一分钟执行一次
-     */
-    public void sendOpenImCourse(){
-        String redisKey = "openIm:batchSendMsg:sendCourse";
-        Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
-        if(cacheMap == null || cacheMap.isEmpty()){
-            logger.info("=====================会员IM定时发课,不存在对应的redisKey==================");
-            return;
-        }
-        List<Map.Entry<String, BatchSendCourseAllDTO>> toSendMap = cacheMap.entrySet().parallelStream().filter((v) -> {
-            String[] split = v.getKey().split(":");
-            long timestamp = Long.parseLong(split[2]);
-            return timestamp < System.currentTimeMillis();
-        }).collect(Collectors.toList());
-
-        if(toSendMap.isEmpty()){
-            logger.info("=====================会员IM定时发课,不存在可执行的发课任务==================");
-            return;
-        }
-        for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
-            //执行发送消息任务
-            BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
-            openIMService.batchSendCourseTask(batchSendCourseAllDTO.getBatchSendCourseDTO(), batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getProject(), batchSendCourseAllDTO.getImMsgSendDetailList());
-
-            // 执行结束,删除
-            this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());
-
-        }
-
-    }
-
-
-    /**
-     * 定时任务-im 会员催课,每一分钟执行一次
-     */
-    public void urgeOpenImCourse(){
-        String redisKey = "openIm:batchSendMsg:urgeCourse";
-        Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
-        if(cacheMap == null || cacheMap.isEmpty()){
-            logger.info("===================== 会员-IM发消息催课,不存在对应的redisKey==================");
-            return;
-        }
-        List<Map.Entry<String, BatchSendCourseAllDTO>> toSendMap = cacheMap.entrySet().parallelStream().filter((v) -> {
-            String[] split = v.getKey().split(":");
-            long timestamp = Long.parseLong(split[2]);
-            return timestamp < System.currentTimeMillis();
-        }).collect(Collectors.toList());
-
-        if(toSendMap.isEmpty()){
-            logger.info("===================== 会员-IM发消息催课,不存在可发送的消息==================");
-            return;
-        }
-        for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
-            //执行发送消息任务
-            BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
-            openIMService.batchUrgeCourseTask(batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getImMsgSendDetailList());
-
-            // 执行结束,删除
-            this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());
-
-        }
-    }
-
-
 }

+ 50 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactTransferCompanyAuditController.java

@@ -0,0 +1,50 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.qw.domain.QwExternalContactTransferCompanyAudit;
+import com.fs.qw.dto.CompanyTransferAuditDTO;
+import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
+import com.fs.qw.service.IQwExternalContactTransferCompanyAuditUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/externalContactTransferCompanyAudit")
+public class QwExternalContactTransferCompanyAuditController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactTransferCompanyAuditService auditService;
+    @Autowired
+    private IQwExternalContactTransferCompanyAuditUserService auditUserService;
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwExternalContactTransferCompanyAudit param) {
+        startPage();
+        List<QwExternalContactTransferCompanyAudit> list = auditService.selectQwExternalContactTransferCompanyAuditList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:detail')")
+    @GetMapping("/detail/{auditId}")
+    public AjaxResult detail(@PathVariable Long auditId) {
+        return AjaxResult.success(auditUserService.getListByAuditId(auditId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:audit')")
+    @PostMapping("/audit")
+    public AjaxResult audit(@Valid @RequestBody CompanyTransferAuditDTO param) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        auditService.audit(param, loginUser.getUser().getUserName());
+        return AjaxResult.success();
+    }
+
+}

+ 154 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java

@@ -0,0 +1,154 @@
+package com.fs.qw.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.qw.domain.QwPushCount;
+import com.fs.qw.service.IQwPushCountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 定义销售推送不同类型的企业消息的次数Controller
+ *
+ * @author fs
+ * @date 2025-08-22
+ */
+@RestController
+@RequestMapping("/qw/qwPushCount")
+public class QwPushCountController extends BaseController {
+    @Autowired
+    private IQwPushCountService qwPushCountService;
+    @Autowired
+    private ResourceLoader resourceLoader;
+
+    /**
+     * 查询定义销售推送不同类型的企业消息的次数列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwPushCount qwPushCount) {
+        startPage();
+        List<QwPushCount> list = qwPushCountService.selectQwPushCountList(qwPushCount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出定义销售推送不同类型的企业消息的次数列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:export')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwPushCount qwPushCount) {
+        List<QwPushCount> list = qwPushCountService.selectQwPushCountList(qwPushCount);
+        ExcelUtil<QwPushCount> util = new ExcelUtil<QwPushCount>(QwPushCount.class);
+        return util.exportExcel(list, "定义销售推送不同类型的企业消息的次数数据");
+    }
+
+    /**
+     * 获取定义销售推送不同类型的企业消息的次数详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        List<Long> companyIdList=new ArrayList<>();
+        QwPushCount qwPushCount = qwPushCountService.selectQwPushCountById(id);
+        companyIdList.add(qwPushCount.getCompanyId());
+        qwPushCount.setCompanyIdList(companyIdList);
+        return AjaxResult.success(qwPushCount);
+    }
+
+
+    /**
+     * 新增定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:add')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwPushCount qwPushCount) {
+        List<Long> existingIds = new ArrayList<>();
+        Map<String, Long> existingDataMap = new HashMap<>();
+        List<QwPushCount> existingPushCounts = qwPushCountService.selectQwPushCountLists();
+        if (existingPushCounts != null && !existingPushCounts.isEmpty()) {
+            existingPushCounts.forEach(item -> {
+                String key = buildDataKey(item.getType(), item.getCompanyId());
+                existingDataMap.put(key, item.getId());
+            });
+        }
+        // 处理公司ID列表(可能为null或空)
+        List<Long> companyIdList = qwPushCount.getCompanyIdList();
+        boolean isEmptyList = companyIdList == null || companyIdList.isEmpty();
+
+        if (isEmptyList) {
+            // 处理无公司ID列表的情况
+            String key = buildDataKey(qwPushCount.getType(), null);
+            if (existingDataMap.containsKey(key)) {
+                existingIds.add(qwPushCount.getId());
+            } else {
+                qwPushCountService.insertQwPushCount(qwPushCount);
+            }
+        } else {
+            // 处理有公司ID列表的情况
+            companyIdList.forEach(companyId -> {
+                String key = buildDataKey(qwPushCount.getType(), companyId);
+                if (existingDataMap.containsKey(key)) {
+                    existingIds.add(companyId);
+                } else {
+                    qwPushCount.setCompanyId(companyId);
+                    qwPushCountService.insertQwPushCount(qwPushCount);
+                }
+            });
+        }
+
+        // 统一处理返回结果
+        if (!existingIds.isEmpty()) {
+            return error("新增限定类型已存在:失败条数" + existingIds.size());
+        } else {
+            return toAjax(1);
+        }
+    }
+    private String buildDataKey(Integer type, Long companyId) {
+        return type + "_" + (companyId == null ? "null" : companyId);
+    }
+
+    /**
+     * 修改定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:edit')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwPushCount qwPushCount) {
+        QwPushCount pushCount;
+        if (qwPushCount.getCompanyId() != null) {
+            pushCount = qwPushCountService.SelectQwPushCountByCompanyId(qwPushCount.getType(), qwPushCount.getCompanyId());
+        } else {
+            pushCount = qwPushCountService.SelectQwPushCountByType(qwPushCount.getType());
+        }
+        if (pushCount != null) {
+            if (!Objects.equals(pushCount.getId(), qwPushCount.getId()) && Objects.equals(pushCount.getCompanyId(), qwPushCount.getCompanyId()) && Objects.equals(pushCount.getType(), qwPushCount.getType())) {
+                return toAjax(0);
+            }
+            if (Objects.equals(pushCount.getPushCount(), qwPushCount.getPushCount())) {
+                return toAjax(0);
+            }
+        }
+        return toAjax(qwPushCountService.updateQwPushCount(qwPushCount));
+    }
+
+    /**
+     * 删除定义销售推送不同类型的企业消息的次数
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:remove')")
+    @Log(title = "定义销售推送不同类型的企业消息的次数", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(qwPushCountService.deleteQwPushCountByIds(ids));
+    }
+}

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

@@ -52,6 +52,7 @@ public class QwSopController extends BaseController
     private FsUserCourseMapper fsUserCourseMapper;
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+
     /**
      * 查询企微sop列表
      */
@@ -133,7 +134,6 @@ public class QwSopController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwSop.setCreateBy(loginUser.getUser().getNickName());
         qwSop.setCreateTime(sdf.format(new Date()));
-
         return toAjax(qwSopService.insertQwSop(qwSop));
 
     }

+ 34 - 0
fs-admin/src/main/java/com/fs/task/FsCompanyTask.java

@@ -0,0 +1,34 @@
+package com.fs.task;
+
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@AllArgsConstructor
+@Component("companyTask")
+public class FsCompanyTask {
+
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+    private ICompanyService companyService;
+
+    public void refreshCompanyMoney() {
+        LocalDateTime now = LocalDateTime.now();
+        // 获取上一个小时的开始时间
+        LocalDateTime startTime = now.minusHours(1)
+                .withMinute(0)
+                .withSecond(0);
+        // 获取上一个小时的结束时间
+        LocalDateTime endTime = startTime
+                .withMinute(59)
+                .withSecond(59);
+        List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogHourseByCompany(startTime, endTime);
+        for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
+            companyService.subtractCompanyMoneyHourse(redPacketMoneyVO.getMoney(), redPacketMoneyVO.getCompanyId(), startTime.toLocalTime(), endTime.toLocalTime());
+        }
+    }
+}

+ 1 - 1
fs-admin/src/main/resources/application.yml

@@ -4,7 +4,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-jnmy-test
+    active: dev
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz

+ 25 - 0
fs-common/src/main/java/com/fs/common/utils/DateUtils.java

@@ -270,5 +270,30 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         cal.add(Calendar.DATE, days);
         return new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
     }
+    /**
+     * 获取到当天时间的开始:当天0时0分0秒0毫秒
+     * @return
+     */
+    public static Long toStartTime( ) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar.getTimeInMillis();
+    }
+
+    /**
+     * 获取到当天时间的结束:当天23时59分59秒999毫秒
+     * @return
+     */
+    public static Long toEndTime() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 59);
+        calendar.set(Calendar.SECOND, 59);
+        calendar.set(Calendar.MILLISECOND, 999);
+        return calendar.getTimeInMillis();
+    }
 
 }

+ 10 - 0
fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java

@@ -13,6 +13,8 @@ import com.fs.his.service.IFsUserService;
 import io.jsonwebtoken.Claims;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.concurrent.TimeUnit;
+
 
 public class AppBaseController {
 	@Autowired
@@ -27,8 +29,16 @@ public class AppBaseController {
 	public Long getCompanyId() {
 		String headValue =  ServletUtils.getRequest().getHeader("APPToken");
 		Claims claims=jwtUtils.getClaimByToken(headValue);
+		if (ObjectUtil.isEmpty(claims)){
+			throw new FSException("未授权,请先登录!");
+		}
 		String userId = claims.getSubject().toString();
 		Long companyId =(Long)redisCache.getCacheObject("companyId:"+userId);
+		if (companyId==null){
+			CompanyUser companyUser = companyUserService.selectCompanyUserById(Long.parseLong(userId));
+			companyId = companyUser.getCompanyId();
+			redisCache.setCacheObject("companyId:" + companyUser.getUserId(), companyUser.getCompanyId(), 604800, TimeUnit.SECONDS);
+		}
 		return companyId;
 	}
 	public String getUserId()

+ 2 - 0
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -123,12 +123,14 @@ public class FsUserCourseVideoController extends AppBaseController {
     @GetMapping("/participationRecord")
     public ResponseResult<Object> participationRecord(@RequestParam Long videoId,
                                                       @RequestParam Integer type,
+                                                      @RequestParam(required = false) Long periodId,
                                                       @RequestParam(required = false) String keyword,
                                                       @RequestParam(required = false, defaultValue = "1") Integer pageNum,
                                                       @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         log.debug("参与记录 videoId:{}, type:{}, keyword: {}, pageNum: {}, pageSize: {}", videoId, type, keyword, pageNum, pageSize);
         Map<String, Object> params = new HashMap<>();
         params.put("videoId", videoId);
+        params.put("periodId", periodId);
         params.put("type", type);
         params.put("keyword", keyword);
 

+ 6 - 4
fs-company/src/main/java/com/fs/company/controller/company/CompanyController.java

@@ -17,10 +17,7 @@ import com.fs.framework.service.TokenService;
 import com.fs.his.vo.OptionsVO;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
@@ -119,4 +116,9 @@ public class CompanyController extends BaseController
         return R.ok();
     }
 
+    @GetMapping("/getCompanyListByCorId/{corpId}")
+    public R getCompanyListByCorId(@PathVariable String corpId) {
+        List<OptionsVO> list = companyService.getCompanyListByCorpId(corpId);
+        return R.ok().put("data",list);
+    }
 }

+ 43 - 9
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -22,6 +22,8 @@ import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.service.*;
 import com.fs.company.utils.DomainUtil;
+import com.fs.company.utils.QwStatusEnum;
+import com.fs.company.vo.BatchUserRolesVO;
 import com.fs.company.vo.CompanyUserImportVO;
 import com.fs.company.vo.CompanyUserQwListVO;
 import com.fs.company.vo.CompanyUserVO;
@@ -37,6 +39,7 @@ import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.IQwCompanyService;
+import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.system.service.ISysConfigService;
@@ -48,6 +51,7 @@ import org.json.JSONArray;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.Assert;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -60,13 +64,10 @@ import java.util.stream.Collectors;
 
 /**
  * 用户信息
- *
-
  */
 @RestController
 @RequestMapping("/company/user")
-public class CompanyUserController extends BaseController
-{
+public class CompanyUserController extends BaseController {
 
     @Autowired
     private ICompanyRoleService roleService;
@@ -79,19 +80,28 @@ public class CompanyUserController extends BaseController
 
     @Autowired
     private ICompanyUserService companyUserService;
+
     @Autowired
     private ICompanyService companyService;
+
     @Autowired
     private ICompanyUserDelayTimeService companyUserDelayTimeService;
+
     @Autowired
     private ISysConfigService configService;
+
     @Autowired
     private RedisCache redisCache;
+
     @Autowired
     private OpenIMService openIMService;
+
     @Autowired
     IQwCompanyService iQwCompanyService;
 
+    @Autowired
+    private IQwUserService qwUserService;
+
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
 
     /**
@@ -125,24 +135,34 @@ public class CompanyUserController extends BaseController
         return getDataTable(list);
     }
     @GetMapping("/qwList")
-    public TableDataInfo qwList(CompanyUserQwParam user)
-    {
+    public TableDataInfo qwList(CompanyUserQwParam user) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         user.setCompanyId(loginUser.getCompany().getCompanyId());
         startPage();
         List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
         for (CompanyUserQwListVO companyUserQwListVO : list) {
-             CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(),companyUserQwListVO.getUserId());
-            if (ObjectUtil.isEmpty(companyUserDelayTime)){
+            CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+            if (ObjectUtil.isEmpty(companyUserDelayTime)) {
                 String json = configService.selectConfigByKey("course.config");
                 CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
                 companyUserQwListVO.setSendDelayTime(config.getSendDelayTime());
-            }else {
+            } else {
                 companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
             }
+            //是否绑定
+            if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
+                if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                    Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
+                            .map(Long::parseLong)
+                            .toArray(Long[]::new);
+                    List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
+                    companyUserQwListVO.setQwUsers(qwUserVOS);
+                }
+            }
         }
         return getDataTable(list);
     }
+
     @Log(title = "用户管理导出", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('company:user:export')")
     @GetMapping("/export")
@@ -567,6 +587,20 @@ public class CompanyUserController extends BaseController
         return companyUserService.bindDoctor(companyUser);
     }
 
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO
+     */
+    @PreAuthorize("@ss.hasPermi('company:user:edit')")
+    @Log(title = "批量修改角色", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateBatchUserRoles")
+    public R updateBatchUserRoles(@RequestBody BatchUserRolesVO batchUserRolesVO){
+        Assert.notEmpty(batchUserRolesVO.getRoleIds(), "角色不能为空");
+        Assert.notEmpty(batchUserRolesVO.getUserIds(), "用户不能为空");
+        return companyUserService.updateBatchUserRoles(batchUserRolesVO);
+    }
+
+
     @ApiOperation("校验客服是否注册新的im")
     @PostMapping("/accountCheck")
     public R accountCheck(@RequestBody Map<String, String> userIdMap){

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

@@ -9,11 +9,10 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.course.domain.FsCourseWatchLog;
-import com.fs.course.param.FsCourseOverParam;
-import com.fs.course.param.FsCourseUserStatisticsListParam;
-import com.fs.course.param.FsCourseWatchLogListParam;
-import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.param.*;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.IFsUserCoursePeriodDaysService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseUserStatisticsListVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
@@ -54,6 +53,12 @@ public class FsCourseWatchLogController extends BaseController
     @Autowired
     private CompanyDeptServiceImpl companyDeptService;
 
+    @Autowired
+    private IFsUserCoursePeriodService userCoursePeriodService;
+
+    @Autowired
+    private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
+
     /**
      * 查询短链课程看课记录列表
      */
@@ -62,9 +67,26 @@ public class FsCourseWatchLogController extends BaseController
     public TableDataInfo list(FsCourseWatchLogListParam param)
     {
 
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
+
+        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+            List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
+
+            if (!periodIds.isEmpty()){
+                List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, loginUser.getCompany().getCompanyId());
+                if (!longs.isEmpty()){
+                    param.setPeriodIds(longs);
+                }else {
+                    return getDataTable(new ArrayList<>());
+                }
+            }else {
+                return getDataTable(new ArrayList<>());
+            }
+
+        }
+
+        startPage();
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         return getDataTable(list);
     }
@@ -96,6 +118,24 @@ public class FsCourseWatchLogController extends BaseController
         param.setUserType(loginUser.getUser().getUserType());
         param.setCompanyId(loginUser.getCompany().getCompanyId());
 
+
+        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+            List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
+
+            if (!periodIds.isEmpty()){
+                List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, loginUser.getCompany().getCompanyId());
+                if (!longs.isEmpty()){
+                    param.setPeriodIds(longs);
+                }else {
+                    return getDataTable(new ArrayList<>());
+                }
+            }else {
+                return getDataTable(new ArrayList<>());
+            }
+
+        }
+
+
         startPage();
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
 
@@ -108,9 +148,27 @@ public class FsCourseWatchLogController extends BaseController
     @GetMapping("/myList")
     public TableDataInfo myList(FsCourseWatchLogListParam param)
     {
-        startPage();
+
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyUserId( loginUser.getUser().getUserId());
+
+        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+            List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
+            if (!periodIds.isEmpty()){
+                List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, loginUser.getCompany().getCompanyId());
+                if (!longs.isEmpty()){
+                    param.setPeriodIds(longs);
+                }else {
+                    return getDataTable(new ArrayList<>());
+                }
+            }else {
+                return getDataTable(new ArrayList<>());
+            }
+
+        }
+
+        startPage();
+
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         return getDataTable(list);
     }

+ 167 - 3
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -11,6 +11,7 @@ import com.fs.common.utils.PubFun;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.course.param.FsUserCourseListUParam;
 import com.fs.course.service.IFsUserCourseStudyService;
@@ -20,24 +21,30 @@ import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.vo.CrmMyCustomerListQueryVO;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
+import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.fs.qw.domain.QwContactWay;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwTag;
+import com.fs.qw.dto.CompanyTransferDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwExternalContactVO;
 import com.fs.qw.vo.QwFsUserVO;
 import com.fs.qw.vo.QwUserDelLossLogVO;
+import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import javax.validation.Valid;
 import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -51,6 +58,7 @@ import static com.fs.his.utils.PhoneUtil.encryptPhone;
  * @author fs
  * @date 2024-06-20
  */
+@Slf4j
 @RestController
 @RequestMapping("/qw/externalContact")
 public class QwExternalContactController extends BaseController
@@ -78,6 +86,11 @@ public class QwExternalContactController extends BaseController
 
     @Autowired
     private CompanyDeptServiceImpl companyDeptService;
+    @Autowired
+    private IQwExternalContactTransferCompanyAuditService auditService;
+
+    @Autowired
+    private IQwContactWayService qwContactWayService;
 
     /**
      * 查询企业微信客户列表
@@ -86,8 +99,13 @@ public class QwExternalContactController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(QwExternalContactParam qwExternalContact)
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        QwContactWay qwContactWay=new QwContactWay();
+        qwContactWay.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
+
+        startPage();
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
@@ -105,11 +123,47 @@ public class QwExternalContactController extends BaseController
 
                 item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
             }
+
+            if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
+                item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+            }
+
         });
 
         return getDataTable(list);
     }
 
+
+    public String getContactWayNameStream(String configStr, List<QwContactWay> wayList) {
+        if (configStr == null || wayList == null || wayList.isEmpty()) {
+            return null;
+        }
+
+        return wayList.stream()
+                .filter(way -> way.getId() != null &&
+                        way.getId().toString().equals(extractLastValue(configStr)))
+                .map(QwContactWay::getName)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 提取最后一个冒号后的值
+     */
+    private String extractLastValue(String input) {
+        if (input == null || input.isEmpty()) {
+            return null;
+        }
+
+        int lastColonIndex = input.lastIndexOf(":");
+        if (lastColonIndex == -1) {
+            return input;
+        }
+
+        return input.substring(lastColonIndex + 1);
+    }
+
+
     @GetMapping("/test")
     public AjaxResult test()
     {
@@ -154,6 +208,10 @@ public class QwExternalContactController extends BaseController
         qwExternalContact.setUserType(loginUser.getUser().getUserType());
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
 
+        QwContactWay qwContactWay=new QwContactWay();
+        qwContactWay.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
+
         startPage();
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
@@ -171,6 +229,11 @@ public class QwExternalContactController extends BaseController
 
                 item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
             }
+
+            if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
+                item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+            }
+
         });
 
         return getDataTable(list);
@@ -183,8 +246,14 @@ public class QwExternalContactController extends BaseController
         if(qwExternalContact.getQwUserId()==null){
             return null;
         }
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        QwContactWay qwContactWay=new QwContactWay();
+        qwContactWay.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
+
+
+        startPage();
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
@@ -202,10 +271,38 @@ public class QwExternalContactController extends BaseController
 
                 item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
             }
+
+            if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
+                item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+            }
+            //获取用户下单次数
+            try {
+                fillOrderCount(item);
+            }catch (Exception e){
+                log.error("获取用户下单次数异常:{}",e.getMessage());
+            }
+
         });
 
         return getDataTable(list);
     }
+    /**
+     * 根据用户ID查询并设置下单次数(若用户不存在或ID无效,则设为0)
+     */
+    private void fillOrderCount(QwExternalContactVO item){
+        Long fsUserId = item.getFsUserId();
+        if (fsUserId==null){
+            item.setOrderCount(0L);
+            return;
+        }
+        FsUser fsUser = fsUserService.selectFsUserById(fsUserId);
+        if (fsUser == null) {
+            item.setOrderCount(0L);
+            return;
+        }
+        Long orderCount = fsUser.getOrderCount();
+        item.setOrderCount(orderCount != null ? orderCount : 0);
+    }
 
 
     @Log(title = "同步我的企业微信客户", businessType = BusinessType.INSERT)
@@ -224,7 +321,7 @@ public class QwExternalContactController extends BaseController
     /**
      * 导出企业微信客户列表
      */
-    @PreAuthorize("@ss.hasPermi('qw:externalContact:export')")
+    @PreAuthorize("@ss.hasAnyPermi('qw:externalContact:export,qw:externalContact:companyExport')")
     @Log(title = "企业微信客户", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(QwExternalContactParam qwExternalContact)
@@ -232,7 +329,13 @@ public class QwExternalContactController extends BaseController
         if (qwExternalContact.getCorpId()==null){
             return AjaxResult.success();
         }
+
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        QwContactWay qwContactWay=new QwContactWay();
+        qwContactWay.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
+
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
         list.forEach(item->{
@@ -250,6 +353,10 @@ public class QwExternalContactController extends BaseController
 
                 item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
             }
+
+            if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
+                item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+            }
         });
         ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
         return util.exportExcel(list, "企业微信客户数据");
@@ -266,6 +373,10 @@ public class QwExternalContactController extends BaseController
         }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+        QwContactWay qwContactWay=new QwContactWay();
+        qwContactWay.setCompanyId(loginUser.getCompany().getCompanyId());
+        List<QwContactWay> wayList = qwContactWayService.selectQwContactWayList(qwContactWay);
+
 
         List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
 
@@ -284,6 +395,10 @@ public class QwExternalContactController extends BaseController
 
                 item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
             }
+
+            if (!StringUtil.strIsNullOrEmpty(item.getState()) && !wayList.isEmpty()) {
+                item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
+            }
         });
 
         ExcelUtil<QwExternalContactVO> util = new ExcelUtil<QwExternalContactVO>(QwExternalContactVO.class);
@@ -469,6 +584,14 @@ public class QwExternalContactController extends BaseController
     {
         return qwExternalContactService.updateQwExternalContactUnBindUserId(id);
     }
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:changeStatus')")
+    @Log(title = "修改用户状态", businessType = BusinessType.UPDATE)
+    @GetMapping("/status")
+    public R changeStatus(QwExternalContact qwExternalContact)
+    {
+        qwExternalContactService.updateQwExternalContactStatusById(qwExternalContact);
+        return R.ok();
+    }
     /**
      * 删除企业微信客户
      */
@@ -645,4 +768,45 @@ public class QwExternalContactController extends BaseController
         return util.exportExcel(qwUserDelLossLogVOS, "企微用户删除流失统计");
     }
 
+    /**
+     * 公司客户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:companyExtList')")
+    @GetMapping("/companyExtList")
+    public TableDataInfo companyExtList(QwExternalContactParam qwExternalContact)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+
+        startPage();
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+        list.forEach(item->{
+
+            if (!Objects.equals(item.getTagIds(), "[]") && item.getTagIds()!=null) {
+                QwTagSearchParam param = new QwTagSearchParam();
+                Gson gson = new Gson();
+                List<String> tagIds = gson.fromJson(
+                        item.getTagIds(),
+                        new TypeToken<List<String>>() {
+                        }.getType()
+                );
+
+                param.setTagIds(tagIds);
+
+                item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
+            }
+        });
+
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:companyTransfer')")
+    @PostMapping("/companyTransfer")
+    public R companyTransfer(@Valid @RequestBody CompanyTransferDTO param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyUser user = loginUser.getUser();
+        auditService.addAudit(param, user.getCompanyId(), user.getUserName());
+        return R.ok();
+    }
+
 }

+ 56 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferCompanyAuditController.java

@@ -0,0 +1,56 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.qw.domain.QwExternalContactTransferCompanyAudit;
+import com.fs.qw.service.IQwExternalContactTransferCompanyAuditService;
+import com.fs.qw.service.IQwExternalContactTransferCompanyAuditUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/externalContactTransferCompanyAudit")
+public class QwExternalContactTransferCompanyAuditController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactTransferCompanyAuditService auditService;
+    @Autowired
+    private IQwExternalContactTransferCompanyAuditUserService auditUserService;
+    @Autowired
+    private TokenService tokenService;
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwExternalContactTransferCompanyAudit param) {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setOperCompanyId(companyId);
+        List<QwExternalContactTransferCompanyAudit> list = auditService.selectQwExternalContactTransferCompanyAuditList(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:sync')")
+    @Log(title = "公司转接同步", businessType = BusinessType.INSERT)
+    @PostMapping("/sync/{auditId}")
+    public R sync(@PathVariable("auditId") Long auditId) {
+        auditService.syncTransfer(auditId);
+        return R.ok();
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:externalContactTransferCompanyAudit:detail')")
+    @GetMapping("/detail/{auditId}")
+    public AjaxResult detail(@PathVariable Long auditId) {
+        return AjaxResult.success(auditUserService.getListByAuditId(auditId));
+    }
+}

+ 86 - 4
fs-company/src/main/java/com/fs/company/controller/qw/QwSopController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
@@ -17,21 +18,29 @@ import com.fs.framework.service.TokenService;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.QwUserVO;
 import com.fs.sop.domain.QwSop;
+import com.fs.sop.domain.QwSopTempContent;
+import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
 import com.fs.sop.service.ICompanySopRoleService;
 import com.fs.sop.service.IQwSopService;
+import com.fs.sop.service.IQwSopTempContentService;
+import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.vo.SopVoiceListVo;
+import org.apache.commons.beanutils.ConvertUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 企微sopController
@@ -64,6 +73,13 @@ public class QwSopController extends BaseController
     @Autowired
     private IQwUserService iQwUserService;
 
+
+    @Autowired
+    private IQwSopTempContentService qwSopTempContentService;
+
+    @Autowired
+    private IQwSopTempVoiceService voiceService;
+
     /**
      * 查询企微sop列表
      */
@@ -228,8 +244,13 @@ public class QwSopController extends BaseController
         qwSop.setCompanyId(companyId);
         qwSop.setCreateBy(loginUser.getUser().getNickName());
         qwSop.setCreateTime(sdf.format(new Date()));
-
-        return toAjax(qwSopService.insertQwSop(qwSop));
+        int count = qwSopService.insertQwSop(qwSop);
+        if(count > 0){
+            if(qwSop.getQwUserIds() != null){
+                updateTempVoiceInfo(qwSop);
+            }
+        }
+        return toAjax(count);
 
     }
 
@@ -241,7 +262,14 @@ public class QwSopController extends BaseController
     @PutMapping
     public R edit(@RequestBody QwSop qwSop)
     {
-        return qwSopService.updateQwSop(qwSop);
+        R sop = qwSopService.updateQwSop(qwSop);
+        String code = sop.get("code").toString();
+        if(code.equals("200")){
+            if(qwSop != null && qwSop.getQwUserIds() != null){
+                updateTempVoiceInfo(qwSop);
+            }
+        }
+        return sop;
     }
 
 
@@ -267,6 +295,52 @@ public class QwSopController extends BaseController
         return toAjax(qwSopService.updateAutoSopTime(param));
     }
 
+    /**
+     * 修改qwSop任务,新增或者删除qwUser
+     * @param qwSop
+     */
+    private void updateTempVoiceInfo(QwSop qwSop) {
+        try {
+            String tempId = qwSop.getTempId();
+            String[] split = qwSop.getQwUserIds().split(",");
+            Long[] qwUserIds = (Long[]) ConvertUtils.convert(split, Long.class);
+
+            List<QwSopTempContent> qwSopTempContentList = qwSopTempContentService.selectQwSopTempContentByTempId(tempId);
+            if(qwSopTempContentList != null && !qwSopTempContentList.isEmpty()){
+                for (QwSopTempContent qwSopTemp : qwSopTempContentList) {
+                    if(qwSopTemp != null && qwSopTemp.getContent() != null){
+                        String content = qwSopTemp.getContent();
+                        JSONObject jsonObject = JSONObject.parseObject(content);
+                        String voiceTxt = jsonObject.getString("value");
+                        String contentType = jsonObject.getString("contentType");
+                        if("7".equals(contentType)){
+                            List<QwUserVO> qwUserVoS = qwUserService.selectQwUserVOByIds(qwUserIds);
+                            if(qwUserVoS != null && !qwUserVoS.isEmpty()){
+                                List<Long> companyUserIdList = qwUserVoS.stream().map(QwUserVO::getCompanyUserId).collect(Collectors.toList());
+                                for (Long companyUserId : companyUserIdList) {
+                                    QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId,voiceTxt);
+                                    if(qwSopTempVoice == null){
+                                        if(companyUserId != null && voiceTxt != null){
+                                            QwSopTempVoice tempVoice = new QwSopTempVoice();
+                                            tempVoice.setVoiceTxt(voiceTxt);
+                                            tempVoice.setCompanyUserId(companyUserId);
+                                            tempVoice.setRecordType(0);
+                                            tempVoice.setCreateTime(LocalDateTime.now());
+                                            tempVoice.setTempId(tempId);
+                                            voiceService.insertQwSopTempVoice(tempVoice);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            logger.error("修改企微sop任务,新增或者删除qwUser异常:" + e.getMessage());
+        }
+    }
+
     /**
      * 删除企微sop
      */
@@ -307,7 +381,15 @@ public class QwSopController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:sop:updateSopQwUser')")
     public R updateSopQwUser(@RequestBody QwSopEditQwUserParam param)
     {
-        return qwSopService.updateSopQwUser(param);
+        R sop = qwSopService.updateSopQwUser(param);
+        String code = sop.get("code").toString();
+        if(code.equals("200")){
+            QwSop qwSop = qwSopService.selectQwSopById(param.getId());
+            if(qwSop != null && qwSop.getQwUserIds() != null){
+                updateTempVoiceInfo(qwSop);
+            }
+        }
+        return sop;
     }
 
     /**

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

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
@@ -392,6 +393,12 @@ public class QwUserController extends BaseController
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("1")){
+            qwUser.setCompanyUserId(loginUser.getUser().getUserId());
+        }else if (ObjectUtil.isNotEmpty(qwUser.getIsRemark())&&qwUser.getIsRemark().equals("2")){
+            qwUser.setDeptId(loginUser.getUser().getDeptId());
+            qwUser.setCorpId(null);
+        }
 
         List<QwUserVO> list = qwUserService.selectQwUserListVO(qwUser);
         return getDataTable(list);
@@ -842,4 +849,13 @@ public class QwUserController extends BaseController
         return R.ok();
     }
 
+    @GetMapping("/companyQwUserlist")
+    public TableDataInfo companyQwUserlist(@RequestParam Long companyId,
+                                           @RequestParam String corpId,
+                                           @RequestParam(required = false) String nickName)
+    {
+        startPage();
+        List<QwUserVO> list = qwUserService.selectQwUserListByCompanyIdAndCorpIdAndNickName(companyId, corpId, nickName);
+        return getDataTable(list);
+    }
 }

+ 29 - 0
fs-company/src/main/java/com/fs/company/utils/QwStatusEnum.java

@@ -0,0 +1,29 @@
+package com.fs.company.utils;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/16 下午3:00
+ */
+public enum QwStatusEnum {
+
+    BOUND(1, "已绑定"),
+    UNBOUND(0, "未绑定");
+
+    private Integer code;
+
+    private String detail;
+
+    QwStatusEnum(Integer code, String detail) {
+        this.code = code;
+        this.detail = detail;
+    }
+
+    public int getCode(){
+        return this.code;
+    }
+
+    public String getDetail() {
+        return this.detail;
+    }
+}

+ 11 - 20
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java

@@ -1,6 +1,9 @@
 package com.fs.hisStore.controller;
 
+import cn.hutool.core.lang.TypeReference;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
@@ -10,9 +13,11 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.domain.FsIntegralOrder;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
+import com.fs.his.mapper.FsIntegralGoodsMapper;
 import com.fs.his.param.FsIntegralOrderCreateParam;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.IFsExpressService;
@@ -26,7 +31,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.util.List;
+import java.util.*;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
 import static com.fs.his.utils.PhoneUtil.decryptPhone;
@@ -45,6 +50,9 @@ public class FsIntegralOrderController extends BaseController
     private IFsIntegralOrderService fsIntegralOrderService;
     @Autowired
     private IFsExpressService expressService;
+
+    @Autowired
+    private FsIntegralGoodsMapper fsIntegralGoodsMapper;
     /**
      * 查询积分商品订单列表
      */
@@ -66,25 +74,8 @@ public class FsIntegralOrderController extends BaseController
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsIntegralOrder fsIntegralOrder)
-    {
-        List<FsIntegralOrder> list = fsIntegralOrderService.selectFsIntegralOrderList(fsIntegralOrder);
-        for (FsIntegralOrder vo : list) {
-            //商品名称以及原价赋值
-            String itemJson = vo.getItemJson();
-            if(StringUtils.isNotBlank(itemJson)){
-                JSONObject jsonObject = JSONObject.parseObject(itemJson);
-                vo.setGoodsName(jsonObject.getString("goodsName"));
-                vo.setOtPrice(jsonObject.getBigDecimal("otPrice"));
-            }
-            if (vo.getUserPhone()!=null&&!vo.getUserPhone().equals("")){
-                if(vo.getUserPhone().chars().allMatch(Character::isDigit)){continue;}
-//                vo.setUserPhone(vo.getUserPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
-                vo.setUserPhone(PhoneUtil.decryptPhone(vo.getUserPhone()));
-            }
-        }
-        ExcelUtil<FsIntegralOrder> util = new ExcelUtil<FsIntegralOrder>(FsIntegralOrder.class);
-        return util.exportExcel(list, "积分商品订单数据");
+    public AjaxResult export(FsIntegralOrder fsIntegralOrder) {
+        return fsIntegralOrderService.export(fsIntegralOrder);
     }
     /**
      * 发货

+ 2 - 2
fs-company/src/main/resources/application.yml

@@ -4,12 +4,12 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-#    active: dev
+#    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft
+    active: dev-jnlzjk
 #    active: dev-yjb
-    active: druid-myhk-test

+ 2 - 2
fs-doctor-app/src/main/resources/application.yml

@@ -5,5 +5,5 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-myhk-test
-#    active: druid-jnmy
+#    active: dev
+    active: dev-yjb

+ 69 - 20
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.service.IpadSendServer;
 import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
@@ -12,8 +13,12 @@ import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.ipad.vo.BaseVo;
 import com.fs.qw.domain.QwIpadServer;
+import com.fs.qw.domain.QwPushCount;
+import com.fs.qw.domain.QwRestrictionPushRecord;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwIpadServerMapper;
+import com.fs.qw.mapper.QwPushCountMapper;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
@@ -55,6 +60,8 @@ public class SendMsg {
     private final AsyncSopTestService asyncSopTestService;
     private final ICompanyMiniappService companyMiniappService;
     private final IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+    private final QwPushCountMapper qwPushCountMapper;
+    private final QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
 
     @Value("${group-no}")
     private String groupNo;
@@ -65,7 +72,7 @@ public class SendMsg {
     @Qualifier("customThreadPool")
     private ThreadPoolTaskExecutor customThreadPool;
 
-    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService) {
+    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheT<Long> redisCache, AsyncSopTestService asyncSopTestService, ICompanyMiniappService companyMiniappService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper) {
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.sendServer = sendServer;
@@ -76,6 +83,8 @@ public class SendMsg {
         this.asyncSopTestService = asyncSopTestService;
         this.companyMiniappService = companyMiniappService;
         this.fsCoursePlaySourceConfigService = fsCoursePlaySourceConfigService;
+        this.qwPushCountMapper = qwPushCountMapper;
+        this.qwRestrictionPushRecordMapper = qwRestrictionPushRecordMapper;
     }
     private List<QwUser> getQwUserList() {
         if (qwUserList.isEmpty()) {
@@ -92,8 +101,8 @@ public class SendMsg {
 
     private Map<String, FsCoursePlaySourceConfig> getMiniMap() {
         List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(new QueryWrapper<FsCoursePlaySourceConfig>().ne("type", 2).eq("is_del", 0));
-        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
-        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
+//        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
+//        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
         return PubFun.listToMapByGroupObject(list, FsCoursePlaySourceConfig::getAppid);
     }
 
@@ -191,27 +200,67 @@ public class SendMsg {
                 continue;
             }
             redisCache.setCacheObject(key, System.currentTimeMillis(), 24, TimeUnit.HOURS);
+            List<QwPushCount> pushCountList = qwPushCountMapper.selectQwPushCountLists();
+            Map<Integer, List<QwPushCount>> pushMap = pushCountList.stream().collect(Collectors.groupingBy(QwPushCount::getType));
             // 循环发送消息里面的每一条消息
             for (QwSopCourseFinishTempSetting.Setting content : setting.getSetting()) {
                 long start4 = System.currentTimeMillis();
-                // 发送
-                sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
-                long end4 = System.currentTimeMillis();
-                log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - start4);
-                if(content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks()) || "请求失败:请求频率异常".equals(content.getSendRemarks()))){
-                    QwUser update = new QwUser();
-                    update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
-                    update.setUpdateTime(new Date());
-                    qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
-                    redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
-                    return;
+                //判断当前销售推送客户消息限制
+                Long qwUserId = qwUser.getId();//销售的Id
+                Integer type = Integer.valueOf(content.getContentType());//发送消息的类型
+                Long customerId = qwSopLogs.getExternalId();//客户ID
+                Long companyId = qwSopLogs.getCompanyId();//公司ID
+                Integer pushCount = -99;
+                if(pushMap.containsKey(type)){
+                    List<QwPushCount> qwPushCounts = pushMap.get(type);
+                    Optional<QwPushCount> optional = qwPushCounts.stream().filter(e -> Objects.equals(e.getCompanyId(), companyId)).findFirst();
+                    if(optional.isPresent()){
+                        pushCount = optional.get().getPushCount();
+                    }else{
+                        Optional<QwPushCount> nullCount = qwPushCounts.stream().filter(e -> e.getCompanyId() == null).findFirst();
+                        if(nullCount.isPresent()){
+                            pushCount = nullCount.get().getPushCount();
+                        }
+                    }
                 }
-                try {
-                    int delay = ThreadLocalRandom.current().nextInt(300, 1000);
-                    log.debug("pad发送消息等待:{}ms", delay);
-                    Thread.sleep(delay);
-                } catch (InterruptedException e) {
-                    log.error("线程等待错误!");
+                //查询是否有设置限制客服推送消息次数
+//                    Integer pushCount=pushCountMap.containsKey(String.valueOf(companyId)) ? pushCountMap.get(String.valueOf(companyId)): pushCountMap.getOrDefault(String.valueOf(type), -99);
+                int salesPushCustomerMessageCount = qwRestrictionPushRecordMapper.selectQwRestrictionPushRecord(qwUserId, customerId, type, DateUtils.toStartTime(), DateUtils.toEndTime());
+                if (pushCount != -99 && salesPushCustomerMessageCount >= pushCount) {
+                    content.setSendStatus(2);//设置发送失败状态
+                    content.setSendRemarks("发送次数达到上限");
+                } else {
+                    // 发送
+                    sendServer.send(content, user, qwSopLogs, miniMap, parentVo);
+                    //判断销售推送成功:保存记录
+                    if (content.getSendStatus() != 2) {
+                        QwRestrictionPushRecord qrpr = new QwRestrictionPushRecord();
+                        qrpr.setType(type);
+                        qrpr.setQwUserId(qwUserId);
+                        qrpr.setQwExternalId(customerId);
+                        qrpr.setCompanyId(companyId);
+                        qrpr.setStatus(1);
+                        qrpr.setCreateTime(DateUtils.getTime());
+                        qrpr.setTime(System.currentTimeMillis());
+                        qwRestrictionPushRecordMapper.insert(qrpr);
+                    }
+                    long end4 = System.currentTimeMillis();
+                    log.info("请求pad发送完成:{}, {}, 时长4:{}", user.getQwUserName(), qwSopLogs.getId(), end4 - start4);
+                    if(content.getSendStatus() == 2 && ("请求失败:消息发送过于频繁,请稍后再试".equals(content.getSendRemarks()) || "请求失败:请求频率异常".equals(content.getSendRemarks()))){
+                        QwUser update = new QwUser();
+                        update.setRemark("请求频率异常,暂停发送,三小时后恢复继续发送");
+                        update.setUpdateTime(new Date());
+                        qwUserMapper.update(update, new QueryWrapper<QwUser>().eq("id", user.getId()));
+                        redisCache.setCacheObject("qw:user:id:" + user.getId(), user.getId(), 3, TimeUnit.HOURS);
+                        return;
+                    }
+                    try {
+                        int delay = ThreadLocalRandom.current().nextInt(300, 1000);
+                        log.debug("pad发送消息等待:{}ms", delay);
+                        Thread.sleep(delay);
+                    } catch (InterruptedException e) {
+                        log.error("线程等待错误!");
+                    }
                 }
             }
             // 推送 APP

+ 29 - 1
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -16,10 +16,12 @@ import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwUserVideo;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwUserVideoService;
 import com.fs.qw.service.IQwUserVoiceLogService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
@@ -57,6 +59,8 @@ public class QwMsgController {
     @Autowired
     IQwUserService qwUserService;
     @Autowired
+    IQwUserVideoService qwUserVideoService;
+    @Autowired
     IQwUserVoiceLogService qwUserVoiceLogService;
     @Autowired
     IFsStoreOrderService fsStoreOrderService;
@@ -304,7 +308,7 @@ public class QwMsgController {
                 if (wxWorkMessageDTO.getReferid()!=0){
                     break;
                 }
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
@@ -342,6 +346,30 @@ public class QwMsgController {
                     else if (wxWorkMessageDTO.getMsgtype() == 104){
                         content = wxWorkMessageDTO.getUrl();
                         log.info("id:{}, 用户发送表情"+content, id);
+                    }//视频号
+                    else if (wxWorkMessageDTO.getMsgtype()==141){
+                        QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
+                        if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
+                            QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
+                            if(qwUserVideo == null){
+                                QwUserVideo userVideo=new QwUserVideo();
+                                userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
+                                userVideo.setAppKey(qwUserByAppKey.getAppKey());
+                                userVideo.setNickName(wxWorkMessageDTO.getNickname());
+                                userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
+                                userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
+                                userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
+                                userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
+                                userVideo.setDesc(wxWorkMessageDTO.getDesc());
+                                userVideo.setUrl(wxWorkMessageDTO.getUrl());
+                                userVideo.setExtras(wxWorkMessageDTO.getExtras());
+                                userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
+                                userVideo.setQwUserId(qwUserByAppKey.getId());
+                                userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
+                                userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
+                                qwUserVideoService.insertQwUserVideo(userVideo);
+                            }
+                        }
                     }
 
                     if (2000000000000000L-receiver>0){

+ 42 - 5
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -2,12 +2,14 @@ package com.fs.app.controller;
 
 
 import cn.hutool.core.date.DateUtil;
-import com.fs.app.taskService.QwExternalContactRatingService;
-import com.fs.app.taskService.SopLogsChatTaskService;
-import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.app.taskService.SopWxLogsService;
+import com.fs.app.taskService.*;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.service.*;
@@ -15,10 +17,12 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.utils.qrcode.QRCodeUtils;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwMaterialService;
+import com.fs.qwApi.domain.QwExternalContactResult;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
@@ -37,6 +41,7 @@ import org.springframework.web.bind.annotation.RestController;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -64,6 +69,8 @@ public class CommonController {
     private IFsCourseWatchLogService watchLogService;
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
 
     @Autowired
     private IQwSopLogsService qwSopLogsService;
@@ -76,6 +83,10 @@ public class CommonController {
 
     @Autowired
     private IFsCourseLinkService courseLinkService;
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+    @Autowired
+    private ICompanyService companyService;
 
     @Autowired
     private SopUserLogsMapper sopUserLogsMapper;
@@ -105,10 +116,13 @@ public class CommonController {
     @Autowired
     private IQwMaterialService iQwMaterialService;
 
-
     @Autowired
     private IFsCourseLinkService iFsCourseLinkService;
 
+    @Autowired
+    private SyncQwExternalContactService syncQwExternalContactService;
+
+
     /**
     * 发官方通连
     */
@@ -298,4 +312,27 @@ public class CommonController {
         }
         return R.ok();
     }
+    @GetMapping("/updateRedPack")
+    public R updateRedPack(String start , String end    ){
+        LocalDateTime startTime = DateUtil.parseLocalDateTime(start);
+        LocalDateTime endTime = DateUtil.parseLocalDateTime(end);
+        List<RedPacketMoneyVO> redPacketMoneyVOS = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogHourseByCompany(startTime, endTime);
+        for (RedPacketMoneyVO redPacketMoneyVO : redPacketMoneyVOS) {
+            companyService.subtractCompanyMoneyHourse(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId(), startTime.toLocalTime(), endTime.toLocalTime());
+        }
+        return R.ok();
+    }
+
+    @GetMapping("/syncQwExternalContactUnionid")
+    public R syncQwExternalContactUnionid(){
+        return syncQwExternalContactService.syncQwExternalContactUnionid();
+    }
+
+
+    @GetMapping("/queryRedPacketResult")
+    public R queryRedPacketResult(String startTime , String  endTime) {
+        fsCourseRedPacketLogService.queryRedPacketResult(startTime, endTime);
+        return R.ok();
+    }
+
 }

+ 11 - 0
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -78,6 +78,9 @@ public class qwTask {
     @Autowired
     private QwExternalContactRatingMoreSevenDaysService qwExternalContactRatingMoreSevenDaysService;
 
+    @Autowired
+    private SyncQwExternalContactService syncQwExternalContactService;
+
     /**
      * 定时任务:检查SOP规则时间
      * 执行时间:每天凌晨 1:10:00
@@ -374,4 +377,12 @@ public class qwTask {
         long endTimeMillis = System.currentTimeMillis();
         log.info("====== 更新掉所有前一天的所有待发送,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }
+
+    @Scheduled(cron = "0 1 0 */2 * ?")
+    public void updateQwExternalContactUnionid() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 同步外部联系人的UnionId ======");
+        syncQwExternalContactService.syncQwExternalContactUnionid();
+
+    }
 }

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/SyncQwExternalContactService.java

@@ -0,0 +1,10 @@
+package com.fs.app.taskService;
+
+import com.fs.common.core.domain.R;
+
+public interface SyncQwExternalContactService {
+
+    R syncQwExternalContactUnionid();
+
+
+}

+ 271 - 35
fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java

@@ -10,14 +10,22 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+
+import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.client.producer.SendCallback;
 import org.apache.rocketmq.client.producer.SendResult;
+import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.apache.rocketmq.spring.support.RocketMQHeaders;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.support.MessageBuilder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import java.util.Optional;
+import java.util.concurrent.*;
 
 @Slf4j
 @Service
@@ -36,68 +44,296 @@ public class AsyncCourseWatchFinishService {
     @Autowired
     RedisCache redisCache;
 
+    // 重试队列和调度器
+    private final BlockingQueue<RetryMessage> retryQueue = new LinkedBlockingQueue<>(10000);
+    private final ScheduledExecutorService retryExecutor = Executors.newSingleThreadScheduledExecutor();
+
+    // 主题映射配置
+    private static final String TOPIC = "course-finish-notes";
+
+    @PostConstruct
+    public void init() {
+        // 启动重试任务,每5秒处理一次重试队列
+        retryExecutor.scheduleWithFixedDelay(this::processRetryQueue, 10, 5, TimeUnit.SECONDS);
+        log.info("AsyncCourseWatchFinishService 重试队列处理器已启动");
+    }
+
     /**
     * 异步处理完课打备注的
     */
     @Async("scheduledExecutorService")
     public void executeCourseWatchFinish(FsCourseWatchLog finishLog) {
+//        原代码
+//        FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
+//        watchLog.setFinishTime(finishLog.getFinishTime());
+//        watchLog.setQwUserId(finishLog.getQwUserId());
+//
+//
+//        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
+//        if (qwUserByRedis == null) {
+//            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
+//            return;
+//        }
+//
+//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
+//
+//        if (qwCompany == null) {
+//            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(),watchLog);
+//            return;
+//        }
+//
+//        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
+//            @Override public void onSuccess(SendResult sendResult) {
+//                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+//            }  // 空实现
+//            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//        });
+
+
+//        // 定义默认值
+//         final Integer DEFAULT_SERVER_NUM = 99;
+//
+//        // 使用
+//        Integer companyServerNum = Optional.ofNullable(qwCompany.getCompanyServerNum())
+//                .orElse(DEFAULT_SERVER_NUM);
+//        switch (companyServerNum){
+//            case 1:
+//                rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
+//                    @Override public void onSuccess(SendResult sendResult) {
+//                     log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+//                     }  // 空实现
+//                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//                });
+//                break;
+//            case 2:
+//
+//                rocketMQTemplate.asyncSend("course-finish-notesTwo", JSON.toJSONString(finishLog),     new SendCallback() {
+//                    @Override public void onSuccess(SendResult sendResult) {}  // 空实现
+//                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败2:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//                });
+//                break;
+//            case 3:
+//                rocketMQTemplate.asyncSend("course-finish-notesThree", JSON.toJSONString(finishLog),     new SendCallback() {
+//                    @Override public void onSuccess(SendResult sendResult) {}  // 空实现
+//                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败3:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//                });
+//                break;
+//            default:
+//                break;
+//        }
+
+
+        // 1. 数据验证和准备
+        ValidationResult validationResult = validateAndPrepareData(finishLog);
+        if (!validationResult.isValid()) {
+            return;
+        }
+
+
+        //  2. 发送消息(使用Tag区分)
+        sendWithFlowControl(finishLog, validationResult, 0);
+
+    }
 
+    /**
+     * 数据验证和准备
+     */
+    private ValidationResult validateAndPrepareData(FsCourseWatchLog finishLog) {
+        // 准备日志对象
         FsCourseWatchLog watchLog = new FsCourseWatchLog();
         watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
         watchLog.setFinishTime(finishLog.getFinishTime());
         watchLog.setQwUserId(finishLog.getQwUserId());
 
-
+        // 验证企微用户信息
         QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
         if (qwUserByRedis == null) {
             log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
-            return;
+            return ValidationResult.invalid();
         }
 
+        // 验证企业主体
         QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
-
         if (qwCompany == null) {
-            log.error("企业微信主体为空 {} 跳过处理。", qwUserByRedis.getCorpId());
+            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(), watchLog);
+            return ValidationResult.invalid();
+        }
+
+        return ValidationResult.valid(watchLog, qwUserByRedis, qwCompany);
+    }
+
+
+    /**
+     * 带流控处理的消息发送
+     */
+    private void sendWithFlowControl(FsCourseWatchLog finishLog,
+                                     ValidationResult validationResult, int retryCount) {
+        if (retryCount >= 3) {
+            log.warn("消息重试超过最大次数,转入重试队列: topic={}, qwUserId={}",
+                    TOPIC, finishLog.getQwUserId());
+            offerToRetryQueue(finishLog, validationResult);
             return;
         }
 
-        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
-            @Override public void onSuccess(SendResult sendResult) {}  // 空实现
-            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+        rocketMQTemplate.asyncSend(TOPIC, JSON.toJSONString(finishLog), new SendCallback() {
+            @Override
+            public void onSuccess(SendResult sendResult) {
+                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+            }
+
+            @Override
+            public void onException(Throwable e) {
+                if (isFlowControlException(e)) {
+                    // 流控异常处理
+                    handleFlowControlRetry(TOPIC, finishLog, validationResult, retryCount, e);
+                    log.error("推送完课打备注失败1流控异常:finishLog={},e={}",JSON.toJSONString(finishLog),e.getMessage());
+                } else {
+                    // 其他异常
+                    log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());
+                }
+            }
         });
+    }
+
+    /**
+     * 放入重试队列
+     */
+    private void offerToRetryQueue(FsCourseWatchLog finishLog,
+                                   ValidationResult validationResult) {
+        RetryMessage retryMessage = new RetryMessage(finishLog, validationResult);
+        boolean offered = retryQueue.offer(retryMessage);
+        if (offered) {
+            log.info("消息已加入重试队列: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+        } else {
+            log.error("重试队列已满,消息可能丢失: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+            // 这里可以接入告警系统
+        }
+    }
+
+    /**
+     * 处理重试队列
+     */
+    private void processRetryQueue() {
+        try {
+            int processedCount = 0;
+            RetryMessage retryMessage;
+
+            while (processedCount < 100 && (retryMessage = retryQueue.poll()) != null) {
+                try {
+                    // 重新发送消息
+                    sendWithFlowControl(retryMessage.getFinishLog(),
+                            retryMessage.getValidationResult(), 0);
+                    processedCount++;
+
+                    Thread.sleep(10);
+                } catch (Exception e) {
+                    log.error("重试队列处理失败: {}", e.getMessage());
+                    offerToRetryQueue(retryMessage.getFinishLog(), retryMessage.getValidationResult());
+                }
+            }
+
+            if (processedCount > 0) {
+                log.debug("重试队列处理完成,本次处理数量: {}", processedCount);
+            }
+        } catch (Exception e) {
+            log.error("处理重试队列异常: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 判断是否为流控异常
+     */
+    private boolean isFlowControlException(Throwable e) {
+        if (e instanceof MQClientException) {
+            return ((MQClientException) e).getResponseCode() == 215;
+        }
+        // 检查异常链
+        Throwable cause = e.getCause();
+        if (cause instanceof MQClientException) {
+            return ((MQClientException) cause).getResponseCode() == 215;
+        }
+        return false;
+    }
+
+    /**
+     * 流控重试处理
+     */
+    private void handleFlowControlRetry(String topic, FsCourseWatchLog finishLog,
+                                        ValidationResult validationResult, int retryCount, Throwable e) {
+        long backoffTime = calculateBackoffTime(retryCount);
+        log.warn("流控触发,{}ms后第{}次重试: topic={}, qwUserId={}",
+                backoffTime, retryCount + 1, topic, finishLog.getQwUserId());
 
+        // 使用 ScheduledExecutorService 进行延迟执行
+        retryExecutor.schedule(() -> {
+            try {
+                sendWithFlowControl(finishLog, validationResult, retryCount + 1);
+            } catch (Exception ex) {
+                log.error("延迟重试执行异常: {}", ex.getMessage(), ex);
+            }
+        }, backoffTime, TimeUnit.MILLISECONDS);
+    }
+    /**
+     * 计算退避时间(指数退避)
+     */
+    private long calculateBackoffTime(int retryCount) {
+        return Math.min(1000 * (long) Math.pow(2, retryCount), 10000); // 最大10秒
+    }
+
+    @PreDestroy
+    public void destroy() {
+        retryExecutor.shutdown();
+        try {
+            if (!retryExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
+                retryExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            retryExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("AsyncCourseWatchFinishService 已关闭");
+    }
 
-        // 定义默认值
-         final Integer DEFAULT_SERVER_NUM = 99;
-
-        // 使用
-        Integer companyServerNum = Optional.ofNullable(qwCompany.getCompanyServerNum())
-                .orElse(DEFAULT_SERVER_NUM);
-        switch (companyServerNum){
-            case 1:
-                rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
-                    @Override public void onSuccess(SendResult sendResult) {}  // 空实现
-                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
-                });
-                break;
-            case 2:
-
-                rocketMQTemplate.asyncSend("course-finish-notesTwo", JSON.toJSONString(finishLog),     new SendCallback() {
-                    @Override public void onSuccess(SendResult sendResult) {}  // 空实现
-                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败2:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
-                });
-                break;
-            case 3:
-                rocketMQTemplate.asyncSend("course-finish-notesThree", JSON.toJSONString(finishLog),     new SendCallback() {
-                    @Override public void onSuccess(SendResult sendResult) {}  // 空实现
-                    @Override public void onException(Throwable e) {log.error("推送完课打备注失败3:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
-                });
-                break;
-            default:
-                break;
+    // 内部辅助类
+    private static class ValidationResult {
+        private final boolean valid;
+        private final FsCourseWatchLog watchLog;
+        private final QwUser qwUser;
+        private final QwCompany qwCompany;
+
+        public ValidationResult(boolean valid, FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            this.valid = valid;
+            this.watchLog = watchLog;
+            this.qwUser = qwUser;
+            this.qwCompany = qwCompany;
         }
 
+        public static ValidationResult valid(FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            return new ValidationResult(true, watchLog, qwUser, qwCompany);
+        }
+
+        public static ValidationResult invalid() {
+            return new ValidationResult(false, null, null, null);
+        }
+
+        public boolean isValid() { return valid; }
+        public FsCourseWatchLog getWatchLog() { return watchLog; }
+        public QwUser getQwUser() { return qwUser; }
+        public QwCompany getQwCompany() { return qwCompany; }
+    }
+
+    private static class RetryMessage {
+        private final FsCourseWatchLog finishLog;
+        private final ValidationResult validationResult;
+
+        public RetryMessage(FsCourseWatchLog finishLog, ValidationResult validationResult) {
+            this.finishLog = finishLog;
+            this.validationResult = validationResult;
+        }
 
+        public FsCourseWatchLog getFinishLog() { return finishLog; }
+        public ValidationResult getValidationResult() { return validationResult; }
     }
 
 }

+ 277 - 4
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -76,6 +76,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
+
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 
     private static final String QWSOP_KEY_PREFIX = "qwsop:";
@@ -674,11 +675,40 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if(content.getSetting() == null){
             return;
         }
+        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType())).collect(Collectors.toList());
+        if (!setting.isEmpty()) {
+            List<String> valuesList = PubFun.listToNewList(setting, QwSopTempSetting.Content.Setting::getValue);
+            if (valuesList != null && !valuesList.isEmpty()) {
+                try {
+                    List<QwSopTempVoice> voiceList = qwSopTempVoiceService.getVoiceByText(Long.parseLong(companyUserId), valuesList);
+                    if (voiceList != null && !voiceList.isEmpty()) {
+                        Map<String, QwSopTempVoice> collect = voiceList.stream().collect(Collectors.toMap(QwSopTempVoice::getVoiceTxt, e -> e));
+                        setting.parallelStream().filter(e -> "7".equals(e.getContentType())).forEach(st -> {
+                            QwSopTempVoice voice = collect.get(st.getValue());
+                            if (voice.getVoiceUrl() == null) {
+                                return;
+                            }
+                            st.setVoiceUrl(voice.getVoiceUrl());
+                            st.setVoiceDuration(voice.getDuration() + "");
+                        });
+                    }
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
 //        // 发送语音 end
         if (content.getType()==5){
             sopAddTag(logVo,content,sendTime);
         }
 
+        //当语音模板的qw_sop_temp_voice中无对应语音,就不生成qw_sop_logs记录
+        if (content.getType() == 7 && content.getSetting() != null && !content.getSetting().isEmpty()) {
+            if (content.getSetting().get(0).getVoiceUrl() == null) {
+                return;
+            }
+        }
+
         if (StringUtils.isNotEmpty(logVo.getChatId())) {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             ruleTimeVO.setSendType(6);
@@ -839,11 +869,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             case 5:
 //                handleTagMessage(sopLogs, content);
                 break;
+            case 7:
+                handleVoiceMessage(sopLogs, content, companyUserId);
+                break;
             default:
                 log.error("未知的消息类型 {},跳过处理。", type);
                 break;
         }
     }
+    private void handleVoiceMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
+        sopLogs.setContentJson(JSON.toJSONString(content));
+        enqueueQwSopLogs(sopLogs);
+    }
 
     private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {
 
@@ -863,6 +900,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                      Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
                     Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType,
                                      List<Company> companies) {
+        QwExternalContact contact = null;
+        if(logVo.getExternalId() != null){
+            contact = qwExternalContactMapper.selectById(logVo.getExternalId());
+        }
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -932,6 +973,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 } else {
                                     setting.setValue(currentValue
                                             .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
+                                            .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
                                             + "\n" + link);
                                 }
                             }
@@ -942,7 +984,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     } else {
                         if ("1".equals(setting.getContentType())) {
                             setting.setValue(setting.getValue()
-                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText));
+                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
+                                    .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus()));
                         }
                     }
                     break;
@@ -952,7 +995,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
-                            qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
+                            qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId(), isGroupChat ? groupChat.getChatId() : null);
 
                     if(sopLogs.getSendType()==1){
                         setting.setMiniprogramAppid(miniAppId);
@@ -1287,7 +1330,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                        Long courseId, Long videoId, String qwUserId,
-                                       String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId) {
+                                       String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId, String chatId) {
         // 获取缓存的配置
         CourseConfig config;
         synchronized(configLock) {
@@ -1315,6 +1358,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         link.setCourseId(courseId.longValue());
         link.setQwExternalId(Long.parseLong(externalId));
         link.setProjectCode(cloudHostProper.getProjectCode());
+        link.setChatId(chatId);
 
         if (StringUtil.strIsNullOrEmpty(isOfficial)){
             link.setLinkType(3);
@@ -1925,7 +1969,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if (settings == null) {
             return null;
         }
-
+        //完课后若是小程序发送另外一堂课
+        saveWacthLogOfCourseLink(settings,sopLogs,newTimeString,finishLog,finishTemp);
         // 处理音频内容
 //        for (QwSopCourseFinishTempSetting.Setting st : settings) {
 //            if (st.getContentType().equals("7")) {
@@ -1944,6 +1989,234 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         return sopLogs;
     }
 
+    /**
+     * 判定小程序的话新增创建看课记录,以及fsCourseLink
+     *
+     * @param settings
+     */
+    public void saveWacthLogOfCourseLink(List<QwSopCourseFinishTempSetting.Setting> settings, QwSopLogs sopLogs,  String newTimeString, FsCourseWatchLog finishLog, FsCourseFinishTemp finishTemp){
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+        Date dataTime = new Date();
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(sopLogs.getCorpId());
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(sopLogs.getCorpId(), sopLogs.getQwUserid());
+        if (qwUser == null){
+            return;
+        }
+        for (QwSopCourseFinishTempSetting.Setting st : settings) {
+            switch (st.getContentType()) {
+                //小程序单独
+                case "4":
+                    addWatchLogIfNeeded(sopLogs.getSopId(), st.getVideoId().intValue(), st.getCourseId().intValue(), sopLogs.getFsUserId(),  String.valueOf(qwUser.getId()),qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),
+                            sopLogs.getExternalId(), newTimeString.substring(0, 10), dataTime);
+
+                    String linkByMiniApp = createLinkByMiniApp(st, sopLogs.getCorpId(), dataTime, finishTemp.getCourseId().intValue(), Integer.valueOf(st.getVideoId().toString()),
+                            String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), sopLogs.getExternalId(), config);
+
+
+                    String miniAppId = null;
+                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(qwUser.getCompanyId()));
+                        if (integerListMap != null) {
+                            int listIndex = 0;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    miniAppId = companyMiniapp.getAppId();
+                                }
+                            }
+                        }
+                    }
+
+                    if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                        miniAppId = qwCompany.getMiniAppId();
+                    }
+
+                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                        st.setMiniprogramAppid(miniAppId);
+                    } else {
+                        log.error("企业未配置小程序-" + sopLogs.getCorpId());
+                    }
+
+                    String miniprogramTitle = st.getMiniprogramTitle();
+                    int maxLength = 17;
+                    st.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength)+"..." : miniprogramTitle);
+                    st.setMiniprogramPage(linkByMiniApp);
+                    break;
+                default:
+                    break;
+
+            }
+        }
+    }
+    private Date processDate(String sendTimeParam) {
+        // 1. 获取当前日期(年月日)
+        LocalDate currentDate = LocalDate.now();
+
+        // 2. 解析传入的时分(支持 "HH:mm" 或 "H:mm")
+        LocalTime sendTime = LocalTime.parse(sendTimeParam);
+
+        // 3. 合并为 LocalDateTime
+        LocalDateTime dateTime = LocalDateTime.of(currentDate, sendTime);
+
+        // 4. 转换为 Date(需通过 Instant 和系统默认时区)
+        Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+        return date;
+    }
+
+    /**
+     * 新增courseLink
+     *
+     * @param setting
+     * @param corpId
+     * @param sendTime
+     * @param courseId
+     * @param videoId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param config
+     * @return
+     */
+    private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
+                                       Integer courseId, Integer videoId, String qwUserId,
+                                       String companyUserId, String companyId, Long externalId, CourseConfig config) {
+
+        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+                companyUserId, companyId, externalId, 3, null);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = miniappRealLink + courseJson;
+        link.setRealLink(realLinkFull);
+
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+
+        link.setUpdateTime(updateTime);
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        return link.getRealLink();
+    }
+
+    /**
+     * 创建courselink
+     * @param corpId
+     * @param sendTime
+     * @param courseId
+     * @param videoId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param type
+     * @param chatId
+     * @return
+     */
+    public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Integer courseId, Integer videoId, String qwUserId,
+                                           String companyUserId, String companyId, Long externalId, Integer type, String chatId) {
+        // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setVideoId(videoId.longValue());
+        link.setCorpId(corpId);
+        link.setCourseId(courseId.longValue());
+        link.setChatId(chatId);
+        link.setQwExternalId(externalId);
+        link.setLinkType(type); //小程序
+        link.setUNo(UUID.randomUUID().toString());
+        link.setProjectCode(cloudHostProper.getProjectCode());
+        String randomString = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+
+        link.setCreateTime(sendTime);
+
+        return link;
+    }
+
+
+    /**
+     * 计算过期时间
+     * @param setting
+     * @param sendTime
+     * @param config
+     * @return
+     */
+    private Date createUpdateTime(QwSopCourseFinishTempSetting.Setting setting, Date sendTime, CourseConfig config) {
+
+        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
+                ? config.getVideoLinkExpireDate()
+                : setting.getExpiresDays();
+
+//         使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+        return updateTime;
+    }
+
+    /**
+     * 增加看课记录
+     *
+     * @param sopId
+     * @param videoId
+     * @param courseId
+     * @param fsUserId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param startTime
+     * @param createTime
+     * @return
+     */
+    private Long addWatchLogIfNeeded(String sopId, Integer videoId, Integer courseId,
+                                     Long fsUserId, String qwUserId, String companyUserId,
+                                     String companyId, Long externalId, String startTime, Date createTime) {
+
+        try {
+            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+            watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
+            watchLog.setQwExternalContactId(externalId);
+            watchLog.setSendType(2);
+            watchLog.setQwUserId(Long.valueOf(qwUserId));
+            watchLog.setSopId(sopId);
+            watchLog.setDuration(0L);
+            watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
+            watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
+            watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
+            watchLog.setCreateTime(createTime);
+            watchLog.setUpdateTime(createTime);
+            watchLog.setLogType(3);
+            watchLog.setUserId(fsUserId);
+            watchLog.setCampPeriodTime(convertStringToDate(startTime, "yyyy-MM-dd"));
+
+            //存看课记录
+            int i = fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+            return watchLog.getLogId();
+        } catch (Exception e) {
+            log.error("插入观看记录失败:" + e.getMessage());
+            return null;
+        }
+    }
+
+
     /**
      * 解析模板设置
      */

+ 78 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SyncQwExternalContactServiceImpl.java

@@ -0,0 +1,78 @@
+package com.fs.app.taskService.impl;
+
+import com.fs.app.taskService.SyncQwExternalContactService;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qwApi.domain.QwExternalContactResult;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+@Slf4j
+public class SyncQwExternalContactServiceImpl implements SyncQwExternalContactService {
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    private QwApiService qwApiService;
+    @Override
+    public R syncQwExternalContactUnionid() {
+        // 测试环境需要在sql加上:and corp_id='ww51717e2b71d5e2d3'
+        // 查询这次同步的最大id
+        Long maxId = qwExternalContactMapper.selectSyncMaxId();
+        log.info("同步最大id值:"+maxId);
+        if (maxId == null) {
+            return R.ok("无需同步");
+        }
+        Long recordId = 0L;
+        String recordIdStr = redisCache.getCacheObject("syncQwExternalContactUnionId");
+        if (StringUtils.isNotEmpty(recordIdStr)) {
+            try {
+                recordId = Long.parseLong(recordIdStr);
+            } catch (NumberFormatException e) {
+                log.info("Failed to parse recordId from redis: {}", recordIdStr);
+                recordId = 0L;
+            }
+        }
+        log.info("开始同步的recordId值:"+recordId);
+        // 循环同步直到recordId等于maxId
+        while (recordId < maxId) {
+            // 每次查询500条数据
+            List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectSyncData(recordId, maxId);
+            if (qwExternalContacts.isEmpty()) {
+                break;
+            }
+            List<QwExternalContact> batchList = new ArrayList<>();
+            // 调用接口
+            for (QwExternalContact info : qwExternalContacts) {
+                QwExternalContactResult externalcontact = qwApiService.getExternalcontact(info.getExternalUserId(), info.getCorpId());
+                if (null!=externalcontact && null!=externalcontact.getExternal_contact() && null!=externalcontact.getExternal_contact().getUnionid() ) {
+                    info.setUnionid(externalcontact.getExternal_contact().getUnionid());
+                    batchList.add(info);
+                }
+            }
+            if (!batchList.isEmpty()) {
+                for (QwExternalContact qwExternalContact : batchList) {
+                    qwExternalContactMapper.batchUpdateUnionId(qwExternalContact);
+                }
+            }else{
+                log.info("集合为空:{recordId->"+recordId+";syncId->"+qwExternalContacts.get(qwExternalContacts.size() - 1).getId()+"}");
+            }
+            // 更新recordId为本次处理的最后一条记录的id
+            recordId = qwExternalContacts.get(qwExternalContacts.size() - 1).getId();
+            // 更新redis中的记录值
+            redisCache.setCacheObject("syncQwExternalContactUnionId", recordId.toString());
+        }
+        log.info("同步成功,同步完之后的recordId:"+recordId);
+        return R.ok();
+    }
+}

+ 8 - 6
fs-qw-voice/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -77,12 +77,14 @@ public class RocketMQConsumerService implements RocketMQListener<String> {
                                             Long companyUserId = qwUserVO.getCompanyUserId();
                                             QwSopTempVoice qwSopTempVoice = qwSopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId,text);
                                             if(qwSopTempVoice == null){
-                                                QwSopTempVoice sopTempVoice = new QwSopTempVoice();
-                                                sopTempVoice.setCompanyUserId(companyUserId);
-                                                sopTempVoice.setVoiceTxt(text);
-                                                sopTempVoice.setTempId(tempId);
-                                                sopTempVoice.setRecordType(0);
-                                                qwSopTempVoiceService.insertQwSopTempVoice(sopTempVoice);
+                                                if(companyUserId != null && text != null){
+                                                    QwSopTempVoice sopTempVoice = new QwSopTempVoice();
+                                                    sopTempVoice.setCompanyUserId(companyUserId);
+                                                    sopTempVoice.setVoiceTxt(text);
+                                                    sopTempVoice.setTempId(tempId);
+                                                    sopTempVoice.setRecordType(0);
+                                                    qwSopTempVoiceService.insertQwSopTempVoice(sopTempVoice);
+                                                }
                                             }
                                         }
                                     }

+ 5 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java

@@ -4,6 +4,7 @@ import com.fs.app.params.SopLogsEditParam;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.fastGpt.param.FastGptChatSessionParam;
 import com.fs.fastGpt.service.IFastGptChatSessionService;
 import com.fs.qw.domain.QwTagGroup;
 import com.fs.qw.param.SopMsgParam;
@@ -95,6 +96,10 @@ public class ApisQwSopController {
         return qwSopLogsService.deleteQwSopLogsByJsApi(param);
 
     }
+    @PostMapping("/artificialInfo")
+    public R artificialInfo(@RequestBody FastGptChatSessionParam sessionParam) {
+        return R.ok().put("type",fastGptChatSessionService.selectFastGptChatSessionArtificialType(sessionParam));
+    }
 
     @GetMapping("/getQwSopLogs")
     public R getQwSopLogs(SopMsgParam param) throws Exception {

+ 11 - 2
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java

@@ -97,9 +97,18 @@ public class QwUserController extends BaseController {
         if(qwExternalContactId == null) {
             throw new CustomException("企微外部联系人id不能为空!");
         }
+//        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwExternalContactId);
+        QwExternalContactInfo contactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        if (contactInfo==null){
 
-        QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
-        return R.ok().put("data",qwExternalContactService.selectQwExternalContactById(qwExternalContactId)).put("moreInfo",qwExternalContactInfo);
+            contactInfo = new QwExternalContactInfo();
+            contactInfo.setExternalContactId(qwExternalContactId);
+            qwExternalContactInfoService.insertQwExternalContactInfo(contactInfo);
+
+        }
+
+//        return R.ok().put("data",qwExternalContact).put("moreInfo",contactInfo);
+        return R.ok().put("moreInfo",contactInfo);
     }
 
     @PostMapping("/updateQwUserInfo")

+ 165 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisQwSopController.java

@@ -0,0 +1,165 @@
+package com.fs.app.controller;
+
+import com.fs.app.params.SopLogsEditParam;
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.fastGpt.param.FastGptChatSessionParam;
+import com.fs.fastGpt.service.IFastGptChatSessionService;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.param.SopMsgParam;
+import com.fs.qw.param.sidebar.ExternalContactInfoParam;
+import com.fs.qw.param.sidebar.TagGroupListParam;
+import com.fs.qw.param.sidebar.TagGroupUpdateParam;
+import com.fs.qw.result.QwExternalContactByQwResult;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.vo.QwTagGroupListVO;
+import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactTagVO;
+import com.fs.sop.domain.QwSopLogs;
+import com.fs.sop.params.GetQwSopLogsByJsApiParam;
+import com.fs.sop.params.SendSopParamDetailsC;
+import com.fs.sop.service.IQwSopLogsService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+
+@RestController
+@RequestMapping("/apis/app/qwSop")
+public class ApisQwSopController {
+
+    @Autowired
+    RedisCache redisCache;
+    @Autowired
+    IFastGptChatSessionService fastGptChatSessionService;
+    @Autowired
+    private IQwSopLogsService qwSopLogsService;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
+    /**
+     * 更新AI发送状态
+     */
+    @PostMapping("/updateQwSopLogs")
+    public R updateCourseSopLogs(@RequestBody SopLogsEditParam param){
+
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            QwSopLogs qwSopLogs=new QwSopLogs();
+            qwSopLogs.setId(param.getId());
+            qwSopLogs.setReceivingStatus(param.getReceivingStatus());
+            qwSopLogs.setSendStatus(param.getSendStatus());
+            qwSopLogs.setRealSendTime(sdf.format(new Date()));
+            qwSopLogs.setRemark(param.getRemark());
+            qwSopLogsService.updateQwSopLogsSendType(qwSopLogs);
+                return  R.ok();
+        }catch (Exception e){
+                return R.error("更新失败");
+            }
+
+    }
+
+    //主动获取发送信息
+    @PostMapping("/getQwSopLogsByJsApi")
+    public R getQwSopLogsByJsApi(@RequestBody GetQwSopLogsByJsApiParam param) {
+
+        SendSopParamDetailsC qwSopLogsByJsApi = qwSopLogsService.getQwSopLogsByJsApi(param);
+
+        return R.ok().put("data",qwSopLogsByJsApi);
+    }
+
+    //获取销售的某个联系人
+    @GetMapping("/getExternalContactByAppKey/{appKey}")
+    public R getExternalContactByAppKey(@PathVariable("appKey") String appKey) {
+
+        QwExternalContactByQwResult result=qwSopLogsService.getExternalContactByAppKey(appKey);
+
+        return R.ok().put("data",result);
+    }
+
+    //清除不是当前员工的 外部联系以及营期
+    @PostMapping("/deleteQwSopLogsByJsApi")
+    public R deleteQwSopLogsByJsApi(@RequestBody GetQwSopLogsByJsApiParam param) {
+
+        return qwSopLogsService.deleteQwSopLogsByJsApi(param);
+
+    }
+    @PostMapping("/artificialInfo")
+    public R artificialInfo(@RequestBody FastGptChatSessionParam sessionParam) {
+        return R.ok().put("type",fastGptChatSessionService.selectFastGptChatSessionArtificialType(sessionParam));
+    }
+
+    @GetMapping("/getQwSopLogs")
+    public R getQwSopLogs(SopMsgParam param) throws Exception {
+        //获取记录
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwSopLogs> list = qwSopLogsService.selectQwSopLogsListVO(param);
+        PageInfo<QwSopLogs> listPageInfo=new PageInfo<>(list);
+        return R.ok().put("data",listPageInfo);
+    }
+
+    @GetMapping("/externalContact")
+    @ApiOperation("获取侧边栏外部联系人信息")
+    public R getExternalContactInfo(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId) {
+        ExternalContactInfoVO externalContactInfo = qwExternalContactService.getExternalContactInfo(qwExternalContactId);
+        return R.ok().put("data", externalContactInfo);
+    }
+
+
+    @GetMapping("/externalContact/tag")
+    @ApiOperation("获取侧边栏外部联系人标签")
+    public R getExternalContactTag(@RequestParam(value = "qwExternalContactId") Long qwExternalContactId) {
+        List<ExternalContactTagVO> tagList = qwExternalContactService.getExternalContactTag(qwExternalContactId);
+        return R.ok().put("data", tagList);
+    }
+
+    @PutMapping("/externalContact")
+    @ApiOperation("编辑外部联系人信息")
+    public R updateExternalContactInfo(@RequestBody ExternalContactInfoParam param) {
+        return qwExternalContactService.updateExternalContactInfo(param);
+    }
+
+    @GetMapping("/tagGroupList")
+    @ApiOperation("获取所有标签组和其下标签")
+    public R getTagGroupList(TagGroupListParam param) {
+        QwTagGroup qwTagGroup = new QwTagGroup();
+        BeanCopyUtils.copy(param, qwTagGroup);
+        qwTagGroup.setName(param.getTagName());
+
+        PageHelper.startPage(qwTagGroup.getPageNum(), qwTagGroup.getPageSize());
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwGroupTagList(qwTagGroup);
+
+        PageInfo<QwTagGroupListVO> result = new PageInfo<>(list);
+        return R.ok().put("data", result);
+    }
+
+//    @GetMapping("/searchTags")
+//    @ApiOperation("搜索标签-跟管理端保持一致")
+//    public R searchTagList(QwTagParam param) {
+//        List<QwTagGroupListVO> list = qwTagService.searchTags(param);
+//        return R.ok().put("data", list);
+//    }
+
+    @PutMapping("/externalContact/tag")
+    @ApiOperation("编辑标签")
+    public R updateExternalContactTag(@RequestBody TagGroupUpdateParam param) {
+        int i = qwExternalContactService.updateExternalContactTag(param);
+        if (i > 0) {
+            return R.ok();
+        }
+        return R.error();
+    }
+
+}

+ 5 - 1
fs-qwhook/src/main/java/com/fs/app/controller/QwSopController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.fastGpt.param.FastGptChatSessionParam;
 import com.fs.fastGpt.param.SendHookAIParam;
 import com.fs.fastGpt.service.IFastGptChatSessionService;
 import com.fs.qw.param.QwLoginParam;
@@ -69,7 +70,10 @@ public class QwSopController {
         return R.ok("已关闭");
     }
 
-
+    @PostMapping("/artificialInfo")
+    public R artificialInfo(@RequestBody FastGptChatSessionParam sessionParam) {
+        return R.ok().put("type",fastGptChatSessionService.selectFastGptChatSessionArtificialType(sessionParam));
+    }
     /**
      * 更新AI发送状态
      */

+ 11 - 2
fs-qwhook/src/main/java/com/fs/app/controller/QwUserController.java

@@ -99,9 +99,18 @@ public class QwUserController extends BaseController {
         if(qwExternalContactId == null) {
             throw new CustomException("企微外部联系人id不能为空!");
         }
+//        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwExternalContactId);
+        QwExternalContactInfo contactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        if (contactInfo==null){
 
-        QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
-        return R.ok().put("data",qwExternalContactService.selectQwExternalContactById(qwExternalContactId)).put("moreInfo",qwExternalContactInfo);
+            contactInfo = new QwExternalContactInfo();
+            contactInfo.setExternalContactId(qwExternalContactId);
+            qwExternalContactInfoService.insertQwExternalContactInfo(contactInfo);
+
+        }
+
+//        return R.ok().put("data",qwExternalContact).put("moreInfo",contactInfo);
+        return R.ok().put("moreInfo",contactInfo);
     }
 
     @PostMapping("/updateQwUserInfo")

+ 1 - 1
fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -20,7 +20,7 @@ import java.util.function.Function;
 @Slf4j
 @Service
 @AllArgsConstructor
-@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
+@RocketMQMessageListener(topic = "repeat-upload", consumerGroup = "common-group")
 public class RocketMQConsumerService implements RocketMQListener<String> {
 
     private final RepeatService repeatService;

+ 5 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java

@@ -198,4 +198,9 @@ public interface CompanyMapper
 
 
     List<CompanyUser> selectCompanyListByIds(@Param("userIds") String result);
+
+    /**
+     * 查询企微主体管理公司列表
+     */
+    List<OptionsVO> getCompanyListByCorpId(@Param("corpId") String corpId);
 }

+ 21 - 8
fs-service/src/main/java/com/fs/company/mapper/CompanyUserRoleMapper.java

@@ -1,20 +1,33 @@
 package com.fs.company.mapper;
 
 import com.fs.company.domain.CompanyUserRole;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
 
 /**
  * 用户和角色关联Mapper接口
- * 
+ *
  * @author fs
  * @date 2021-05-25
  */
-public interface CompanyUserRoleMapper 
+public interface CompanyUserRoleMapper
 {
+    @Select("\n" +
+            "SELECT \n" +
+            "   cur.user_id\n" +
+            "FROM \n" +
+            "    company_user_role cur\n" +
+            "JOIN \n" +
+            "    company_role cr ON cur.role_id = cr.role_id and cr.role_key = 'admin'\n" +
+            "WHERE \n" +
+            "    cur.user_id = #{userId} " +
+            "LIMIT 1")
+    public Long companyUserIsAdmin(@Param("userId") Long userId);
     /**
      * 查询用户和角色关联
-     * 
+     *
      * @param userId 用户和角色关联ID
      * @return 用户和角色关联
      */
@@ -22,7 +35,7 @@ public interface CompanyUserRoleMapper
 
     /**
      * 查询用户和角色关联列表
-     * 
+     *
      * @param companyUserRole 用户和角色关联
      * @return 用户和角色关联集合
      */
@@ -30,7 +43,7 @@ public interface CompanyUserRoleMapper
 
     /**
      * 新增用户和角色关联
-     * 
+     *
      * @param companyUserRole 用户和角色关联
      * @return 结果
      */
@@ -38,7 +51,7 @@ public interface CompanyUserRoleMapper
 
     /**
      * 修改用户和角色关联
-     * 
+     *
      * @param companyUserRole 用户和角色关联
      * @return 结果
      */
@@ -46,7 +59,7 @@ public interface CompanyUserRoleMapper
 
     /**
      * 删除用户和角色关联
-     * 
+     *
      * @param userId 用户和角色关联ID
      * @return 结果
      */
@@ -54,7 +67,7 @@ public interface CompanyUserRoleMapper
 
     /**
      * 批量删除用户和角色关联
-     * 
+     *
      * @param userIds 需要删除的数据ID
      * @return 结果
      */

+ 8 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyService.java

@@ -1,6 +1,7 @@
 package com.fs.company.service;
 
 import java.math.BigDecimal;
+import java.time.LocalTime;
 import java.util.List;
 
 import com.fs.common.core.domain.R;
@@ -159,4 +160,11 @@ public interface ICompanyService
      * @return
      */
     List<CompanyUser> selectCompanyListByIds(String result);
+
+    /**
+     * 查询企微主体管理公司列表
+     */
+    List<OptionsVO> getCompanyListByCorpId(String corpId);
+
+    void subtractCompanyMoneyHourse(BigDecimal money, Long companyId, LocalTime start, LocalTime end);
 }

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

@@ -238,4 +238,10 @@ public interface ICompanyUserService {
     R unBindDoctor(Long userId);
 
     R getBindInfo(Long companyUserId);
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO 批量修改角色参数
+     */
+    R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO);
 }

+ 42 - 94
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -1,9 +1,11 @@
 package com.fs.company.service.impl;
 
 import java.math.BigDecimal;
+import java.time.LocalTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
@@ -822,10 +824,17 @@ public class CompanyServiceImpl implements ICompanyService
                 .collect(Collectors.toList());
     }
 
+    @Autowired
+    private CompanyUserRoleMapper companyUserRoleMapper;
     @Override
     public List<DeptDataVO> getDeptData(Long companyId, Long currentCompanyUserId, Long currentDeptId) {
         List<DeptDataVO> result = new ArrayList<>();
 
+        Long isAdmin = companyUserRoleMapper.companyUserIsAdmin(currentCompanyUserId);
+        logger.info("当前用户 {} 是公司admin 返回公司所有部门树",currentDeptId);
+        if(isAdmin!=null){
+            return getDeptData(companyId);
+        }
         // 1. 获取所有部门数据
         List<CompanyDept> allCompanyDepts = companyDeptMapper.queryDeptDataAll();
 
@@ -1064,34 +1073,6 @@ public class CompanyServiceImpl implements ICompanyService
         return companyNode;
     }
 
-    /**
-     * 构建公司节点,包含其下属多级部门和用户
-     */
-    private DeptDataVO buildCompanyNode(Company company,
-                                        Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
-                                        Map<Long, List<CompanyDept>> companyDeptGroupByCompanyId,
-                                        Map<Long, List<CompanyDept>> deptGroupByParentId,
-                                        Long currentDeptId,
-                                        Long currentCompanyUserId
-                                        ) {
-        DeptDataVO companyNode = new DeptDataVO();
-        companyNode.setLabel(company.getCompanyName());
-        companyNode.setId("company_"+company.getCompanyId());
-
-        // 获取公司下的顶级部门(parentId为null或为公司ID的部门)
-        List<CompanyDept> topLevelDepts = companyDeptGroupByCompanyId.get(company.getCompanyId());
-        if (topLevelDepts != null) {
-            topLevelDepts = topLevelDepts.stream()
-                    .filter(dept -> dept.getParentId() == null || dept.getParentId().equals(0L))
-                    .collect(Collectors.toList());
-        }
-
-        List<DeptDataVO> deptDataList = buildDeptTree(topLevelDepts, companyUserGroupByDeptId, deptGroupByParentId,currentDeptId,currentCompanyUserId);
-        companyNode.setChildren(deptDataList.isEmpty() ? null : deptDataList);
-
-        return companyNode;
-    }
-
     /**
      * 递归构建部门树
      */
@@ -1136,72 +1117,6 @@ public class CompanyServiceImpl implements ICompanyService
 
         return result;
     }
-    /**
-     * 递归构建部门树
-     */
-    /**
-     *
-     * @param depts
-     * @param companyUserGroupByDeptId
-     * @param deptGroupByParentId
-     * @param currentDeptId 当前部门id
-     * @param currentCompanyUserId 当前销售id
-     * @return
-     */
-    private List<DeptDataVO> buildDeptTree(List<CompanyDept> depts,
-                                           Map<Long, List<CompanyUser>> companyUserGroupByDeptId,
-                                           Map<Long, List<CompanyDept>> deptGroupByParentId,
-                                           Long currentDeptId,
-                                           Long currentCompanyUserId) {
-        if (depts == null || depts.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        List<DeptDataVO> result = new ArrayList<>();
-
-        for (CompanyDept dept : depts) {
-            DeptDataVO deptNode = new DeptDataVO();
-            deptNode.setLabel(dept.getDeptName());
-            deptNode.setId("dept_"+dept.getDeptId());
-
-            List<DeptDataVO> children = new ArrayList<>();
-
-            // 1. 添加子部门(递归)
-            List<CompanyDept> childDepts = deptGroupByParentId.get(dept.getDeptId());
-            if (childDepts != null && !childDepts.isEmpty()) {
-                List<DeptDataVO> childDeptNodes = buildDeptTree(childDepts, companyUserGroupByDeptId, deptGroupByParentId);
-                children.addAll(childDeptNodes);
-            }
-
-            // 2. 添加部门下的用户
-            List<CompanyUser> deptUsers = companyUserGroupByDeptId.get(dept.getDeptId());
-            if (deptUsers != null && !deptUsers.isEmpty()) {
-                for (CompanyUser user : deptUsers) {
-                    // 如果是销售当前部门,不显示同级其他销售
-                    if(ObjectUtils.equals(dept.getDeptId(),currentDeptId)) {
-                        if(ObjectUtils.equals(user.getUserId(),currentCompanyUserId)) {
-                            DeptDataVO userNode = new DeptDataVO();
-                            userNode.setLabel(user.getNickName()+"_"+user.getUserName());
-                            userNode.setId("user_"+user.getUserId());
-                            userNode.setChildren(null);
-                            children.add(userNode);
-                        }
-                    } else {
-                        DeptDataVO userNode = new DeptDataVO();
-                        userNode.setLabel(user.getNickName()+"_"+user.getUserName());
-                        userNode.setId("user_"+user.getUserId());
-                        userNode.setChildren(null);
-                        children.add(userNode);
-                    }
-                }
-            }
-
-            deptNode.setChildren(children.isEmpty() ? null : children);
-            result.add(deptNode);
-        }
-
-        return result;
-    }
 
     @Override
     @Transactional
@@ -1335,4 +1250,37 @@ public class CompanyServiceImpl implements ICompanyService
     public List<CompanyUser> selectCompanyListByIds(String result) {
         return companyMapper.selectCompanyListByIds(result);
     }
+
+    /**
+     * 查询企微主体管理公司列表
+     */
+    @Override
+    public List<OptionsVO> getCompanyListByCorpId(String corpId) {
+        return companyMapper.getCompanyListByCorpId(corpId);
+    }
+
+    @Override
+    @Transactional
+    public void subtractCompanyMoneyHourse(BigDecimal money, Long companyId, LocalTime start, LocalTime end) {
+        if(companyId!=null&&companyId>0){
+            Company company=companyMapper.selectCompanyByIdForUpdate(companyId);
+            if(company!=null){
+                logger.info("每个小时扣除红包金额:{}", money);
+                company.setMoney(company.getMoney().subtract(money));
+                companyMapper.updateCompany(company);
+                CompanyMoneyLogs log=new CompanyMoneyLogs();
+                log.setCompanyId(company.getCompanyId());
+                if(end != null && start.getHour() != end.getHour()){
+                    log.setRemark("扣除"+start.getHour() + "到" + end.getHour() +"点红包金额");
+                }else{
+                    log.setRemark("扣除"+start.getHour()+"点红包金额");
+                }
+                log.setMoney(money.multiply(new BigDecimal(-1)));
+                log.setLogsType(15);
+                log.setBalance(company.getMoney());
+                log.setCreateTime(new Date());
+                moneyLogsMapper.insertCompanyMoneyLogs(log);
+            }
+        }
+    }
 }

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

@@ -3,6 +3,7 @@ package com.fs.company.service.impl;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.QRutils;
 import com.fs.common.annotation.DataScope;
@@ -22,8 +23,7 @@ import com.fs.company.mapper.*;
 import com.fs.company.param.CompanyUserAreaParam;
 import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
-import com.fs.company.service.ICompanyService;
-import com.fs.company.service.ICompanyUserService;
+import com.fs.company.service.*;
 import com.fs.company.vo.*;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.his.mapper.FsUserMapper;
@@ -43,8 +43,13 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.CompanyUserQwVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
+import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysRoleService;
+import com.fs.system.service.ISysUserService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import org.slf4j.Logger;
@@ -72,6 +77,7 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 {
     @Autowired
     private CompanyUserMapper companyUserMapper;
+
     @Autowired
     private CompanyRoleMapper roleMapper;
 
@@ -99,6 +105,7 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 
     @Autowired
     private FsUserMapper fsUserMapper;
+
     @Autowired
     private IFsUserCompanyUserService userCompanyUserService;
 
@@ -108,10 +115,19 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Autowired
     private ICompanyService companyService;
 
-
     @Autowired
     private IQwUserService qwUserService;
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+
+    @Autowired
+    private ICompanyConfigService companyConfigService;
+
+//    @Autowired
+//    private ICompanyUserRoleService userRoleService;
+
+
     /**
      * 查询物业公司管理员信息
      *
@@ -649,7 +665,20 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Override
     @DataScope(deptAlias = "u", userAlias = "u")
     public List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user) {
-        return companyUserMapper.selectCompanyUserQwListVO(user);
+//        CompanyConfig companyConfig = companyConfigService.selectCompanyConfigByKey(user.getCompanyId(), "company:admin:show");
+        boolean  isAdminShow = false;
+//        if(!StringUtils.isEmpty(companyConfig.getConfigValue())){
+//            isAdminShow = Boolean.parseBoolean(companyConfig.getConfigValue());
+//        }
+        List<CompanyUserQwListVO> companyUserQwListVOS = companyUserMapper.selectCompanyUserQwListVO(user);
+        if(!isAdminShow){
+            Company company = companyService.selectCompanyById(user.getCompanyId());
+            Long userId = company.getUserId();
+            companyUserQwListVOS = companyUserQwListVOS.stream()
+                    .filter(vo -> !vo.getUserId().equals(userId))
+                    .collect(Collectors.toList());
+        }
+        return companyUserQwListVOS;
     }
 
     @Override
@@ -1006,4 +1035,30 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         }
         return R.error();
     }
+
+    /**
+     * 批量修改角色
+     * @param batchUserRolesVO 批量修改角色参数
+     */
+    @Override
+    public R updateBatchUserRoles(BatchUserRolesVO batchUserRolesVO) {
+        Long[] roleIds = batchUserRolesVO.getRoleIds();
+        Long[] userIds = batchUserRolesVO.getUserIds();
+        //先删除之前关联的角色
+        userRoleMapper.deleteCompanyUserRoleByIds(userIds);
+        for (Long userId : userIds){
+            try {
+                CompanyUser companyUser = selectCompanyUserById(userId);
+                companyUser.setRoleIds(roleIds);
+                //updateUser(companyUser) 直接使用这个方法后屏蔽下方代码也可以
+                insertUserRole(companyUser);
+                userPostMapper.deleteUserPostByUserId(userId);
+                insertUserPost(companyUser);
+                companyUserMapper.updateCompanyUser(companyUser);
+            }catch (Exception exception){
+                throw new CustomException("修改失败");
+            }
+        }
+        return R.ok("修改成功");
+    }
 }

+ 21 - 0
fs-service/src/main/java/com/fs/company/vo/BatchUserRolesVO.java

@@ -0,0 +1,21 @@
+package com.fs.company.vo;
+
+import lombok.*;
+
+/**
+ * @description: 批量修改用户角色入参
+ * @author: Guos
+ * @time: 2025/10/16 上午9:23
+ */
+
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+public class BatchUserRolesVO {
+
+    private Long[] roleIds;
+
+    private Long[] userIds;
+}

+ 2 - 1
fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java

@@ -116,7 +116,8 @@ public class CompanyUserImportVO extends BaseEntity {
     private String callerNo;
 
     private String voicePrintUrl;
-    @Excel(name = "销售区域编码")
+
+    @Excel(name = "销售区域编号")
     private String addressId;
 
     /** 看课域名 */

+ 4 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import com.fs.company.domain.CompanyDept;
 import com.fs.company.domain.CompanyRole;
+import com.fs.qw.vo.QwUserVO;
 import lombok.Data;
 
 import java.util.Date;
@@ -133,4 +134,7 @@ public class CompanyUserQwListVO extends BaseEntity {
     /** 医生id */
     private Long doctorId;
 
+    /** 企微用户列表 */
+    List<QwUserVO> qwUsers;
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -62,6 +62,11 @@ public class CourseConfig implements Serializable {
      * 是否绑定
      */
     private Boolean isBound;
+
+    /**
+     * 是否显示企微二维码
+     */
+    private Boolean showQwCode;
     private Boolean dept;
     /**
      * 是否单销售观看(只能在第一次绑定的销售头上看课)

+ 83 - 0
fs-service/src/main/java/com/fs/course/domain/FsBlackTalent.java

@@ -0,0 +1,83 @@
+package com.fs.course.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 达人或视频举报拉黑功能对象 fs_black_talent
+ *
+ * @author fs
+ * @date 2025-08-17
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsBlackTalent extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 达人id */
+    @Excel(name = "达人id")
+    private Long talentId;
+
+    /** 类型1:拉黑,2:举报 */
+    @Excel(name = "类型1:拉黑,2:举报")
+    private String type;
+
+    /** 举报说明(拉黑为空) */
+    @Excel(name = "举报说明", readConverterExp = "拉=黑为空")
+    private String reportDesc;
+
+    /** 是否审核-1:驳回,0:待审核,1:通过(拉黑不需要审核) */
+    @Excel(name = "是否审核-1:驳回,0:待审核,1:通过", readConverterExp = "拉=黑不需要审核")
+    private String isAudit;
+
+    /** 审核时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "审核时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date auditTime;
+
+    /** 审核人 */
+    @Excel(name = "审核人")
+    private Long auditUser;
+
+    /** 审核说明 */
+    @Excel(name = "审核说明")
+    private String auditDesc;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date creatTime;
+
+    /** 短视频id */
+    @Excel(name = "短视频id")
+    private Long videoId;
+
+    /** 1:达人,2:短视频 */
+    @Excel(name = "1:达人,2:短视频")
+    private String style;
+
+    @Excel(name = "联系方式")
+    private String phone;
+
+    /** 图片地址 */
+    @Excel(name = "图片地址")
+    private String urls;
+
+    @Excel(name = "投诉模板id")
+    private Long templateId;
+    //交易截图
+    private String tradeImage;
+
+
+}

+ 25 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserTalent.java

@@ -111,4 +111,29 @@ public class FsUserTalent extends BaseEntity
     @Excel(name = "已提现佣金")
     private BigDecimal extractMoney;
 
+    /** 达人类别1:普通达人2:带货达人 */
+    @Excel(name = "达人类别1:普通达人2:带货达人")
+    private String talentType;
+
+    @Excel(name = "状态1正常状态,2禁用状态")
+    private Long status;
+
+    @Excel(name = "生日")
+    private String birthDay;
+
+    @Excel(name = "背景")
+    private String backGround;
+
+    /** 收货人所在省 */
+    @Excel(name = "收货人所在省")
+    private String province;
+
+    /** 收货人所在市 */
+    @Excel(name = "收货人所在市")
+    private String city;
+
+    /** 收货人所在区 */
+    @Excel(name = "收货人所在区")
+    private String district;
+
 }

+ 36 - 0
fs-service/src/main/java/com/fs/course/dto/CourseRedPacketStatisticsDTO.java

@@ -0,0 +1,36 @@
+package com.fs.course.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Data
+public class CourseRedPacketStatisticsDTO {
+
+    // 公司名称
+    private String companyName;
+
+    // 员工姓名
+    private String nickName;
+
+    // 员工id
+    private Long companyUserId;
+
+    // 红包数
+    private Long redPacketNum;
+
+   // 红包总金额
+    private BigDecimal redPacketTotalMoney;
+
+
+
+
+}

+ 76 - 0
fs-service/src/main/java/com/fs/course/mapper/FsBlackTalentMapper.java

@@ -0,0 +1,76 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.vo.FsBlackTalentPVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Mapper接口
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+public interface FsBlackTalentMapper extends BaseMapper<FsBlackTalent>{
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    FsBlackTalent selectFsBlackTalentById(Long id);
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能集合
+     */
+    List<FsBlackTalent> selectFsBlackTalentList(FsBlackTalent fsBlackTalent);
+
+    List<FsBlackTalentPVO> selectFsBlackTalentPVOList(FsBlackTalent fsBlackTalent);
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int insertFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int updateFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 删除达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    int deleteFsBlackTalentById(Long id);
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsBlackTalentByIds(Long[] ids);
+
+    int deleteFsBlackVideo(@Param("userId")String userId, @Param("videoId") String videoId);
+
+    int deleteFsBlackTalent(@Param("userId")String userId,@Param("talentId") String talentId);
+
+    int audit(@Param("map") FsBlackTalentAuditParam fsBlackTalentAuditParam);
+
+    List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(@Param("userId") Long userId);
+
+    int selectBlackTalent(@Param("talentId") Long talentId,@Param("loginUserId") Long loginUserId);
+}

+ 5 - 3
fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java

@@ -117,11 +117,13 @@ public interface FsCourseAnswerLogsMapper
             "<if test = 'qwUserId !=null '> " +
             "and qw_user_id = #{qwUserId} " +
             "</if>" +
+            "<if test = 'project !=null '> " +
+            "and project = #{project} " +
+            "</if>" +
             "</script>"})
-    int selectErrorCountByCourseVideo(@Param("videoId") Long videoId,@Param("userId") Long userId,@Param("qwUserId") String qwUserId);
+    int selectErrorCountByCourseVideo(@Param("videoId") Long videoId, @Param("userId") Long userId, @Param("qwUserId") String qwUserId,@Param("project") Long project);
 
-    @Select("select count(log_id) from fs_course_red_packet_log where user_id = #{userId} and video_id = #{videoId}")
-    Long selectRedStatus(@Param("userId") Long userId, @Param("videoId") Long videoId);
+    Long selectRedStatus(@Param("userId") Long userId, @Param("videoId") Long videoId, @Param("periodId") Long periodId);
 
     List<FsCourseAnswerLogsListVO> selectFsCourseAnswerLogsListVONew(FsCourseAnswerLogsParam param);
 

+ 10 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java

@@ -1,10 +1,14 @@
 package com.fs.course.mapper;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.domain.FsCourseRedPacketLog;
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
 import com.fs.course.param.FsCourseRedPacketLogParam;
 import com.fs.course.param.FsUserCourseOrderParam;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
@@ -168,4 +172,10 @@ public interface FsCourseRedPacketLogMapper
 
     @Select("SELECT * FROM fs_course_red_packet_log WHERE status = 0 and create_time > DATE_SUB(NOW(), INTERVAL 2 day) and company_user_id =#{userId}")
     List<FsCourseRedPacketLog> selectFail(@Param("userId") Long userId);
+
+    List<RedPacketMoneyVO> selectFsCourseRedPacketLogHourseByCompany(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
+
+    List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param);
+
+    List<FsCourseRedPacketLog> selectFsCourseRedPacketLogListBySending(@Param("maps") Map<String, Object> map);
 }

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

@@ -2,7 +2,6 @@ package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsCourseWatchLog;
-import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
@@ -13,7 +12,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
-import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -313,6 +312,11 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             " WHERE  DATE(l.create_time) = DATE_SUB(CURDATE(), INTERVAL 1 DAY) and l.video_id =#{videoId}")
     List<FsQwCourseWatchLogVO> selectFsCourseWatchLogByNoDayAndVoidId(Long videoId);
 
+    @Select("SELECT l.qw_external_contact_id,l.log_type,l.qw_user_id,l.create_time ,u.first_time,u.create_time lineTime FROM fs_course_watch_log  l " +
+            "LEFT JOIN qw_external_contact u ON u.id=l.qw_external_contact_id  " +
+            " WHERE  l.create_time between #{start} and #{end} and l.video_id =#{videoId}")
+    List<FsQwCourseWatchLogVO> selectFsCourseWatchLogByNoDayAndVoidIdByTime(@Param("videoId") Long videoId, @Param("start") String start, @Param("end") String end);
+
     @Select("SELECT l.qw_external_contact_id,l.log_type,l.qw_user_id,l.create_time ,u.first_time,u.create_time lineTime FROM fs_course_watch_log  l " +
             "LEFT JOIN qw_external_contact u ON u.id=l.qw_external_contact_id  " +
             " WHERE  DATE(l.create_time) = DATE_SUB(CURDATE(), INTERVAL 2 DAY) and l.video_id =#{videoId}")

+ 5 - 2
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -233,9 +233,9 @@ public interface FsUserCourseMapper
 
     @Select("select course_id dict_value, course_name dict_label,img_url dict_imgUrl  from fs_user_course where is_del = 0 and is_private = 1 ")
     List<OptionsVO> selectFsUserCourseAllList();
-    
+
     @Select("select course_id dict_value, course_name dict_label,img_url dict_imgUrl  from fs_user_course where is_del = 0 and is_private = 1" +
-            " and find_in_set(#{companyId},company_ids) ")
+            " and find_in_set(#{companyId},company_ids) ORDER BY sort ASC, course_id DESC ")
     List<OptionsVO> selectFsUserCourseByCompany(@Param("companyId") Long companyId);
 
     @Select("select course_id ,project   from fs_user_course where is_del = 0 and is_private = 1")
@@ -297,4 +297,7 @@ public interface FsUserCourseMapper
             "         AND user_id =#{userId}" +
             "         ORDER BY create_time DESC")
     List<OptionsVO> selectFsUserCourseAllListByUserId(@Param("userId") Long userId);
+
+    @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where course_id = #{courseId} and is_del = 0")
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(@Param("courseId") Long courseId);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsUserCoursePeriodDays;
 import com.fs.course.domain.FsUserWatchCourseStatistics;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
 import java.time.LocalDateTime;
@@ -118,4 +119,7 @@ public interface FsUserCoursePeriodDaysMapper extends BaseMapper<FsUserCoursePer
     int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 
     Long selectFsUserCoursePeriodDaysCount(FsUserCoursePeriodDays fsUserCoursePeriodDays);
+
+    @Select("SELECT distinct period_id from fs_user_course_period_days  where start_date_time >=#{periodSTime} and end_date_time <=#{periodETime} ")
+    List<Long> selectFsUserCoursePeriodDaysByTime(@Param("periodSTime") String periodSTime,@Param("periodETime") String periodETime);
 }

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

@@ -5,7 +5,6 @@ import com.fs.course.param.CompanyRedPacketParam;
 import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.vo.FsUserCoursePeriodVO;
 import com.fs.course.vo.PeriodRedPacketVO;
-import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -158,4 +157,17 @@ public interface FsUserCoursePeriodMapper
     Long setlectCorrectAnswerNum(PeriodStatisticCountParam param);
 
     List<FsUserCoursePeriod> selectFsPeriodlist(PeriodStatisticCountParam param);
+
+    @Select("select cp.period_id from fs_user_course_period cp left where company_id=#{companyId}")
+    List<Long> selectCoursePeriodDaysByTime(@Param("periodSTime") String periodSTime,@Param("periodETime") String periodETime,@Param("companyId") Long companyId);
+
+    @Select("<script>" +
+            "select period_id from fs_user_course_period where " +
+            "FIND_IN_SET(#{companyId}, company_id) > 0 " +
+            " AND period_id IN" +
+            "<foreach collection='periodIds' item='periodId' open='(' separator=',' close=')'> " +
+            "#{periodId} " +
+            "</foreach> " +
+            "</script> ")
+    List<Long> selectFsUserCoursePeriodListByPeriodId(@Param("periodIds") List<Long> qwUserIds,@Param("companyId") Long companyId);
 }

+ 11 - 5
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -6,10 +6,7 @@ import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseVideoListUParam;
 import com.fs.course.param.FsUserCourseVideoParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.vo.FsCourseVideoListBySidebarVO;
-import com.fs.course.vo.FsUserCourseVO;
-import com.fs.course.vo.FsUserCourseVideoListUVO;
-import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.Param;
@@ -140,7 +137,7 @@ public interface FsUserCourseVideoMapper
     Long selectFsUserCourseVideoByCourseSort(@Param("courseId")Long courseId, @Param("courseSort")Long courseSort);
 
 
-    @Select("select video_id dict_value, title dict_label  from fs_user_course_video where course_id=#{id} and is_del = 0 ")
+    @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> " +
@@ -222,4 +219,13 @@ public interface FsUserCourseVideoMapper
 
     FsUserCourseVideo selectFsUserCourseVideoByVideoIdAndUserId(@Param("videoId") Long videoId,@Param("userId") Long userId);
 
+    /**
+     * 查询选择使用的视频列表
+     */
+    List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据视频id集合查询列表
+     */
+    List<FsUserCourseVideoAppletVO> getFsUserCourseVideoAppletVOListByIds(@Param("videoIds") List<Long> videoIds);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserTalentFollowMapper.java

@@ -2,6 +2,9 @@ package com.fs.course.mapper;
 
 import java.util.List;
 import com.fs.course.domain.FsUserTalentFollow;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -68,4 +71,12 @@ public interface FsUserTalentFollowMapper
 
     @Delete("delete from fs_user_talent_follow  where talent_id =#{talentId} and user_id=#{userId} ")
     int deleteFollow(@Param("talentId") Long talentId,@Param("userId")long userId);
+
+    Integer queryFansCount(Long talentId);
+
+    Integer queryIdolCount(Long userId);
+
+    List<FsUserTalentFansVo> selectFsUserTalentFansVoList(@Param("maps") FsUserTalentFansParam param);
+
+    List<FsUserTalentFollowVo> selectFsUserFollowVoList(@Param("maps")FsUserTalentFansParam param);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserTalentMapper.java

@@ -66,4 +66,9 @@ public interface FsUserTalentMapper
 
     @Update("update fs_user_talent set fans=fans-1 where talent_id=#{talentId}")
     int minusFans(Long talentId);
+
+    //根据userID查询达人数据
+    FsUserTalent queryTalentByUserId(Long userId);
+
+    int updateFsUserTalentByUser(FsUserTalent fsUserTalent);
 }

+ 25 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java

@@ -138,6 +138,7 @@ public interface FsUserVideoMapper
             "left join fs_user_talent t on t.talent_id = v.talent_id " +
             " left join fs_package p on p.package_id = v.product_id " +
             "where v.is_del = 0 and v.status = 1  " +
+            " and v.is_audit = 1 " +
             "<if test = ' maps.keyword!=null and maps.keyword != \"\" '> " +
             "and v.title like CONCAT('%',#{maps.keyword},'%') " +
             "</if>" +
@@ -242,5 +243,29 @@ public interface FsUserVideoMapper
 
     @Select("select * from fs_user_video where url like CONCAT('%','https://obs.jy.cc','%') ")
     List<FsUserVideo> selectVideo();
+
+    List<FsUserVideo> selectVideoByTalentId(Long talentId);
+
+    @Select("SELECT count(1) from fs_user_video_favorite f LEFT JOIN " +
+            "fs_user_video v ON v.video_id = f.video_id  " +
+            "where v.is_del = 0 and  v.status = 1 and f.user_id = #{userId}")
+    int countFavoriteVideos(@Param("userId") Long userId);
+
+    @Select({"<script> " +
+            "select v.video_id as id,v.title,v.description as msg,t.nick_name as username,t.avatar as headImg, " +
+            "v.thumbnail as cover,v.url as src,v.likes as likeNum,v.comments as smsNum,v.favorite_num," +
+            "v.create_time,v.views as playNumber,v.product_id,p.img_url,p.package_name,v.upload_type,v.shares,v.add_num,v.is_audit,v.fail_reason,v.status from fs_user_video v " +
+            "left join fs_user_talent t on t.talent_id = v.talent_id " +
+            " left join fs_package p on p.package_id = v.product_id " +
+            "where v.is_del = 0 and (" +
+            "(#{oneSelf} = true and (v.is_audit = -1 or v.is_audit = 0 or v.is_audit = 1)) or " +
+            "(#{oneSelf} = false and v.is_audit = 1 and v.status = 1)" +
+            ") " +
+            "<if test = ' talentId!=null and talentId != \"\" '> " +
+            "and v.talent_id = #{talentId}" +
+            " order by v.create_time" +
+            "</if>" +
+            "</script>"})
+    List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(@Param("talentId") Long talentId, @Param("oneSelf") boolean oneSelf);
 }
 

+ 3 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserVideoTagsMapper.java

@@ -64,4 +64,7 @@ public interface FsUserVideoTagsMapper
 
     @Select("select * from fs_user_video_tags where pid !=0 and is_del = 0 ")
     List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList();
+
+    @Select("select * from fs_user_video_tags where is_del = 0")
+    List<FsUserVideoTags> selectTagList();
 }

+ 38 - 0
fs-service/src/main/java/com/fs/course/param/CourseRedPacketStatisticsParam.java

@@ -0,0 +1,38 @@
+package com.fs.course.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @description: 看客红包发送统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Data
+public class CourseRedPacketStatisticsParam {
+
+    // 公司id
+    private Long companyId;
+
+    // 公司用户id
+    private Long companyUserId;
+
+    // 状态 状态 0 发送中  1  已发送  2余额不足待发送
+    private Integer status;
+
+    // 开始时间
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date startTime;
+
+    // 结束时间
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date endTime;
+
+
+
+
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/param/FsBlackTalentAuditParam.java

@@ -0,0 +1,14 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsBlackTalentAuditParam {
+    private Long auditUser;
+    private String isAudit;
+    private Long id;
+    private Date auditTime;
+    private String auditDesc;
+}

+ 9 - 1
fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java

@@ -2,6 +2,8 @@ package com.fs.course.param;
 
 import lombok.Data;
 
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 
 /**
@@ -14,19 +16,25 @@ import java.io.Serializable;
 public class FsCourseSendRewardUParam implements Serializable
 {
     private Long userId;
+    @NotNull(message = "课程参数不能为空")
     private Long videoId;//小节Id
+    @NotBlank(message = "客服参数不能为空")
     private String qwUserId;
+    @NotNull(message = "客服参数不能为空")
     private Long companyUserId;
+    @NotNull(message = "经销商参数不能为空")
     private Long companyId;
+    @NotNull(message = "课程参数不能为空")
     private Long courseId;
     private String corpId;
     private Integer linkType;
+    @NotNull(message = "课程参数不能为空")
     private Long qwExternalId;
     private Integer source=1;//来源 1:h5  2:小程序 3:app
     private Integer isRoom;
     private Integer sendType;
     private Long periodId;
-
+    @NotBlank(message = "小程序参数不能为空")
     private String appId; //前端传来的小程序的appid
 
     private String code;

+ 10 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -54,10 +54,20 @@ public class FsCourseWatchLogListParam implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd")
     private String qecETime;
 
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String periodSTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String periodETime;
+
     private List<String> sopIds;
 
 
     private Long taskId;//任务ID
+    private Long project;//任务ID
+
+    private List<Long> periodIds;//训练营期ID
 
     private String customPageStr;
 

+ 3 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java

@@ -35,10 +35,12 @@ public class FsUserCourseVideoAddKfUParam implements Serializable {
     /**
     *   companyUserId
     */
+    @NotNull(message = "客服参数不能为空")
     private Long companyUserId;
     /**
     * 公司id
     */
+    @NotNull(message = "经销商参数参数不能为空")
     private Long companyId;
 
     /**
@@ -50,6 +52,7 @@ public class FsUserCourseVideoAddKfUParam implements Serializable {
     /**
     * 外部联系的id
     */
+//    @NotNull(message = "客户参数不能为空")
     private Long qwExternalId;
 
     private Integer sendType; //归属发送方式:1 个微  2 企微

+ 10 - 0
fs-service/src/main/java/com/fs/course/param/FsUserTalentFansParam.java

@@ -0,0 +1,10 @@
+package com.fs.course.param;
+
+import com.fs.watch.param.BaseQueryParam;
+import lombok.Data;
+
+@Data
+public class FsUserTalentFansParam extends BaseQueryParam {
+    private Long talentId;
+    private Long userId;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/course/service/CourseRedPacketStatisticsService.java

@@ -0,0 +1,11 @@
+package com.fs.course.service;
+
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+
+import java.util.List;
+
+
+public interface CourseRedPacketStatisticsService {
+    List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param);
+}

+ 77 - 0
fs-service/src/main/java/com/fs/course/service/IFsBlackTalentService.java

@@ -0,0 +1,77 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.vo.FsBlackTalentPVO;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Service接口
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+public interface IFsBlackTalentService extends IService<FsBlackTalent>{
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    FsBlackTalent selectFsBlackTalentById(Long id);
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能集合
+     */
+    List<FsBlackTalentPVO> selectFsBlackTalentList(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int insertFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    int updateFsBlackTalent(FsBlackTalent fsBlackTalent);
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的达人或视频举报拉黑功能主键集合
+     * @return 结果
+     */
+    int deleteFsBlackTalentByIds(Long[] ids);
+
+    /**
+     * 删除达人或视频举报拉黑功能信息
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    int deleteFsBlackTalentById(Long id);
+
+    int addBlack(FsBlackTalent blackTalent);
+
+    int deleteFsBlackVideo(String userId,String videoId);
+
+    int deleteFsBlackTalent(String userId,String talentId);
+
+    int audit(FsBlackTalentAuditParam fsBlackTalentAuditParam);
+
+    List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(Long userId);
+
+    int selectBlackTalent(Long talentId,Long loginUserId);
+
+}

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

@@ -85,4 +85,6 @@ public interface IFsCourseRedPacketLogService
     R retryCourseRedPacketLog(Long[] logIds);
 
     void sendRedPacketBf();
+
+    void queryRedPacketResult(String startTime, String endTime);
 }

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

@@ -10,6 +10,8 @@ import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.vo.QwWatchLogStatisticsListVO;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.List;
 import java.util.Map;
 
@@ -100,6 +102,7 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     List<FsCourseOverVO> selectFsCourseWatchLogOverStatisticsListVO(FsCourseOverParam param);
 
     void addCourseWatchLogDay();
+    void addCourseWatchLogDayMinute(LocalDateTime start, LocalDateTime end);
 
     void addCourseWatchLogDay2();
 

+ 0 - 7
fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java

@@ -69,13 +69,6 @@ public interface IFsUserCompanyUserService extends IService<FsUserCompanyUser>{
      */
     int deleteFsUserCompanyUserById(Long id);
 
-    /**
-     * 根据用户ID和项目ID查询微信用户与销售的关系
-     * @param userId            用户ID
-     * @param projectId   项目ID
-     * @return FsUserCompanyUser
-     */
-    FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId,Long companyUserId);
     FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId);
 
     /**

+ 4 - 1
fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java

@@ -118,5 +118,8 @@ public interface IFsUserCoursePeriodDaysService extends IService<FsUserCoursePer
      */
     void changePeriodCourseStatus();
 
-        long periodCourseByCount(PeriodCountParam param);
+    long periodCourseByCount(PeriodCountParam param);
+
+    List<Long> selectFsUserCoursePeriodDaysByTime(String periodSTime,String periodETime);
+
     }

+ 5 - 1
fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java

@@ -5,7 +5,6 @@ import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.vo.FsCourseStaticsCountVO;
 import com.fs.course.vo.FsUserCoursePeriodVO;
 
-import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -97,4 +96,9 @@ public interface IFsUserCoursePeriodService
      * @return
      */
     List<FsUserCoursePeriod> selectFsPeriodlist(PeriodStatisticCountParam param);
+
+    List<Long> selectCoursePeriodDaysByTime(String periodSTime,String periodETime,Long companyId);
+
+    List<Long> selectFsUserCoursePeriodListByPeriodId(List<Long> periodIds,Long companyId);
+
 }

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

@@ -129,5 +129,5 @@ public interface IFsUserCourseService
 
     List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
 
-    R createAppCourseSortLink(FsCourseLinkCreateParam fsCourseLinkCreateParam);
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId);
 }

+ 6 - 4
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -8,10 +8,7 @@ import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.vo.FsCourseVideoListBySidebarVO;
-import com.fs.course.vo.FsUserCourseVideoListUVO;
-import com.fs.course.vo.FsUserCourseVideoQVO;
-import com.fs.course.vo.FsUserCourseVideoVO;
+import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -193,4 +190,9 @@ public interface IFsUserCourseVideoService
     R isAddKfIsOpen(FsUserCourseVideoAddKfUParam param);
 
     R getInternetTrafficIsOpen(FsUserCourseVideoFinishUParam param);
+
+    /**
+     * 查询选择使用的视频列表
+     */
+    List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserTalentFollowService.java

@@ -4,6 +4,9 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserTalentFollow;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 
 /**
  * 达人关注Service接口
@@ -64,4 +67,12 @@ public interface IFsUserTalentFollowService
     R checkFollow(Long talentId, long userId);
 
     int deleteFollow(Long talentId, long userId);
+
+    Integer queryFansCount(Long talentId);
+
+    Integer queryIdolCount(Long userId);
+
+    List<FsUserTalentFansVo> selectFsUserTalentFansVoList(FsUserTalentFansParam param);
+
+    List<FsUserTalentFollowVo> selectFsUserFollowVoList(FsUserTalentFansParam param);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserTalentService.java

@@ -1,6 +1,8 @@
 package com.fs.course.service;
 
 import java.util.List;
+
+import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserTalent;
 
 /**
@@ -61,4 +63,12 @@ public interface IFsUserTalentService
 
 
     int updateFans(Long talentId, Integer type);
+
+    FsUserTalent queryTalentByUserId(Long userId);
+
+    R getTalentDetail(Long userId, Long loginUser);
+
+    int addFsUserTalent(Long userId);
+
+    int updateFsUserTalentByUser(FsUserTalent fsUserTalent);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java

@@ -92,4 +92,16 @@ public interface IFsUserVideoService {
     void updateVideoUrl();
 
     List<FsUserVideoListUVO> addNum(List<FsUserVideoListUVO> oldList);
+
+    List<FsUserVideo> selectVideoByTalentId(Long talentId);
+
+    int countFavoriteVideos(Long userId);
+
+    R addUserVideoByTalent(FsUserVideoAddParam param);
+
+    List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(Long talentId, boolean oneSelf, Long userId);
+
+    R deleteFsUserVideoByVideoIdWithVerify(Long videoId, Long userId);
+
+    R updateVideoStatusWithVerify(FsUserVideo fsUserVideo, Long userId);
 }

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

@@ -61,4 +61,6 @@ public interface IFsUserVideoTagsService
     public int deleteFsUserVideoTagsByTagId(Long tagId);
 
     List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList();
+
+    List<FsUserVideoTags> selectTagList();
 }

+ 28 - 0
fs-service/src/main/java/com/fs/course/service/impl/CourseRedPacketStatisticsServiceImpl.java

@@ -0,0 +1,28 @@
+package com.fs.course.service.impl;
+
+import com.fs.course.dto.CourseRedPacketStatisticsDTO;
+import com.fs.course.mapper.FsCourseRedPacketLogMapper;
+import com.fs.course.param.CourseRedPacketStatisticsParam;
+import com.fs.course.service.CourseRedPacketStatisticsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @description: 看客红包统计
+ * @author: Xgb
+ * @createDate: 2025/10/14
+ * @version: 1.0
+ */
+@Service
+public class CourseRedPacketStatisticsServiceImpl implements CourseRedPacketStatisticsService {
+
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+
+    @Override
+    public List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param) {
+        return fsCourseRedPacketLogMapper.statistics(param);
+    }
+}

+ 126 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsBlackTalentServiceImpl.java

@@ -0,0 +1,126 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.domain.FsBlackTalent;
+import com.fs.course.mapper.FsBlackTalentMapper;
+import com.fs.course.param.FsBlackTalentAuditParam;
+import com.fs.course.service.IFsBlackTalentService;
+import com.fs.course.vo.FsBlackTalentPVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 达人或视频举报拉黑功能Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-08-17
+ */
+@Service
+public class FsBlackTalentServiceImpl extends ServiceImpl<FsBlackTalentMapper, FsBlackTalent> implements IFsBlackTalentService {
+    @Autowired
+    private FsBlackTalentMapper blackTalentMapper;
+    /**
+     * 查询达人或视频举报拉黑功能
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 达人或视频举报拉黑功能
+     */
+    @Override
+    public FsBlackTalent selectFsBlackTalentById(Long id)
+    {
+        return baseMapper.selectFsBlackTalentById(id);
+    }
+
+    /**
+     * 查询达人或视频举报拉黑功能列表
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 达人或视频举报拉黑功能
+     */
+    @Override
+    public List<FsBlackTalentPVO> selectFsBlackTalentList(FsBlackTalent fsBlackTalent)
+    {
+        return blackTalentMapper.selectFsBlackTalentPVOList(fsBlackTalent);
+    }
+
+    /**
+     * 新增达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    @Override
+    public int insertFsBlackTalent(FsBlackTalent fsBlackTalent)
+    {
+        return baseMapper.insertFsBlackTalent(fsBlackTalent);
+    }
+
+    /**
+     * 修改达人或视频举报拉黑功能
+     * 
+     * @param fsBlackTalent 达人或视频举报拉黑功能
+     * @return 结果
+     */
+    @Override
+    public int updateFsBlackTalent(FsBlackTalent fsBlackTalent)
+    {
+        return baseMapper.updateFsBlackTalent(fsBlackTalent);
+    }
+
+    /**
+     * 批量删除达人或视频举报拉黑功能
+     * 
+     * @param ids 需要删除的达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsBlackTalentByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsBlackTalentByIds(ids);
+    }
+
+    /**
+     * 删除达人或视频举报拉黑功能信息
+     * 
+     * @param id 达人或视频举报拉黑功能主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsBlackTalentById(Long id)
+    {
+        return baseMapper.deleteFsBlackTalentById(id);
+    }
+
+    @Override
+    public int addBlack(FsBlackTalent blackTalent) {
+        return blackTalentMapper.insertFsBlackTalent(blackTalent);
+    }
+
+    @Override
+    public int deleteFsBlackVideo(String userId,String videoId) {
+        return blackTalentMapper.deleteFsBlackVideo(userId,videoId);
+    }
+
+    @Override
+    public int deleteFsBlackTalent(String userId,String talentId) {
+        return blackTalentMapper.deleteFsBlackTalent(userId,talentId);
+    }
+
+    @Override
+    public int audit(FsBlackTalentAuditParam fsBlackTalentAuditParam) {
+        return blackTalentMapper.audit(fsBlackTalentAuditParam);
+    }
+
+    @Override
+    public List<FsBlackTalent> selectBlackAndReportVideoIdsByUserId(Long userId) {
+        return blackTalentMapper.selectBlackAndReportVideoIdsByUserId(userId);
+    }
+
+    @Override
+    public int selectBlackTalent(Long talentId, Long loginUserId) {
+
+        return blackTalentMapper.selectBlackTalent(talentId,loginUserId);
+    }
+}

+ 102 - 16
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java

@@ -28,6 +28,7 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
@@ -209,9 +210,10 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     /**
      * 完课用户 打备注
      */
+    @Async
     @Override
     public void finishCourseExtContactIdByRemark(FsCourseWatchLog watchLog) {
-
+        addRandomDelay();
         Long qwExternalContactId = watchLog.getQwExternalContactId();
 
         Date finishTime = watchLog.getFinishTime();
@@ -300,7 +302,8 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
             remarkParam.setUserid(externalContact.getUserId());
             remarkParam.setExternal_userid(externalContact.getExternalUserId());
 
-            for (int attempt = 1; attempt <= 2; attempt++) {
+//            for (int attempt = 1; attempt <= 2; attempt++) {
+            for (int attempt = 1; attempt <= 3; attempt++) { // 增加到3次重试
                 try {
                     QwExternalContactRemarkResult qwResult = qwApiService.externalcontactRemark(remarkParam, externalContact.getCorpId());
                     if (qwResult.getErrcode() == 0) {
@@ -314,23 +317,46 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
 
                         break;
                     } else {
-                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
-                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
-                            remarkRty.setQwUserId(externalContact.getUserId());
-                            remarkRty.setCorpId(externalContact.getCorpId());
-                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
-                            remarkRty.setExternalId(externalContact.getId());
-                            remarkRty.setRemark(newRemark);
-                            remarkRty.setCreateTime(new Date());
-                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
-
+//                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
+//                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+//                            remarkRty.setQwUserId(externalContact.getUserId());
+//                            remarkRty.setCorpId(externalContact.getCorpId());
+//                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+//                            remarkRty.setExternalId(externalContact.getId());
+//                            remarkRty.setRemark(newRemark);
+//                            remarkRty.setCreateTime(new Date());
+//                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+//
+//                        }
+//
+//                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
+
+                        // 根据错误码智能处理
+                        if (isRateLimitError(qwResult.getErrcode())) {
+                            // 保存到重试表
+                            saveToRetryTable(externalContact, newRemark);
+
+                            // 智能延迟
+                            if (attempt < 3) {
+                                smartDelayByErrorCode(qwResult.getErrcode(), attempt);
+                                continue; // 继续重试
+                            }
                         }
-
-                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
-
+                        log.error("完课加备注失败:{}|{}|{}|{}|{}|原因{}",
+                                externalContact.getName(), externalContact.getExternalUserId(),
+                                externalContact.getCorpId(), externalContact.getUserId(),
+                                newRemark, qwResult.getErrmsg());
                     }
                 } catch (Exception e) {
-                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+//                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+                    log.error("添加备注异常 [尝试第 {} 次]:{}|{}|{}|{}|{}|{}",
+                            attempt, externalContact.getName(), externalContact.getExternalUserId(),
+                            externalContact.getCorpId(), externalContact.getUserId(),
+                            newRemark, e.getMessage());
+
+                    if (attempt < 3) {
+                        smartDelayByErrorCode(-1, attempt);
+                    }
                 }
 
                 // 若不是最后一次尝试,则等待3秒再试
@@ -411,4 +437,64 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     public List<FsCourseFinishTempListVO> selectFsCourseFinishTempListVO(FsCourseFinishTemp fsCourseFinishTemp) {
         return fsCourseFinishTempMapper.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
     }
+
+    /**
+     * 添加随机延迟,分散请求峰值
+     */
+    private void addRandomDelay() {
+        try {
+            // 随机延迟100-500ms,避免同时大量请求
+            Thread.sleep(100 + (long)(Math.random() * 400));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("延迟被中断", e);
+        }
+    }
+
+    /**
+     * 根据错误码智能延迟
+     */
+    private void smartDelayByErrorCode(Integer errcode, int attempt) {
+        try {
+            long delayMs;
+            if (errcode == 45033) { // 并发限制
+                delayMs = 5000 + attempt * 2000L; // 递增延迟
+            } else if (errcode == 60020) { // 频率限制
+                delayMs = 3000 + attempt * 1000L;
+            } else if (errcode == -1) { // 系统繁忙
+                delayMs = 2000;
+            } else {
+                delayMs = 1000; // 默认延迟
+            }
+            log.info("因错误码 {} 延迟 {}ms", errcode, delayMs);
+            Thread.sleep(delayMs);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * 判断是否为频率限制类错误
+     */
+    private boolean isRateLimitError(Integer errcode) {
+        return errcode == 45033 || errcode == 60020 || errcode == -1;
+    }
+
+    /**
+     * 保存到重试表
+     */
+    private void saveToRetryTable(QwExternalContact externalContact, String remark) {
+        try {
+            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+            remarkRty.setQwUserId(externalContact.getUserId());
+            remarkRty.setCorpId(externalContact.getCorpId());
+            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+            remarkRty.setExternalId(externalContact.getId());
+            remarkRty.setRemark(remark);
+            remarkRty.setCreateTime(new Date());
+            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+        } catch (Exception e) {
+            log.error("保存重试记录失败", e);
+        }
+    }
 }

+ 5 - 6
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.validation.constraints.Size;
 import java.util.*;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
@@ -177,7 +176,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                 return R.ok("答题成功");
             }
         }
-        errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+        errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(),log.getProject());
 
 
 
@@ -263,7 +262,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog!=null){
                 return R.error("该课程已答题完成,不可重复答题");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null);
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null, null);
 
         }else {
             FsCourseWatchLog log;
@@ -284,7 +283,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog != null) {
                 return R.ok("答题成功");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(), log.getProject());
         }
 
 
@@ -368,7 +367,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (rightLog!=null){
                 return R.error("该课程已答题完成,不可重复答题");
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null);
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),null, null);
 
         }else {
             FsCourseWatchLog log;
@@ -399,7 +398,7 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
                     return R.ok("答题成功");
                 }
             }
-            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId());
+            errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(), log.getProject());
         }
 
 

+ 71 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -1,18 +1,24 @@
 package com.fs.course.service.impl;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMoneyLogs;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMoneyLogsMapper;
 import com.fs.course.config.CourseConfig;
+import com.fs.course.config.RedPacketConfig;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
@@ -22,9 +28,16 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.TransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
@@ -300,6 +313,64 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
         }
     }
 
+    @Override
+    public void queryRedPacketResult(String startTime, String endTime) {
+        LocalDateTime tenMinutesAgo;
+        LocalDateTime twentyMinutesAgo;
+        if(StringUtils.isEmpty(startTime) || StringUtils.isEmpty(endTime)){
+            // 获取前十分钟时间 和 前二十分钟时间
+            tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
+            twentyMinutesAgo = tenMinutesAgo.minusMinutes(10);
+        }else {
+            tenMinutesAgo = LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            twentyMinutesAgo = LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        }
+
+        // 获取前分钟时间
+        Map<String, Object> params = new HashMap<>();
+        params.put("startTime", twentyMinutesAgo);
+        params.put("endTime",tenMinutesAgo);
+        // 获取前十分钟红包记录状态为发送中的记录
+        List<FsCourseRedPacketLog> redPacketLogs = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogListBySending(params);
+        if(redPacketLogs!=null && !redPacketLogs.isEmpty()){
+            String json = configService.selectConfigByKey("redPacket.config");
+            RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+            //创建微信订单
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config,payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            TransferService transferService=wxPayService.getTransferService();
+            for(FsCourseRedPacketLog redPacket:redPacketLogs){
+                // 获取批次号
+                // redPacket.getResult() {"msg":"发送红包成功","code":200,"mchId":"1703311381","data":{"createTime":"2025-06-26T18:00:48+08:00","outBillNo":"fsCourse1938175604536901632","packageInfo":"ABBQO+oYAAABAAAAAABRil0NtaWxBS5JURpdaBAAAADnGpepZahT9IkJjn90+1qgtzWOmCRNZJfek1QMbZ9ktG8idrj37//0xOSt0T67XUFE+PGeXO8qZoNKHYlU3RicIHExIjZr342xE+QjrpjaHIFYoPg=","state":"WAIT_USER_CONFIRM","transferBillNo":"1330007292140242506260028904279364"},"isNew":1}
+                // 获取 transferBillNo
+                String batchId;
+                try {
+                    batchId = StringUtils.isNotEmpty(redPacket.getBatchId())?redPacket.getBatchId():JSON.parseObject(redPacket.getResult()).getJSONObject("data").getString("transferBillNo");
+                }catch (Exception e){
+                    logger.error("【红包处理】获取批次号失败,FsCourseRedPacketLog-log_id:{}",redPacket.getLogId());
+                    continue;
+                }
+
+                try {
+                    TransferBillsGetResult queryRedPacketResult = transferService.getBillsByTransferBillNo(batchId);
+                    logger.info("FsCourseRedPacketLog-log_id:{},【红包处理】查询批次结果:{}",redPacket.getLogId(),queryRedPacketResult);
+                    if(queryRedPacketResult!=null && "SUCCESS".equals(queryRedPacketResult.getState())){
+                        FsCourseRedPacketLog fsCourseRedPacketLog=new FsCourseRedPacketLog();
+                        fsCourseRedPacketLog.setLogId(redPacket.getLogId());
+                        fsCourseRedPacketLog.setStatus(1); // 已发送
+                        fsCourseRedPacketLog.setUpdateTime(new Date());
+//                        updateFsCourseRedPacketLog(fsCourseRedPacketLog);
+                    }
+                } catch (WxPayException e) {
+                    logger.error(e.getMessage());
+                }
+            }
+        }
+    }
+
+
     private void processRedPacket(FsCourseRedPacketLog redPacket, CourseConfig config) {
         // 获取用户信息
         FsUser user = fsUserMapper.selectFsUserByUserId(redPacket.getUserId());

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

@@ -185,8 +185,6 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         }
         if (ObjectUtils.isNotEmpty(param.getTabType())&&param.getTabType().equals("common")){
             param.setCommon(param.getTabType());
-        }else if(ObjectUtils.isNotNull(param.getCompanyId())){
-            param.setCommon("company");
         }
         List<FsCourseTrafficLogListVO> fsCourseTrafficLogListVOS = fsCourseTrafficLogMapper.selectTrafficNew(param);
         for (FsCourseTrafficLogListVO log : fsCourseTrafficLogListVOS) {

部分文件因文件數量過多而無法顯示