瀏覽代碼

Merge remote-tracking branch 'origin/master'

Long 3 天之前
父節點
當前提交
b265adb340
共有 42 個文件被更改,包括 1610 次插入240 次删除
  1. 6 2
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  2. 7 12
      fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java
  3. 64 6
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  4. 32 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  5. 239 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  6. 76 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCompanyBind.java
  7. 66 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyBindMapper.java
  8. 4 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodDaysMapper.java
  9. 13 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCoursePeriodMapper.java
  10. 9 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  11. 84 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCompanyBindService.java
  12. 4 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodDaysService.java
  13. 5 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCoursePeriodService.java
  14. 64 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  15. 247 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyBindServiceImpl.java
  16. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodDaysServiceImpl.java
  17. 11 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  18. 57 65
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  19. 7 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  20. 30 0
      fs-service/src/main/java/com/fs/course/vo/UserWatchLogListVo.java
  21. 42 37
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreAfterSalesServiceImpl.java
  22. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwCompanyMapper.java
  23. 1 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  24. 2 1
      fs-service/src/main/java/com/fs/qw/mapper/QwWatchLogMapper.java
  25. 14 0
      fs-service/src/main/java/com/fs/qw/param/UserWatchLogParam.java
  26. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  27. 38 18
      fs-service/src/main/java/com/fs/qw/service/impl/QwContactWayServiceImpl.java
  28. 24 3
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  29. 9 0
      fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java
  30. 79 70
      fs-service/src/main/java/com/fs/qw/vo/QwWatchLogAllStatisticsListVO.java
  31. 4 0
      fs-service/src/main/java/com/fs/system/service/impl/SysConfigServiceImpl.java
  32. 2 2
      fs-service/src/main/resources/application-config-myhk.yml
  33. 162 0
      fs-service/src/main/resources/application-druid-jnsyj-test.yml
  34. 19 2
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  35. 162 0
      fs-service/src/main/resources/mapper/course/FsUserCompanyBindMapper.xml
  36. 5 0
      fs-service/src/main/resources/mapper/qw/QwCompanyMapper.xml
  37. 4 2
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  38. 1 3
      fs-store/src/main/java/com/fs/store/controller/common/CommonController.java
  39. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/StoreAfterSalesScrmController.java
  40. 5 1
      fs-user-app/src/main/java/com/fs/app/controller/store/StoreOrderScrmController.java
  41. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/UserScrmController.java
  42. 1 1
      fs-user-app/src/main/resources/logback.xml

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

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

+ 7 - 12
fs-company/src/main/java/com/fs/company/controller/company/CompanyDeptController.java

@@ -149,21 +149,16 @@ public class CompanyDeptController extends BaseController
     @PreAuthorize("@ss.hasPermi('company:dept:edit')")
     @Log(title = "部门管理", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@Validated @RequestBody CompanyDept dept)
-    {
+    public AjaxResult edit(@Validated @RequestBody CompanyDept dept) {
+        if(dept.getParentId().equals(dept.getDeptId())) {
+            return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
+        }
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         dept.setCompanyId(loginUser.getCompany().getCompanyId());
-        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept)))
-        {
+        if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) {
             return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
-        }
-        else if (dept.getParentId().equals(dept.getDeptId()))
-        {
-            return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
-        }
-        else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
-                && deptService.selectNormalChildrenDeptById(dept.getDeptId()) > 0)
-        {
+        } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
+                && deptService.selectNormalChildrenDeptById(dept.getDeptId()) > 0) {
             return AjaxResult.error("该部门包含未停用的子部门!");
         }
         dept.setUpdateBy(SecurityUtils.getUsername());

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

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

+ 32 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -14,8 +14,10 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.impl.CompanyDeptServiceImpl;
 import com.fs.course.param.FsUserCourseListUParam;
+import com.fs.course.service.IFsUserCompanyBindService;
 import com.fs.course.service.IFsUserCourseStudyService;
 import com.fs.course.vo.FsUserCourseStudyListUVO;
+import com.fs.course.vo.UserWatchLogListVo;
 import com.fs.crm.param.CrmMyCustomerListQueryParam;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.vo.CrmMyCustomerListQueryVO;
@@ -92,6 +94,9 @@ public class QwExternalContactController extends BaseController
     @Autowired
     private IQwContactWayService qwContactWayService;
 
+    @Autowired
+    private IFsUserCompanyBindService fsUserCompanyBindService;
+
     /**
      * 查询企业微信客户列表
      */
@@ -584,6 +589,14 @@ public class QwExternalContactController extends BaseController
     {
         return qwExternalContactService.updateQwExternalContactUnBindUserId(id);
     }
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:changeStatus')")
+    @Log(title = "修改用户状态", businessType = BusinessType.UPDATE)
+    @GetMapping("/status")
+    public R changeStatus(QwExternalContact qwExternalContact)
+    {
+        qwExternalContactService.updateQwExternalContactStatusById(qwExternalContact);
+        return R.ok();
+    }
     /**
      * 删除企业微信客户
      */
@@ -801,4 +814,23 @@ public class QwExternalContactController extends BaseController
         return R.ok();
     }
 
+    /**
+     * 重粉客服查询
+     */
+    @GetMapping("/getRepeat")
+    public R getRepeat(RepeatParam param){
+        return  qwExternalContactService.getRepeat(param);
+    }
+    /**
+     * 重粉看课记录查询
+     */
+    @GetMapping("/getWatchLogList")
+    public TableDataInfo getWatchLogList(UserWatchLogParam param){
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setUserId(loginUser.getUser().getUserId());
+        List<UserWatchLogListVo> list= fsUserCompanyBindService.getWatchLogList(param);
+        return getDataTable(list);
+    }
+
 }

+ 239 - 1
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsUserCompanyBindService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
@@ -76,6 +77,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private static final String miniappRealLink = "/pages_course/video.html?course=";
     private static final String appRealLink = "/pages/courseAnswer/index?link=";
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
+
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 
     private static final String QWSOP_KEY_PREFIX = "qwsop:";
@@ -176,6 +178,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     @Autowired
     private AsyncCourseWatchFinishService asyncCourseWatchFinishService;
 
+    @Autowired
+    private IFsUserCompanyBindService fsUserCompanyBindService;
 
     @PostConstruct
     public void init() {
@@ -1909,6 +1913,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 } else {
                     log.info("完课消息-客户信息有误,不生成完课消息: {}", finishLog.getQwExternalContactId());
                 }
+                try {
+                    fsUserCompanyBindService.finish(externalContact.getFsUserId(), externalContact.getQwUserId(), externalContact.getCompanyUserId(), finishLog);
+                }catch (Exception e){
+                    log.error("更新重粉看课状态失败",e);
+                }
             } catch (Exception e) {
                 log.error("处理完课记录失败: {}", finishLog.getLogId(), e);
             }
@@ -1968,7 +1977,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         if (settings == null) {
             return null;
         }
-
+        //完课后若是小程序发送另外一堂课
+        saveWacthLogOfCourseLink(settings,sopLogs,newTimeString,finishLog,finishTemp);
         // 处理音频内容
 //        for (QwSopCourseFinishTempSetting.Setting st : settings) {
 //            if (st.getContentType().equals("7")) {
@@ -1987,6 +1997,234 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         return sopLogs;
     }
 
