瀏覽代碼

feat: 新增空闲时间计算器和催课看板功能

新增空闲时间计算器,用于计算可选时间范围。
实现催课看板会员列表接口,方便查看会员信息。
更新相关Controller和服务层代码。
吴树波 1 月之前
父節點
當前提交
c5288c30eb

+ 100 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseFinishTempController.java

@@ -0,0 +1,100 @@
+package com.fs.course.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.course.domain.FsCourseFinishTemp;
+import com.fs.course.service.IFsCourseFinishTempService;
+import com.fs.course.vo.FsCourseFinishTempListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 完课模板Controller
+ *
+ * @author fs
+ * @date 2024-12-19
+ */
+@RestController
+@RequestMapping("/course/courseFinishTemp")
+public class FsCourseFinishTempController extends BaseController
+{
+    @Autowired
+    private IFsCourseFinishTempService fsCourseFinishTempService;
+
+    /**
+     * 查询完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseFinishTemp fsCourseFinishTemp)
+    {
+        startPage();
+        List<FsCourseFinishTempListVO> list = fsCourseFinishTempService.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 导出完课模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:export')")
+    @Log(title = "完课模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseFinishTemp fsCourseFinishTemp)
+    {
+        List<FsCourseFinishTemp> list = fsCourseFinishTempService.selectFsCourseFinishTempList(fsCourseFinishTemp);
+        ExcelUtil<FsCourseFinishTemp> util = new ExcelUtil<FsCourseFinishTemp>(FsCourseFinishTemp.class);
+        return util.exportExcel(list, "完课模板数据");
+    }
+
+    /**
+     * 获取完课模板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+//        return AjaxResult.success(fsCourseFinishTempService.selectFsCourseFinishTempById(id));
+        return AjaxResult.success(fsCourseFinishTempService.selectFsCourseFinishTempByIdVO(id));
+    }
+
+    /**
+     * 新增完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:add')")
+    @Log(title = "完课模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseFinishTemp fsCourseFinishTemp)
+    {
+        return toAjax(fsCourseFinishTempService.insertFsCourseFinishTemp(fsCourseFinishTemp));
+    }
+
+    /**
+     * 修改完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:edit')")
+    @Log(title = "完课模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseFinishTemp fsCourseFinishTemp)
+    {
+        return toAjax(fsCourseFinishTempService.updateFsCourseFinishTemp(fsCourseFinishTemp));
+    }
+
+    /**
+     * 删除完课模板
+     */
+    @PreAuthorize("@ss.hasPermi('courseFinishTemp:course:remove')")
+    @Log(title = "完课模板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCourseFinishTempService.deleteFsCourseFinishTempByIds(ids));
+    }
+}

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

@@ -3,6 +3,7 @@ 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.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
@@ -187,4 +188,8 @@ public class QwSopTempController extends BaseController
         qwSopTempService.updateRedPackage(data.getList());
         return AjaxResult.success();
     }
+    @GetMapping("/getSelectableRange")
+    public R getSelectableRange(){
+        return R.ok().put("data", qwSopTempService.getSelectableRange());
+    }
 }

+ 12 - 0
fs-company-app/src/main/java/com/fs/app/controller/QwWorkTaskController.java

@@ -8,6 +8,7 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.service.IQwWorkTaskService;
 import com.fs.app.param.QwWorkTaskQueryParam;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.qw.vo.UserVOs;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -62,4 +63,15 @@ public class QwWorkTaskController extends AppBaseController {
         return R.ok().put("data", new PageInfo<>(list));
     }
 
+    @Login
+    @ApiOperation("催课看板会员列表")
+    @GetMapping("/getUserList")
+    public R getUserList(@RequestParam(required = false, defaultValue = "1") Integer pageNum,
+                      @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
+        Long userId = Long.parseLong(getUserId());
+        PageHelper.startPage(pageNum, pageSize);
+        List<UserVOs> list = qwUserService.getUserList(userId);
+        return R.ok().put("data", new PageInfo<>(list));
+    }
+
 }

+ 5 - 0
fs-company/src/main/java/com/fs/qw/QwSopTempController.java

@@ -193,4 +193,9 @@ public class QwSopTempController extends BaseController
         qwSopTempService.updateRedPackage(data.getList());
         return AjaxResult.success();
     }
+
+    @GetMapping("/getSelectableRange")
+    public R getSelectableRange(){
+        return R.ok().put("data", qwSopTempService.getSelectableRange());
+    }
 }

+ 98 - 97
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -100,7 +100,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     private SopUserLogsMapper sopUserLogsMapper;
     @Autowired
-    private QwSopTagMapper qwSopTagMapper ;
+    private QwSopTagMapper qwSopTagMapper;
     @Autowired
     private QwSopMapper sopMapper;
     @Autowired
