Ver Fonte

Merge branch 'master' into 跨公司客户接替

# Conflicts:
#	fs-service/src/main/java/com/fs/company/service/ICompanyService.java
#	fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
Long há 1 semana atrás
pai
commit
fbfee4f17f
100 ficheiros alterados com 3216 adições e 547 exclusões
  1. 36 2
      fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java
  2. 14 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchLogController.java
  3. 21 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  4. 19 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserVideoController.java
  5. 14 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  6. 26 4
      fs-admin/src/main/java/com/fs/his/controller/FsDoctorController.java
  7. 20 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  8. 1 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  9. 24 8
      fs-admin/src/main/java/com/fs/his/controller/FsPackageController.java
  10. 71 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveController.java
  11. 41 0
      fs-admin/src/main/java/com/fs/his/controller/FsPromotionalActiveLogController.java
  12. 0 1
      fs-admin/src/main/java/com/fs/his/controller/FsTestTempItemController.java
  13. 154 0
      fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java
  14. 34 0
      fs-admin/src/main/java/com/fs/task/FsCompanyTask.java
  15. 26 3
      fs-admin/src/main/java/com/fs/web/controller/system/SysUserController.java
  16. 11 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  17. 25 0
      fs-common/src/main/java/com/fs/common/utils/DateUtils.java
  18. 10 0
      fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java
  19. 2 0
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  20. 85 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  21. 129 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralGoodsController.java
  22. 182 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java
  23. 1 1
      fs-company/src/main/resources/application.yml
  24. 69 20
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  25. 17 0
      fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java
  26. 271 35
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java
  27. 36 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  28. 5 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java
  29. 11 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java
  30. 165 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwSopController.java
  31. 5 1
      fs-qwhook/src/main/java/com/fs/app/controller/QwSopController.java
  32. 11 2
      fs-qwhook/src/main/java/com/fs/app/controller/QwUserController.java
  33. 1 35
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  34. 21 8
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserRoleMapper.java
  35. 3 0
      fs-service/src/main/java/com/fs/company/service/ICompanyService.java
  36. 34 94
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  37. 1 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserImportVO.java
  38. 2 0
      fs-service/src/main/java/com/fs/company/vo/CompanyUserQwListVO.java
  39. 2 1
      fs-service/src/main/java/com/fs/company/vo/FsStoreOrderStatisticsVO.java
  40. 5 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  41. 83 0
      fs-service/src/main/java/com/fs/course/domain/FsBlackTalent.java
  42. 25 0
      fs-service/src/main/java/com/fs/course/domain/FsUserTalent.java
  43. 76 0
      fs-service/src/main/java/com/fs/course/mapper/FsBlackTalentMapper.java
  44. 1 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java
  45. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java
  46. 0 2
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  47. 5 2
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  48. 10 4
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  49. 11 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentFollowMapper.java
  50. 5 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserTalentMapper.java
  51. 25 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoMapper.java
  52. 3 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserVideoTagsMapper.java
  53. 14 0
      fs-service/src/main/java/com/fs/course/param/FsBlackTalentAuditParam.java
  54. 9 1
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  55. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  56. 3 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java
  57. 10 0
      fs-service/src/main/java/com/fs/course/param/FsUserTalentFansParam.java
  58. 77 0
      fs-service/src/main/java/com/fs/course/service/IFsBlackTalentService.java
  59. 0 7
      fs-service/src/main/java/com/fs/course/service/IFsUserCompanyUserService.java
  60. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  61. 6 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  62. 11 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentFollowService.java
  63. 10 0
      fs-service/src/main/java/com/fs/course/service/IFsUserTalentService.java
  64. 12 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoService.java
  65. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserVideoTagsService.java
  66. 126 0
      fs-service/src/main/java/com/fs/course/service/impl/FsBlackTalentServiceImpl.java
  67. 102 16
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java
  68. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java
  69. 7 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  70. 0 14
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyUserServiceImpl.java
  71. 6 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  72. 130 248
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  73. 24 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentFollowServiceImpl.java
  74. 117 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java
  75. 93 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java
  76. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoTagsServiceImpl.java
  77. 44 0
      fs-service/src/main/java/com/fs/course/vo/FsBlackTalentPVO.java
  78. 23 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoChooseVO.java
  79. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserTalentFansVo.java
  80. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserTalentFollowVo.java
  81. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserVideoPVO.java
  82. 14 0
      fs-service/src/main/java/com/fs/course/vo/FsUserVideoTagsVo.java
  83. 60 0
      fs-service/src/main/java/com/fs/event/ActiveClickEvent.java
  84. 27 0
      fs-service/src/main/java/com/fs/event/ActiveClickListener.java
  85. 7 2
      fs-service/src/main/java/com/fs/fastGpt/param/FastGptChatSessionParam.java
  86. 2 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatSessionService.java
  87. 16 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatSessionServiceImpl.java
  88. 2 0
      fs-service/src/main/java/com/fs/his/domain/FsAdv.java
  89. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActive.java
  90. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveLog.java
  91. 39 0
      fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveResource.java
  92. 100 0
      fs-service/src/main/java/com/fs/his/domain/RedPacketLog.java
  93. 38 0
      fs-service/src/main/java/com/fs/his/dto/FsPromotionalActiveDTO.java
  94. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java
  95. 15 1
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralGoodsMapper.java
  96. 11 0
      fs-service/src/main/java/com/fs/his/mapper/FsPackageMapper.java
  97. 17 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveLogMapper.java
  98. 29 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveMapper.java
  99. 15 0
      fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveResourceMapper.java
  100. 11 4
      fs-service/src/main/java/com/fs/his/mapper/FsStoreAfterSalesMapper.java

+ 36 - 2
fs-admin/src/main/java/com/fs/company/controller/CompanyStatisticsController.java

@@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RestController;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -182,10 +183,17 @@ public class CompanyStatisticsController extends BaseController
                 timeList.add(beginTime);
                 beginTime = TimeUtils.formatTime(beginTime);
             }
-            List<JSONObject> jsonObjectList = storeOrderService.selectFsPackageOrderCounts(timeEntity.toMap());
+            Map<String, Object> map = timeEntity.toMap();
+            if (StringUtils.isNotBlank(param.getStartTime())){
+                map.put("startTime",param.getStartTime());
+            }
+            if (StringUtils.isNotBlank(param.getEndTime())){
+                map.put("endTime",param.getEndTime());
+            }
+            List<JSONObject> jsonObjectList = storeAfterSalesService.selectFsStoreAfterSales(map);
             List<String> dates = jsonObjectList.stream().map(jsonObject -> jsonObject.getString("type")).collect(Collectors.toList());
             List<Integer> orderCount = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("orderCount")).collect(Collectors.toList());
-            List<Integer> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getInteger("payPrice")).collect(Collectors.toList());
+            List<BigDecimal> payPrice = jsonObjectList.stream().map(jsonObject -> jsonObject.getBigDecimal("payPrice")).collect(Collectors.toList());
             return R.ok().put("list",list).put("dates",dates).put("orderCount",orderCount).put("payPrice",payPrice);
         }
         else {
@@ -291,6 +299,32 @@ public class CompanyStatisticsController extends BaseController
         return util.exportExcel(list, "orderLogs");
     }
 
+    @GetMapping("/exportAfterSalesOrder")
+    public AjaxResult exportAfterSalesOrder(FsStoreStatisticsParam param)
+    {
+        if(StringUtils.isNotEmpty(param.getUserIds())){
+            String[] userIds=param.getUserIds().split(",");
+            Long[] ids=new Long[userIds.length];
+            for(int i=0;i<ids.length; i++){
+                ids[i]=Long.parseLong(userIds[i]);
+            }
+            param.setUsers(ids);
+        }
+        else{
+            //获取所有员工
+            CompanyUser usermap=new CompanyUser();
+            usermap.setDeptId(param.getDeptId());
+            List<CompanyUser> users = userService.getUserListByDeptId(usermap);
+            List<Long> userIds = users.stream().map(element -> element.getUserId()).collect(Collectors.toList());
+            param.setUsers(userIds.toArray(new Long[userIds.size()]));
+        }
+
+        List<FsStoreOrderStatisticsVO> list= storeAfterSalesService.selectFsStoreAfterSalesServiceStatisticsList(param);
+
+        ExcelUtil<FsStoreOrderStatisticsVO> util = new ExcelUtil<FsStoreOrderStatisticsVO>(FsStoreOrderStatisticsVO.class);
+        return util.exportExcel(list, "orderLogs");
+    }
+
 
     @GetMapping("/exportInquiryOrder")
     public AjaxResult exportInquiryOrder(FsStoreStatisticsParam param)

+ 14 - 0
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;
@@ -164,4 +167,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);
+    }
 }

+ 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));
+    }
 }

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

@@ -66,23 +66,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);
     }
     /**
      * 发货

+ 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);
+    }
+}

+ 0 - 1
fs-admin/src/main/java/com/fs/his/controller/FsTestTempItemController.java

@@ -4,7 +4,6 @@ import java.util.List;
 
 import com.fs.his.param.FsTestTempItemParam;
 import com.fs.his.vo.FsTestTempItemListVO;
-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;

+ 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));
+    }
+}

+ 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());
+        }
+    }
+}

+ 26 - 3
fs-admin/src/main/java/com/fs/web/controller/system/SysUserController.java

@@ -1,10 +1,14 @@
 package com.fs.web.controller.system;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.PageDomain;
+import com.fs.common.core.page.TableSupport;
 import com.fs.common.utils.ServletUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -59,9 +63,28 @@ public class SysUserController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(SysUser user)
     {
-        startPage();
-        List<SysUser> list = userService.selectUserList(user);
-        return getDataTable(list);
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+
+
+        long total = userService.selectUserCount(user);
+
+
+        List<Long> userIds = userService.selectUserIdsWithPage(user, pageNum, pageSize);
+
+        List<SysUser> list = new ArrayList<>();
+        if (!userIds.isEmpty()) {
+
+            list = userService.selectUserListByIds(userIds);
+        }
+
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(total);
+        return rspData;
     }
 
     @Log(title = "用户管理", businessType = BusinessType.EXPORT)

+ 11 - 0
fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java

@@ -95,11 +95,22 @@ public class SysUser extends BaseEntity
     /** 角色ID */
     private Long roleId;
 