+    /**
+     * 判定小程序的话新增创建看课记录,以及fsCourseLink
+     *
+     * @param settings
+     */
+    public void saveWacthLogOfCourseLink(List<QwSopCourseFinishTempSetting.Setting> settings, QwSopLogs sopLogs,  String newTimeString, FsCourseWatchLog finishLog, FsCourseFinishTemp finishTemp){
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+        Date dataTime = new Date();
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(sopLogs.getCorpId());
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(sopLogs.getCorpId(), sopLogs.getQwUserid());
+        if (qwUser == null){
+            return;
+        }
+        for (QwSopCourseFinishTempSetting.Setting st : settings) {
+            switch (st.getContentType()) {
+                //小程序单独
+                case "4":
+                    addWatchLogIfNeeded(sopLogs.getSopId(), st.getVideoId().intValue(), st.getCourseId().intValue(), sopLogs.getFsUserId(),  String.valueOf(qwUser.getId()),qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),
+                            sopLogs.getExternalId(), newTimeString.substring(0, 10), dataTime);
+
+                    String linkByMiniApp = createLinkByMiniApp(st, sopLogs.getCorpId(), dataTime, finishTemp.getCourseId().intValue(), Integer.valueOf(st.getVideoId().toString()),
+                            String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), sopLogs.getExternalId(), config);
+
+
+                    String miniAppId = null;
+                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(qwUser.getCompanyId()));
+                        if (integerListMap != null) {
+                            int listIndex = 0;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    miniAppId = companyMiniapp.getAppId();
+                                }
+                            }
+                        }
+                    }
+
+                    if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                        miniAppId = qwCompany.getMiniAppId();
+                    }
+
+                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                        st.setMiniprogramAppid(miniAppId);
+                    } else {
+                        log.error("企业未配置小程序-" + sopLogs.getCorpId());
+                    }
+
+                    String miniprogramTitle = st.getMiniprogramTitle();
+                    int maxLength = 17;
+                    st.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength)+"..." : miniprogramTitle);
+                    st.setMiniprogramPage(linkByMiniApp);
+                    break;
+                default:
+                    break;
+
+            }
+        }
+    }
+    private Date processDate(String sendTimeParam) {
+        // 1. 获取当前日期(年月日)
+        LocalDate currentDate = LocalDate.now();
+
+        // 2. 解析传入的时分(支持 "HH:mm" 或 "H:mm")
+        LocalTime sendTime = LocalTime.parse(sendTimeParam);
+
+        // 3. 合并为 LocalDateTime
+        LocalDateTime dateTime = LocalDateTime.of(currentDate, sendTime);
+
+        // 4. 转换为 Date(需通过 Instant 和系统默认时区)
+        Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+        return date;
+    }
+
+    /**
+     * 新增courseLink
+     *
+     * @param setting
+     * @param corpId
+     * @param sendTime
+     * @param courseId
+     * @param videoId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param config
+     * @return
+     */
+    private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
+                                       Integer courseId, Integer videoId, String qwUserId,
+                                       String companyUserId, String companyId, Long externalId, CourseConfig config) {
+
+        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+                companyUserId, companyId, externalId, 3, null);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = miniappRealLink + courseJson;
+        link.setRealLink(realLinkFull);
+
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+
+        link.setUpdateTime(updateTime);
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        return link.getRealLink();
+    }
+
+    /**
+     * 创建courselink
+     * @param corpId
+     * @param sendTime
+     * @param courseId
+     * @param videoId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param type
+     * @param chatId
+     * @return
+     */
+    public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Integer courseId, Integer videoId, String qwUserId,
+                                           String companyUserId, String companyId, Long externalId, Integer type, String chatId) {
+        // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setVideoId(videoId.longValue());
+        link.setCorpId(corpId);
+        link.setCourseId(courseId.longValue());
+        link.setChatId(chatId);
+        link.setQwExternalId(externalId);
+        link.setLinkType(type); //小程序
+        link.setUNo(UUID.randomUUID().toString());
+        link.setProjectCode(cloudHostProper.getProjectCode());
+        String randomString = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+
+        link.setCreateTime(sendTime);
+
+        return link;
+    }
+
+
+    /**
+     * 计算过期时间
+     * @param setting
+     * @param sendTime
+     * @param config
+     * @return
+     */
+    private Date createUpdateTime(QwSopCourseFinishTempSetting.Setting setting, Date sendTime, CourseConfig config) {
+
+        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
+                ? config.getVideoLinkExpireDate()
+                : setting.getExpiresDays();
+
+//         使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+
+        return updateTime;
+    }
+
+    /**
+     * 增加看课记录
+     *
+     * @param sopId
+     * @param videoId
+     * @param courseId
+     * @param fsUserId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param startTime
+     * @param createTime
+     * @return
+     */
+    private Long addWatchLogIfNeeded(String sopId, Integer videoId, Integer courseId,
+                                     Long fsUserId, String qwUserId, String companyUserId,
+                                     String companyId, Long externalId, String startTime, Date createTime) {
+
+        try {
+            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+            watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
+            watchLog.setQwExternalContactId(externalId);
+            watchLog.setSendType(2);
+            watchLog.setQwUserId(Long.valueOf(qwUserId));
+            watchLog.setSopId(sopId);
+            watchLog.setDuration(0L);
+            watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
+            watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
+            watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
+            watchLog.setCreateTime(createTime);
+            watchLog.setUpdateTime(createTime);
+            watchLog.setLogType(3);
+            watchLog.setUserId(fsUserId);
+            watchLog.setCampPeriodTime(convertStringToDate(startTime, "yyyy-MM-dd"));
+
+            //存看课记录
+            int i = fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+            return watchLog.getLogId();
+        } catch (Exception e) {
+            log.error("插入观看记录失败:" + e.getMessage());
+            return null;
+        }
+    }
+
+
     /**
      * 解析模板设置
      */

+ 76 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCompanyBind.java