@@ -190,7 +190,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
 
-
     private void startConsumers() {
         qwSopLogsExecutor = Executors.newSingleThreadExecutor(r -> {
             Thread t = new Thread(r, "QwSopLogsConsumer");
@@ -224,7 +223,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     // Scheduled tasks to refresh configurations and domain names periodically
     @Scheduled(fixedDelay = 60000) // 每60秒刷新一次
     public void refreshCourseConfig() {
-        synchronized(configLock) {
+        synchronized (configLock) {
             try {
                 String json = configService.selectConfigByKey("course.config");
                 CourseConfig newConfig = JSON.parseObject(json, CourseConfig.class);
@@ -241,7 +240,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
 
-
     @PreDestroy
     public void shutdownConsumers() {
         running = false;
@@ -272,7 +270,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         log.info("====== 开始选择和处理 SOP 用户日志 ======");
 
         CourseConfig config;
-        synchronized(configLock) {
+        synchronized (configLock) {
             config = cachedCourseConfig;
         }
         List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime();
@@ -282,7 +280,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         }
         String[] array = sopUserLogsVos.stream().map(SopUserLogsVo::getChatId).filter(StringUtils::isNotEmpty).toArray(String[]::new);
         Map<String, QwGroupChat> groupChatMap = new HashMap<>();
-        if(array.length > 0){
+        if (array.length > 0) {
             List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(array);
             List<QwGroupChatUser> qwGroupChatUserList = qwGroupChatUserService.selectQwGroupChatUserByChatIds(array);
             Map<String, List<QwGroupChatUser>> chatUserMap = PubFun.listToMapByGroupList(qwGroupChatUserList, QwGroupChatUser::getChatId);
@@ -333,7 +331,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch, currentTime, groupChatMap);
         }
 
         // 等待所有 SOP 分组处理完成
@@ -349,13 +347,13 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     @Async("sopTaskExecutor")
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
-    public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch , LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
+    public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
         try {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap);
+            processSopGroup(sopId, userLogsVos, currentTime, groupChatMap);
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } finally {
@@ -413,13 +411,13 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     @Async("sopTaskExecutor")
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
     public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings, CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap) {
         try {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap);
+            processUserLog(logVo, ruleTimeVO, tempSettings, currentTime, groupChatMap);
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } finally {
@@ -448,9 +446,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 return;
             }
             long day = daysBetween;
-            if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
+            if (day == 0 && ruleTimeVO.getIsAutoSop() == 1) {
                 day = 1;
-            }else{
+            } else {
                 day++;
             }
             List<QwSopTempSetting.Content> contents = getDay(tempSettings, day);
@@ -470,9 +468,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
             //获取企业微信员工的称呼//从redis里或者从库里取
-            QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedis(logVo.getCorpId(),logVo.getQwUserId());
-            if (qwUserByRedis==null){
-                log.error("无企微员工信息 {} 跳过处理。:{}", logVo.getUserId(),logVo.getCorpId());
+            QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedis(logVo.getCorpId(), logVo.getQwUserId());
+            if (qwUserByRedis == null) {
+                log.error("无企微员工信息 {} 跳过处理。:{}", logVo.getUserId(), logVo.getCorpId());
                 return;
             }
 
@@ -488,9 +486,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);
                 intervalDay = (int) (daysBetween / tempGap);
                 day = daysBetween;
-                if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
+                if (day == 0 && ruleTimeVO.getIsAutoSop() == 1) {
                     day = 1;
-                }else{
+                } else {
                     day++;
                 }
 
@@ -505,7 +503,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
             // 只有整倍数才做事
             if (daysBetween % tempGap != 0) {
-                log.error("天数差 {} 不是 tempGap {} 的整数倍,跳过操作,SopId {} ", daysBetween, tempGap,logVo.getSopId());
+                log.error("天数差 {} 不是 tempGap {} 的整数倍,跳过操作,SopId {} ", daysBetween, tempGap, logVo.getSopId());
                 return;
             }
 
@@ -544,16 +542,16 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //                        userLogsInfo.setSopId(logVo.getSopId());
 //                        userLogsInfo.setUserLogsId(logVo.getId());
 
-                        SopUserLogsInfoParam logsInfoParam=new SopUserLogsInfoParam();
+                        SopUserLogsInfoParam logsInfoParam = new SopUserLogsInfoParam();
                         logsInfoParam.setSopId(logVo.getSopId());
                         logsInfoParam.setUserLogsId(logVo.getId());
                         logsInfoParam.setIsRegister(logVo.getIsRegister());
 
                         List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoListByIsRegister(logsInfoParam);
 
-                        if(logVo.getIsRegister() == 1){
+                        if (logVo.getIsRegister() == 1) {
                             List<Long> externalContactIdList = PubFun.listToNewList(sopUserLogsInfos, SopUserLogsInfo::getExternalId);
-                            if(!externalContactIdList.isEmpty()){
+                            if (!externalContactIdList.isEmpty()) {
                                 List<QwExternalContact> list = qwExternalContactService.list(new QueryWrapper<QwExternalContact>().isNotNull("fs_user_id").in("id", externalContactIdList));
                                 Map<Long, QwExternalContact> map = PubFun.listToMapByGroupObject(list, QwExternalContact::getId);
                                 sopUserLogsInfos = sopUserLogsInfos.stream().filter(e -> map.containsKey(e.getExternalId())).collect(Collectors.toList());
@@ -574,7 +572,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         }
 
 //                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content);
-                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId, companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(), groupChatMap);
+                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId, companyUserId, companyId, qwUserByRedis.getWelcomeText(), qwUserByRedis.getQwUserName(), groupChatMap);
 
 
                     }
@@ -589,7 +587,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
 
-    private List<QwSopTempSetting.Content> getDay(List<QwSopTempRules> tempSettings, long days){
+    private List<QwSopTempSetting.Content> getDay(List<QwSopTempRules> tempSettings, long days) {
         List<QwSopTempRules> collect = tempSettings.stream().filter(e -> e.getDayNum() == days).collect(Collectors.toList());
         AtomicInteger i = new AtomicInteger();
         return collect.stream().map(e -> {
@@ -625,10 +623,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         Long courseId = content.getCourseId();
         Long videoId = content.getVideoId();
 
-        Integer isOfficial = content.getIsOfficial() != null ? Integer.valueOf(content.getIsOfficial()) : 0;
+        Integer isOfficial = content.getIsOfficial() != null ? Integer.parseInt(content.getIsOfficial()) : 0;
 
         // 发送语言 start
-        if(content.getSetting() == null){
+        if (content.getSetting() == null) {
             return;
         }
 //        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType())).collect(Collectors.toList());
@@ -662,28 +660,28 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //            });
 //        }
         // 发送语言 end
-        if (content.getType()==5){
-            sopAddTag(logVo,content,sendTime);
+        if (content.getType() == 5) {
+            sopAddTag(logVo, content, sendTime);
         }
 
-        if(StringUtils.isNotEmpty(logVo.getChatId())){
+        if (StringUtils.isNotEmpty(logVo.getChatId())) {
             QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
             ruleTimeVO.setSendType(6);
             ruleTimeVO.setType(2);
-            if(content.getIndex() == 0){
+            if (content.getIndex() == 0) {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null);
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText,qwUserName, null, true);
-            }else{
+                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true);
+            } else {
                 groupChat.getChatUserList().forEach(user -> {
                     ruleTimeVO.setSendType(2);
                     ruleTimeVO.setRemark("客户群催课");
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null);
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, user.getUserId(), welcomeText,qwUserName, null, false);
+                            type, qwUserId, companyUserId, companyId, user.getUserId(), welcomeText, qwUserName, null, false);
                 });
             }
-        }else{
+        } else {
             // 处理每个 externalContactId
             sopUserLogsInfos.forEach(contactId -> {
                 try {
@@ -692,7 +690,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     Long fsUserId = contactId.getFsUserId();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId);
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName,fsUserId, false);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false);
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
@@ -706,7 +704,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         String addTag = content.getAddTag();
         String delTag = content.getDelTag();
         String corpId = logVo.getCorpId();
-        if (addTag!=null || delTag!=null) {
+        if (addTag != null || delTag != null) {
             QwSopTag qwSopTag = new QwSopTag();
             qwSopTag.setAddTags(addTag);
             qwSopTag.setDelTags(delTag);
@@ -751,11 +749,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                       String qwUserName, Long fsUserId, boolean isGroupChat) {
         switch (type) {
             case 1:
-                handleNormalMessage(sopLogs, content,companyUserId);
+                handleNormalMessage(sopLogs, content, companyUserId);
                 break;
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName,fsUserId, isGroupChat);
+                        qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, isGroupChat);
                 break;
             case 3:
                 handleOrderMessage(sopLogs, content);
@@ -772,7 +770,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         }
     }
 
-    private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {
+    private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
 
         sopLogs.setContentJson(JSON.toJSONString(content));
         enqueueQwSopLogs(sopLogs);
@@ -814,7 +812,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 case "3":
                     if ("1".equals(setting.getIsBindUrl())) {
                         String link;
-                        if(isGroupChat){
+                        if (isGroupChat) {
                             FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
                             createParam.setCourseId(courseId);
                             createParam.setVideoId(videoId);
@@ -825,14 +823,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             createParam.setQwUserId(qwUserId);
                             createParam.setDays(setting.getExpiresDays());
                             R createLink = courseLinkService.createRoomLinkUrl(createParam);
-                            if (createLink.get("code").equals(500)){
+                            if (createLink.get("code").equals(500)) {
                                 throw new BaseException("链接生成失败!");
                             }
                             link = (String) createLink.get("url");
-                        }else{
-                            addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                        } else {
+                            addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
                             link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
-                                    qwUserId, companyUserId, companyId, externalId,fsUserId);
+                                    qwUserId, companyUserId, companyId, externalId, fsUserId);
                         }
 
                         if (StringUtils.isNotEmpty(link)) {
@@ -845,7 +843,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                 } else {
 //                                    setting.setValue(currentValue + "\n" + sortLink);
                                     setting.setValue(currentValue
-                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(welcomeText)?"":welcomeText)
+                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                             + "\n" + link);
                                 }
                             }
@@ -853,36 +851,36 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             log.error("生成短链失败,跳过设置 URL。");
                         }
 
-                    }else {
+                    } else {
                         if ("1".equals(setting.getContentType())) {
                             setting.setValue(setting.getValue()
-                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText)?"":welcomeText));
+                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText));
                         }
                     }
                     break;
                 //小程序单独
                 case "4":
 
-                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
 
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
-                            qwUserId, companyUserId, companyId, externalId,fsUserId);
+                            qwUserId, companyUserId, companyId, externalId, fsUserId);
 
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
 
                     try {
-                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl())?"https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png":setting.getMiniprogramPicUrl());
-                    }catch (Exception e){
-                        log.error("赋值-小程序封面地址失败-"+e);
+                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
+                    } catch (Exception e) {
+                        log.error("赋值-小程序封面地址失败-" + e);
                     }
 
                     break;
                 //app
                 case "9":