+    @Excel(name = "角色名称")
+    private List<String> roleName;
+
     public SysUser()
     {
 
     }
 
+    public List<String> getRoleName() {
+        return roleName;
+    }
+
+    public void setRoleName(List<String> roleName) {
+        this.roleName = roleName;
+    }
+
     public SysUser(Long userId)
     {
         this.userId = userId;

+ 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

@@ -108,12 +108,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);
 

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

@@ -22,6 +22,7 @@ import com.fs.crm.vo.CrmMyCustomerListQueryVO;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 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;
@@ -30,6 +31,7 @@ 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;
@@ -84,6 +86,9 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IQwExternalContactTransferCompanyAuditService auditService;
 
+    @Autowired
+    private IQwContactWayService qwContactWayService;
+
     /**
      * 查询企业微信客户列表
      */
@@ -91,8 +96,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->{
@@ -110,11 +120,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()
     {
@@ -159,6 +205,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->{
@@ -176,6 +226,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);
@@ -188,8 +243,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->{
@@ -207,6 +268,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));
+            }
         });
 
         return getDataTable(list);
@@ -237,7 +302,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->{
@@ -255,6 +326,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, "企业微信客户数据");
@@ -271,6 +346,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);
 
@@ -289,6 +368,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);

+ 129 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralGoodsController.java

@@ -0,0 +1,129 @@
+package com.fs.hisStore.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.his.domain.FsIntegralGoods;
+import com.fs.his.service.IFsIntegralGoodsService;
+import com.fs.his.utils.RedisCacheUtil;
+import com.fs.his.vo.FsIntegralGoodsListVO;
+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.List;
+
+/**
+ * 积分商品Controller
+ *
+ * @author fs
+ * @date 2023-11-02
+ */
+@RestController
+@RequestMapping("/his/integralGoods")
+public class FsIntegralGoodsController extends BaseController
+{
+    @Autowired
+    private IFsIntegralGoodsService fsIntegralGoodsService;
+    @Autowired
+    RedisCacheUtil redisCacheUtil;
+    /**
+     * 查询积分商品列表
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsIntegralGoods fsIntegralGoods)
+    {
+        startPage();
+        List<FsIntegralGoodsListVO> list = fsIntegralGoodsService.selectFsIntegralGoodsListVO(fsIntegralGoods);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出积分商品列表
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:export')")
+    @Log(title = "积分商品", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsIntegralGoods fsIntegralGoods)
+    {
+        List<FsIntegralGoods> list = fsIntegralGoodsService.selectFsIntegralGoodsList(fsIntegralGoods);
+        ExcelUtil<FsIntegralGoods> util = new ExcelUtil<FsIntegralGoods>(FsIntegralGoods.class);
+        return util.exportExcel(list, "积分商品数据");
+    }
+
+    /**
+     * 获取积分商品详细信息
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:query')")
+    @GetMapping(value = "/{goodsId}")
+    public AjaxResult getInfo(@PathVariable("goodsId") Long goodsId)
+    {
+        return AjaxResult.success(fsIntegralGoodsService.selectFsIntegralGoodsByGoodsId(goodsId));
+    }
+
+
+    @Log(title = "商品导入", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
+        ExcelUtil<FsIntegralGoods> util = new ExcelUtil<>(FsIntegralGoods.class);
+        List<FsIntegralGoods> list = util.importExcel(file.getInputStream());
+        String message = fsIntegralGoodsService.importIntegralGoodsService(list);
+        return AjaxResult.success(message);
+    }
+
+    @GetMapping("/importTemplate")
+    public AjaxResult importTemplate()
+    {
+        ExcelUtil<FsIntegralGoods> util = new ExcelUtil<>(FsIntegralGoods.class);
+        return util.importTemplateExcel("商品数据");
+    }
+
+
+    /**
+     * 新增积分商品
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:add')")
+    @Log(title = "积分商品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsIntegralGoods fsIntegralGoods)
+    {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
+        return toAjax(fsIntegralGoodsService.insertFsIntegralGoods(fsIntegralGoods));
+    }
+
+    /**
+     * 修改积分商品
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:edit')")
+    @Log(title = "积分商品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsIntegralGoods fsIntegralGoods)
+    {
+
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
+        return toAjax(fsIntegralGoodsService.updateFsIntegralGoods(fsIntegralGoods));
+    }
+
+    /**
+     * 删除积分商品
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralGoods:remove')")
+    @Log(title = "积分商品", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{goodsIds}")
+    public AjaxResult remove(@PathVariable Long[] goodsIds)
+    {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
+        return toAjax(fsIntegralGoodsService.deleteFsIntegralGoodsByGoodsIds(goodsIds));
+    }
+}

+ 182 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsIntegralOrderController.java

@@ -0,0 +1,182 @@
+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;
+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.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;
+import com.fs.his.service.IFsIntegralOrderService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.his.vo.FsIntegralOrderListVO;
+import com.fs.his.vo.FsIntegralOrderPVO;
+import com.fs.his.vo.FsStoreProductDeliverExcelVO;
+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.*;
+
+import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+/**
+ * 积分商品订单Controller
+ *
+ * @author fs
+ * @date 2023-11-02
+ */
+@RestController
+@RequestMapping("/his/integralOrder")
+public class FsIntegralOrderController extends BaseController
+{
+    @Autowired
+    private IFsIntegralOrderService fsIntegralOrderService;
+    @Autowired
+    private IFsExpressService expressService;
+
+    @Autowired
+    private FsIntegralGoodsMapper fsIntegralGoodsMapper;
+    /**
+     * 查询积分商品订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
+    {
+        startPage();
+        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        for (FsIntegralOrderListVO vo : list) {
+            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出积分商品订单列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
+    @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsIntegralOrder fsIntegralOrder) {
+        return fsIntegralOrderService.export(fsIntegralOrder);
+    }
+    /**
+     * 发货
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:sendGoods')")
+    @PutMapping("/sendGoods")
+    public AjaxResult sendGoods(@RequestBody FsIntegralOrder fsIntegralOrder)
+    {
+        return toAjax(fsIntegralOrderService.sendGoods(fsIntegralOrder));
+    }
+
+    @GetMapping("/importTemplate")
+    public AjaxResult sendExport()
+    {
+        ExcelUtil<FsStoreProductDeliverExcelVO> util = new ExcelUtil<>(FsStoreProductDeliverExcelVO.class);
+        return util.importTemplateExcel("导入运单号");
+    }
+    @Log(title = "导入运单号", businessType = BusinessType.IMPORT)
+    @PostMapping("/importData")
+    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
+    {
+        ExcelUtil<FsStoreProductDeliverExcelVO> util = new ExcelUtil<>(FsStoreProductDeliverExcelVO.class);
+        List<FsStoreProductDeliverExcelVO> list = util.importExcel(file.getInputStream());
+        String message = fsIntegralOrderService.importProductDeliver(list);
+        return AjaxResult.success(message);
+    }
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:express')")
+    @GetMapping(value = "/getExpress/{id}")
+    public R getExpress(@PathVariable("id") Long id)
+    {
+        FsIntegralOrder fsIntegralOrder = fsIntegralOrderService.selectFsIntegralOrderByOrderId(id);
+        ExpressInfoDTO expressInfoDTO=null;
+        if(StringUtils.isNotEmpty(fsIntegralOrder.getDeliverySn())){
+            String lastFourNumber = "";
+            if (fsIntegralOrder.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+
+                lastFourNumber = fsIntegralOrder.getUserPhone();
+                if (lastFourNumber.length() == 11) {
+                    lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                }
+            }
+            expressInfoDTO=expressService.getExpressInfo(fsIntegralOrder.getOrderCode(),fsIntegralOrder.getDeliveryCode(),fsIntegralOrder.getDeliverySn(),lastFourNumber);
+        }
+        return R.ok().put("data",expressInfoDTO);
+    }
+    /**
+     * 获取积分商品订单详细信息
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:query')")
+    @GetMapping(value = "/{orderId}")
+    public AjaxResult getInfo(@PathVariable("orderId") Long orderId)
+    {
+        FsIntegralOrderPVO order = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
+
+        order.setUserPhone(decryptAutoPhoneMk(order.getUserPhone()));
+        return AjaxResult.success(order);
+    }
+
+    @GetMapping(value = "/queryPhone/{orderId}")
+    @Log(title = "积分订单电话", businessType = BusinessType.GRANT)
+    @PreAuthorize("@ss.hasPermi('his:integralOrder:queryPhone')")
+    public R getPhone(@PathVariable("orderId") Long orderId)
+    {
+        FsIntegralOrderPVO order = fsIntegralOrderService.selectFsIntegralOrderPVO(orderId);
+        String userPhone = order.getUserPhone();
+        if (userPhone.length()>11){
+            userPhone = decryptPhone(userPhone);
+        }
+        return R.ok().put("userPhone",userPhone);
+    }
+
+    /**
+     * 新增积分商品订单
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:add')")
+    @Log(title = "积分商品订单", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody FsIntegralOrderCreateParam param)
+    {
+        return fsIntegralOrderService.createOrder(param);
+    }
+
+    /**
+     * 修改积分商品订单
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:edit')")
+    @Log(title = "积分商品订单", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsIntegralOrder fsIntegralOrder)
+    {
+        return toAjax(fsIntegralOrderService.updateFsIntegralOrder(fsIntegralOrder));
+    }
+
+    /**
+     * 删除积分商品订单
+     */
+//    @PreAuthorize("@ss.hasPermi('his:integralOrder:remove')")
+    @Log(title = "积分商品订单", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{orderIds}")
+    public AjaxResult remove(@PathVariable Long[] orderIds)
+    {
+        return toAjax(fsIntegralOrderService.deleteFsIntegralOrderByOrderIds(orderIds));
+    }
+}

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

@@ -4,7 +4,7 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev
+    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz

+ 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

+ 17 - 0
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -8,6 +8,9 @@ import com.fs.app.taskService.SopLogsTaskService;
 import com.fs.app.taskService.SopWxLogsService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
+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.*;
@@ -76,6 +79,10 @@ public class CommonController {
 
     @Autowired
     private IFsCourseLinkService courseLinkService;
+    @Autowired
+    private FsCourseRedPacketLogMapper fsCourseRedPacketLogMapper;
+    @Autowired
+    private ICompanyService companyService;
 
     @Autowired
     private SopUserLogsMapper sopUserLogsMapper;
@@ -298,4 +305,14 @@ 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();
+    }
 }

+ 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; }
     }
 
 }