@@ -0,0 +1,76 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+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.time.LocalDateTime;
+
+/**
+ * 用户客服关联对象 fs_user_company_bind
+ *
+ * @author fs
+ * @date 2025-07-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserCompanyBind extends BaseEntity{
+
+    /** id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long fsUserId;
+
+    /** 客服id */
+    @Excel(name = "客服id")
+    private Long companyUserId;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 课程项目ID */
+    @Excel(name = "课程项目ID")
+    private Long projectId;
+
+    /** 企微用户ID */
+    @Excel(name = "企微用户ID")
+    private Long qwUserId;
+
+    /** 企微外部联系人ID */
+    @Excel(name = "企微外部联系人ID")
+    private Long qwExternalContactId;
+
+    /** 企微主体ID */
+    @Excel(name = "企微主体ID")
+    private Long qwCompanyId;
+
+    /** 课程ID */
+    @Excel(name = "课程ID")
+    private Long courseId;
+
+    /** 视频ID */
+    @Excel(name = "视频ID")
+    private Long videoId;
+
+    /** 看课记录ID */
+    private Long watchLogId;
+
+    /** 完课状态 */
+    @Excel(name = "完课状态")
+    private Integer logType;
+
+    /** 完课时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "完课时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private LocalDateTime finishTime;
+
+
+}

+ 66 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyBindMapper.java

@@ -0,0 +1,66 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserCompanyBind;
+import com.fs.course.vo.UserWatchLogListVo;
+import com.fs.qw.param.UserWatchLogParam;
+
+import java.util.List;
+
+/**
+ * 用户客服关联Mapper接口
+ *
+ * @author fs
+ * @date 2025-07-22
+ */
+public interface FsUserCompanyBindMapper extends BaseMapper<FsUserCompanyBind>{
+    /**
+     * 查询用户客服关联
+     *
+     * @param id 用户客服关联主键
+     * @return 用户客服关联
+     */
+    FsUserCompanyBind selectFsUserCompanyBindById(Long id);
+
+    /**
+     * 查询用户客服关联列表
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 用户客服关联集合
+     */
+    List<FsUserCompanyBind> selectFsUserCompanyBindList(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 新增用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    int insertFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 修改用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    int updateFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 删除用户客服关联
+     *
+     * @param id 用户客服关联主键
+     * @return 结果
+     */
+    int deleteFsUserCompanyBindById(Long id);
+
+    /**
+     * 批量删除用户客服关联
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserCompanyBindByIds(Long[] ids);
+
+    List<UserWatchLogListVo> getWatchLogList(UserWatchLogParam param);
+}

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

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

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

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

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

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

+ 84 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCompanyBindService.java

@@ -0,0 +1,84 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCompanyBind;
+import com.fs.course.vo.UserWatchLogListVo;
+import com.fs.qw.param.UserWatchLogParam;
+
+import java.util.List;
+
+/**
+ * 用户客服关联Service接口
+ *
+ * @author fs
+ * @date 2025-07-22
+ */
+public interface IFsUserCompanyBindService extends IService<FsUserCompanyBind>{
+    /**
+     * 查询用户客服关联
+     *
+     * @param id 用户客服关联主键
+     * @return 用户客服关联
+     */
+    FsUserCompanyBind selectFsUserCompanyBindById(Long id);
+
+    /**
+     * 查询用户客服关联列表
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 用户客服关联集合
+     */
+    List<FsUserCompanyBind> selectFsUserCompanyBindList(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 新增用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    int insertFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 修改用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    int updateFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind);
+
+    /**
+     * 批量删除用户客服关联
+     *
+     * @param ids 需要删除的用户客服关联主键集合
+     * @return 结果
+     */
+    int deleteFsUserCompanyBindByIds(Long[] ids);
+
+    /**
+     * 删除用户客服关联信息
+     *
+     * @param id 用户客服关联主键
+     * @return 结果
+     */
+    int deleteFsUserCompanyBindById(Long id);
+
+    /**
+     * 根据看课记录看课记录
+     * @param fsUserId       小程序用户ID
+     * @param externalUserId 外部联系人ID
+     * @param watchLogId     看课记录ID
+     */
+    boolean bindFsUser(Long fsUserId, Long externalUserId, Long watchLogId);
+
+    /**
+     * 更新完课装填
+     * @param fsUserId      小程序用户ID
+     * @param qwUserId      员工企微ID
+     * @param companyUserId 员工ID
+     * @param watchLog      看课记录
+     */
+    void finish(Long fsUserId, Long qwUserId, Long companyUserId, FsCourseWatchLog watchLog);
+
+    List<UserWatchLogListVo> getWatchLogList(UserWatchLogParam param);
+}

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

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

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

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

+ 64 - 5
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -16,13 +16,12 @@ import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.config.CourseConfig;
-import com.fs.course.domain.FsCourseFinishTemp;
-import com.fs.course.domain.FsCourseWatchLog;
-import com.fs.course.domain.FsUserCourse;
-import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.IFsUserCoursePeriodDaysService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
 import com.fs.his.config.FsSysConfig;
@@ -127,6 +126,12 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Autowired
     private HyWatchLogMapper hyWatchLogMapper;
 
+    @Autowired
+    private IFsUserCoursePeriodService userCoursePeriodService;
+
+    @Autowired
+    private IFsUserCoursePeriodDaysService userCoursePeriodDaysService;
+
     /**
      * 查询短链课程看课记录
      *
@@ -616,7 +621,27 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
-        return fsCourseWatchLogMapper.selectFsCourseWatchLogListVO(param);
+
+        List<FsCourseWatchLogListVO> fsCourseWatchLogListVOS = fsCourseWatchLogMapper.selectFsCourseWatchLogListVO(param);
+        List<FsUserCoursePeriod> fsUserCoursePeriods = userCoursePeriodService.selectFsUserCoursePeriodList(new FsUserCoursePeriod());
+
+        // 将 fsUserCoursePeriods 转换为 Map,便于快速查找
+        Map<Long, String> periodIdToNameMap = fsUserCoursePeriods.stream()
+                .collect(Collectors.toMap(
+                        FsUserCoursePeriod::getPeriodId,
+                        FsUserCoursePeriod::getPeriodName,
+                        (existing, replacement) -> existing // 如果有重复key,保留现有的
+                ));
+
+        // 遍历并赋值
+        fsCourseWatchLogListVOS.forEach(vo -> {
+            String periodName = periodIdToNameMap.get(vo.getPeriodId());
+            if (periodName != null) {
+                vo.setPeriodIdName(periodName);
+            }
+        });
+
+        return fsCourseWatchLogListVOS;
     }
 
     @Override
@@ -1095,8 +1120,42 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param) {
 
+
+        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+            List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
+
+            if (!periodIds.isEmpty()){
+                List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, param.getCompanyId());
+                if (!longs.isEmpty()){
+                    param.setPeriodIds(longs);
+                }else {
+                    return new ArrayList<>();
+                }
+            }else {
+                return new ArrayList<>();
+            }
+
+        }
+
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogMapper.selectFsCourseWatchLogListVOexport(param);
+
+        //查询所有营期
+        List<FsUserCoursePeriod> fsUserCoursePeriods = userCoursePeriodService.selectFsUserCoursePeriodList(new FsUserCoursePeriod());
+
         for (FsCourseWatchLogListVO item : list) {
+            //营期名称
+            if (ObjectUtils.isNotNull(item.getPeriodId())) {
+                // 获取匹配的periodName
+                String periodName = fsUserCoursePeriods.stream()
+                        .filter(period -> item.getPeriodId().equals(period.getPeriodId()))
+                        .map(FsUserCoursePeriod::getPeriodName)
+                        .findFirst()
+                        .orElse("无营期名称");
+                item.setPeriodIdName(periodName);
+            }else {
+                item.setPeriodIdName("自动发课无营期名称");
+            }
+
             // 项目
             if(ObjectUtils.isNotNull(item.getProject())) {
                 String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));

+ 247 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCompanyBindServiceImpl.java

@@ -0,0 +1,247 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCompanyBind;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCompanyBindMapper;
+import com.fs.course.mapper.FsUserCourseMapper;
+import com.fs.course.service.IFsUserCompanyBindService;
+import com.fs.course.vo.UserWatchLogListVo;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.param.UserWatchLogParam;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户客服关联Service业务层处理
+ *
+ * @author fs
+ * @date 2025-07-22
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class FsUserCompanyBindServiceImpl extends ServiceImpl<FsUserCompanyBindMapper, FsUserCompanyBind> implements IFsUserCompanyBindService {
+
+    private final CompanyUserMapper companyUserMapper;
+    private FsUserMapper fsUserMapper;
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    private QwExternalContactMapper qwExternalContactMapper;
+    private FsUserCourseMapper fsUserCourseMapper;
+    private QwCompanyMapper qwCompanyMapper;
+    private CompanyMapper companyMapper;
+
+    /**
+     * 查询用户客服关联
+     *
+     * @param id 用户客服关联主键
+     * @return 用户客服关联
+     */
+    @Override
+    public FsUserCompanyBind selectFsUserCompanyBindById(Long id) {
+        return baseMapper.selectFsUserCompanyBindById(id);
+    }
+
+    /**
+     * 查询用户客服关联列表
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 用户客服关联
+     */
+    @Override
+    public List<FsUserCompanyBind> selectFsUserCompanyBindList(FsUserCompanyBind fsUserCompanyBind) {
+        return baseMapper.selectFsUserCompanyBindList(fsUserCompanyBind);
+    }
+
+    /**
+     * 新增用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind) {
+        fsUserCompanyBind.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserCompanyBind(fsUserCompanyBind);
+    }
+
+    /**
+     * 修改用户客服关联
+     *
+     * @param fsUserCompanyBind 用户客服关联
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserCompanyBind(FsUserCompanyBind fsUserCompanyBind) {
+        fsUserCompanyBind.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsUserCompanyBind(fsUserCompanyBind);
+    }
+
+    /**
+     * 批量删除用户客服关联
+     *
+     * @param ids 需要删除的用户客服关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCompanyBindByIds(Long[] ids) {
+        return baseMapper.deleteFsUserCompanyBindByIds(ids);
+    }
+
+    /**
+     * 删除用户客服关联信息
+     *
+     * @param id 用户客服关联主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCompanyBindById(Long id) {
+        return baseMapper.deleteFsUserCompanyBindById(id);
+    }
+
+    @Override
+    public boolean bindFsUser(Long fsUserId, Long externalUserId, Long watchLogId) {
+        try {
+            QwExternalContact qwExternalContact = qwExternalContactMapper.selectById(externalUserId);
+            FsCourseWatchLog watchLog = fsCourseWatchLogMapper.selectById(watchLogId);
+            FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(watchLog.getCourseId());
+            Company company = companyMapper.selectCompanyById(qwExternalContact.getCompanyId());
+            QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(qwExternalContact.getCorpId());
+            // 员工ID
+            Long companyUserId = qwExternalContact.getCompanyUserId();
+            // 公司ID
+            Long companyId = qwExternalContact.getCompanyId();
+            // 企微用户ID
+            Long qwUserId = qwExternalContact.getQwUserId();
+            // 项目
+            Long project = course.getProject() == null ? 0 : course.getProject();
+            // 企微主体ID
+            Long qwCompanyId = qwCompany.getId();
+            // 课程ID
+            Long courseId = watchLog.getCourseId();
+            // 视频ID
+            Long videoId = watchLog.getVideoId();
+            // 看课记录ID
+            Long logId = watchLog.getLogId();
+            // 看课状态
+            Integer logType = watchLog.getLogType();
+            if (company.getRepeat() == 1 && project != 0) {
+                List<FsUserCompanyBind> list = baseMapper.selectList(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId).eq("project_id", project).last(" GROUP BY qw_user_id"));
+                if (!list.isEmpty() && list.stream().noneMatch(e -> e.getQwUserId().equals(qwUserId))) {
+                    log.error("当前客户:{}已经有所属项目{}, 企微ID:{}, 当前企微ID:{}", fsUserId, project, PubFun.listToNewList(list, FsUserCompanyBind::getQwUserId), qwUserId);
+                    return false;
+                }
+            }
+            if (qwExternalContact.getUserRepeat() == 0) {
+                Integer i = baseMapper.selectCount(new QueryWrapper<FsUserCompanyBind>().eq("fs_user_id", fsUserId).ne("company_user_id", companyUserId));
+                if (i > 0) {
+                    qwExternalContact.setUserRepeat(1);
+                    qwExternalContactMapper.updateById(qwExternalContact);
+                }
+            }
+            // 当前登录账号名称
+            FsUserCompanyBind one = baseMapper.selectOne(new QueryWrapper<FsUserCompanyBind>()
+                    .eq("fs_user_id", fsUserId)
+                    .eq("project_id", project)
+                    .eq("course_id", courseId)
+                    .eq("qw_user_id", qwUserId)
+                    .eq("company_user_id", companyUserId)
+                    .eq("video_id", videoId));
+            if (one == null) {
+                FsUserCompanyBind bind = new FsUserCompanyBind();
+                bind.setFsUserId(fsUserId);
+                bind.setCompanyUserId(companyUserId);
+                bind.setCompanyId(companyId);
+                bind.setQwUserId(qwUserId);
+                bind.setQwExternalContactId(externalUserId);
+                bind.setProjectId(project);
+                bind.setQwCompanyId(qwCompanyId);
+                bind.setCourseId(courseId);
+                bind.setVideoId(videoId);
+                bind.setWatchLogId(logId);
+                bind.setLogType(logType);
+                bind.setCreateTime(new Date());
+                bind.setUpdateTime(new Date());
+                if (bind.getLogType() == 2) {
+                    bind.setFinishTime(LocalDateTime.now());
+                }
+                try {
+                    save(bind);
+                } catch (Exception e) {
+                    log.error("添加重粉失败", e);
+                }
+            }
+            return true;
+        }catch (Exception e){
+            log.error("绑定关系错误", e);
+            return false;
+        }
+    }
+
+    @Override
+    public void finish(Long fsUserId, Long qwUserId, Long companyUserId, FsCourseWatchLog watchLog) {
+        FsUserCourse course = fsUserCourseMapper.selectFsUserCourseByCourseId(watchLog.getCourseId());
+        FsUserCompanyBind one = baseMapper.selectOne(new QueryWrapper<FsUserCompanyBind>()
+                .eq("fs_user_id", fsUserId)
+                .eq("project_id", course.getProject())
+                .eq("course_id", watchLog.getCourseId())
+                .eq("qw_user_id", qwUserId)
+                .eq("company_user_id", companyUserId)
+                .eq("video_id", watchLog.getVideoId()));
+        if(one == null){
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserById(companyUserId);
+            QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(companyUser.getCorpId());
+            one = new FsUserCompanyBind();
+            one.setFsUserId(fsUserId);
+            one.setCompanyUserId(companyUserId);
+            one.setCompanyId(companyUser.getCompanyId());
+            one.setQwUserId(qwUserId);
+            one.setQwExternalContactId(watchLog.getQwExternalContactId());
+            one.setProjectId(course.getProject());
+            one.setQwCompanyId(qwCompany != null ? qwCompany.getId() : null);
+            one.setCourseId(watchLog.getCourseId());
+            one.setVideoId(watchLog.getVideoId());
+            one.setWatchLogId(watchLog.getLogId());
+            one.setLogType(2);
+            one.setFinishTime(LocalDateTime.now());
+            one.setCreateTime(new Date());
+            one.setUpdateTime(new Date());
+            try {
+                save(one);
+            } catch (Exception e) {
+                log.error("添加重粉失败", e);
+            }
+        }else{
+            one.setFinishTime(LocalDateTime.now());
+            one.setLogType(2);
+            updateById(one);
+        }
+    }
+
+    @Override
+    public List<UserWatchLogListVo> getWatchLogList(UserWatchLogParam param) {
+        if(param.getExternalUserId() == null &&  param.getFsUserId() == null){
+            return Collections.emptyList();
+        }
+        return baseMapper.getWatchLogList(param);
+    }
+}

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

@@ -419,6 +419,11 @@ public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCourseP
         return baseMapper.selectFsUserCoursePeriodDaysCount(fsUserCoursePeriodDays);
     }
 
+    @Override
+    public List<Long> selectFsUserCoursePeriodDaysByTime(String periodSTime, String periodETime) {
+        return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByTime(periodSTime,periodETime);
+    }
+
     private static FsCourseAnalysisCountVO getCourseAnalysisCountVO(FsUserCoursePeriodDays v, Map<Long, FsCourseAnalysisCountVO> courseMap, Map<Long, FsCourseAnalysisCountVO> redPacketMap, Map<Long, FsCourseAnalysisCountVO> answerMap) {
         FsCourseAnalysisCountVO countVO = new FsCourseAnalysisCountVO();
         FsCourseAnalysisCountVO courseVO = courseMap.getOrDefault(v.getVideoId(), countVO);

+ 11 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -25,10 +25,7 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -301,4 +298,14 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
     public List<FsUserCoursePeriod> selectFsPeriodlist(PeriodStatisticCountParam param) {
         return fsUserCoursePeriodMapper.selectFsPeriodlist(param);
     }
+
+    @Override
+    public List<Long> selectCoursePeriodDaysByTime(String periodSTime, String periodETime,Long companyId) {
+        return fsUserCoursePeriodMapper.selectCoursePeriodDaysByTime(periodSTime,periodETime,companyId);
+    }
+
+    @Override
+    public List<Long> selectFsUserCoursePeriodListByPeriodId(List<Long> periodIds, Long companyId) {
+        return fsUserCoursePeriodMapper.selectFsUserCoursePeriodListByPeriodId(periodIds,companyId);
+    }
 }

+ 57 - 65
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -32,6 +32,7 @@ import com.fs.course.dto.CoursePackageDTO;
 import com.fs.course.mapper.*;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.*;
+import com.fs.course.service.IFsUserCompanyBindService;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsVideoResourceService;
@@ -48,10 +49,10 @@ import com.fs.his.service.IFsUserService;
 import com.fs.his.service.IFsUserWxService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.OptionsVO;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwGroupChatMapper;
+import com.fs.qw.mapper.QwGroupChatUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactService;
@@ -59,6 +60,7 @@ import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
@@ -119,6 +121,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     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=";
+    // 排除看课数量限制的公司集合
+    private static final Set<String> EXCLUDE_PROJECTS = new HashSet<>(Arrays.asList(
+           "福本源","宽益堂","叮当国医"
+    ));
     @Autowired
     ICompanyService companyService;
     @Autowired
@@ -126,6 +132,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
     @Autowired
+    private QwGroupChatMapper qwGroupChatMapper;
+    @Autowired
+    private QwGroupChatUserMapper qwGroupChatUserMapper;
+    @Autowired
     private IFsUserService fsUserService;
     @Autowired
     private FsUserCourseCompanyUserTimeMapper companyUserTimeMapper;
@@ -234,6 +244,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private FsUserCoursePeriodCompanyMapper periodCompanyMapper;
 
+    @Autowired
+    private IFsUserCompanyBindService fsUserCompanyBindService;
+
 
 
     /**
@@ -503,36 +516,27 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLink(param.getLink());
         String msg = "<div style=\"color: red;margin-bottom: 15px;font-weight: bold;\">本课程为群会员独享<br>请长按二维码</div>\n" +
                 "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
-        QwGroupChatDetailsResult result = qwApiService.groupChatDetails(courseLink.getChatId(), param.getCorpId());
-        log.info("群聊参数:{},链接参数:{}, 企微返回:{}", JSON.toJSONString(param), JSON.toJSONString(courseLink), JSON.toJSONString(result));
-        if(result.getErrCode() != 0){
-            return R.error("企微接口请求失败,请联系管理员:" +result.getErrMsg());
+        QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(courseLink.getChatId());
+        if(qwGroupChat == null){
+            return R.error("群参数异常");
+        }
+        SopUserLogsInfo sopUserLogsInfo =  new SopUserLogsInfo();
+        sopUserLogsInfo.setChatId(courseLink.getChatId());
+        List<QwGroupChatUser> qwGroupChatUsers = qwGroupChatUserMapper.selectByChatId(sopUserLogsInfo);
+        if(qwGroupChatUsers == null || qwGroupChatUsers.isEmpty()){
+            return R.error("群参数异常");
         }
-//            List<QwGroupChatDetailsResult.Member> collect = result.getGroupChat().getMemberList().stream().filter(e -> e.getType() == 2).collect(Collectors.toList());
-//            if(collect.isEmpty()){
-//                return addCustomerService(param.getQwUserId(),msg);
-//            }
-//            Optional<QwGroupChatDetailsResult.Member> optional = collect.stream().filter(e -> e.getName().equals(fsUser.getNickName()) || e.getName().equals(param.getNickName())).findFirst();
-//            if(!optional.isPresent()){
-//                return addCustomerService(param.getQwUserId(),msg);
-//            }
-//            QwGroupChatDetailsResult.Member member = optional.get();
-//            QwExternalContact qwExternalContact =
-//                    qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>()
-//                    .eq("user_id", result.getGroupChat().getOwner())
-//                    .eq("external_user_id", member.getUserId())
-//                    );
         QwExternalContact qwExternalContact =
                 qwExternalContactMapper.selectOne(new QueryWrapper<QwExternalContact>()
-                        .eq("user_id", result.getGroupChat().getOwner())
+                        .eq("user_id", qwGroupChat.getOwner())
                         .eq("fs_user_id", param.getUserId())
                         .eq("corp_id", param.getCorpId())
                         .eq("status",0));
         if(qwExternalContact==null){
             return addCustomerService(param.getQwUserId(),msg);
         }
-        log.info("外部联系人数据:{}", qwExternalContact);
-        if(result.getGroupChat().getMemberList().stream().noneMatch(e -> e.getUserId().equals(qwExternalContact.getExternalUserId()))){
+        if(qwGroupChatUsers.stream().noneMatch(e -> e.getUserId().equals(qwExternalContact.getExternalUserId()))){
+            log.error("客户不在群:{},里面:{}", qwGroupChat.getChatId(), qwExternalContact.getExternalUserId());
             return addCustomerService(param.getQwUserId(),msg);
         }
         Long qwExternalId = qwExternalContact.getId();
@@ -700,27 +704,28 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     }
 
     private R handleExt(FsUserCourseVideoAddKfUParam param, String msg, boolean oneCompanyCourse){
-        if (param.getLinkType()!=null&&param.getLinkType()==1){
-            if (param.getLinkId()!=null){
-                // 从数据库中查找短链对应的真实链接
-                FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLinkId(param.getLinkId());
-                if (courseLink != null) {
-                    // 获取当前时间
-                    Date currentTime = new Date();
-                    // 获取链接的更新时间(假设这个字段代表过期时间)
-                    Date updateTime = courseLink.getUpdateTime();
-
-                    // 判断是否过期
-                    if (currentTime.after(updateTime)) {
-                        // 链接已过期
-                        return R.error("链接已过期");
-                    }else {
-                        return R.ok();
-                    }
-                }
-            }
-            return R.error("链接过期");
-        }
+        //暂时注释应急短链逻辑
+//        if (param.getLinkType()!=null&&param.getLinkType()==1){
+//            if (param.getLinkId()!=null){
+//                // 从数据库中查找短链对应的真实链接
+//                FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLinkId(param.getLinkId());
+//                if (courseLink != null) {
+//                    // 获取当前时间
+//                    Date currentTime = new Date();
+//                    // 获取链接的更新时间(假设这个字段代表过期时间)
+//                    Date updateTime = courseLink.getUpdateTime();
+//
+//                    // 判断是否过期
+//                    if (currentTime.after(updateTime)) {
+//                        // 链接已过期
+//                        return R.error("链接已过期");
+//                    }else {
+//                        return R.ok();
+//                    }
+//                }
+//            }
+//            return R.error("链接过期");
+//        }
 
         Long qwExternalId = param.getQwExternalId();
 
@@ -780,6 +785,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
             iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
 
+            fsUserCompanyBindService.bindFsUser(param.getUserId(), qwExternalId, log.getLogId());
+
+
 
             if (param.getLinkType()!=null&&param.getLinkType()==5){
                 FsCourseLink fsCourseLink = fsCourseLinkMapper.selectExpireLinkByQwExternalId(param.getQwUserId(), param.getVideoId(), qwExternalId);
@@ -804,25 +812,6 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             user.setQwExtId(param.getQwExternalId());
             fsUserMapper.updateFsUser(user);
 
-
-//            //小访客特有
-//            SendXfkParam xfkParam=new SendXfkParam();
-//            xfkParam.setCorpId(externalContact.getCorpId());
-//            xfkParam.setUserId(externalContact.getUserId());
-//            xfkParam.setName(externalContact.getName());
-//            xfkParam.setAddWay(externalContact.getAddWay());
-//            xfkParam.setState(externalContact.getState());
-//            if (externalContact.getCreateTime()!=null){
-//                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-//                String formattedTime = sdf.format(externalContact.getCreateTime());
-//                xfkParam.setCreateTime(formattedTime);
-//            }else {
-//                xfkParam.setCreateTime("");
-//            }
-//
-//
-//            xfkService.executeSopByIds(xfkParam);
-
             iSopUserLogsInfoService.updateSopUserInfoByExternalId(qwExternalId,param.getUserId());
 
             //绑定上之后 更新观看记录
@@ -834,6 +823,9 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             log.setUpdateTime(new Date());
             courseWatchLogMapper.updateFsCourseWatchLog(log);
 
+            fsUserCompanyBindService.bindFsUser(param.getUserId(), qwExternalId, log.getLogId());
+
+
             if (param.getLinkType()!=null&&param.getLinkType()==5){
                 FsCourseLink fsCourseLink = fsCourseLinkMapper.selectExpireLinkByQwExternalId(param.getQwUserId(), param.getVideoId(), qwExternalId);
                 return R.error(566,"官方群发通用链接").put("courseLink",fsCourseLink);
@@ -1911,7 +1903,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             return ResponseResult.fail(504, "请观看最新的课程项目");
         }
         // 项目看课数限制
-        if(!"福本源".equals(signProjectName) && !"宽益堂".equals(signProjectName) && !CloudHostUtils.hasCloudHostName("弘德堂")) {
+        if(!EXCLUDE_PROJECTS.contains(signProjectName) && !CloudHostUtils.hasCloudHostName("弘德堂")) {
             Integer logCount = fsUserCourseMapper.selectTodayCourseWatchLogCountByUserIdAndProjectId(param.getUserId(), courseProject);
             if (Objects.isNull(watchCourseVideo) && logCount > 0) {
                 return ResponseResult.fail(504, "超过项目看课数量限制");

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

@@ -44,9 +44,16 @@ public class FsCourseWatchLogListVO extends BaseEntity
      * 项目
      */
     private Integer project;
+
+    /** 营期id */
+    private Long periodId;
+
     @Excel(name = "项目名称")
     private String projectName;
 
+    @Excel(name = "营期名称")
+    private String periodIdName;
+
     @Excel(name = "课程名称")
     private String courseName;
 

+ 30 - 0
fs-service/src/main/java/com/fs/course/vo/UserWatchLogListVo.java

@@ -0,0 +1,30 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 课程对象 fs_user_course
+ *
+ * @author fs
+ * @date 2024-05-15
+ */
+@Data
+public class UserWatchLogListVo {
+
+    private Integer logType;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime finishTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+    private String fsUserName;
+    private String projectName;
+    private String courseName;
+    private String videoName;
+    private String qwUserName;
+
+}

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

@@ -16,6 +16,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
@@ -328,50 +329,54 @@ public class FsStoreAfterSalesServiceImpl implements IFsStoreAfterSalesService {
         or.setOrderId(order.getOrderId());
         or.setStatus(order.getOrderStatus());
         fsStoreOrderMapper.updateFsStoreOrder(or);
-        if (order.getOrderStatus() == 2) {
-            FsStoreOrder fsStoreOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderId(order.getOrderId());
-            String newOrderSn = OrderCodeUtils.getOrderSn();
-            if (StringUtils.isEmpty(newOrderSn)) {
-                newOrderSn = OrderCodeUtils.getOrderSn();
-            }
-            FsStoreOrder newOrder = new FsStoreOrder();
-            newOrder.setOrderId(order.getOrderId());
-            newOrder.setOrderCode(newOrderSn);
-            fsStoreOrderMapper.updateFsStoreOrder(newOrder);
-            or.setOrderCode(newOrderSn);
-            if (fsStoreOrder.getPackageOrderId() != null) {
-                FsPackageOrder fsPackageOrder = new FsPackageOrder();
-                fsPackageOrder.setOrderId(fsStoreOrder.getPackageOrderId());
-                fsPackageOrder.setOrderSn(newOrderSn);
-                fsPackageOrderMapper.updateFsPackageOrder(fsPackageOrder);
-            }
-            if (fsStoreOrder.getInquiryOrderId() != null) {
-                FsInquiryOrder fsInquiryOrder = new FsInquiryOrder();
-                fsInquiryOrder.setOrderSn(newOrderSn);
-                fsInquiryOrder.setOrderId(fsStoreOrder.getInquiryOrderId());
-                fsInquiryOrderMapper.updateFsInquiryOrder(fsInquiryOrder);
-            }
+        //金牛订单取消售后审核不需要操作
+        if(!CloudHostUtils.hasCloudHostName("金牛名医","康年堂")){
+            if (order.getOrderStatus() == 2) {
+                FsStoreOrder fsStoreOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderId(order.getOrderId());
+                String newOrderSn = OrderCodeUtils.getOrderSn();
+                if (StringUtils.isEmpty(newOrderSn)) {
+                    newOrderSn = OrderCodeUtils.getOrderSn();
+                }
+                FsStoreOrder newOrder = new FsStoreOrder();
+                newOrder.setOrderId(order.getOrderId());
+                newOrder.setOrderCode(newOrderSn);
+                fsStoreOrderMapper.updateFsStoreOrder(newOrder);
+                or.setOrderCode(newOrderSn);
+                if (fsStoreOrder.getPackageOrderId() != null) {
+                    FsPackageOrder fsPackageOrder = new FsPackageOrder();
+                    fsPackageOrder.setOrderId(fsStoreOrder.getPackageOrderId());
+                    fsPackageOrder.setOrderSn(newOrderSn);
+                    fsPackageOrderMapper.updateFsPackageOrder(fsPackageOrder);
+                }
+                if (fsStoreOrder.getInquiryOrderId() != null) {
+                    FsInquiryOrder fsInquiryOrder = new FsInquiryOrder();
+                    fsInquiryOrder.setOrderSn(newOrderSn);
+                    fsInquiryOrder.setOrderId(fsStoreOrder.getInquiryOrderId());
+                    fsInquiryOrderMapper.updateFsInquiryOrder(fsInquiryOrder);
+                }
 
-            List<FsStorePayment> payments = fsStorePaymentMapper.selectFsStorePaymentByPay(2, fsStoreOrder.getOrderId());
-            if (fsStoreOrder.getPackageOrderId() != null) {
-                payments = fsStorePaymentMapper.selectFsStorePaymentByPay(3, fsStoreOrder.getPackageOrderId());
-            }
-            for (FsStorePayment payment : payments) {
-                FsStorePayment fsStorePayment = new FsStorePayment();
-                fsStorePayment.setPaymentId(payment.getPaymentId());
-                fsStorePayment.setBusinessCode(newOrderSn);
-                fsStorePaymentMapper.updateFsStorePayment(fsStorePayment);
-            }
+                List<FsStorePayment> payments = fsStorePaymentMapper.selectFsStorePaymentByPay(2, fsStoreOrder.getOrderId());
+                if (fsStoreOrder.getPackageOrderId() != null) {
+                    payments = fsStorePaymentMapper.selectFsStorePaymentByPay(3, fsStoreOrder.getPackageOrderId());
+                }
+                for (FsStorePayment payment : payments) {
+                    FsStorePayment fsStorePayment = new FsStorePayment();
+                    fsStorePayment.setPaymentId(payment.getPaymentId());
+                    fsStorePayment.setBusinessCode(newOrderSn);
+                    fsStorePaymentMapper.updateFsStorePayment(fsStorePayment);
+                }
 
-            try {
-                fsStoreOrderService.createOmsOrder(order.getOrderId());
-            } catch (ParseException e) {
+                try {
+                    fsStoreOrderService.createOmsOrder(order.getOrderId());
+                } catch (ParseException e) {
 
-            }
+                }
 
+            }
         }
 
 
+
         return 1;
     }
 

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

@@ -70,4 +70,5 @@ public interface QwCompanyMapper
 
     List<QwOptionsVO> selectQwCompanyListOptionsVO(@Param("userId") Long userId, @Param("deptId") Long deptId);
 
+    List<QwCompany> selectByCorpIds(@Param("corpIds") List<String> corpIds);
 }

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

@@ -517,4 +517,5 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Update("update qw_external_contact set unionid = #{item.unionid} where id = #{item.id}")
     int batchUpdateUnionId(@Param("item") QwExternalContact item);
 
+    void updateQwExternalContactStatusById(QwExternalContact qwExternalContact);
 }

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

@@ -182,7 +182,8 @@ public interface QwWatchLogMapper extends BaseMapper<QwWatchLog>{
             "    qec.qw_user_id id,\n" +
             "    qu.qw_user_name AS qw_user_name, \n" +
             "    DATE(qec.create_time) AS create_time, \n" +
-            "    COUNT(1) AS line\n" +
+            "    COUNT(1) AS line," +
+            "    COUNT(CASE WHEN fs_user_id IS NOT NULL THEN 1 ELSE NULL END) AS reg_num\n" +
             "FROM\n" +
             "    qw_external_contact qec\n" +
             "JOIN\n" +

+ 14 - 0
fs-service/src/main/java/com/fs/qw/param/UserWatchLogParam.java

@@ -0,0 +1,14 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class UserWatchLogParam {
+
+    private Long externalUserId;
+    private Long courseId;
+    private Long videoId;
+    private Long userId;
+    private Long fsUserId;
+
+}

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

@@ -247,4 +247,7 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      */
     List<QwUserDelLossLogVO> selectQwUserDelLossLogList(QwUserDelLossLogParam param);
 
+    void updateQwExternalContactStatusById(QwExternalContact qwExternalContact);
+
+    R getRepeat(RepeatParam param);
 }

+ 38 - 18
fs-service/src/main/java/com/fs/qw/service/impl/QwContactWayServiceImpl.java

@@ -6,6 +6,7 @@ import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.FsCourseLinkCreateParam;
@@ -371,10 +372,9 @@ public class QwContactWayServiceImpl implements IQwContactWayService
 
     }
 