-                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
+                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
 
                     QwCreateLinkByAppDTO linkByApp = createLinkByApp(setting, logVo, sendTime, courseId, videoId,
-                            qwUserId, companyUserId, companyId, externalId,sopLogs.getCorpId(),qwUserName,fsUserId);
+                            qwUserId, companyUserId, companyId, externalId, sopLogs.getCorpId(), qwUserName, fsUserId);
 
                     setting.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
                     setting.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
@@ -892,13 +890,13 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 case "10":
                     // 获取缓存的配置
                     CourseConfig config;
-                    synchronized(configLock) {
+                    synchronized (configLock) {
                         config = cachedCourseConfig;
                     }
                     if (config != null) {
-                        String URL= config.getRegisterDomainName()+registerLink+externalId;
+                        String URL = config.getRegisterDomainName() + registerLink + externalId;
                         setting.setLinkUrl(URL.replaceAll("^[\\s\\u2005]+", ""));
-                    }else {
+                    } else {
                         log.error("获取缓存的配置为空:注册链接");
                     }
 
@@ -937,10 +935,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private QwCreateLinkByAppDTO createLinkByApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                                  Long courseId, Long videoId, String qwUserId,
-                                                 String companyUserId, String companyId, String externalId, String corpId, String qwUserName,Long fsUserId){
+                                                 String companyUserId, String companyId, String externalId, String corpId, String qwUserName, Long fsUserId) {
         // 获取缓存的配置
         CourseConfig config;
-        synchronized(configLock) {
+        synchronized (configLock) {
             config = cachedCourseConfig;
         }
 
@@ -952,7 +950,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 companyUserId, companyId, externalId, 4);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
-        BeanUtils.copyProperties(link,courseMap);
+        BeanUtils.copyProperties(link, courseMap);
         courseMap.setFsUserId(fsUserId);
 
         String courseJson = JSON.toJSONString(courseMap);
@@ -963,11 +961,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         link.setUpdateTime(updateTime);
 
-        String sortLink = appLink+link.getLink()+"&videoId="+videoId;
+        String sortLink = appLink + link.getLink() + "&videoId=" + videoId;
 
-        String appMsgLink=appRealLink+link.getLink();
+        String appMsgLink = appRealLink + link.getLink();
 
-        QwCreateLinkByAppDTO byAppDTO=new QwCreateLinkByAppDTO();
+        QwCreateLinkByAppDTO byAppDTO = new QwCreateLinkByAppDTO();
         byAppDTO.setSortLink(sortLink);
         byAppDTO.setAppMsgLink(appMsgLink);
 
@@ -983,11 +981,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     public FsCourseSopAppLink createFsCourseSopAppLink(String link, Date sendTime, Date updateTime, String companyId,
-                                                       String companyUserId,String qwUserId,String qwUserName,String corpId,
-                                                       Long courseId,String linkTile,String linkImageUrl,Long videoId,
-                                                       String linkDescribe,String appMsgLink,String externalId){
+                                                       String companyUserId, String qwUserId, String qwUserName, String corpId,
+                                                       Long courseId, String linkTile, String linkImageUrl, Long videoId,
+                                                       String linkDescribe, String appMsgLink, String externalId) {
 
-        FsCourseSopAppLink sopAppLink=new FsCourseSopAppLink();
+        FsCourseSopAppLink sopAppLink = new FsCourseSopAppLink();
         sopAppLink.setLink(link);
         sopAppLink.setCreateTime(sendTime);
         sopAppLink.setUpdateTime(updateTime);
@@ -1007,12 +1005,13 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         return sopAppLink;
     }
+
     private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                        Long courseId, Long videoId, String qwUserId,
-                                       String companyUserId, String companyId, String externalId,Long fsUserId) {
+                                       String companyUserId, String companyId, String externalId, Long fsUserId) {
         // 获取缓存的配置
         CourseConfig config;
-        synchronized(configLock) {
+        synchronized (configLock) {
             config = cachedCourseConfig;
         }
 
@@ -1041,7 +1040,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
-        BeanUtils.copyProperties(link,courseMap);
+        BeanUtils.copyProperties(link, courseMap);
         courseMap.setFsUserId(fsUserId);
 
         String courseJson = JSON.toJSONString(courseMap);
@@ -1056,7 +1055,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         // 使用 Java 8 时间 API 计算过期时间
         LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
-        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
         expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
         Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
         link.setUpdateTime(updateTime);
@@ -1076,7 +1075,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         return content.clone();
     }
 
-    private Date createUpdateTime(QwSopTempSetting.Content.Setting setting,Date sendTime,CourseConfig config){
+    private Date createUpdateTime(QwSopTempSetting.Content.Setting setting, Date sendTime, CourseConfig config) {
 
         Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
                 ? config.getVideoLinkExpireDate()
@@ -1084,7 +1083,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 //         使用 Java 8 时间 API 计算过期时间
         LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
-        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
         expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
         Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
 
@@ -1097,8 +1096,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         enqueueQwSopLogs(sopLogs);
     }
 
-    public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Long courseId,Long videoId, String qwUserId,
-                                           String companyUserId, String companyId,String externalId,Integer type){
+    public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Long courseId, Long videoId, String qwUserId,
+                                           String companyUserId, String companyId, String externalId, Integer type) {
         // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(Long.parseLong(companyId));
@@ -1122,7 +1121,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                      String companyUserId, String companyId, String externalId, Long fsUserId) {
         // 获取缓存的配置
         CourseConfig config;
-        synchronized(configLock) {
+        synchronized (configLock) {
             config = cachedCourseConfig;
         }
 
@@ -1167,7 +1166,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
         // 使用 Java 8 时间 API 计算过期时间
         LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
-        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
         expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
         Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
         link.setUpdateTime(updateTime);
@@ -1181,7 +1180,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     private void addWatchLogIfNeeded(QwSopLogs sopLogs, Long videoId, Long courseId,
                                      Date sendTime, String qwUserId, String companyUserId,
-                                     String companyId, String externalId,SopUserLogsVo logsVo) {
+                                     String companyId, String externalId, SopUserLogsVo logsVo) {
         FsCourseWatchLog watchLog = new FsCourseWatchLog();
         watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
         watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
@@ -1192,20 +1191,21 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
         watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
         watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
-        watchLog.setCreateTime(convertStringToDate(sopLogs.getSendTime(),"yyyy-MM-dd HH:mm:ss"));
+        watchLog.setCreateTime(convertStringToDate(sopLogs.getSendTime(), "yyyy-MM-dd HH:mm:ss"));
         watchLog.setUpdateTime(new Date());
         watchLog.setLogType(3);
         watchLog.setUserId(sopLogs.getFsUserId());
-        watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(),"yyyy-MM-dd"));
+        watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(), "yyyy-MM-dd"));
         enqueueWatchLog(watchLog);
     }
 
     /**
      * 时间字符串转Date时间
+     *
      * @param dateString
      * @return
      */