+ 36 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -674,11 +674,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 +868,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) {
 

+ 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 - 35
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -151,41 +151,7 @@ public interface CompanyUserMapper
     @Select("select * from  qw_user  where corp_id=#{corpId} and company_id=#{companyId}")
     List<QwUserVO> selectCompanyQwUserList(@Param("corpId") String corpId,@Param("companyId")Long companyId);
 
-    @Select({"<script> " +
-            "select u.*, d.dept_name, d.leader from company_user u\n" +
-            "        left join company_dept d on u.dept_id = d.dept_id\n" +
-            "        where u.del_flag = '0'\n" +
-            "        <if test=\"userName != null and userName != ''\">\n" +
-            "            AND u.user_name like concat('%', #{userName}, '%')\n" +
-            "        </if>\n" +
-            "\n" +
-            "        <if test=\"nickName != null and nickName != ''\">\n" +
-            "            AND u.nick_name like concat( #{nickName}, '%')\n" +
-            "        </if>\n" +
-            "        <if test=\"companyId != null and companyId != ''\">\n" +
-            "            AND u.company_id = #{companyId}\n" +
-            "        </if>\n" +
-            "        <if test=\"status != null and status != ''\">\n" +
-            "            AND u.status = #{status}\n" +
-            "        </if>\n" +
-            "        <if test=\"qwStatus != null \">\n" +
-            "            AND u.qw_status = #{qwStatus} \n" +
-            "        </if>\n" +
-            "        <if test=\"phonenumber != null and phonenumber != ''\">\n" +
-            "            AND u.phonenumber like concat('%', #{phonenumber}, '%')\n" +
-            "        </if>\n" +
-            "        <if test=\"beginTime != null and beginTime != ''\"><!-- 开始时间检索 -->\n" +
-            "            AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')\n" +
-            "        </if>\n" +
-            "        <if test=\"endTime != null and endTime != ''\"><!-- 结束时间检索 -->\n" +
-            "            AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')\n" +
-            "        </if>\n" +
-            "        <if test=\"deptId != null and deptId != 0\">\n" +
-            "            AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{deptId}, ancestors) ))\n" +
-            "        </if>\n" +
-            "        <!-- 数据范围过滤 -->\n" +
-            "        ${params.dataScope}"+
-            "</script>"})
+
     List<CompanyUserQwListVO> selectCompanyUserQwListVO(CompanyUserQwParam user);
 
 

+ 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 结果
      */

+ 3 - 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;
@@ -164,4 +165,6 @@ public interface ICompanyService
      * 查询企微主体管理公司列表
      */
     List<OptionsVO> getCompanyListByCorpId(String corpId);
+
+    void subtractCompanyMoneyHourse(BigDecimal money, Long companyId, LocalTime start, LocalTime end);
 }

+ 34 - 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;
@@ -820,10 +822,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();
 
@@ -1062,34 +1071,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;
-    }
-
     /**
      * 递归构建部门树
      */
@@ -1134,72 +1115,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
@@ -1341,4 +1256,29 @@ public class CompanyServiceImpl implements ICompanyService
     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);
+            }
+        }
+    }
 }

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

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

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

@@ -93,6 +93,8 @@ public class CompanyUserQwListVO extends BaseEntity {
     /** 角色对象 */
     private List<CompanyRole> roles;
 
+    /** 角色名称 */
+    private List<String> roleNames;
     /** 角色组 */
     private Long[] roleIds;
 

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

@@ -4,6 +4,7 @@ import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 @Data
 public class FsStoreOrderStatisticsVO implements Serializable
@@ -13,7 +14,7 @@ public class FsStoreOrderStatisticsVO implements Serializable
     @Excel(name = "订单数")
     Integer orderCount;
     @Excel(name = "订单金额")
-    Integer payPrice;
+    BigDecimal payPrice;
 
 
 

+ 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;
+
 }

+ 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);
+}

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

@@ -120,8 +120,7 @@ public interface FsCourseAnswerLogsMapper
             "</script>"})
     int selectErrorCountByCourseVideo(@Param("videoId") Long videoId,@Param("userId") Long userId,@Param("qwUserId") String qwUserId);
 
-    @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);
 

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

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.List;
 
 import com.fs.company.vo.RedPacketMoneyVO;
@@ -168,4 +169,6 @@ 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);
 }

+ 0 - 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.*;
@@ -12,7 +11,6 @@ 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.util.ArrayList;
 import java.util.Date;
 import java.util.List;

+ 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);
 }

+ 10 - 4
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;
@@ -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();
 }

+ 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:小程序
     private Integer isRoom;
     private Integer sendType;
     private Long periodId;
-
+    @NotBlank(message = "小程序参数不能为空")
     private String appId; //前端传来的小程序的appid
 
     private String code;

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

@@ -61,6 +61,7 @@ public class FsCourseWatchLogListParam implements Serializable {
 
 
     private Long taskId;//任务ID
+    private Long project;//任务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;
+}

+ 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);
+
+}