-    @Override
-    public boolean sendWelcomeMsg(QwContactWay qwContactWay, String corpId, String welcomeCode,QwUser qwUser,Long qwExternalId) {
-
 
+    @Override
+    public boolean sendWelcomeMsg(QwContactWay qwContactWay, String corpId, String welcomeCode, QwUser qwUser, Long qwExternalId) {
         if (qwContactWay.getIsSpanWelcome()==1){
             String welcomeJson = qwContactWay.getWelcomeJson();
             if (welcomeJson!=null&&welcomeJson!=""){
@@ -395,9 +395,7 @@ public class QwContactWayServiceImpl implements IQwContactWayService
 
                             SendWelcomeMsgParam copy = BeanCopyUtils.copy(qwWelcomeJsonFrom, SendWelcomeMsgParam.class);
                             copy.setWelcome_code(welcomeCode);
-
                             QwResult qwResult = qwApiService.sendWelcomeMsg(copy, corpId);
-
                             if (qwResult.getErrcode()==0){
                                 return false;
                             }else {
@@ -411,23 +409,45 @@ public class QwContactWayServiceImpl implements IQwContactWayService
 
 
         }
+
         SendWelcomeMsgParam sendWelcomeMsgParam = new SendWelcomeMsgParam();
         sendWelcomeMsgParam.setWelcome_code(welcomeCode);
-        TextMessage textMessage = new TextMessage();
-        textMessage.setContent(qwContactWay.getTextContent());
-        sendWelcomeMsgParam.setText(textMessage);
-        SendWelcomeMsgParam.Attachment attachment = new SendWelcomeMsgParam.Attachment();
-        SendWelcomeMsgParam.Attachment.ImageAttachment imageAttachment = new SendWelcomeMsgParam.Attachment.ImageAttachment();
-        imageAttachment.setPic_url(qwContactWay.getImagePicUrl());
-        attachment.setMsgtype("image");
-        attachment.setImage(imageAttachment);
-        sendWelcomeMsgParam.setAttachments(Arrays.asList(attachment));
+
+        // 构建文本消息
+        if (StringUtils.isNotBlank(qwContactWay.getTextContent())) {
+            TextMessage textMessage = new TextMessage();
+            textMessage.setContent(qwContactWay.getTextContent());
+            sendWelcomeMsgParam.setText(textMessage);
+        }
+
+        // 构建图片附件 - 修正结构
+        List<SendWelcomeMsgParam.Attachment> attachments = new ArrayList<>();
+
+        if (StringUtils.isNotBlank(qwContactWay.getImagePicUrl())) {
+            SendWelcomeMsgParam.Attachment imageAttachment = new SendWelcomeMsgParam.Attachment();
+            imageAttachment.setMsgtype("image");
+
+            // 创建图片内容对象
+            SendWelcomeMsgParam.Attachment.ImageAttachment imageContent = new SendWelcomeMsgParam.Attachment.ImageAttachment();
+            imageContent.setPic_url(qwContactWay.getImagePicUrl());
+            imageAttachment.setImage(imageContent);
+
+            attachments.add(imageAttachment);
+        }
+
+        if (!attachments.isEmpty()) {
+            sendWelcomeMsgParam.setAttachments(attachments);
+        }
+
+        // 发送请求
         QwResult qwResult = qwApiService.sendWelcomeMsg(sendWelcomeMsgParam, corpId);
-        if (qwResult.getErrcode()==0){
-            return false;
-        }else {
-            return true;
+
+        if ("levent".equals(qwUser.getQwUserId())) {
+            log.debug("发送结果: {}", qwResult);
         }
+
+        // 修正返回值逻辑 - 0表示成功
+        return qwResult.getErrcode() == 0;
     }
 
     public List<QwWelcomeJsonFrom.Attachment> attachmentsTools( List<QwWelcomeJsonFrom.Attachment> attachments,

+ 24 - 3
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONException;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.ad.enums.AdUploadType;
@@ -2308,7 +2309,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                     }
                                 }
                             }
-                            if (qwContactWay.getIsSpanWelcome() == 1 && isClose) {
+                            if (qwContactWay.getIsWelcome() == 1 && isClose) {
                                 isSend = qwContactWayService.sendWelcomeMsg(qwContactWay, corpId, welcomeCode,qwUser,contact.getId());
                             }
                         }
@@ -2347,7 +2348,6 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                 LocalTime start = LocalTime.parse(schedule.getStartTime());
                                 LocalTime end = LocalTime.parse(schedule.getEndTime().equals("24:00") ? "23:59:59" : schedule.getEndTime());
                                 if (!now.isBefore(start) && !now.isAfter(end)) {
-
                                     TextMessage textMessage = new TextMessage();
                                     textMessage.setContent(schedule.getWelcomeText());
                                     sendWelcomeMsgParam.setText(textMessage);
@@ -4261,7 +4261,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
         //客户删除销售-找这个销售的sop任务中所有的营期里有没有这个客户,删了(暂时不要删-这种流失的有些一样能发消息)
 //        sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(userID,corpId, externalUserID);
-        logger.error("客户删除销售-"+"|"+externalUserID+"|"+userID+"|"+corpId);
+//        logger.error("客户删除销售-"+"|"+externalUserID+"|"+userID+"|"+corpId);
 
         QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactUserIdAndExternalIdAndCompanyId(externalUserID, userID, corpId);
         if (qwExternalContact != null) {
@@ -5835,6 +5835,27 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return qwExternalContactMapper.selectQwUserDelLossList(param);
     }
 
+    @Override
+    public void updateQwExternalContactStatusById(QwExternalContact qwExternalContact) {
+        qwExternalContactMapper.updateQwExternalContactStatusById(qwExternalContact);
+    }
+
+    @Override
+    public R getRepeat(RepeatParam param) {
+        List<QwExternalContact> list = qwExternalContactMapper.selectList(new QueryWrapper<QwExternalContact>().eq("external_user_id", param.getExternalUserId()));
+        if(list.isEmpty()) return R.error("未找到外部联系人");
+        QwExternalContact qwExternalContact = list.get(0);
+        List<QwExternalContact> qwExternalContacts = qwExternalContactMapper.selectList(new QueryWrapper<QwExternalContact>().eq("repeat_no", qwExternalContact.getRepeatNo()));
+        List<String> userIdList = PubFun.listToNewList(qwExternalContacts, QwExternalContact::getUserId);
+        List<QwUser> qwUsers = qwUserMapper.selectList(new QueryWrapper<QwUser>().in("qw_user_id", userIdList).eq("corp_id", qwExternalContact.getCorpId()));
+        List<String> companyIds = PubFun.listToNewList(qwUsers, QwUser::getCorpId);
+        List<QwCompany> companyList = qwCompanyMapper.selectByCorpIds(companyIds);
+        Map<String, QwCompany> companyMap = PubFun.listToMapByGroupObject(companyList, QwCompany::getCorpId);
+        QwCompany qwCompany = new QwCompany();
+        qwCompany.setCorpName("未找到主题");
+        return R.ok().put("data", qwUsers.stream().map(e -> QwUserVO.builder().qwUserId(e.getQwUserId()).qwUserName(e.getQwUserName()).companyName(companyMap.getOrDefault(e.getCorpId(), qwCompany).getCorpName()).build()));
+    }
+
 
     public Boolean getSopAiChatByRedis(String qwUserId,String corpId,String externalUserId){
 

+ 9 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java

@@ -107,6 +107,15 @@ public class QwSopCourseFinishTempSetting implements Serializable,Cloneable{
         // 原因
         private String sendRemarks;
 
+        // 封面标题
+        private String nickname;
+        //头像
+        private String avatar;
+        //封面图片地址
+        private String coverUrl;
+
+        //课程id
+        private Long courseId;
         @Override
         public Setting clone() {
             try {

+ 79 - 70
fs-service/src/main/java/com/fs/qw/vo/QwWatchLogAllStatisticsListVO.java

@@ -7,77 +7,88 @@ import java.util.Date;
 
 @Data
 public class QwWatchLogAllStatisticsListVO {
-    Long id;
-    String qwUserName;
+    private Long id;
+    private String qwUserName;
     @JsonFormat(pattern = "yyyy-MM-dd")
-    Date createTime;
+    private Date createTime;
 
-    Long line;//进线数
-
-    Long firstOnline;//先导课上线
-
-    Long firstOver;//先导课完课
+    /**
+     * 进线数
+     */
+    private Long line;
+    /**
+     * 先导课上线
+     */
+    private Long firstOnline;
+    /**
+     * 先导课完课
+     */
+    private Long firstOver;
+    /**
+     * 注册数
+     */
+    private Long regNum;
 
-    Long d1Online;
-    Long d1Over;
-    Long d2Online;
-    Long d2Over;
-    Long d3Online;
-    Long d3Over;
-    Long d4Online;
-    Long d4Over;
-    Long d5Online;
-    Long d5Over;
-    Long d6Online;
-    Long d6Over;
-    Long d7Online;
-    Long d7Over;
-    Long d8Online;
-    Long d8Over;
-    Long d9Online;
-    Long d9Over;
-    Long d10Online;
-    Long d10Over;
-    Long d11Online;
-    Long d11Over;
-    Long d12Online;
-    Long d12Over;
-    Long d13Online;
-    Long d13Over;
-    Long d14Online;
-    Long d14Over;
-    Long d15Online;
-    Long d15Over;
-    Long d16Online;
-    Long d16Over;
-    Long d17Online;
-    Long d17Over;
-    Long d18Online;
-    Long d18Over;
-    Long d19Online;
-    Long d19Over;
-    Long d20Online;
-    Long d20Over;
-    Long d21Online;
-    Long d21Over;
-    Long d22Online;
-    Long d22Over;
-    Long d23Online;
-    Long d23Over;
-    Long d24Online;
-    Long d24Over;
-    Long d25Online;
-    Long d25Over;
-    Long d26Online;
-    Long d26Over;
-    Long d27Online;
-    Long d27Over;
-    Long d28Online;
-    Long d28Over;
-    Long d29Online;
-    Long d29Over;
-    Long d30Online;
-    Long d30Over;
+    private Long d1Online;
+    private Long d1Over;
+    private Long d2Online;
+    private Long d2Over;
+    private Long d3Online;
+    private Long d3Over;
+    private Long d4Online;
+    private Long d4Over;
+    private Long d5Online;
+    private Long d5Over;
+    private Long d6Online;
+    private Long d6Over;
+    private Long d7Online;
+    private Long d7Over;
+    private Long d8Online;
+    private Long d8Over;
+    private Long d9Online;
+    private Long d9Over;
+    private Long d10Online;
+    private Long d10Over;
+    private Long d11Online;
+    private Long d11Over;
+    private Long d12Online;
+    private Long d12Over;
+    private Long d13Online;
+    private Long d13Over;
+    private Long d14Online;
+    private Long d14Over;
+    private Long d15Online;
+    private Long d15Over;
+    private Long d16Online;
+    private Long d16Over;
+    private Long d17Online;
+    private Long d17Over;
+    private Long d18Online;
+    private Long d18Over;
+    private Long d19Online;
+    private Long d19Over;
+    private Long d20Online;
+    private Long d20Over;
+    private Long d21Online;
+    private Long d21Over;
+    private Long d22Online;
+    private Long d22Over;
+    private Long d23Online;
+    private Long d23Over;
+    private Long d24Online;
+    private Long d24Over;
+    private Long d25Online;
+    private Long d25Over;
+    private Long d26Online;
+    private Long d26Over;
+    private Long d27Online;
+    private Long d27Over;
+    private Long d28Online;
+    private Long d28Over;
+    private Long d29Online;
+    private Long d29Over;
+    private Long d30Online;
+    private Long d30Over;
     /**
      * 项目
      */
@@ -103,6 +114,4 @@ public class QwWatchLogAllStatisticsListVO {
     private Long fsUserId;
 
     private String fsUserName;
-
-
 }

+ 4 - 0
fs-service/src/main/java/com/fs/system/service/impl/SysConfigServiceImpl.java

@@ -231,6 +231,10 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     @Override
     public SysConfig selectConfigByConfigKey(String configKey) {
+        //TODO 可以先查询这个key是否在数据库中存在,
+        // 如果不存在就返回表中id+1的数为新id,以及将查询的key作为key返回,
+        // 并在前端提示更新就可以了
+        // 否做会出现bug
         return configMapper.selectConfigByConfigKey(configKey);
     }
 

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

@@ -88,8 +88,8 @@ ipad:
   ipadUrl: http://qwipad.muyi88.com
 #  aiApi: http://152.136.202.157:3000/api
   aiApi: http://49.232.181.28:3000/api
-  voiceApi:
-  commonApi:
+  voiceApi: http://106.52.21.84:8009
+  commonApi: http://106.52.21.84:7771
 wx_miniapp_temp:
   pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek
   inquiry_temp_id: 9POPYeqhI48LOPvq-Rfoklze7H-9SlunJKh10Qt4_2I

+ 162 - 0
fs-service/src/main/resources/application-druid-jnsyj-test.yml

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

+ 19 - 2
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -25,10 +25,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="campPeriodTime"    column="camp_period_time"    />
         <result property="lastHeartbeatTime"    column="last_heartbeat_time"    />
         <result property="project"    column="project"    />
+        <result property="periodId"    column="period_id"    />
     </resultMap>
 
     <sql id="selectFsCourseWatchLogVo">
-        select log_id, user_id,finish_time,send_finish_msg,sop_id,video_id,reward_type, log_type, create_time, update_time, qw_external_contact_id, duration, qw_user_id, company_user_id, company_id, course_id,camp_period_time,project from fs_course_watch_log
+        select log_id, user_id,finish_time,send_finish_msg,sop_id,video_id,reward_type, log_type, create_time, update_time, qw_external_contact_id, duration, qw_user_id, company_user_id, company_id, course_id,camp_period_time,project,period_id  from fs_course_watch_log
     </sql>
 
     <select id="selectFsCourseWatchLogList" parameterType="FsCourseWatchLog" resultMap="FsCourseWatchLogResult">
@@ -55,7 +56,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </select>
 
     <select id="selectFsCourseWatchLogListVO" resultType="com.fs.course.vo.FsCourseWatchLogListVO">
-        select l.log_id,l.project,l.user_id,uc.course_name,v.title as video_name,qec.avatar as external_user_avatar,
+        select l.log_id,l.project,l.period_id,l.user_id,uc.course_name,v.title as video_name,qec.avatar as external_user_avatar,
         l.log_type,SEC_TO_TIME(l.duration) as duration,c.company_name,l.camp_period_time,l.finish_time,
         cu.nick_name as company_user_name ,l.send_type,l.create_time,l.update_time,l.last_heartbeat_time,
         qu.qw_user_name,qec.name as external_user_name,c.company_id,u.avatar as fsAvatar,u.nick_name as fsNickName,qec.create_time as qec_create_time,
@@ -144,6 +145,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.sopId != null  and maps.sopId != '' ">
                 and l.sop_id = #{maps.sopId}
             </if>
+
+            <if test="maps.periodIds != null and maps.periodIds.size() > 0">
+                and l.period_id in
+                <foreach item="periodId" index="index" collection="maps.periodIds" open="(" separator="," close=")">
+                    #{periodId}
+                </foreach>
+            </if>
+
         </where>
          order by l.finish_time desc,l.update_time desc,l.create_time desc
     </select>
@@ -741,6 +750,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         SELECT
         l.log_id,
         l.project AS project,
+        l.period_id,
         l.user_id,
         l.log_type,
         SEC_TO_TIME(l.duration) AS duration,
@@ -829,6 +839,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                     #{sopId}
                 </foreach>
             </if>
+            <if test="maps.periodIds != null and maps.periodIds.size() > 0">
+                and l.period_id in
+                <foreach item="periodId" index="index" collection="maps.periodIds" open="(" separator="," close=")">
+                    #{periodId}
+                </foreach>
+            </if>
+
         </where>
         order by l.finish_time desc,l.update_time desc,l.create_time desc
     </select>

+ 162 - 0
fs-service/src/main/resources/mapper/course/FsUserCompanyBindMapper.xml

@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsUserCompanyBindMapper">
+
+    <resultMap type="FsUserCompanyBind" id="FsUserCompanyBindResult">
+        <result property="id"    column="id"    />
+        <result property="fsUserId"    column="fs_user_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="projectId"    column="project_id"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="qwExternalContactId"    column="qw_external_contact_id"    />
+        <result property="qwCompanyId"    column="qw_company_id"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="watchLogId"    column="watch_log_id"    />
+        <result property="logType"    column="log_type"    />
+        <result property="finishTime"    column="finish_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectFsUserCompanyBindVo">
+        select id, fs_user_id, company_user_id, company_id, project_id, qw_user_id, qw_external_contact_id, qw_company_id, course_id, video_id, watch_log_id, log_type, finish_time, create_time, create_by, update_by, update_time, remark from fs_user_company_bind
+    </sql>
+
+    <select id="selectFsUserCompanyBindList" parameterType="FsUserCompanyBind" resultMap="FsUserCompanyBindResult">
+        <include refid="selectFsUserCompanyBindVo"/>
+        <where>
+            <if test="fsUserId != null "> and fs_user_id = #{fsUserId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="projectId != null "> and project_id = #{projectId}</if>
+            <if test="qwUserId != null "> and qw_user_id = #{qwUserId}</if>
+            <if test="qwExternalContactId != null "> and qw_external_contact_id = #{qwExternalContactId}</if>
+            <if test="qwCompanyId != null "> and qw_company_id = #{qwCompanyId}</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="logType != null "> and log_type = #{logType}</if>
+            <if test="finishTime != null "> and finish_time = #{finishTime}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCompanyBindById" parameterType="Long" resultMap="FsUserCompanyBindResult">
+        <include refid="selectFsUserCompanyBindVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsUserCompanyBind" parameterType="FsUserCompanyBind" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_user_company_bind
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="fsUserId != null">fs_user_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="projectId != null">project_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="qwExternalContactId != null">qw_external_contact_id,</if>
+            <if test="qwCompanyId != null">qw_company_id,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="watchLogId != null">watch_log_id,</if>
+            <if test="logType != null">log_type,</if>
+            <if test="finishTime != null">finish_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="fsUserId != null">#{fsUserId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="projectId != null">#{projectId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="qwExternalContactId != null">#{qwExternalContactId},</if>
+            <if test="qwCompanyId != null">#{qwCompanyId},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="watchLogId != null">#{watchLogId},</if>
+            <if test="logType != null">#{logType},</if>
+            <if test="finishTime != null">#{finishTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserCompanyBind" parameterType="FsUserCompanyBind">
+        update fs_user_company_bind
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="fsUserId != null">fs_user_id = #{fsUserId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="projectId != null">project_id = #{projectId},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="qwExternalContactId != null">qw_external_contact_id = #{qwExternalContactId},</if>
+            <if test="qwCompanyId != null">qw_company_id = #{qwCompanyId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="watchLogId != null">watch_log_id = #{watchLogId},</if>
+            <if test="logType != null">log_type = #{logType},</if>
+            <if test="finishTime != null">finish_time = #{finishTime},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsUserCompanyBindById" parameterType="Long">
+        delete from fs_user_company_bind where id = #{id}
+    </delete>
+
+    <delete id="deleteFsUserCompanyBindByIds" parameterType="String">
+        delete from fs_user_company_bind where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="getWatchLogList" resultType="com.fs.course.vo.UserWatchLogListVo">
+        select
+        a.*,
+        b.nick_name fsUserName,
+        c.dict_label projectName,
+        e.course_name,
+        IF(g.company_user_id = #{userId}, g.qw_user_name, '其他') qwUserName,
+        f.title videoName
+        from
+        fs_user_company_bind a
+        left join fs_user b on a.fs_user_id = b.user_id
+        left join sys_dict_data c on c.dict_type = 'sys_course_project' and dict_value = a.project_id
+        left join fs_user_course e on a.course_id = e.course_id
+        left join fs_user_course_video f on a.video_id = f.video_id
+        left join qw_user g on g.id = a.qw_user_id
+        <where>
+            <if test="externalUserId != null">
+                and a.qw_external_contact_id = #{externalUserId}
+            </if>
+            <if test="fsUserId != null">
+                and a.fs_user_id = #{fsUserId}
+            </if>
+            <if test="courseId != null">
+                and a.course_id = #{courseId}
+            </if>
+            <if test="videoId != null">
+                and a.video_id = #{videoId}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+</mapper>

+ 5 - 0
fs-service/src/main/resources/mapper/qw/QwCompanyMapper.xml

@@ -179,4 +179,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
     </select>
+
+    <select id="selectByCorpIds" resultType="com.fs.qw.domain.QwCompany">
+        select * from qw_company where corp_id in <foreach collection="corpIds" item="item" open="(" close=")" separator=",">#{item}</foreach>
+    </select>
+
 </mapper>

+ 4 - 2
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -603,7 +603,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <update id="updateQwExternalContactIsRePlyById">
         update qw_external_contact set is_reply = 1 where id = #{id}
     </update>
-
+    <update id="updateQwExternalContactStatusById">
+        update qw_external_contact set status = #{status} where id = #{id}
+    </update>
     <select id="selectExternalByFsUserIds" resultType="QwExternalContact">
         select * from qw_external_contact
         where fs_user_id in
@@ -623,7 +625,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                left join company_user cu on cu.user_id=qu.company_user_id
         where fs_user_id = #{userId}
     </select>
-    
+
     <insert id="insertQwUserDelLossLog" parameterType="com.fs.qw.domain.QwUserDelLossLog">
         insert into qw_user_del_loss_log (external_contact_id,company_id,company_user_id,type,time,qw_user_id,corp_id) values
         (#{param.externalContactId},#{param.companyId},#{param.companyUserId},#{param.type},#{param.time},#{param.qwUserId},#{param.corpId})

+ 1 - 3
fs-store/src/main/java/com/fs/store/controller/common/CommonController.java

@@ -91,9 +91,7 @@ public class CommonController
             ajax.put("fileName", fileName);
             ajax.put("url", url);
             return ajax;
-        }
-        catch (Exception e)
-        {
+        } catch (Exception e) {
             return AjaxResult.error(e.getMessage());
         }
     }

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

@@ -53,7 +53,7 @@ public class StoreAfterSalesScrmController extends AppBaseController {
     private IFsStoreOrderScrmService orderService;
     @Login
     @ApiOperation("获取订单项列表")
-    @GetMapping("/getStoreOrderItems")
+    @GetMapping({"/getStoreOrderItems", "/getMyStoreOrderItemByOrderId"})
     public R getMyStoreOrderById(@RequestParam("orderId") Long orderId, HttpServletRequest request){
         List<FsStoreOrderItemVO> list=itemService.selectFsStoreOrderItemListByOrderId(orderId);
         FsStoreOrderScrm order=orderService.selectFsStoreOrderById(orderId);

+ 5 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/StoreOrderScrmController.java

@@ -47,6 +47,7 @@ import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxErrorException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,6 +64,8 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 
+
+@Slf4j
 @Api("商城接口")
 @RestController
 @RequestMapping(value="/store/app/storeOrder")
@@ -270,7 +273,8 @@ public class StoreOrderScrmController extends AppBaseController {
     @ApiOperation("创建订单")
     @PostMapping("/create")
     public R create(@Validated @RequestBody FsStoreOrderCreateParam param, HttpServletRequest request){
-        String userId=getUserId();
+        String userId= getUserId();
+        log.info("开始创建订单,登录用户id:{}", userId);
         param.setIsUserApp(false);
         return orderService.createOrder(Long.parseLong(getUserId()),param);
     }

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

@@ -90,7 +90,7 @@ public class UserScrmController extends AppBaseController {
             }
             return R.ok().put("user",user);
         } catch (Exception e){
-
+            log.error("【获取用户信息】:{}",e);
             return R.error("操作异常");
         }
     }

+ 1 - 1
fs-user-app/src/main/resources/logback.xml

@@ -72,7 +72,7 @@
     </appender>
 
 	<!-- 系统模块日志级别控制  -->
-	<logger name="com.fs" level="info" />
+	<logger name="com.fs" level="debug" />
 	<!-- Spring日志级别控制  -->
 	<logger name="org.springframework" level="warn" />