-    public static Date convertStringToDate(String dateString,String pattern) {
+    public static Date convertStringToDate(String dateString, String pattern) {
         if (dateString == null || dateString.isEmpty()) {
             return null;
         }
@@ -1213,12 +1213,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         LocalDateTime localDateTime;
         LocalDate localDate;
         // 先解析成 LocalDate(只含年月日)
-        if (pattern.equals("yyyy-MM-dd")){
+        if (pattern.equals("yyyy-MM-dd")) {
             // 先解析成 LocalDate(只含年月日)
             localDate = LocalDate.parse(dateString, formatter);
             // 将 LocalDate 转为当天 00:00:00 的 LocalDateTime
             localDateTime = localDate.atStartOfDay();
-        }else {
+        } else {
             localDateTime = LocalDateTime.parse(dateString, formatter);
         }
         return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
@@ -1412,7 +1412,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
      */
     @Transactional
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
@@ -1431,7 +1431,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
      */
     @Transactional
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
@@ -1451,7 +1451,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
      */
     @Transactional
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
@@ -1471,7 +1471,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
      */
     @Transactional
     @Retryable(
-            value = { Exception.class },
+            value = {Exception.class},
             maxAttempts = 3,
             backoff = @Backoff(delay = 2000)
     )
@@ -1489,7 +1489,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Override
     public void updateSopLogsByCancel() {
         List<QwSopLogs> sopLogs = qwSopLogsMapper.selectQwSopLogsByCancel();
-        log.info("补发过期完课消息总条数:{}",sopLogs.size());
+        log.info("补发过期完课消息总条数:{}", sopLogs.size());
         processUpdateQwSopLogs(sopLogs);
     }
 
@@ -1508,7 +1508,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             // 直接使用批次数据进行批量更新,不需要额外的 List
             try {
                 qwSopLogsMapper.batchUpdateQwSopLogsByCancel(batchList);
-                log.info("正在补发条数:{}",batchSize);
+                log.info("正在补发条数:{}", batchSize);
             } catch (Exception e) {
                 // 记录异常日志,方便后续排查问题
                 log.error("批量更新数据时发生异常,处理的批次起始索引为: " + i, e);
@@ -1554,7 +1554,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //                sopLogs.setSopId(finishLog.getSopId());
 //                sopLogs.setExternalUserId(externalContact.getExternalUserId());
 //                sopLogs.setExternalUserName(externalContact.getName());
-////                    log.info("外部联系人名称:{}",externalContact.getName());
+
+    /// /                    log.info("外部联系人名称:{}",externalContact.getName());
 //                sopLogs.setFsUserId(finishLog.getUserId());
 //                //解析模板
 //                String jsonData = finishTemp.getSetting();
@@ -1606,7 +1607,6 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 //        QwSopTempSetting.Content content = JSON.parseObject(logs.getContentJson(), QwSopTempSetting.Content.class);
 //        handleNormalMessage(logs, content,null);
 //    }
-
     @Override
     public void createCourseFinishMsg() {
         // 查询所有需要处理的完课记录
@@ -1636,14 +1636,14 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
                 List<QwGroupChatUser> qwGroupChatUserList = qwGroupChatUserService.selectUserIsChat(externalContact.getExternalUserId());
-                if(!qwGroupChatUserList.isEmpty()){
-                    if(finishTemp != null){
+                if (!qwGroupChatUserList.isEmpty()) {
+                    if (finishTemp != null && StringUtils.isNotEmpty(finishTemp.getChatSetting())) {
                         List<SopUserLogs> sopLogsList = sopUserLogsMapper.selectSopUserLogByChatIds(PubFun.listToNewList(qwGroupChatUserList, QwGroupChatUser::getChatId));
                         List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(PubFun.listToNewList(sopLogsList, SopUserLogs::getChatId).toArray(new String[0]));
                         Map<String, QwGroupChat> groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
                         sopLogsList.forEach(e -> {
                             QwGroupChat groupChat = groupChatMap.get(e.getChatId());
-                            if(groupChat != null){
+                            if (groupChat != null) {
                                 e.setChatName(groupChat.getName());
                             }
                         });
@@ -1652,7 +1652,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     }
                 }
                 if (finishTemp == null) {
-//                    log.warn("完课模板不存在: " + finishLog.getCompanyUserId() + ", " + finishLog.getVideoId());
+                    log.warn("完课模板不存在: " + finishLog.getCompanyUserId() + ", " + finishLog.getVideoId());
                     continue;
                 }
                 // 构建 sopLogs 对象
@@ -1703,6 +1703,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         }
     }
 
+    // 创建群聊完课消息
     private List<QwSopLogs> buildSopLogsChat(FsCourseWatchLog finishLog, FsCourseFinishTemp finishTemp, QwExternalContact externalContact, List<SopUserLogs> sopLogsList) {
         return sopLogsList.stream().map(e -> {
             QwSopChatTempSetting setting = new QwSopChatTempSetting();
@@ -1724,7 +1725,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             sopLogs.setSopId(e.getSopId());
             sopLogs.setExternalUserId(e.getChatId());
             sopLogs.setExternalUserName(e.getChatName());
-            if(finishTemp.getChatSetting() == null || finishTemp.getChatSetting().isEmpty()){
+            if (finishTemp.getChatSetting() == null || finishTemp.getChatSetting().isEmpty()) {
                 throw new BaseException("消息为空");
             }
             JSONArray list = new JSONArray();

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

@@ -1,9 +1,12 @@
 package com.fs.course.config;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.time.LocalTime;
+import java.util.List;
 
 @Data
 public class CourseConfig implements Serializable {
@@ -23,5 +26,14 @@ public class CourseConfig implements Serializable {
     private Integer redPacketMode;//红包模式 1总公司 2销售公司
     private BigDecimal moneyPri;//充值手续费百分比
     private BigDecimal redPackageMoney;//充值手续费百分比
+    private List<DisabledTimeVo> disabledTimeList;//充值手续费百分比
+
+    @Data
+    public static class DisabledTimeVo{
+        @JsonFormat(pattern = "HH:mm")
+        private LocalTime startDisabledTime;
+        @JsonFormat(pattern = "HH:mm")
+        private LocalTime endDisabledTime;
+    }
 
 }

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

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.vo.QwWorkTaskListVO;
+import com.fs.qw.vo.UserVOs;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -126,4 +127,6 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
     List<QwWorkTask> selectQwWorkTaskListByMap(@Param("params") Map<String, Object> params);
 
     Long selectQwWorkTaskListVONewCount(QwWorkTaskListParam qwWorkTask);
+
+    List<UserVOs> getUserList(@Param("userId") Long userId);
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -9,6 +9,7 @@ import com.fs.qw.param.QwUserParam;
 import com.fs.qw.vo.QwHookAuthVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
+import com.fs.qw.vo.UserVOs;
 
 import java.util.List;
 
@@ -148,4 +149,6 @@ public interface IQwUserService
     QwUser getByQwUserIdAndCorId(String qwUserId, String corpId);
 
     R selectCloudByCompany(Long companyId, Long userId);
+
+    List<UserVOs> getUserList(Long userId);
 }

+ 11 - 4
fs-service-system/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -12,16 +12,14 @@ import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.dto.QwUserByToolDTO;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.QwWorkTaskMapper;
 import com.fs.qw.param.*;
 import com.fs.qw.result.QwFsServerBindResult;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.utils.RSASignatureUtils;
 import com.fs.qw.utils.RSAUtils;
-import com.fs.qw.vo.QwCloudAPVO;
-import com.fs.qw.vo.QwHookAuthVO;
-import com.fs.qw.vo.QwOptionsVO;
-import com.fs.qw.vo.QwUserVO;
+import com.fs.qw.vo.*;
 import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.Result.QwUploadImgResult;
 import com.fs.qwApi.domain.QwUserIdResult;
@@ -43,6 +41,7 @@ import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Base64;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
@@ -76,6 +75,9 @@ public class QwUserServiceImpl implements IQwUserService
     @Autowired
     private ConfigUtil configUtil;
 
+    @Autowired
+    private QwWorkTaskMapper qwWorkTaskMapper;
+
 //    @Value("${hook.path}")
 //    private String hookPath;
 
@@ -906,6 +908,11 @@ public class QwUserServiceImpl implements IQwUserService
         return R.ok().put("data",qwUserByToolDTOS);
     }
 
+    @Override
+    public List<UserVOs> getUserList(Long userId) {
+        return qwWorkTaskMapper.getUserList(userId);
+    }
+
     public R GetIPAdminAndPassWord(String ipAddress ) throws Exception {
 
         String bodyKey = HttpRequest.get("http://watch.ylrzcloud.com/prod-api/server/getKey?serverIp="+ipAddress)

+ 17 - 0
fs-service-system/src/main/java/com/fs/qw/vo/UserVOs.java

@@ -0,0 +1,17 @@
+package com.fs.qw.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class UserVOs {
+    private String name;
+    private String avatar;
+    private String title;
+    private Integer score;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+    private Integer type;
+}

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

@@ -97,4 +97,6 @@ public interface IQwSopTempService
     List<QwSopTempRedPackageVo> redList(String id);
 
     void updateRedPackage(List<QwSopTempRedPackageVo> list);
+
+    List<String> getSelectableRange();
 }

+ 9 - 0
fs-service-system/src/main/java/com/fs/sop/service/impl/QwSopTempServiceImpl.java

@@ -26,6 +26,7 @@ import com.fs.sop.domain.QwSopTempRules;
 import com.fs.sop.mapper.QwSopTempMapper;
 import com.fs.sop.params.QwSopShareTempParam;
 import com.fs.sop.service.*;
+import com.fs.sop.util.TimeCalculator;
 import com.fs.sop.vo.QwSopTempRedPackageVo;
 import com.fs.sop.vo.VoiceVo;
 import com.fs.system.domain.SysConfig;
@@ -336,6 +337,14 @@ public class QwSopTempServiceImpl implements IQwSopTempService
         fsUserCourseVideoRedPackageService.batchSaveFsUserCourseVideoRedPackage(redPackage);
     }
 
+    @Override
+    public List<String> getSelectableRange() {
+        SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("course.config");
+        CourseConfig courseConfig = JSON.parseObject(sysConfig.getConfigValue(), CourseConfig.class);
+        List<CourseConfig.DisabledTimeVo> disabledTimeList = courseConfig.getDisabledTimeList();
+        return TimeCalculator.calculateAvailableTimes(disabledTimeList);
+    }
+
     @Override
     @DataSource(DataSourceType.SOP)
     public int update(QwSopTemp qwSopTemp) {

+ 89 - 0
fs-service-system/src/main/java/com/fs/sop/util/TimeCalculator.java

@@ -0,0 +1,89 @@
+package com.fs.sop.util;
+
+import com.fs.course.config.CourseConfig;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class TimeCalculator {
+
+    // 计算可用时间段的入口方法
+    public static List<String> calculateAvailableTimes(List<CourseConfig.DisabledTimeVo> disabledTimeList) {
+        List<CourseConfig.DisabledTimeVo> merged = mergeTimeRanges(disabledTimeList);
+        List<String> availableTimes = new ArrayList<>();
+        LocalTime prevEnd = LocalTime.MIN; // 初始化为00:00
+
+        for (CourseConfig.DisabledTimeVo interval : merged) {
+            LocalTime currentStart = interval.getStartDisabledTime();
+            LocalTime currentEnd = interval.getEndDisabledTime();
+
+            // 计算两个禁用时间段之间的间隙
+            if (currentStart.isAfter(prevEnd)) {
+                LocalTime availableStart = prevEnd.plusMinutes(1);
+                LocalTime availableEnd = currentStart.minusMinutes(1);
+                if (!availableStart.isAfter(availableEnd)) {
+                    availableTimes.add(formatTimeRange(availableStart, availableEnd));
+                }
+            }
+            // 更新prevEnd为较大的currentEnd
+            prevEnd = prevEnd.isBefore(currentEnd) ? currentEnd : prevEnd;
+        }
+
+        // 处理最后一个禁用时间段之后的时间(直到23:59)
+        LocalTime dayEnd = LocalTime.of(23, 59);
+        if (prevEnd.isBefore(dayEnd)) {
+            LocalTime availableStart = prevEnd.plusMinutes(1);
+            availableTimes.add(formatTimeRange(availableStart, dayEnd));
+        }
+
+        return availableTimes;
+    }
+
+    // 合并重叠或相邻的时间段
+    private static List<CourseConfig.DisabledTimeVo> mergeTimeRanges(List<CourseConfig.DisabledTimeVo> disabledTimeList) {
+        if (disabledTimeList.isEmpty()) return new ArrayList<>();
+
+        // 1. 按开始时间排序
+        List<CourseConfig.DisabledTimeVo> sorted = disabledTimeList.stream()
+                .sorted(Comparator.comparing(CourseConfig.DisabledTimeVo::getStartDisabledTime))
+                .collect(Collectors.toList());
+
+        // 2. 合并时间段
+        List<CourseConfig.DisabledTimeVo> merged = new ArrayList<>();
+        CourseConfig.DisabledTimeVo current = copy(sorted.get(0));
+        merged.add(current);
+
+        for (int i = 1; i < sorted.size(); i++) {
+            CourseConfig.DisabledTimeVo next = sorted.get(i);
+            // 如果下一个时间段的开始 <= 当前时间段的结束,合并
+            if (!next.getStartDisabledTime().isAfter(current.getEndDisabledTime())) {
+                LocalTime newEnd = next.getEndDisabledTime().isAfter(current.getEndDisabledTime())
+                        ? next.getEndDisabledTime()
+                        : current.getEndDisabledTime();
+                current.setEndDisabledTime(newEnd);
+            } else {
+                current = copy(next);
+                merged.add(current);
+            }
+        }
+        return merged;
+    }
+
+    // 辅助方法:复制对象以避免修改原始数据
+    private static CourseConfig.DisabledTimeVo copy(CourseConfig.DisabledTimeVo source) {
+        CourseConfig.DisabledTimeVo target = new CourseConfig.DisabledTimeVo();
+        target.setStartDisabledTime(source.getStartDisabledTime());
+        target.setEndDisabledTime(source.getEndDisabledTime());
+        return target;
+    }
+
+    // 格式化时间段为"HH:mm:ss - HH:mm:ss"
+    private static String formatTimeRange(LocalTime start, LocalTime end) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
+        return start.format(formatter) + " - " + end.format(formatter);
+    }
+}

+ 7 - 0
fs-service-system/src/main/resources/mapper/qw/QwWorkTaskMapper.xml

@@ -184,4 +184,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <select id="getUserList" resultType="com.fs.qw.vo.UserVOs">
+        select b.name,b.avatar,a.title,a.score,a.create_time,a.type from
+        qw_work_task a
+        inner join qw_external_contact b on a.ext_id = b.id
+        where a.company_user_id = #{userId}
+    </select>
 </mapper>