+ 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);
 
     /**

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

@@ -128,4 +128,6 @@ public interface IFsUserCourseService
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
 
     List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
+
+    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();
 }

+ 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);
+        }
+    }
 }

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

@@ -215,6 +215,7 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
                 packetParam.setSource(2);
                 packetParam.setRedPacketMode(1);
                 packetParam.setCompanyId(param.getCompanyId());
+                packetParam.setUser(user);
                 R sendRedPacket = paymentService.sendRedPacket(packetParam);
                 if (sendRedPacket.get("code").equals(200)) {
                     FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();

+ 7 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -383,7 +383,13 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             watchLog.setDuration(duration);
 
             //取对应视频的时长
-            Long videoDuration = getFsUserVideoDuration(videoId);
+            Long videoDuration = 0L;
+            try {
+                videoDuration = getFsUserVideoDuration(videoId);
+            }catch (Exception e){
+                log.error("视频时长识别错误:{}", key);
+                continue;
+            }
             if (videoDuration != null && videoDuration != 0) {
                 //判断是否完课
                 long percentage = (duration * 100 / videoDuration);

+ 0 - 14
fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyUserServiceImpl.java

@@ -105,20 +105,6 @@ public class FsUserCompanyUserServiceImpl extends ServiceImpl<FsUserCompanyUserM
         return baseMapper.deleteFsUserCompanyUserById(id);
     }
 
-    /**
-     * 根据用户ID和项目ID查询微信用户与销售的关系
-     * @param userId            用户ID
-     * @param projectId   项目ID
-     * @return FsUserCompanyUser
-     */
-    @Override
-    public FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId,Long companyUserId) {
-        LambdaQueryWrapper<FsUserCompanyUser> queryWrapper = Wrappers.<FsUserCompanyUser>lambdaQuery()
-                .eq(FsUserCompanyUser::getUserId, userId)
-                .eq(FsUserCompanyUser::getProjectId, projectId)
-                .eq(FsUserCompanyUser::getCompanyUserId, companyUserId);
-        return getOne(queryWrapper);
-    }
 
     @Override
     public FsUserCompanyUser selectByUserIdAndProjectId(Long userId, Long projectId) {

+ 6 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -540,7 +540,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             recordVO.setWatchLxCount(lxDay);
 
             // 领取状态
-            Long count = fsCourseAnswerLogsMapper.selectRedStatus(recordVO.getUserId(), recordVO.getVideoId());
+            Long count = fsCourseAnswerLogsMapper.selectRedStatus(recordVO.getUserId(), recordVO.getVideoId(), (Long) params.get("periodId"));
             if (Objects.nonNull(count) && count > 0) {
                 recordVO.setRedStatus(1);
             } else {
@@ -710,6 +710,11 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return fsUserCourseMapper.selectFsUserCourseVideoAppletByCourseId(courseId);
     }
 
+    @Override
+    public List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoAppletListByCourseId(Long courseId) {
+        return fsUserCourseMapper.selectFsUserCourseVideoAppletListByCourseId(courseId);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

+ 130 - 248
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -34,10 +34,7 @@ import com.fs.course.param.newfs.*;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsVideoResourceService;
-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.*;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
@@ -92,6 +89,7 @@ import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
@@ -116,32 +114,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private Boolean isNewWxMerchant;
     private static final Logger logger = LoggerFactory.getLogger(FsUserCourseVideoServiceImpl.class);
 
-    /**
-     * 红包账户锁
-     */
-    private static final String REDPACKET_POOL_LOCK = "redpacket_pool_lock";
-
-    /**
-     * 公司红包金额
-     */
-    private static final String REDPACKET_COMPANY_MONEY = "redpacket_money";
-
-    /**
-     * 用户领取红包限制
-     */
-    private static final String REDPACKET_USER_LIMIT = "redpacket_user_limit:%s:%d";
-
-    /**
-     * 红包改变记录
-     */
-    private static final String REDPACKET_COMPANY_MONEY_CHANGE = "redpacket_money_change";
-
-    /**
-     * 是否开启红包账户扣减
-     */
-    @Value("${enableRedPackAccount:0}")
-    private String ENABLE_RED_PACK_ACCOUNT;
-
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
     private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
@@ -258,17 +230,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     ConfigUtil configUtil;
 
-    @Autowired
-    private RedisTemplate<String,BigDecimal> redisTemplate;
 
-    @Autowired
-    private RedisTemplate<String,Integer> redisTemplateInteger;
-
-    /**
-     * 红包领取数量限制 默认一个用户当天最多只能领取10个
-     */
-    @Value("${RED_PACKET_LIMIT_COUNT:10}")
-    private Integer RED_PACKET_LIMIT_COUNT;
 
     /**
      * 查询课堂视频
@@ -794,7 +756,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     }
 
     private R addCustomerService(String qwUserById,String msg){
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         String contactWay = "";
+        if (ObjectUtils.isNotEmpty(config.getShowQwCode())&&!config.getShowQwCode()){
+            return R.error(400,msg).put("qrcode",contactWay);
+        }
         QwUser qwUser = qwUserMapper.selectQwUserById(Long.parseLong(qwUserById));
         if (qwUser==null){
             return R.error("客服不存在");
@@ -1023,31 +990,40 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Override
     @Transactional
     public R sendReward(FsCourseSendRewardUParam param) {
+//        boolean isWithin10Minutes = isWithin10Minutes(param.getUserId());
+//        if (isWithin10Minutes){
+//            return R.error("非有效期内,不允许领取!");
+//        }
         // 获取用户信息
         FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
 //        if (StringUtils.isEmpty(user.getMpOpenId())){
 //            return R.error("未识别到领取信息");
 //        }
+        log.info("查询会员信息:{}", user);
         if (user.getStatus()==0){
             return R.error("会员被停用,无权限,请联系客服!");
         }
-        FsCourseWatchLog log = new FsCourseWatchLog();
+        FsCourseWatchLog watchLog = new FsCourseWatchLog();
 
         // 根据链接类型判断是否已发放奖励
-        log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
-        if (log == null) {
+        watchLog = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(), param.getVideoId(), param.getQwUserId(), param.getQwExternalId());
+        log.info("看课记录:{}", watchLog);
+        if (watchLog == null) {
             return R.error("无记录");
         }
-        if (log.getLogType() != 2) {
+        if (watchLog.getLogType() != 2) {
             return R.error("未完课");
         }
-        if (log.getRewardType() != null) {
+        if (watchLog.getRewardType() != null) {
             FsCourseRedPacketLog packetLog = redPacketLogMapper.selectFsCourseRedPacketLogByTemporary(param.getVideoId(), param.getUserId());
+            log.info("课程红包:{}", packetLog);
             if(packetLog != null && packetLog.getStatus() == 1) {
                 return R.error("已领取该课程奖励,不可重复领取!");
             }
             if(packetLog != null && packetLog.getStatus() == 0) {
+                log.info("判断领取记录");
                 if(StringUtils.isNotEmpty(packetLog.getResult())){
+                    log.info("是否有结果");
                     R r = JSON.parseObject(packetLog.getResult(), R.class);
                     return r;
                 } else {
@@ -1065,29 +1041,53 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         // 获取配置信息
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-
+        log.info("奖励类型:{}", config.getRewardType());
         // 根据奖励类型发放不同奖励
         switch (config.getRewardType()) {
             // 红包奖励
             case 1:
-                return sendRedPacketReward(param, user, log, video, config);
+                return sendRedPacketReward(param, user, watchLog, video, config);
             // 积分奖励
             case 2:
-                return sendIntegralReward(param,user, log, config);
+                return sendIntegralReward(param,user, watchLog, config);
             // 红包+积分
             case 3:
-                R sendRed = sendRedPacketReward(param, user, log, video, config);
+                R sendRed = sendRedPacketReward(param, user, watchLog, video, config);
                 if (!Objects.equals(sendRed.get("code"), 200)) {
                     return sendRed;
                 }
-                return sendIntegralReward(param,user, log, config);
+                return sendIntegralReward(param,user, watchLog, config);
             default:
                 return R.error("参数错误!");
         }
     }
 
+
+    /**
+     * 检查是否在10分钟有效期内
+     */
+    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+    private static final int EXPIRE_MINUTES = 10;
+    private boolean isWithin10Minutes(Long userId) {
+        String key = "h5user:redPacket:" + userId;
+
+        String storedTimeStr = redisCache.getCacheObject(key);
+        if (storedTimeStr == null) {
+            return false;
+        }
+
+        try {
+            LocalDateTime storedTime = LocalDateTime.parse(storedTimeStr, FORMATTER);
+            long minutes = Duration.between(storedTime, LocalDateTime.now()).toMinutes();
+            return minutes <= EXPIRE_MINUTES;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     @Override
     public R sendRewardByFsUser(FsCourseSendRewardUParam param) {
+        log.info("进入用户判断");
         FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
         if (user == null){
             return R.error("未识别到用户信息");
@@ -1163,7 +1163,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
      * @return 处理结果
      */
     private R sendRedPacketReward(FsCourseSendRewardUParam param, FsUser user, FsCourseWatchLog log, FsUserCourseVideo video, CourseConfig config) {
-
+        logger.info("进入发放红包");
         // 确定红包金额
         BigDecimal amount = BigDecimal.ZERO;
         FsUserCourseVideoRedPackage redPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(param.getVideoId(), param.getCompanyId(), param.getPeriodId());
@@ -1176,23 +1176,46 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         // 准备发送红包参数
         WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
-        packetParam.setOpenId(user.getMpOpenId());
-        // 来源是小程序切换openId
-        if (param.getSource() == 2) {
-            //处理多小程序问题
+
+        if (user.getMpOpenId()!=null&&!isNewWxMerchant){
+            packetParam.setOpenId(user.getMpOpenId());
+        }else {
+            //修复数据
             FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
             if (fsUserWx ==null){
+                if (user.getCourseMaOpenId()==null){
+                    logger.error(" 【转账openId参数错误】:{}", user.getUserId());
+                    return R.error("openId参数错误,请清理缓存后重新授权!");
+                }
+                packetParam.setOpenId(user.getCourseMaOpenId());
                 try {
                     handleFsUserWx(user,param.getAppId());
                 }catch (Exception e){
-                    logger.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
+                    logger.error(" 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(),e);
                 }
+
             }else {
                 packetParam.setOpenId(fsUserWx.getOpenId());
             }
-            //查出公司绑定openid并赋值
         }
 
+//        packetParam.setOpenId(user.getMpOpenId());
+//        // 来源是小程序切换openId
+//        if (param.getSource() == 2) {
+//            //处理多小程序问题
+//            FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
+//            if (fsUserWx ==null){
+//                try {
+//                    handleFsUserWx(user,param.getAppId());
+//                }catch (Exception e){
+//                    logger.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
+//                }
+//            }else {
+//                packetParam.setOpenId(fsUserWx.getOpenId());
+//            }
+//            //查出公司绑定openid并赋值
+//        }
+
         //判断服务号配置是否存在
         if (StringUtils.isNotEmpty(config.getMpAppId())){
             packetParam.setMpAppId(config.getMpAppId());
@@ -1207,28 +1230,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         logger.info("红包金额 {},红包商户号 {}",amount,packetParam);
         //2025.6.19 红包金额为0的时候
         if (amount.compareTo(BigDecimal.ZERO)>0){
-
-            //---------------发红包前先判断润天账户余额是否足够---------
-            RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
-            try{
-                boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
-
-                if (!locked) {
-                    logger.error("获取锁失败");
-                    return R.error("[红包领取] 系统繁忙,请重试!");
-                }
-
-                // 发送红包
-                return sendRedPacketRewardToUser(param, log, config, packetParam, amount);
-
-            }catch (Exception e){
-                logger.error("领取红包失败原因:{}", ExceptionUtils.getFullStackTrace(e),e);
-                throw new RuntimeException(e);
-            }finally {
-                if (lock.isHeldByCurrentThread()) {
-                    lock.unlock();
-                }
-            }
+            // 发送红包
+            return sendRedPacketRewardToUser(param, log, config, packetParam, amount);
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             // 添加红包记录
@@ -1256,44 +1259,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
     private R sendRedPacketRewardToUser(FsCourseSendRewardUParam param, FsCourseWatchLog log, CourseConfig config, WxSendRedPacketParam packetParam, BigDecimal amount) {
 
-        // 判断当前用户是否限流
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-        String today = sdf.format(new Date());
-        String userLimitKey = String.format(REDPACKET_USER_LIMIT, today, param.getUserId());
-        Integer userCount =  redisTemplateInteger.opsForValue().get(userLimitKey);
-
-        // 首次领取
-        if(userCount == null) {
-            userCount = 0;
-            long expireSeconds = getExpireSeconds();
-            redisTemplateInteger.opsForValue().set(userLimitKey, userCount, expireSeconds, TimeUnit.SECONDS);
-        }
-
-        if(userCount >= RED_PACKET_LIMIT_COUNT){
-            logger.info("[红包领取] 用户{} 领取红包已经达到最大限制!",param.getUserId());
-            return R.error("[红包领取] 当前用户当前已经领取红包已经达到限制!");
-        }
-
-
-        BigDecimal companyMoney = null;
-        if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-            companyMoney = redisTemplate.opsForValue().get(REDPACKET_COMPANY_MONEY);
-
-            if(ObjectUtils.isNull(companyMoney)){
-                SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("company.money");
-                if(ObjectUtils.isNull(sysConfig)){
-                    throw new IllegalArgumentException("润天公司账户余额不能为空!请检查配置!");
-                }
-                String configValue = sysConfig.getConfigValue();
-                companyMoney = new BigDecimal(configValue);
-                logger.info("缓存公司余额为空,从数据库读取 companyMoney: {}",companyMoney);
-            }
-
-            if (companyMoney.compareTo(BigDecimal.ZERO) <= 0) {
-                logger.info("润天账户余额: {} 不足!", companyMoney);
-                return R.error("[红包领取] 账户余额不足,请联系管理员!");
-            }
-        }
 
         // 发送红包
         R sendRedPacket = paymentService.sendRedPacket(packetParam);
@@ -1319,26 +1284,13 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             redPacketLog.setAmount(amount);
             redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
             redPacketLog.setPeriodId(param.getPeriodId());
-            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                redPacketLog.setAccBalanceBefore(companyMoney);
-                redPacketLog.setAccBalanceAfter(companyMoney.subtract(amount));
-            }
 
             redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
             // 更新观看记录的奖励类型
             log.setRewardType(config.getRewardType());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
 
-            if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                // 更新账户余额
-                logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
-
-                companyMoney = companyMoney.subtract(amount);
-                redisTemplate.opsForValue().set(REDPACKET_COMPANY_MONEY,companyMoney);
-            }
-
-            // 用户领取红包次数+1
-            redisTemplateInteger.opsForValue().increment(userLimitKey, 1);
+//            redisCache.setCacheObject("h5user:redPacket:"+param.getUserId(),LocalDateTime.now().toString());
 
             return sendRedPacket;
         } else {
@@ -1346,17 +1298,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
     }
 
-    private static long getExpireSeconds() {
-        Calendar calendar = Calendar.getInstance();
-        calendar.add(Calendar.DAY_OF_YEAR, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.MILLISECOND, 0);
-        // 计算从现在到明天凌晨的秒数
-        long expireSeconds = (calendar.getTimeInMillis() - System.currentTimeMillis()) / 1000;
-        return expireSeconds;
-    }
+
 
 
     private void handleFsUserWx(FsUser user, String appId) {
@@ -1463,112 +1405,43 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
                 return R.error("服务商余额不足,请联系群主服务器充值!");
             }
 
-            //---------------发红包前先判断润天账户余额是否足够---------
-            RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
-            try{
-                boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
-
-                if (!locked) {
-                    logger.error("获取锁失败");
-                    return R.error("[红包领取] 系统繁忙,请重试!");
-                }
-
-                // 判断当前用户是否限流
-                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
-                String today = sdf.format(new Date());
-                String userLimitKey = String.format(REDPACKET_USER_LIMIT, today, param.getUserId());
-                Integer userCount =  redisTemplateInteger.opsForValue().get(userLimitKey);
-
-                // 首次领取
-                if(userCount == null) {
-                    userCount = 0;
-                    long expireSeconds = getExpireSeconds();
-                    redisTemplateInteger.opsForValue().set(userLimitKey, userCount, expireSeconds, TimeUnit.SECONDS);
-                }
-
-                if(userCount >= RED_PACKET_LIMIT_COUNT){
-                    logger.info("[红包领取] 用户{} 领取红包已经达到最大限制!",param.getUserId());
-                    return R.error("[红包领取] 当前用户当前已经领取红包已经达到限制!");
-                }
-
-                BigDecimal companyMoney = null;
-                if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                    companyMoney = redisTemplate.opsForValue().get(REDPACKET_COMPANY_MONEY);
-                    if(ObjectUtils.isNull(companyMoney)){
-                        SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("company.money");
-                        if(ObjectUtils.isNull(sysConfig)){
-                            throw new IllegalArgumentException("润天公司账户余额不能为空!请检查配置!");
-                        }
-                        String configValue = sysConfig.getConfigValue();
-                        companyMoney = new BigDecimal(configValue);
-                        logger.info("缓存公司余额为空,从数据库读取 companyMoney: {}",companyMoney);
-                    }
-
-                    if (companyMoney.compareTo(BigDecimal.ZERO) <= 0) {
-                        logger.info("润天账户余额: {} 不足!", companyMoney);
-                        return R.error("[红包领取] 账户余额不足,请联系管理员!");
-                    }
-                }
-                // 发送红包
-                R sendRedPacket = paymentService.sendRedPacket(packetParam);
-                if (sendRedPacket.get("code").equals(200)) {
-                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
-                    TransferBillsResult transferBillsResult;
-                    if (sendRedPacket.get("isNew").equals(1)){
-                        transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
-                        redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
-                        redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
-                    }else {
-                        redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
-                        redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
-                    }
-                    // 添加红包记录
-                    redPacketLog.setCourseId(param.getCourseId());
-                    redPacketLog.setCompanyId(param.getCompanyId());
-                    redPacketLog.setUserId(param.getUserId());
-                    redPacketLog.setVideoId(param.getVideoId());
-                    redPacketLog.setStatus(0);
-                    redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
-                    redPacketLog.setCompanyUserId(param.getCompanyUserId());
-                    redPacketLog.setCreateTime(new Date());
-                    redPacketLog.setAmount(amount);
-                    redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
-                    redPacketLog.setPeriodId(param.getPeriodId());
-                    redPacketLog.setAppId(param.getAppId());
-                    if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                        redPacketLog.setAccBalanceBefore(companyMoney);
-                        redPacketLog.setAccBalanceAfter(companyMoney.subtract(amount));
-                    }
-                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-                    // 更新观看记录的奖励类型
-                    log.setRewardType(config.getRewardType());
-                    courseWatchLogMapper.updateFsCourseWatchLog(log);
-
-
-                    if(StringUtils.equals(ENABLE_RED_PACK_ACCOUNT,"1")) {
-                        // 更新账户余额
-                        logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
-
-                        companyMoney = companyMoney.subtract(amount);
-                        redisTemplate.opsForValue().set(REDPACKET_COMPANY_MONEY,companyMoney);
-                    }
-
-                    // 用户领取红包次数+1
-                    redisTemplateInteger.opsForValue().increment(userLimitKey, 1);
-                    return sendRedPacket;
-                } else {
-                    return R.error("奖励发送失败,请联系客服");
-                }
-            }catch (Exception e){
-                logger.error("领取红包失败原因:{}", ExceptionUtils.getFullStackTrace(e),e);
-                throw new RuntimeException(e);
-            }finally {
-                if (lock.isHeldByCurrentThread()) {
-                    lock.unlock();
+            // 发送红包
+            R sendRedPacket = paymentService.sendRedPacket(packetParam);
+            if (sendRedPacket.get("code").equals(200)) {
+                FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                TransferBillsResult transferBillsResult;
+                if (sendRedPacket.get("isNew").equals(1)){
+                    transferBillsResult = (TransferBillsResult)sendRedPacket.get("data");
+                    redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
+                    redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                }else {
+                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
                 }
+                // 添加红包记录
+                redPacketLog.setCourseId(param.getCourseId());
+                redPacketLog.setCompanyId(param.getCompanyId());
+                redPacketLog.setUserId(param.getUserId());
+                redPacketLog.setVideoId(param.getVideoId());
+                redPacketLog.setStatus(0);
+                redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
+                redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                redPacketLog.setCreateTime(new Date());
+                redPacketLog.setAmount(amount);
+                redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
+                redPacketLog.setPeriodId(param.getPeriodId());
+                redPacketLog.setAppId(param.getAppId());
+
+                redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+
+                // 更新观看记录的奖励类型
+                log.setRewardType(config.getRewardType());
+                courseWatchLogMapper.updateFsCourseWatchLog(log);
+
+                return sendRedPacket;
+            } else {
+                return R.error("奖励发送失败,请联系客服");
             }
-
         } else {
             FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
             // 添加红包记录
@@ -1726,6 +1599,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         redPacketLog.setAmount(BigDecimal.valueOf(config.getAnswerIntegral()).divide(BigDecimal.valueOf(1000)));
         redPacketLog.setRemark("点播答题领取积分转");
         redPacketLog.setWatchLogId(log.getLogId() !=null ? log.getLogId() : null);
+        redPacketLog.setPeriodId(param.getPeriodId());
         redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
         return R.ok("奖励发放成功").put("rewardType",config.getRewardType());
     }
@@ -1867,8 +1741,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             return ResponseResult.fail(504, "课程配置错误,项目归属为空,课程ID: " + param.getCourseId());
         }
 
-        // 查询【用户-项目】关系
-        FsUserCompanyUser userCompanyUser = userCompanyUserService.selectByUserIdAndProjectId(fsUser.getUserId(), courseProject,companyUser.getUserId());
+        FsUserCompanyUser userCompanyUser = userCompanyUserService.selectByUserIdAndProjectId(fsUser.getUserId(), courseProject);
 
         // 添加逻辑:如果存在fs_user表数据,但是又不存在fs_user_company_user表,则表示是以前企微看课的,需要手动绑定
         if(Objects.isNull(userCompanyUser)) {
@@ -1990,6 +1863,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         LocalDateTime effectiveEndTime = companyUserEndDateTime != null ?
                 companyUserEndDateTime : periodDays.getEndDateTime();
         // 检查时间范围和状态
+        log.error("传入参数:开始时间:{},结束时间:{},periodDays={}",effectiveStartTime,effectiveEndTime,periodDays);
         return DateUtil.isWithinRangeSafe(LocalDateTime.now(), effectiveStartTime, effectiveEndTime)&& periodDays.getStatus() == 1;
     }
 
@@ -2927,5 +2801,13 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return R.ok("生成看课记录成功!");
     }
 
+    /**
+     * 查询选择使用的视频列表
+     */
+    @Override
+    public List<FsUserCourseVideoChooseVO> getChooseCourseVideoListByMap(Map<String, Object> params) {
+        return fsUserCourseVideoMapper.getChooseCourseVideoListByMap(params);
+    }
+
 }
 

+ 24 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentFollowServiceImpl.java

@@ -1,9 +1,13 @@
 package com.fs.course.service.impl;
 
+import java.util.Collections;
 import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.course.param.FsUserTalentFansParam;
+import com.fs.course.vo.FsUserTalentFansVo;
+import com.fs.course.vo.FsUserTalentFollowVo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserTalentFollowMapper;
@@ -107,4 +111,24 @@ public class FsUserTalentFollowServiceImpl implements IFsUserTalentFollowService
         return fsUserTalentFollowMapper.deleteFollow(talentId,userId);
     }
 
+    @Override
+    public Integer queryFansCount(Long talentId) {
+        return fsUserTalentFollowMapper.queryFansCount(talentId);
+    }
+
+    @Override
+    public Integer queryIdolCount(Long userId) {
+        return fsUserTalentFollowMapper.queryIdolCount(userId);
+    }
+
+    @Override
+    public List<FsUserTalentFansVo> selectFsUserTalentFansVoList(FsUserTalentFansParam param) {
+        return fsUserTalentFollowMapper.selectFsUserTalentFansVoList(param);
+    }
+
+    @Override
+    public List<FsUserTalentFollowVo> selectFsUserFollowVoList(FsUserTalentFansParam param) {
+        return fsUserTalentFollowMapper.selectFsUserFollowVoList(param);
+    }
+
 }

+ 117 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserTalentServiceImpl.java

@@ -1,12 +1,26 @@
 package com.fs.course.service.impl;
 
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+
+import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsUserVideo;
+import com.fs.course.service.IFsBlackTalentService;
+import com.fs.course.service.IFsUserTalentFollowService;
+import com.fs.course.service.IFsUserVideoService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.huifuPay.sdk.opps.core.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserTalentMapper;
 import com.fs.course.domain.FsUserTalent;
 import com.fs.course.service.IFsUserTalentService;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 达人Service业务层处理
@@ -19,6 +33,14 @@ public class FsUserTalentServiceImpl implements IFsUserTalentService
 {
     @Autowired
     private FsUserTalentMapper fsUserTalentMapper;
+    @Autowired
+    private IFsUserTalentFollowService userTalentFollowService;
+    @Autowired
+    private IFsBlackTalentService blackTalentService;
+    @Autowired
+    private IFsUserVideoService fsUserVideoService;
+    @Autowired
+    private IFsUserService fsUserService;
 
     /**
      * 查询达人
@@ -101,4 +123,99 @@ public class FsUserTalentServiceImpl implements IFsUserTalentService
             return fsUserTalentMapper.minusFans(talentId);
         }
     }
+
+    @Override
+    public FsUserTalent queryTalentByUserId(Long userId){
+        return fsUserTalentMapper.queryTalentByUserId(userId);
+    }
+
+    @Override
+    @Transactional
+    public int addFsUserTalent(Long userId) {
+        //根据userID查询达人表中是否存在数据
+        FsUserTalent fsUserTalent = queryTalentByUserId(userId);
+        if (null!=fsUserTalent){
+            return 0;
+        }
+        FsUser fsUser1 = fsUserService.selectFsUserByUserId(userId);
+        fsUserTalent = new FsUserTalent();
+        if (!StringUtil.isEmpty(fsUser1.getAvatar())){
+            fsUserTalent.setAvatar(fsUser1.getAvatar());
+        }
+        fsUserTalent.setUserId(fsUser1.getUserId());
+        if (!StringUtil.isEmpty(fsUser1.getPhone())){
+            fsUserTalent.setNickName(fsUser1.getNickName());
+        }
+        if (!StringUtil.isEmpty(fsUser1.getPhone())){
+            String phone = PhoneUtil.decryptPhone(fsUser1.getPhone());
+            fsUserTalent.setPhone(phone);
+
+        }
+        fsUserTalent.setCreateTime(new Date());
+        if (!StringUtil.isEmpty(fsUser1.getSex())){
+            fsUserTalent.setSex(fsUser1.getSex().longValue());
+        }
+        fsUserTalent.setLevel(1l);
+        fsUserTalent.setTalentType("1");
+        fsUserTalent.setIsAudit(1l);
+        fsUserTalent.setAuditTime(new Date());
+        fsUserTalent.setStatus(1l);
+        fsUserTalent.setIsDel(1l);
+        fsUserTalentMapper.insertFsUserTalent(fsUserTalent);
+        return 1;
+    }
+
+    @Override
+    public int updateFsUserTalentByUser(FsUserTalent fsUserTalent)
+    {
+        return fsUserTalentMapper.updateFsUserTalentByUser(fsUserTalent);
+    }
+
+    @Override
+    public R getTalentDetail(Long userId, Long loginUser) {
+
+        HashMap<String, Object> map = new HashMap<>();
+        FsUserTalent fsUserTalent = queryTalentByUserId(userId);
+        if (null==fsUserTalent){
+            return R.error("该用户暂未注册达人身份");
+        }
+
+        //查询达人粉丝数
+        Integer fansCount = userTalentFollowService.queryFansCount(fsUserTalent.getTalentId());
+        //查询关注的达人数量
+        Integer idolCount = userTalentFollowService.queryIdolCount(userId);
+        //查询视频列表
+        List<FsUserVideo> fsUserVideos = fsUserVideoService.selectVideoByTalentId(fsUserTalent.getTalentId());
+        //所有视频的收藏量
+//        Long favoriteNum=0l;
+        Long likeNum = 0l;
+        //拿到用户的所有视频id
+        ArrayList<Long> videoIds = new ArrayList<>();
+        for (FsUserVideo fsUserVideo : fsUserVideos) {
+//            favoriteNum+=fsUserVideo.getFavoriteNum();
+            likeNum+=fsUserVideo.getLikes();
+            //videoIds.add(fsUserVideo.getVideoId());
+        }
+        //查询我的——> 我的视频  视频收藏数量
+        int favoriteNum = fsUserVideoService.countFavoriteVideos(userId);
+        //查询视频收藏数量
+        /*if (fsUserVideos.size()>0){
+            fsUserVideoFavoriteService.queryFavoriteCount(fsUserTalent.getTalentId());
+        }*/
+        //查询当前达人是否被查询的用户拉黑或举报
+        if (loginUser>0){
+            if (blackTalentService.selectBlackTalent(fsUserTalent.getTalentId(),loginUser)>0){
+                map.put("isBlack",1);
+            }
+        }
+        map.put("fsUserTalent",fsUserTalent);
+        map.put("fansCount",fansCount);
+        map.put("idolCount",idolCount);
+        //map.put("fsUserVideos",fsUserVideos);
+        map.put("favoriteNum",favoriteNum);
+        map.put("likeNum",likeNum);
+
+        return R.ok().put("data",map);
+    }
+
 }

+ 93 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoServiceImpl.java

@@ -338,6 +338,99 @@ public class FsUserVideoServiceImpl implements IFsUserVideoService {
         return oldList;
     }
 
+    @Override
+    public List<FsUserVideo> selectVideoByTalentId(Long talentId) {
+        return fsUserVideoMapper.selectVideoByTalentId(talentId);
+    }
+
+    @Override
+    public int countFavoriteVideos(Long userId) {
+        return fsUserVideoMapper.countFavoriteVideos(userId);
+    }
+
+    @Override
+    public R addUserVideoByTalent(FsUserVideoAddParam param) {
+        FsUserVideo fsUserVideo = new FsUserVideo();
+        BeanUtils.copyProperties(param, fsUserVideo);
+        fsUserVideo.setCreateTime(DateUtils.getNowDate());
+        fsUserVideo.setIsAudit(0);
+        fsUserVideo.setSource(2);
+        fsUserVideo.setStatus(1);
+        //fsUserVideo.setCateId(param.getCateId());
+//        fsUserVideo.setComments(0L);
+        fsUserVideoMapper.insertFsUserVideo(fsUserVideo);
+        return R.ok();
+    }
+
+    @Override
+    public List<FsUserVideoListUVO> selectFsUserVideoListUVOByUser(Long talentId, boolean oneSelf, Long userId) {
+        List<FsUserVideoListUVO> list = fsUserVideoMapper.selectFsUserVideoListUVOByUser(talentId, oneSelf);
+        /*if (param != null && param.getUserId() != null) {
+            Long userId = param.getUserId();
+            list = selectLikesAndFavorites(userId, list);
+        }*/
+        // 当前视频是否被自己喜欢或收藏
+        if (list.size() > 0) {
+            selectLikesAndFavoritesByMyself(list,userId);
+        }
+
+
+        return list;
+    }
+
+    /**
+     * 删除达人视频信息
+     *
+     * @param videoId 课堂视频id
+     * @param userId 用户id
+     * @return 结果
+     */
+    @Override
+    public R deleteFsUserVideoByVideoIdWithVerify(Long videoId, Long userId) {
+        if (videoId == null) {
+            return R.error("请选择要删除的视频");
+        }
+        FsUserVideoPVO entity = fsUserVideoMapper.selectFsUserVideoPVO(videoId);
+        if (entity == null) {
+            return R.error("视频不存在");
+        } else if (!Objects.equals(entity.getUserId(), userId)) {
+            return R.error("您没有权限删除此视频");
+        }
+        this.deleteFsUserVideoByVideoId(videoId.toString().trim());
+        return R.ok();
+    }
+
+    @Override
+    public R updateVideoStatusWithVerify(FsUserVideo fsUserVideo, Long userId) {
+        if (fsUserVideo.getVideoId() == null) {
+            return R.error("请选择要操作的视频");
+        }
+        FsUserVideoPVO entity = fsUserVideoMapper.selectFsUserVideoPVO(fsUserVideo.getVideoId());
+        if (entity == null) {
+            return R.error("视频不存在");
+        } else if (!Objects.equals(entity.getUserId(), userId)) {
+            return R.error("您没有权限操作此视频");
+        }
+        this.updateFsUserVideoIsShow(new Long[]{fsUserVideo.getVideoId()}, fsUserVideo.getStatus());
+        return R.ok();
+    }
+
+    private void selectLikesAndFavoritesByMyself(List<FsUserVideoListUVO> list, long userId) {
+        List<Long> videoIds = list.stream().map(vo -> Long.parseLong(vo.getId())).collect(Collectors.toList());
+        Map<Long, VideoLikeStatusDTO> likeMaps = fsUserVideoLikeMapper.checkLikes(videoIds, userId);
+        Map<Long, VideoFavoriteStatusDTO> FavoriteMaps = fsUserVideoFavoriteMapper.checkFavorites(videoIds, userId);
+        long videoId;
+        for (FsUserVideoListUVO entity : list) {
+            videoId = Long.parseLong(entity.getId());
+            if (likeMaps.containsKey(videoId)) {
+                entity.setLike(1);
+            }
+            if (FavoriteMaps.containsKey(videoId)) {
+                entity.setFavorite(1);
+            }
+        }
+    }
+
     public static String updateUrlPrefix(String url) {
         final String oldPrefix = "https://obs.ylrztop.com";
         final String newPrefix = "https://rtobs.ylrztop.com";

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserVideoTagsServiceImpl.java

@@ -100,4 +100,9 @@ public class FsUserVideoTagsServiceImpl implements IFsUserVideoTagsService
     public List<FsUserVideoTagsPVO> selectFsUserVideoTagsSubList() {
         return fsUserVideoTagsMapper.selectFsUserVideoTagsSubList();
     }
+
+    @Override
+    public List<FsUserVideoTags> selectTagList() {
+        return fsUserVideoTagsMapper.selectTagList();
+    }
 }

+ 44 - 0
fs-service/src/main/java/com/fs/course/vo/FsBlackTalentPVO.java

@@ -0,0 +1,44 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsBlackTalentPVO {
+    /** $column.columnComment */
+    private Long id;
+
+    private Long userId;
+
+    private Long talentId;
+
+    private String type;
+
+    private String reportDesc;
+
+    private String isAudit;
+
+    /** 审核时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date auditTime;
+
+    /** 审核人 */
+    private Long auditUser;
+
+    /** 审核说明 */
+    private String auditDesc;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date creatTime;
+
+    /** 短视频id */
+    private Long videoId;
+
+    /** 1:达人,2:短视频 */
+    private String style;
+    private String auditUserName;
+    private String talentName;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoChooseVO.java

@@ -0,0 +1,23 @@
+package com.fs.course.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsUserCourseVideoChooseVO {
+
+    @ApiModelProperty("小节ID")
+    private Long videoId;
+
+    @ApiModelProperty("课程名称")
+    private String courseName;
+
+    @ApiModelProperty("小节名称")
+    private String courseVideoName;
+
+    @ApiModelProperty("文件名称")
+    private String videoName;
+
+    @ApiModelProperty("时长")
+    private Integer duration;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserTalentFansVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsUserTalentFansVo {
+    private Long userId; //粉丝id
+    private Long talentId; //粉丝达人id
+    private String nickName; //粉丝名字
+    private String avatar; //粉丝头像
+    private Long fans; //粉丝的粉丝数
+    private Long videoNum; //粉丝发布视频数
+    private Integer isFollow; //是否关注这个粉丝
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserTalentFollowVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsUserTalentFollowVo {
+
+    private Long talentId; //关注达人id
+    private String nickName; //关注达人名字
+    private String avatar; //关注达人头像
+    private String remark; //关注达人个签
+    private Long userId; //关注达人的
+
+}

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

@@ -94,5 +94,8 @@ public class FsUserVideoPVO extends BaseEntity
     /** 收藏数 */
     private Long favoriteNum;
 
+    /** 视频发布者对应的用户id */
+    private Long userId;
+
     private String auditByName;
 }

+ 14 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserVideoTagsVo.java

@@ -0,0 +1,14 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FsUserVideoTagsVo {
+    private static final long serialVersionUID = 1L;
+    private Long value;
+    private String label;
+    private Long pid;
+    private List<FsUserVideoTagsVo> children; //子集
+}

+ 60 - 0
fs-service/src/main/java/com/fs/event/ActiveClickEvent.java

@@ -0,0 +1,60 @@
+package com.fs.event;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Arrays;
+
+@Getter
+public class ActiveClickEvent extends ApplicationEvent {
+
+    private final Long activeId;
+    private final Long userId;
+    private final Type type;
+    private final ProductType productType;
+    private final Long resourceId;
+
+    public ActiveClickEvent(Object source, Long activeId, Long userId, Type type) {
+        super(source);
+        this.activeId = activeId;
+        this.userId = userId;
+        this.type = type;
+        this.productType = null;
+        this.resourceId = null;
+    }
+
+    public ActiveClickEvent(Object source, Long activeId, Long userId, Type type, ProductType productType, Long resourceId) {
+        super(source);
+        this.activeId = activeId;
+        this.userId = userId;
+        this.type = type;
+        this.productType = productType;
+        this.resourceId = resourceId;
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum Type {
+        HOME(1), VIDEO(2), DOCTOR(3), PRODUCT(4);
+        private final Integer value;
+        public static Type valueOf(Integer value) {
+            if (value == null)
+                return null;
+            return Arrays.stream(Type.values()).filter(t -> t.value.equals(value)).findFirst().orElse(null);
+        }
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum ProductType {
+        PACKAGE(1), INTEGRAL(2);
+        private final Integer value;
+        public static ProductType valueOf(Integer value) {
+            if (value == null)
+                return null;
+            return Arrays.stream(ProductType.values()).filter(productType -> productType.value.equals(value)).findFirst().orElse(null);
+        }
+    }
+
+}

+ 27 - 0
fs-service/src/main/java/com/fs/event/ActiveClickListener.java

@@ -0,0 +1,27 @@
+package com.fs.event;
+
+import com.fs.his.service.IFsPromotionalActiveLogService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class ActiveClickListener {
+
+    @Autowired
+    private IFsPromotionalActiveLogService activeLogService;
+
+    @Async
+    @EventListener
+    public void handleActiveClick(ActiveClickEvent event) {
+        log.debug("用户点击行为: event: {}", event);
+
+        Integer productType = Objects.isNull(event.getProductType()) ? null : event.getProductType().getValue();
+        activeLogService.saveClickActionLog(event.getActiveId(), event.getUserId(), event.getType().getValue(), productType, event.getResourceId());
+    }
+}

+ 7 - 2
fs-service/src/main/java/com/fs/fastGpt/param/FastGptChatSessionParam.java

@@ -35,8 +35,8 @@ public class FastGptChatSessionParam  extends BaseEntity {
     @Excel(name = "是否查看")
     private Long isLook;
 
-    /** 用户类型 1微信用户 2小程序用户 3销售用户 */
-    @Excel(name = "用户类型 1微信用户 2小程序用户 3销售用户")
+    /** 用户类型 1微信用户 2小程序用户 3客服用户 */
+    @Excel(name = "用户类型 1微信用户 2小程序用户 3客服用户")
     private Integer userType;
 
     /** 客户昵称 */
@@ -47,4 +47,9 @@ public class FastGptChatSessionParam  extends BaseEntity {
     private String qwUserName;
     private Integer isArtificial;
 
+    private Long qwExtId;
+    private String qwUserId;
+    private Integer overTime;
+    private String corpId;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatSessionService.java

@@ -78,4 +78,6 @@ public interface IFastGptChatSessionService
     void addAiMsgBySopByStatus(String id);
 
     void qwHookNotifyAddMsg(QwHookMsgVO msgVo);
+
+    Integer selectFastGptChatSessionArtificialType(FastGptChatSessionParam sessionParam);
 }

+ 16 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatSessionServiceImpl.java

@@ -36,6 +36,7 @@ import com.fs.qw.mapper.QwExternalContactInfoMapper;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwContactWayService;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwJsApiService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qwApi.service.QwApiService;
@@ -81,6 +82,8 @@ public class FastGptChatSessionServiceImpl implements IFastGptChatSessionService
     @Autowired
     private QwExternalContactInfoMapper qwExternalContactInfoMapper;
     @Autowired
+    private IQwExternalContactService qwExternalContactService;
+    @Autowired
     private IFastGptRoleService roleService;
     @Autowired
     private ChatService chatService;
@@ -358,6 +361,19 @@ public class FastGptChatSessionServiceImpl implements IFastGptChatSessionService
             }
 
     }
+    @Override
+    public Integer selectFastGptChatSessionArtificialType(FastGptChatSessionParam sessionParam) {
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(sessionParam.getCorpId().trim(), sessionParam.getQwUserId().trim());
+        if(qwUser != null && qwUser.getId() != null){
+            FastGptChatSession chatSession = fastGptChatSessionMapper.selectFastGptChatSessionByQwExternalContactsAndUserId(sessionParam.getQwExtId(), qwUser.getId());
+            if(chatSession != null && chatSession.getIsArtificial() != null){
+                return chatSession.getIsArtificial();
+            }else{
+                return null;
+            }
+        }
+        return null;
+    }
 
     @Override
     public void qwHookNotifyAddMsg(QwHookMsgVO vo) {

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

@@ -55,5 +55,7 @@ public class FsAdv extends BaseEntity
     @Excel(name = "显示类型 1公众号链接 2 小程序页面地址 3文章内容")
     private Integer showType;
 
+    /** 宣传活动ID **/
+    private Long activeId;
 
 }

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActive.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active")
+public class FsPromotionalActive {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动标题文案
+     */
+    private String title;
+    /**
+     * 活动主题
+     */
+    private String theme;
+    /**
+     * 活动内容
+     */
+    private String content;
+    /**
+     * 是否删除 0正常 1删除
+     */
+    private Integer isDel;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+}

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveLog.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active_log")
+public class FsPromotionalActiveLog {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动ID
+     */
+    private Long activeId;
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 资源ID
+     */
+    private Long resourceId;
+    /**
+     * 模块类型 1.首页 2.视频 3.医生 4.产品
+     */
+    private Integer type;
+    /**
+     * 产品类型 1.疗法 2.积分商品
+     */
+    private Integer productType;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 39 - 0
fs-service/src/main/java/com/fs/his/domain/FsPromotionalActiveResource.java

@@ -0,0 +1,39 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_promotional_active_resource")
+public class FsPromotionalActiveResource {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 活动ID
+     */
+    private Long activeId;
+    /**
+     * 资源ID
+     */
+    private Long resourceId;
+    /**
+     * 资源类型 1.视频 2.医生 3.产品
+     */
+    private Integer type;
+    /**
+     * 产品类型 1.疗法 2.积分商品
+     */
+    private Integer productType;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 100 - 0
fs-service/src/main/java/com/fs/his/domain/RedPacketLog.java

@@ -0,0 +1,100 @@
+package com.fs.his.domain;
+
+import lombok.Data;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 红包发放日志表
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class RedPacketLog {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 红包金额
+     */
+    private BigDecimal amount;
+
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+
+    /**
+     * 来源 1:h5 2:看课小程序
+     */
+    private Integer source;
+
+    /**
+     * 红包模式
+     */
+    private Integer redPacketMode;
+
+    /**
+     * 应用ID
+     */
+    private String appId;
+
+    /**
+     * 扣减前余额
+     */
+    private BigDecimal accBalanceBefore;
+
+    /**
+     * 扣减后余额
+     */
+    private BigDecimal accBalanceAfter;
+
+    /**
+     * 状态:0-处理中 1-成功 2-失败
+     */
+    private Integer status;
+
+    /**
+     * 错误信息
+     */
+    private String errorMsg;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 38 - 0
fs-service/src/main/java/com/fs/his/dto/FsPromotionalActiveDTO.java

@@ -0,0 +1,38 @@
+package com.fs.his.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+
+@Data
+public class FsPromotionalActiveDTO {
+
+    @ApiModelProperty("活动ID")
+    private Long id;
+
+    @NotBlank(message = "活动标题不能为空")
+    @ApiModelProperty("活动标题")
+    private String title;
+
+    @NotBlank(message = "活动主题不能为空")
+    @ApiModelProperty("活动主题")
+    private String theme;
+
+    @NotBlank(message = "活动内容不能为空")
+    @ApiModelProperty("活动内容")
+    private String content;
+
+    @ApiModelProperty("积分商品")
+    private List<Long> goodsIds;
+
+    @ApiModelProperty("套餐包商品")
+    private List<Long> packageIds;
+
+    @ApiModelProperty("问诊医生")
+    private List<Long> doctorIds;
+
+    @ApiModelProperty("视频小节")
+    private List<Long> videoIds;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/his/mapper/FsDoctorMapper.java

@@ -1,6 +1,7 @@
 package com.fs.his.mapper;
 
 import java.util.List;
+import java.util.Map;
 
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsDoctor;
@@ -209,4 +210,14 @@ public interface FsDoctorMapper
             "order by doctor_id desc"+
             "</script>"})
     List<FsDoctorVO> selectDocVOByNameAndPhone(@Param("param") FsDoctorParam param);
+
+    /**
+     * 查询医生选择列表
+     */
+    List<FsDoctorChooseVO> getChooseDoctorListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据医生id集合查询列表
+     */
+    List<FsDoctorListUVO> getFsDoctorListUVOListByIds(@Param("doctorIds") List<Long> doctorIds);
 }

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

@@ -2,13 +2,17 @@ package com.fs.his.mapper;
 
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.param.FsIntegralGoodsListUParam;
+import com.fs.his.vo.FsGoodsVO;
+import com.fs.his.vo.FsIntegralGoodsChooseVO;
 import com.fs.his.vo.FsIntegralGoodsListUVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 积分商品Mapper接口
@@ -66,7 +70,7 @@ public interface FsIntegralGoodsMapper
      */
     public int deleteFsIntegralGoodsByGoodsIds(Long[] goodsIds);
 
-    @Select({"<script> select goods_id, img_url, images, goods_name, ot_price, goods_type, status, integral, cash, sort, stock, descs, create_time from fs_integral_goods" +
+    @Select({"<script> select goods_id,bar_code, img_url, images, goods_name, ot_price, goods_type, status, integral, cash, sort, stock, descs, create_time from fs_integral_goods" +
             "<where>  \n" +
             "            <if test=\"goodsName != null  and goodsName != ''\"> and goods_name like concat('%', #{goodsName}, '%')</if>\n" +
             "            <if test=\"goodsType != null \"> and goods_type = #{goodsType}</if>\n" +
@@ -97,4 +101,14 @@ public interface FsIntegralGoodsMapper
 
     @Update("update fs_integral_goods set stock = stock + #{num} where goods_id = #{goodsId}")
     int addStock(@Param("goodsId") Long goodsId, @Param("num") int num);
+
+    /**
+     * 获取选择积分商品列表
+     */
+    List<FsIntegralGoodsChooseVO> getChooseIntegralGoodsListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据id集合查询积分商品列表
+     */
+    List<FsGoodsVO> getFsGoodsVOListByIds(@Param("goodsIds") List<Long> goodsIds);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPackageMapper.java

@@ -1,6 +1,8 @@
 package com.fs.his.mapper;
 
 import java.util.List;
+import java.util.Map;
+
 import com.fs.his.domain.FsPackage;
 import com.fs.his.param.FsPackageListUParam;
 import com.fs.his.param.FsPackageParam;
@@ -155,4 +157,13 @@ public interface FsPackageMapper
 
     List<FsPackage> selectFsPackageListByIds(Long[] packageIds);
 
+    /**
+     * 获取套餐包选择列表
+     */
+    List<FsPackageChooseVO> getChoosePackageListByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 根据套餐包id集合查询列表
+     */
+    List<FsGoodsVO> getFsGoodsVOListByIds(@Param("packageIds") List<Long> packageIds);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveLogMapper.java

@@ -0,0 +1,17 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActiveLog;
+import com.fs.his.vo.FsPromotionalActiveStatVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+public interface FsPromotionalActiveLogMapper extends BaseMapper<FsPromotionalActiveLog> {
+
+    /**
+     * 活动行为统计
+     */
+    List<FsPromotionalActiveStatVO> getPromotionalActiveLogStatByMap(@Param("params") Map<String, Object> params);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveMapper.java

@@ -0,0 +1,29 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActive;
+import com.fs.his.vo.FsPromotionalActiveVO;
+import com.fs.his.vo.OptionsVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+public interface FsPromotionalActiveMapper extends BaseMapper<FsPromotionalActive> {
+
+    /**
+     * 查询活动列表
+     */
+    List<FsPromotionalActiveVO> selectPromotionalActiveVOList(FsPromotionalActive active);
+
+    /**
+     * 根据ID查询活动详情
+     */
+    FsPromotionalActiveVO selectPromotionalActiveVOById(@Param("activeId") Long activeId);
+
+    /**
+     * 获取活动选项列表
+     */
+    @Select("select title as dictLabel, id as dictValue from fs_promotional_active where is_del = 0")
+    List<OptionsVO> getPromotionalActiveOption();
+}

+ 15 - 0
fs-service/src/main/java/com/fs/his/mapper/FsPromotionalActiveResourceMapper.java

@@ -0,0 +1,15 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsPromotionalActiveResource;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface FsPromotionalActiveResourceMapper extends BaseMapper<FsPromotionalActiveResource> {
+
+    /**
+     * 批量添加
+     */
+    void insertBatch(@Param("resources") List<FsPromotionalActiveResource> resources);
+}

+ 11 - 4
fs-service/src/main/java/com/fs/his/mapper/FsStoreAfterSalesMapper.java

@@ -1,7 +1,9 @@
 package com.fs.his.mapper;
 
 import java.util.List;
+import java.util.Map;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.company.param.FsStoreStatisticsParam;
 import com.fs.company.vo.FsStoreOrderStatisticsVO;
 import com.fs.his.domain.FsStoreAfterSales;
@@ -243,9 +245,12 @@ public interface FsStoreAfterSalesMapper
     Long selectFsStoreAfterSalesExcelListVOCount(@Param("maps") FsStoreAfterSalesParam fsStoreAfterSales);
 
     @Select({"<script> " +
-            "select u.nick_name, count(o.order_id) as order_count,sum(IFNULL(o.pay_price,0)) as pay_price  " +
-            "from  fs_store_after_sales o left join company_user u on o.company_user_id=u.user_id  " +
+            "select u.nick_name, count(o.order_id) as order_count,sum(IFNULL(o.refund_amount,0)) as pay_price  " +
+            "from  fs_store_after_sales o " +
+            "left join company_user u on o.company_user_id=u.user_id  " +
+            "left join fs_store_order so on so.order_id=o.order_id  " +
             "where o.status &gt; 1 " +
+            "and o.status = 4 and o.sales_status = 3" + //退款完成的订单
             "<if test = 'maps.type != null and maps.type ==1 '> " +
             "and TO_DAYS(o.create_time) = TO_DAYS(NOW()) " +
             "</if>" +
@@ -277,14 +282,16 @@ public interface FsStoreAfterSalesMapper
             "and  YEAR(o.create_time) = YEAR(DATE_SUB(NOW(),INTERVAL 1 YEAR))" +
             "</if>" +
             "<if test = 'maps.startTime != null and maps.startTime != \"\" '> " +
-            "AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.startTime},'%y%m%d')"+
+            "AND date_format(so.create_time,'%y%m%d') &gt;= date_format(#{maps.startTime},'%y%m%d')"+
             "</if>" +
             "<if test = 'maps.endTime != null and maps.endTime != \"\" '> " +
-            "AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d')"+
+            "AND date_format(so.create_time,'%y%m%d') &lt;= date_format(#{maps.endTime},'%y%m%d')"+
             "</if>" +
             "and  o.`company_user_id` IN " +
             "<foreach  item='item' index='index' collection='maps.users' open='(' separator=',' close=')'> #{item}    </foreach>"+
             " group by o.company_user_id "+
             "</script>"})
     List<FsStoreOrderStatisticsVO> selectFsPackageOrderStatisticsList(@Param("maps")FsStoreStatisticsParam param);
+
+    List<JSONObject> selectFsStoreAfterSales(Map<String, Object> map);
 }

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff