瀏覽代碼

侧边栏 催课管理

三七 1 周之前
父節點
當前提交
6e08f7cc06
共有 42 個文件被更改,包括 1502 次插入37 次删除
  1. 161 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  2. 41 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwStatisticsController.java
  3. 101 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java
  4. 75 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwWorkTaskController.java
  5. 100 4
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  6. 11 0
      fs-service/src/main/java/com/fs/course/dto/WatchLogDTO.java
  7. 58 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  8. 11 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  9. 13 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java
  10. 28 0
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkMiniParam.java
  11. 38 0
      fs-service/src/main/java/com/fs/course/param/FsCourseListBySidebarParam.java
  12. 3 4
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  13. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  14. 6 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  15. 194 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  16. 26 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseListBySidebarVO.java
  17. 37 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseVideoListBySidebarVO.java
  18. 26 3
      fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java
  19. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactInfoMapper.java
  20. 9 9
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  21. 17 8
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  22. 17 0
      fs-service/src/main/java/com/fs/qw/param/ExternalContactDetailsParam.java
  23. 3 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactVOTime.java
  24. 33 0
      fs-service/src/main/java/com/fs/qw/param/SelectQwWorkTaskListParam.java
  25. 3 1
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactInfoService.java
  26. 17 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  27. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  28. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactInfoServiceImpl.java
  29. 29 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  30. 54 4
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  31. 34 0
      fs-service/src/main/java/com/fs/qw/vo/ExternalContactDetailsVO.java
  32. 24 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalListByHeavyVO.java
  33. 10 0
      fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java
  34. 23 0
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  35. 25 0
      fs-service/src/main/java/com/fs/statistics/dto/WatchCourseStatisticsDTO.java
  36. 9 0
      fs-service/src/main/java/com/fs/statistics/mapper/StatisticsServiceMapper.java
  37. 30 0
      fs-service/src/main/java/com/fs/statistics/param/WatchCourseStatisticsParam.java
  38. 18 0
      fs-service/src/main/java/com/fs/statistics/service/IStatisticsService.java
  39. 40 0
      fs-service/src/main/java/com/fs/statistics/service/impl/StatisticsServiceImpl.java
  40. 43 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactInfoMapper.xml
  41. 106 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  42. 12 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

+ 161 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -0,0 +1,161 @@
+package com.fs.app.controller;
+
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.course.param.FsCourseLinkMiniParam;
+import com.fs.course.param.newfs.UserCourseVideoPageParam;
+import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.course.vo.FsCourseListBySidebarVO;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
+import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.voice.utils.StringUtil;
+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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+
+@Api("课程库相关接口")
+@RestController
+@RequestMapping("/apis/app/fs/course")
+@Slf4j
+public class ApisFsUserCourseVideoController extends BaseController {
+
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
+    @Autowired
+    private IFsUserCourseService fsUserCourseService;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+
+
+    @PostMapping("/pageList")
+    @ApiOperation("课程分页列表")
+    public R list(@RequestBody UserCourseVideoPageParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsUserCourseVideoPageListVO> list = fsUserCourseVideoService.pageListCourseVideo(param);
+        PageInfo<FsUserCourseVideoPageListVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+
+    @ApiOperation("课程视频详情")
+    @GetMapping(value = "/videoDetails")
+    public R getVideoDetails(Long videoId) {
+        return R.ok().put("data",fsUserCourseVideoService.getVideoDetails(videoId));
+    }
+
+
+    @PostMapping("/getFsCourseListBySidebar")
+    @ApiOperation("获取视频课程下拉列表 侧边栏")
+    public R getFsCourseListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseListBySidebarVO> fsCourseListBySidebar = fsUserCourseService.getFsCourseListBySidebar(param);
+        PageInfo<FsCourseListBySidebarVO> result = new PageInfo<>(fsCourseListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    @PostMapping("/getFsCourseVideoListBySidebar")
+    @ApiOperation("获取视频课程的课节下拉列表 侧边栏")
+    public R getFsCourseVideoListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseVideoListBySidebarVO> videoListBySidebar = fsUserCourseVideoService.getFsCourseVideoListBySidebar(param);
+        PageInfo<FsCourseVideoListBySidebarVO> result = new PageInfo<>(videoListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    /**
+    * 创建 发客户小程序
+    */
+
+    @RepeatSubmit
+    @PostMapping("/createMiniLink")
+    public R createMiniLink(@RequestBody FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createMiniLink(param);
+    }
+
+    /**
+    * 创建发卡片
+    */
+    @RepeatSubmit
+    @PostMapping("/createCartLink")
+    public R createCartLink(@RequestBody  FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createCartLink(param);
+    }
+
+}

+ 41 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwStatisticsController.java

@@ -0,0 +1,41 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+
+import com.fs.statis.service.IStatisticsService;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 企微统计数据
+ */
+@RestController
+@RequestMapping("/apis/app/qw/statistics")
+public class ApisQwStatisticsController extends BaseController {
+
+    @Autowired
+    private IStatisticsService statisticsService;
+
+
+//    @Login
+    @PostMapping("/course/watch")
+    @ApiOperation("会员看课详情")
+    public R queryCourseWatchStatistics(@RequestBody WatchCourseStatisticsParam param) {
+        if(param.getQwExternalContactId() == null) {
+            throw new CustomException("外部联系人id为空!");
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = statisticsService.queryWatchCourse(param);
+
+        return R.ok().put("data",watchCourseStatisticsDTO);
+    }
+
+}

+ 101 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwUserController.java

@@ -0,0 +1,101 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.param.ExternalContactDetailsParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.vo.ExternalContactDetailsVO;
+import com.fs.qw.vo.QwExternalListByHeavyVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = "企微会员相关接口")
+@RestController
+@RequestMapping("/apis/app/qw/user")
+public class ApisQwUserController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+
+    @GetMapping("/details")
+    @ApiOperation("会员看课详情")
+    public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
+                            @ApiParam(value = "时间tab,不传表示查询全部,分别是:今天、昨天、前天、近七天", required = true) String dateTag) {
+        ExternalContactDetailsParam param = new ExternalContactDetailsParam();
+        param.setUserId(getUserId());
+        param.setContactId(contactId);
+        param.setDateTag(dateTag);
+        ExternalContactDetailsVO userDetails = qwExternalContactService.getUserDetails(param);
+        Map<String, Object> map = new HashMap<>();
+        map.put("userDetails", userDetails);
+        return R.ok(map);
+    }
+
+
+    @GetMapping("/getQwUserInfo")
+    @ApiOperation("获取企微用户信息")
+    public R getQwUserInfo(@RequestParam(value = "qwExternalContactId",required = false) Long qwExternalContactId){
+        if(qwExternalContactId == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+//        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwExternalContactId);
+        QwExternalContactInfo contactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        if (contactInfo==null){
+
+            contactInfo = new QwExternalContactInfo();
+            contactInfo.setExternalContactId(qwExternalContactId);
+            qwExternalContactInfoService.insertQwExternalContactInfo(contactInfo);
+
+        }
+
+//        return R.ok().put("data",qwExternalContact).put("moreInfo",contactInfo);
+        return R.ok().put("moreInfo",contactInfo);
+    }
+
+    @PostMapping("/updateQwUserInfo")
+    @ApiOperation("更新企微用户信息")
+    public R updateQwUserInfo(@RequestBody QwExternalContactInfo qwExternalContactInfo){
+        if(qwExternalContactInfo.getExternalContactId() == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        qwExternalContactInfoService.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+        return R.ok();
+    }
+
+    /**
+    * 获取客户是否加了其他的销售(重粉)
+    */
+    @PostMapping("/getQwExternalListByHeavy")
+    @ApiOperation("获取客户是否加了其他的销售")
+    public R getQwExternalListByHeavy(@RequestBody FsCourseListBySidebarParam param){
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null) {
+            return R.error("未查询到企业微信账号信息!请重试");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwExternalListByHeavyVO> qwExternalListByHeavy = qwExternalContactService.getQwExternalListByHeavy(param);
+        PageInfo<QwExternalListByHeavyVO> result = new PageInfo<>(qwExternalListByHeavy);
+        return R.ok().put("data", result);
+    }
+}

+ 75 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwWorkTaskController.java

@@ -0,0 +1,75 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.SelectQwWorkTaskListParam;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Slf4j
+@Api(tags = "企微任务看板接口")
+@RestController
+@RequestMapping("/apis/app/qw/workTask")
+@AllArgsConstructor
+public class ApisQwWorkTaskController extends BaseController {
+
+    private final IQwWorkTaskService qwWorkTaskService;
+    private final IQwUserService qwUserService;
+
+    @Autowired
+    ICompanyUserService companyUserService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+//    @Login
+    @PostMapping("/list")
+    @ApiOperation("企微任务看板列表")
+    public R selectQwWorkTaskList(@RequestBody SelectQwWorkTaskListParam param) {
+
+        log.info("企微任务看板列表: {}",param);
+
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+
+        param.setUserId(qwUser.getId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwWorkTask> list = qwUserService.selectQwWorkTaskList(param);
+        for (QwWorkTask qwWorkTask : list) {
+            List<Integer> logs = fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTask.getExtId());
+            qwWorkTask.setLogs(logs);
+        }
+
+        PageInfo<QwWorkTask> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+
+
+}

+ 100 - 4
fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -1,31 +1,36 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSONObject;
+import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.FsCourseLinkMiniParam;
 import com.fs.course.param.FsCourseLinkRoomParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.course.vo.FsCourseListBySidebarVO;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.course.vo.newfs.FsUserVideoListVO;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwUserService;
+import com.fs.voice.utils.StringUtil;
 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 org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
@@ -45,10 +50,15 @@ public class FsUserCourseVideoController {
     @Autowired
     private IQwUserService qwUserService;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
     @GetMapping("/pageList")
     @ApiOperation("课程分页列表")
     public ResponseResult<PageInfo<FsUserCourseVideoPageListVO>> list(UserCourseVideoPageParam param) {
-        QwUser qwUser = qwUserService.getByQwUserIdAndCorId(param.getQwUserId(), param.getCorpId());
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
         if (qwUser==null||qwUser.getCompanyId()==null){
             return ResponseResult.fail(500,"无权限");
         }
@@ -93,6 +103,38 @@ public class FsUserCourseVideoController {
         return ResponseResult.ok(result);
     }
 
+
+    @PostMapping("/getFsCourseListBySidebar")
+    @ApiOperation("获取视频课程下拉列表 侧边栏")
+    public R getFsCourseListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseListBySidebarVO> fsCourseListBySidebar = fsUserCourseService.getFsCourseListBySidebar(param);
+        PageInfo<FsCourseListBySidebarVO> result = new PageInfo<>(fsCourseListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    @PostMapping("/getFsCourseVideoListBySidebar")
+    @ApiOperation("获取视频课程的课节下拉列表 侧边栏")
+    public R getFsCourseVideoListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseVideoListBySidebarVO> videoListBySidebar = fsUserCourseVideoService.getFsCourseVideoListBySidebar(param);
+        PageInfo<FsCourseVideoListBySidebarVO> result = new PageInfo<>(videoListBySidebar);
+        return R.ok().put("data", result);
+    }
+
     @Autowired
     private IFsCourseLinkService courseLinkService;
 
@@ -127,4 +169,58 @@ public class FsUserCourseVideoController {
         return R.ok().put("news",news);
     }
 
+    /**
+     * 创建 发客户小程序
+     */
+
+    @RepeatSubmit
+    @PostMapping("/createMiniLink")
+    public R createMiniLink(@RequestBody FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createMiniLink(param);
+    }
+
+    /**
+     * 创建发卡片
+     */
+    @RepeatSubmit
+    @PostMapping("/createCartLink")
+    public R createCartLink(@RequestBody  FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createCartLink(param);
+    }
 }

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

@@ -0,0 +1,11 @@
+package com.fs.course.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class WatchLogDTO implements Serializable {
+    private String date;
+    private Integer logType;
+}

+ 58 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -3,6 +3,7 @@ package com.fs.course.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
 import com.fs.sop.vo.QwRatingVO;
@@ -319,4 +320,61 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     FsCourseWatchLog selectFsCourseWatchLogByCourseSopIdAndVideoId(@Param("userId") Long userId,
                                                                    @Param("videoId") Long videoId,
                                                                    @Param("qwUserId") String qwUserId);
+
+    @Select("WITH date_series AS (\n" +
+            "  SELECT DATE_SUB(CURRENT_DATE(), INTERVAL 6-n DAY) AS report_date\n" +
+            "  FROM (\n" +
+            "    SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 \n" +
+            "    UNION SELECT 4 UNION SELECT 5 UNION SELECT 6\n" +
+            "  ) days\n" +
+            "  ORDER BY n\n" +
+            "),\n" +
+            "daily_data AS (\n" +
+            "  SELECT \n" +
+            "    DATE(create_time) AS log_date,\n" +
+            "    log_type,\n" +
+            "    ROW_NUMBER() OVER (PARTITION BY DATE(create_time) ORDER BY create_time DESC) AS rn\n" +
+            "  FROM fs_course_watch_log\n" +
+            "  WHERE qw_external_contact_id = #{extId}\n" +
+            "    AND create_time >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)\n" +
+            ")\n" +
+            "SELECT \n" +
+            "ds.report_date AS date,"+
+            "  IFNULL(dd.log_type, 0) AS log_type\n" +
+            "FROM date_series ds\n" +
+            "LEFT JOIN (\n" +
+            "  SELECT log_date, log_type FROM daily_data WHERE rn = 1\n" +
+            ") dd ON ds.report_date = dd.log_date\n" +
+            "ORDER BY ds.report_date ASC  ")
+    List<WatchLogDTO> selectFsCourseWatchLog7Day(@Param("extId") Long extId);
+
+    @Select("WITH date_series AS (\n" +
+            "  SELECT DATE_SUB(CURRENT_DATE(), INTERVAL 29-n DAY) AS report_date\n" +
+            "  FROM (\n" +
+            "    SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 \n" +
+            "    UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 \n" +
+            "    UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION SELECT 15 UNION SELECT 16 UNION SELECT 17 \n" +
+            "    UNION SELECT 18 UNION SELECT 19 UNION SELECT 20 UNION SELECT 21 UNION SELECT 22 UNION SELECT 23 \n" +
+            "    UNION SELECT 24 UNION SELECT 25 UNION SELECT 26 UNION SELECT 27 UNION SELECT 28 UNION SELECT 29\n" +
+            "  ) days\n" +
+            "  ORDER BY n\n" +
+            "),\n" +
+            "daily_data AS (\n" +
+            "  SELECT \n" +
+            "    DATE(create_time) AS log_date,\n" +
+            "    log_type,\n" +
+            "    ROW_NUMBER() OVER (PARTITION BY DATE(create_time) ORDER BY create_time DESC) AS rn\n" +
+            "  FROM fs_course_watch_log\n" +
+            "  WHERE qw_external_contact_id = #{extId}\n" +
+            "    AND create_time >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)\n" +
+            ")\n" +
+            "SELECT \n" +
+            "  ds.report_date AS date,\n" +
+            "  IFNULL(dd.log_type, 0) AS log_type\n" +
+            "FROM date_series ds\n" +
+            "LEFT JOIN (\n" +
+            "  SELECT log_date, log_type FROM daily_data WHERE rn = 1\n" +
+            ") dd ON ds.report_date = dd.log_date\n" +
+            "ORDER BY ds.report_date ASC")
+    List<WatchLogDTO> selectFsCourseWatchLog30DayByExtId(@Param("extId") Long extId);
 }

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

@@ -2,6 +2,7 @@ package com.fs.course.mapper;
 
 import java.util.List;
 import com.fs.course.domain.FsUserCourse;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseAddStudyCourseParam;
 import com.fs.course.param.FsUserCourseListUParam;
 import com.fs.course.param.FsUserCourseParam;
@@ -247,4 +248,14 @@ public interface FsUserCourseMapper
 
     @Select("select course_id ,img_url   from fs_user_course where is_del = 0 and is_private = 1")
     List<FsUserCourse> selectFsUserCourseAllCourseByQw();
+
+    @Select("<script> " +
+            "select course_id,course_name,title,img_url from fs_user_course where is_del=0  and is_private = 1 " +
+            "and find_in_set(#{data.companyId},company_ids)" +
+            "        <if test=\"data.keyword != null and data.keyword !='' \">\n" +
+            "            AND course_name LIKE concat('%',#{data.keyword},'%')\n" +
+            "        </if>" +
+            "order by course_id asc" +
+            "</script> ")
+    List<FsCourseListBySidebarVO> getFsCourseListBySidebar(@Param("data") FsCourseListBySidebarParam param);
 }

+ 13 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoMapper.java

@@ -2,9 +2,11 @@ package com.fs.course.mapper;
 
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.param.CourseVideoUpdates;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseVideoListUParam;
 import com.fs.course.param.FsUserCourseVideoParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
 import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
@@ -180,4 +182,15 @@ public interface FsUserCourseVideoMapper
      * @return  list
      */
     List<OptionsVO> selectVideoListByMap(@Param("params") Map<String, Object> params);
+
+    @Select("<script> " +
+            "select v.video_id,v.course_id,v.title,v.video_url,v.thumbnail,v.duration,v.create_time from fs_user_course_video v " +
+            "left join fs_user_course c on c.course_id = v.course_id " +
+            "where c.is_private = 1 and v.is_del = 0 and v.course_id=#{data.courseId} " +
+            "        <if test=\"data.keyword != null and data.keyword !='' \">\n" +
+            "            AND v.title LIKE concat('%',#{data.keyword},'%')\n" +
+            "        </if>" +
+            "order by v.video_id asc " +
+            "</script>")
+    List<FsCourseVideoListBySidebarVO> getFsCourseVideoListBySidebar(@Param("data") FsCourseListBySidebarParam param);
 }

+ 28 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseLinkMiniParam.java

@@ -0,0 +1,28 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+@Data
+public class FsCourseLinkMiniParam {
+
+    private Long videoId;
+
+    private String qwUserId;
+
+    private String corpId;
+
+    private Long courseId;
+
+    private String title;//视频标题
+
+    /**
+    * 客户表的主键
+    */
+    private Long externalUserId;
+
+    /**
+    * 客户的小程序id
+    */
+    private Long fsUserId;
+
+}

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

@@ -0,0 +1,38 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@ApiModel
+public class FsCourseListBySidebarParam implements Serializable {
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "模糊搜索,通过视频名称来匹配")
+    private String keyword;
+
+    @ApiModelProperty(value = "公司id")
+    private Long companyId;
+
+    /**
+    * 课程id
+    */
+    private Long courseId;
+
+    private String corpId;
+
+    private String qwUserId;
+
+    /**
+    * 客户信息的长字符串id
+    */
+    private String externalUserId;
+}

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

@@ -4,10 +4,7 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.course.domain.FsUserCourse;
-import com.fs.course.param.FsUserCourseAddStudyCourseParam;
-import com.fs.course.param.FsUserCourseGetIntegralParam;
-import com.fs.course.param.FsUserCourseListUParam;
-import com.fs.course.param.FsUserCourseParam;
+import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
@@ -108,4 +105,6 @@ public interface IFsUserCourseService
     List<FsUserCourseListVO> getFsUserCourseList(FsUserCourseListParam param);
 
     void  processQwSopCourseMaterialTimer();
+
+    List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param);
 }

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

@@ -8,6 +8,7 @@ import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
 import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoQVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
@@ -157,4 +158,9 @@ public interface IFsUserCourseVideoService
      * @return  list
      */
     List<OptionsVO> selectVideoListByMap(Map<String, Object> params);
+
+    List<FsCourseVideoListBySidebarVO> getFsCourseVideoListBySidebar(FsCourseListBySidebarParam param);
+
+    R createMiniLink(FsCourseLinkMiniParam param);
+    R createCartLink(FsCourseLinkMiniParam param);
 }

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

@@ -18,10 +18,7 @@ import com.fs.course.domain.FsUserCourseStudy;
 import com.fs.course.domain.FsUserCourseStudyLog;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.*;
-import com.fs.course.param.FsUserCourseAddStudyCourseParam;
-import com.fs.course.param.FsUserCourseGetIntegralParam;
-import com.fs.course.param.FsUserCourseListUParam;
-import com.fs.course.param.FsUserCourseParam;
+import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
@@ -432,6 +429,11 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         }
     }
 
+    @Override
+    public List<FsCourseListBySidebarVO> getFsCourseListBySidebar(FsCourseListBySidebarParam param) {
+        return  fsUserCourseMapper.getFsCourseListBySidebar(param);
+    }
+
     /**
      * 上传课程图片到企业微信并缓存
      *

+ 194 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.course.service.impl;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
@@ -21,6 +22,7 @@ import com.fs.course.param.*;
 import com.fs.course.param.newfs.*;
 import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.service.IFsVideoResourceService;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
 import com.fs.course.vo.FsUserCourseVideoListUVO;
 import com.fs.course.vo.FsUserCourseVideoQVO;
 import com.fs.course.vo.FsUserCourseVideoVO;
@@ -33,10 +35,13 @@ import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.service.IFsUserService;
 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.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwCompanyService;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
@@ -44,6 +49,7 @@ import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
@@ -57,11 +63,14 @@ import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 
+import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
+
 /**
  * 课堂视频Service业务层处理
  *
@@ -73,6 +82,12 @@ import java.util.stream.Collectors;
 public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 {
     private static final Logger logger = LoggerFactory.getLogger(FsUserCourseVideoServiceImpl.class);
+
+
+    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=";
+
     @Autowired
     private FsUserCourseVideoMapper fsUserCourseVideoMapper;
     @Autowired
@@ -149,6 +164,12 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private FsUserCompanyUserMapper fsUserCompanyUserMapper;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+
     /**
      * 查询课堂视频
      *
@@ -1207,6 +1228,179 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         return fsUserCourseVideoMapper.selectVideoListByMap(params);
     }
 
+    @Override
+    public List<FsCourseVideoListBySidebarVO> getFsCourseVideoListBySidebar(FsCourseListBySidebarParam param) {
+        return fsUserCourseVideoMapper.getFsCourseVideoListBySidebar(param);
+    }
+
+    @Override
+    public R createMiniLink(FsCourseLinkMiniParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser==null||qwUser.getCompanyId()==null||qwUser.getCompanyUserId()==null){
+            return R.error("员工未绑定 销售公司 或 销售 请先绑定");
+        }
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+        if (qwCompany == null ) {
+            return  R.error().put("msg","企业不存在,请联系管理员");
+        }
+
+        //看课记录
+        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(),param.getFsUserId(),qwUser , param.getExternalUserId());
+
+        //生成小程序链接
+        String linkByMiniApp = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),2,null);
+
+        JSONObject news = new JSONObject(true);
+        news.put("miniprogramAppid", qwCompany.getMiniAppId());
+        news.put("miniprogramTitle", param.getTitle());
+        news.put("miniprogramPicUrl", "https://cos.his.cdwjyyh.com/fs/20250523/9c8af5735d784847818cada7fa776a7b.jpg");
+        news.put("miniprogramPage", linkByMiniApp);
+
+        return R.ok().put("data",news);
+    }
+
+    @Override
+    public R createCartLink(FsCourseLinkMiniParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser==null||qwUser.getCompanyId()==null||qwUser.getCompanyUserId()==null){
+            return R.error("员工未绑定 销售公司 或 销售 请先绑定");
+        }
+
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+
+        if (qwCompany == null ) {
+            return  R.error().put("msg","企业不存在,请联系管理员");
+        }
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+
+        if (config == null) {
+            return R.error().put("msg","课程默认配置为空,请联系管理员");
+        }
+
+        //域名
+        String domainName = companyUserMapper.selectDomainByUserId(qwUser.getCompanyUserId());
+        if (StringUtils.isEmpty(domainName)){
+            domainName = config.getRealLinkDomainName();
+        }
+
+        addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(),param.getFsUserId(),qwUser , param.getExternalUserId());
+
+        String linkByCartLink = createLinkByMiniApp(new Date(), param.getCourseId(), param.getVideoId(), qwUser, param.getExternalUserId(),1,domainName);
+
+
+        //生成卡片链接
+        JSONObject news = new JSONObject(true); // true 表示保持字段顺序
+        news.put("linkTitle", param.getTitle());
+        news.put("linkDescribe", param.getTitle());
+        news.put("linkImageUrl", "https://cos.his.cdwjyyh.com/fs/20250523/9c8af5735d784847818cada7fa776a7b.jpg");
+        news.put("linkUrl", linkByCartLink);
+
+        return R.ok().put("data",news);
+
+    }
+
+    //插入观看记录
+    private void addWatchLogIfNeeded(Long videoId, Long courseId,
+                                     Long fsUserId, QwUser qwUser,Long externalId) {
+
+        try {
+
+            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+            watchLog.setVideoId(videoId);
+            watchLog.setQwExternalContactId(externalId);
+            watchLog.setSendType(2);
+            watchLog.setQwUserId(String.valueOf(qwUser.getId()));
+            watchLog.setDuration(0L);
+            watchLog.setCourseId(courseId);
+            watchLog.setCompanyUserId(qwUser.getCompanyUserId());
+            watchLog.setCompanyId(qwUser.getCompanyId());
+            watchLog.setCreateTime(new Date());
+            watchLog.setUpdateTime(new Date());
+            watchLog.setLogType(3);
+
+            if (fsUserId == null) {
+                fsUserId=0L;
+            }
+
+            watchLog.setUserId(fsUserId);
+
+            //存看课记录
+            courseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+        }catch (Exception e){
+            logger.error("一键群发失败-插入观看记录失败:"+e.getMessage());
+        }
+
+
+    }
+
+    private String createLinkByMiniApp(Date sendTime, Long courseId, Long videoId,
+                                       QwUser qwUser, Long externalId,int type,String domainName) {
+
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(qwUser.getCompanyId());
+        link.setQwUserId(String.valueOf(qwUser.getId()));
+        link.setCompanyUserId(qwUser.getCompanyUserId());
+        link.setVideoId(videoId);
+        link.setCorpId(qwUser.getCorpId());
+        link.setCourseId(courseId);
+        link.setQwExternalId(externalId);
+
+        if (type == 1) {
+            link.setLinkType(0);
+        }else {
+            link.setLinkType(3);
+        }
+
+        String randomString = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(randomString)){
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        }else {
+            link.setLink(randomString);
+        }
+
+        link.setCreateTime(sendTime);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link,courseMap);
+
+        String courseJson = JSON.toJSONString(courseMap);
+
+        String realLinkFull = null;
+
+        if (type == 1) {
+            realLinkFull = REAL_LINK_PREFIX + courseJson;
+        }else {
+            realLinkFull = miniappRealLink + courseJson;
+        }
+
+        link.setRealLink(realLinkFull);
+
+
+        // 使用 Java 8 时间 API 计算过期时间
+        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        LocalDateTime expireDateTime = sendDateTime.plusDays(0);
+        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+        link.setUpdateTime(updateTime);
+
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+
+        if (type==1){
+            return domainName + SHORT_LINK_PREFIX + link.getLink();
+        }else {
+            return link.getRealLink();
+        }
+
+    }
     //会员-更新心跳时间
     public void updateHeartbeatWx(FsUserCourseVideoUParam param) {
         String redisKey = "h5wxuser:watch:heartbeat:" + param.getUserId() + ":" + param.getVideoId() + ":" + param.getCompanyUserId();

+ 26 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseListBySidebarVO.java

@@ -0,0 +1,26 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsCourseListBySidebarVO {
+    /**
+    * 课程id
+    */
+    private Long courseId;
+    /**
+    * 课程名称
+    */
+    private String courseName;
+
+    /**
+    * 标题
+    */
+    private String title;
+
+    /**
+    * 课程封面
+    */
+    private String imgUrl;
+
+}

+ 37 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseVideoListBySidebarVO.java

@@ -0,0 +1,37 @@
+package com.fs.course.vo;
+
+import lombok.Data;
+
+@Data
+public class FsCourseVideoListBySidebarVO {
+
+    private Long videoId;
+
+    private Long courseId;
+
+    /**
+    * 视频标题
+    */
+    private String title;
+
+    /**
+    * 视频地址
+    */
+    private String videoUrl;
+
+    /**
+    * 时长
+    */
+    private Integer duration;
+
+    /**
+    * 视频缩略图
+    */
+    private String thumbnail;
+
+    /**
+    * 视频创建时间
+    */
+    private String createTime;
+
+}

+ 26 - 3
fs-service/src/main/java/com/fs/qw/domain/QwWorkTask.java

@@ -1,10 +1,14 @@
 package com.fs.qw.domain;
 
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
-import com.fs.common.core.domain.BaseEntity;
+import io.swagger.models.auth.In;
 import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
 import lombok.EqualsAndHashCode;
 
+import java.util.List;
+
 /**
  * 企微任务看板对象 qw_work_task
  *
@@ -22,6 +26,11 @@ public class QwWorkTask extends BaseEntity{
     @Excel(name = "外部联系人id")
     private Long extId;
 
+    @Excel(name = "外部联系人长字符串")
+    private String externalContactId;
+
+    private String description;
+
     /** 企微用户id */
     @Excel(name = "企微用户id")
     private Long qwUserId;
@@ -34,6 +43,11 @@ public class QwWorkTask extends BaseEntity{
     @Excel(name = "状态 0 待处理 1 已处理 3 过期")
     private Integer status;
 
+    /**
+     * 最晚看课时间
+     */
+    @Excel(name = "最晚看课时间")
+    private String lastWatchDate;
     /** 分值 */
     @Excel(name = "分值")
     private Integer score;
@@ -46,15 +60,24 @@ public class QwWorkTask extends BaseEntity{
     @Excel(name = "公司id")
     private Long companyId;
 
+    private Integer trackType;
     /** 用户id */
     @Excel(name = "用户id")
     private Long companyUserId;
 
     private String title;
-
     /** 通话时长 */
     @Excel(name = "通话时长")
     private Long duration;
 
-    private Integer trackType;
+    /** 名称 */
+    @Excel(name = "名称")
+    private String name;
+
+    /** 头像 */
+    @Excel(name = "头像")
+    private String avatar;
+
+    List<Integer> logs;
+
 }

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

@@ -61,6 +61,7 @@ public interface QwExternalContactInfoMapper
      * @return 结果
      */
     public int deleteQwExternalContactInfoByIds(Long[] ids);
+
     @Select("select * from qw_external_contact_info where external_contact_id =#{id}")
     QwExternalContactInfo selectQwExternalContactInfoByExternalContactId(Long id);
 
@@ -69,4 +70,6 @@ public interface QwExternalContactInfoMapper
             "ON DUPLICATE KEY UPDATE " +
             "talk = VALUES(talk);")
     int updateQwExternalContactInfoByExtId(Long id);
+
+    void updateQwExternalContactInfoByExternalContactId(QwExternalContactInfo qwExternalContactInfo);
 }

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

@@ -2,17 +2,11 @@ package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.param.ConversionStatisticsParam;
-import com.fs.qw.param.QwCountCustomerParam;
-import com.fs.qw.param.QwExternalContactParam;
-import com.fs.qw.param.QwExternalContactVOTime;
+import com.fs.qw.param.*;
 import com.fs.qw.result.QwExternalContactByQwResult;
 import com.fs.qw.result.QwExternalContactLogVo;
 import com.fs.qw.result.QwExternalContactVo;
-import com.fs.qw.vo.GroupUserExternalVo;
-import com.fs.qw.vo.QwExternalContactFsCrmVO;
-import com.fs.qw.vo.QwExternalContactFsUserVO;
-import com.fs.qw.vo.QwExternalContactVO;
+import com.fs.qw.vo.*;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -301,7 +295,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Update("UPDATE qw_external_contact SET company_id = #{companyId},company_user_id=#{companyUserId} WHERE corp_id = #{corpId} and user_id =#{qwUserId}")
     void updateBindUserByQwUser(@Param("corpId")String corpId, @Param("qwUserId")String qwUserId, @Param("companyId")Long companyId, @Param("companyUserId")Long companyUserId);
 
-    @Select("SELECT * FROM  qw_external_contact " +
+    @Select("SELECT id,external_user_id,name,avatar,remark,description FROM  qw_external_contact " +
             " WHERE user_id = #{map.userId}   " +
             "AND external_user_id = #{map.externalUserId} " +
             "AND corp_id =#{map.corpId} " +
@@ -370,4 +364,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Update("update qw_external_contact set comment_status = #{commentStatus} where fs_user_id = #{fsUserId}")
     int updateQwExternalContactByFsUserId(@Param("commentStatus") Integer commentStatus, @Param("fsUserId")Long fsUserId);
 
+    ExternalContactDetailsVO getCountAnswer (@Param("param") ExternalContactDetailsParam param);
+
+    ExternalContactDetailsVO getCountRedPacket (@Param("param") ExternalContactDetailsParam param);
+
+    ExternalContactDetailsVO getCountCourseWatch(@Param("param") ExternalContactDetailsParam param);
+
 }

+ 17 - 8
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -1,17 +1,13 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.dto.QwUserByToolDTO;
 import com.fs.qw.dto.QwUserKeyDTO;
-import com.fs.qw.param.QwFsServerBindParam;
-import com.fs.qw.param.QwUserListParam;
-import com.fs.qw.param.QwUserParam;
-import com.fs.qw.param.QwWatchLogStatisticsListParam;
-import com.fs.qw.vo.QwHookAuthVO;
-import com.fs.qw.vo.QwOptionsVO;
-import com.fs.qw.vo.QwUserVO;
-import com.fs.qw.vo.QwWatchLogStatisticsListVO;
+import com.fs.qw.param.*;
+import com.fs.qw.vo.*;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
@@ -367,4 +363,17 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "</foreach> " +
             "</script>")
     public List<QwUserVO> getQwUserByIdsNoCropId(@Param("qwUserIds") List<String> qwUserIds);
+
+    @Select("<script> " +
+            "select qu.qw_user_id,qu.qw_user_name,qe.create_time from qw_external_contact qe " +
+            "left join  qw_user qu " +
+            "on qe.user_id=qu.qw_user_id and qe.corp_id=qu.corp_id" +
+            "where qe.external_user_id=#{data.externalUserId} " +
+            "   and qe.corp_id=#{data.corpId} " +
+            "   and qe.status=0 " +
+            "   and qe.user_id != #{data.qwUserId} " +
+            "</script>")
+    List<QwExternalListByHeavyVO> getQwExternalListByHeavy(@Param("data") FsCourseListBySidebarParam param);
+
+    List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param);
 }

+ 17 - 0
fs-service/src/main/java/com/fs/qw/param/ExternalContactDetailsParam.java

@@ -0,0 +1,17 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class ExternalContactDetailsParam {
+
+    @ApiModelProperty(value = "登录用户id,不用传")
+    private Long userId;
+
+    @ApiModelProperty(value = "外部联系人id")
+    private Long contactId;
+
+    @ApiModelProperty(value = "时间tab,不传表示查询全部,分别是:今天、昨天、前天、近七天")
+    private String dateTag;
+}

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

@@ -11,6 +11,7 @@ public class QwExternalContactVOTime {
     private Long id;
 
     private String tagIds;
+
     private List<String> tagIdsName;
 
     private String remark;
@@ -22,4 +23,6 @@ public class QwExternalContactVOTime {
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String createTime;
+
+    private String name;
 }

+ 33 - 0
fs-service/src/main/java/com/fs/qw/param/SelectQwWorkTaskListParam.java

@@ -0,0 +1,33 @@
+package com.fs.qw.param;
+
+import com.fs.watch.param.BaseQueryParam;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class SelectQwWorkTaskListParam extends BaseQueryParam {
+    /**
+     * 企微用户ID
+     */
+    @NotBlank(message = "ID不能为空")
+    private String qwUserId;
+    /**
+     * 时间 (yyyy-MM-dd)
+     */
+    @NotBlank(message = "时间不能为空")
+    private String date;
+
+
+    /**
+     * 企微用户corpId
+     */
+    @NotBlank(message = "corpId不能为空")
+    private String corpId;
+
+    /**
+    * 企业微信员工Qw_User的主键
+    */
+    private Long userId;
+
+}

+ 3 - 1
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactInfoService.java

@@ -60,11 +60,13 @@ public interface IQwExternalContactInfoService
      */
     public int deleteQwExternalContactInfoById(Long id);
 
-    Object selectQwExternalContactInfoByExternalContactId(Long id);
+    QwExternalContactInfo selectQwExternalContactInfoByExternalContactId(Long id);
 
     int updateQwExternalContactInfoByIds(Long[] ids);
 
     void updateQwExternalContactInfoBytalk(String talkType, Long externalId);
 
     int updateQwExternalContactInfoByQwUserId(Long id);
+
+    void updateQwExternalContactInfoByExternalContactId(QwExternalContactInfo qwExternalContactInfo);
 }

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

@@ -3,11 +3,15 @@ package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.param.*;
 import com.fs.qw.result.QwExternalContactLogVo;
 import com.fs.qw.result.QwExternalContactVo;
+import com.fs.qw.vo.ExternalContactDetailsVO;
 import com.fs.qw.vo.QwExternalContactVO;
+import com.fs.qw.vo.QwExternalListByHeavyVO;
 import com.fs.qw.vo.QwSopRuleTimeVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.codehaus.jettison.json.JSONException;
@@ -103,6 +107,9 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     R delUserTag(QwExternalContactAddTagParam param);
 
+    QwUser getQwUserByRedis(String corpId, String userID);
+
+
     void insertQwExternalContactByExternalUserId(String externalUserID, String userID, Long companyId, String corpId, String state, String welcomeCode) throws ParseException;
 
     void insertQwExternalContactByExternalUserId2(String externalUserID, String userID, Long companyId, String corpId, String state, String welcomeCode) throws ParseException;
@@ -164,7 +171,17 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     void synchronizeQwExternalContactTask();
 
+    /**
+     * 获取会员答题和看课情况
+     * @param param 参数
+     * @return
+     */
+    ExternalContactDetailsVO getUserDetails(ExternalContactDetailsParam param);
+
     List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> externalIdList);
 
     Integer selectQwIsRepeat(Long id);
+
+
+    List<QwExternalListByHeavyVO> getQwExternalListByHeavy(FsCourseListBySidebarParam param);
 }

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

@@ -2,6 +2,7 @@ package com.fs.qw.service;
 
 import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.dto.QwUserKeyDTO;
 import com.fs.qw.param.*;
 import com.fs.qw.vo.QwHookAuthVO;
@@ -161,4 +162,6 @@ public interface IQwUserService
     R getQwIpad(QwLoginHookParam loginParam);
 
     R delQwIpad(QwLoginHookParam loginParam);
+
+    List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactInfoServiceImpl.java

@@ -136,4 +136,9 @@ public class QwExternalContactInfoServiceImpl implements IQwExternalContactInfoS
 
         return 1;
     }
+
+    @Override
+    public void updateQwExternalContactInfoByExternalContactId(QwExternalContactInfo qwExternalContactInfo) {
+        qwExternalContactInfoMapper.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+    }
 }

+ 29 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.course.mapper.FsCourseSopLogsMapper;
 import com.fs.course.mapper.FsCourseSopMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.mapper.CrmCustomerMapper;
@@ -59,6 +60,7 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
@@ -292,6 +294,28 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     }
 
+    @Override
+    public ExternalContactDetailsVO getUserDetails(ExternalContactDetailsParam param) {
+        ExternalContactDetailsVO countCourseWatch = qwExternalContactMapper.getCountCourseWatch(param);
+        ExternalContactDetailsVO countAnswer = qwExternalContactMapper.getCountAnswer(param);
+        ExternalContactDetailsVO countRedPacket = qwExternalContactMapper.getCountRedPacket(param);
+        ExternalContactDetailsVO vo = new ExternalContactDetailsVO();
+        if(countCourseWatch != null){
+            BeanUtils.copyProperties(countCourseWatch, vo);
+        }
+        if (countAnswer != null) {
+            vo.setAnswerTime(countAnswer.getAnswerTime());
+            vo.setAnswerRightTime(countAnswer.getAnswerRightTime());
+        }
+        if (countRedPacket != null) {
+            vo.setAnswerRedPacketTime(countRedPacket.getAnswerRedPacketTime());
+            vo.setAnswerRedPacketAmount(countRedPacket.getAnswerRedPacketAmount());
+        } else {
+            vo.setAnswerRedPacketAmount(BigDecimal.ZERO);
+        }
+        return vo;
+    }
+
     @Override
     public List<QwExternalContactVOTime> selectQwExternalContactListVOByUserIds(List<String> ids) {
         List<QwExternalContactVOTime> list = qwExternalContactMapper.selectQwExternalContactListVOByUserIds(ids);
@@ -313,6 +337,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return qwExternalContactMapper.selectQwIsRepeat(id);
     }
 
+    @Override
+    public List<QwExternalListByHeavyVO> getQwExternalListByHeavy(FsCourseListBySidebarParam param) {
+        return qwUserMapper.getQwExternalListByHeavy(param);
+    }
+
     /**
      * 处理一个分组(组内串行)
      */

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

@@ -12,10 +12,7 @@ import com.fs.config.ai.AiHostProper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.utils.ConfigUtil;
-import com.fs.qw.domain.QwCompany;
-import com.fs.qw.domain.QwIpadServerLog;
-import com.fs.qw.domain.QwIpadServerUser;
-import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.*;
 import com.fs.qw.dto.QwUserByToolDTO;
 import com.fs.qw.dto.QwUserKeyDTO;
 import com.fs.qw.mapper.QwUserMapper;
@@ -52,6 +49,8 @@ import java.util.*;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * 企微用户Service业务层处理
@@ -94,6 +93,9 @@ public class QwUserServiceImpl implements IQwUserService
     IQwIpadServerLogService qwIpadServerLogService;
     @Autowired
     IQwIpadServerUserService qwIpadServerUserService;
+    @Autowired
+    IQwExternalContactService externalContactService;
+
     @Override
     public R getQwIpad(QwLoginHookParam loginParam) {
         QwUser qwUser = qwUserMapper.selectQwUserById(loginParam.getQwUserId());
@@ -178,6 +180,54 @@ public class QwUserServiceImpl implements IQwUserService
         return R.ok();
     }
 
+    @Override
+    public List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param) {
+
+        List<QwWorkTask> qwWorkTasks = qwUserMapper.selectQwWorkTaskList(param);
+
+        // 提取非空的 extId 列表
+        List<Long> extIds = qwWorkTasks.stream()
+                .map(QwWorkTask::getExtId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!extIds.isEmpty()){
+
+            // 查询外部联系人数据,并转为 Map<extId, QwExternalContactVOTime>
+            Map<Long, QwExternalContactVOTime> contactMap =
+                    externalContactService.selectQwExternalContactListVOByIds(extIds)
+                            .stream()
+                            .collect(Collectors.toMap(
+                                    QwExternalContactVOTime::getId,
+                                    Function.identity()
+                            ));
+
+            // 遍历 qwWorkTasks,填充 name、avatar、externalContactId
+            qwWorkTasks.forEach(task -> {
+                Optional.ofNullable(task.getExtId())
+                        .map(contactMap::get)
+                        .ifPresent(contact -> {
+                            task.setName(contact.getName());
+                            task.setAvatar(contact.getAvatar());
+                            task.setExternalContactId(contact.getExternalUserId());
+                        });
+            });
+        }
+
+
+//        for (QwWorkTask qwWorkTask : qwWorkTasks) {
+//            if(qwWorkTask.getExtId() != null) {
+//                QwExternalContact qwExternalContact = externalContactService.selectQwExternalContactById(qwWorkTask.getExtId());
+//                if(qwExternalContact != null) {
+//                    qwWorkTask.setName(qwExternalContact.getName());
+//                    qwWorkTask.setAvatar(qwExternalContact.getAvatar());
+//                    qwWorkTask.setExternalContactId(qwExternalContact.getExternalUserId());
+//                }
+//            }
+//        }
+        return qwWorkTasks;
+    }
+
 
     /**
      * 查询企微用户

+ 34 - 0
fs-service/src/main/java/com/fs/qw/vo/ExternalContactDetailsVO.java

@@ -0,0 +1,34 @@
+package com.fs.qw.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 外部联系人/会员详情 输出参数
+ */
+@Data
+@ApiModel
+public class ExternalContactDetailsVO {
+
+    @ApiModelProperty(value = "答题次数")
+    private int answerTime;
+
+    @ApiModelProperty(value = "答题正确次数")
+    private int answerRightTime;
+
+    @ApiModelProperty(value = "答题红包数")
+    private int answerRedPacketTime;
+
+    @ApiModelProperty(value = "答题红包金额")
+    private BigDecimal answerRedPacketAmount;
+
+    @ApiModelProperty(value = "完播次数")
+    private int courseCompleteTime;
+
+    @ApiModelProperty(value = "观看次数")
+    private int courseWatchTime;
+
+}

+ 24 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalListByHeavyVO.java

@@ -0,0 +1,24 @@
+package com.fs.qw.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+@Data
+public class QwExternalListByHeavyVO {
+
+    /**
+    * 员工账号
+    */
+    private String qwUserId;
+
+    /**
+    * 员工姓名
+    */
+    private String qwUserName;
+
+    /**
+    * 添加时间
+    */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String createTime;
+}

+ 10 - 0
fs-service/src/main/java/com/fs/statis/service/IStatisticsService.java

@@ -1,6 +1,8 @@
 package com.fs.statis.service;
 
 import com.fs.statis.dto.*;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
 
 import java.util.List;
 
@@ -109,4 +111,12 @@ public interface IStatisticsService {
      * 本月收款数
      */
     void thisMonthRecvCount();
+
+    /**
+     * 查询看课统计
+     *
+     * @param param
+     * @return
+     */
+    public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -5,7 +5,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.TimeUtils;
 import com.fs.company.cache.ICompanyCacheService;
+import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.his.service.IFsStoreOrderService;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.service.IFsStoreProductService;
@@ -15,6 +17,8 @@ import com.fs.statis.dto.*;
 import com.fs.statis.mapper.ConsumptionBalanceMapper;
 import com.fs.statis.service.IStatisticsService;
 import com.fs.statis.service.utils.TrendDataFiller;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
@@ -65,6 +69,9 @@ public class StatisticsServiceImpl implements IStatisticsService {
     @Autowired
     private IFsStoreProductService productService;
 
+    @Autowired
+    private com.fs.course.mapper.FsCourseWatchLogMapper FsCourseWatchLogMapper;
+
     @Autowired
     private ISysConfigService configService;
 
@@ -965,4 +972,20 @@ public class StatisticsServiceImpl implements IStatisticsService {
         R result = R.ok().put("dates", dates).put("orderCount", orderCount).put("payMoney", payMoney);
         redisCache.setCacheObject(THIS_MONTH_RECV_COUNT,result);
     }
+
+    @Override
+    public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param) {
+        List<WatchLogDTO> data = null;
+        // 七天
+        if(org.apache.commons.lang3.ObjectUtils.equals(param.getType(),0)) {
+            data = FsCourseWatchLogMapper.selectFsCourseWatchLog7Day(param.getQwExternalContactId());
+        } else if(org.apache.commons.lang3.ObjectUtils.equals(param.getType(),1)){
+            data = FsCourseWatchLogMapper.selectFsCourseWatchLog30DayByExtId(param.getQwExternalContactId());
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = new WatchCourseStatisticsDTO();
+        watchCourseStatisticsDTO.setData(data);
+
+        return watchCourseStatisticsDTO;
+    }
 }

+ 25 - 0
fs-service/src/main/java/com/fs/statistics/dto/WatchCourseStatisticsDTO.java

@@ -0,0 +1,25 @@
+package com.fs.statistics.dto;
+
+
+import com.fs.course.dto.WatchLogDTO;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 看课统计
+ */
+@Data
+public class WatchCourseStatisticsDTO implements Serializable {
+//    /**
+//     * 观看次数
+//     */
+//    private Long watchCount;
+//    /**
+//     * 完播次数
+//     */
+//    private Long finishCount;
+
+    private List<WatchLogDTO> data;
+}

+ 9 - 0
fs-service/src/main/java/com/fs/statistics/mapper/StatisticsServiceMapper.java

@@ -0,0 +1,9 @@
+package com.fs.statistics.mapper;
+
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+
+public interface StatisticsServiceMapper {
+    public Long queryWatchUserCount(WatchCourseStatisticsParam param);
+
+    public Long queryCompletedCount(WatchCourseStatisticsParam param);
+}

+ 30 - 0
fs-service/src/main/java/com/fs/statistics/param/WatchCourseStatisticsParam.java

@@ -0,0 +1,30 @@
+ package com.fs.statistics.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 看课统计参数
+ */
+@Data
+public class WatchCourseStatisticsParam implements Serializable {
+//    /**
+//     * 开始时间
+//     */
+//    private String startTime;
+//    /**
+//     * 结束时间
+//     */
+//    private String endTime;
+
+    /**
+     * 0 七天
+     * 1 30天
+     */
+    private Integer type;
+    /**
+     * 企微外部联系人id
+     */
+    private Long qwExternalContactId;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/statistics/service/IStatisticsService.java

@@ -0,0 +1,18 @@
+package com.fs.statistics.service;
+
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+
+
+public interface IStatisticsService {
+    /**
+     * 查询看课统计
+     *
+     * @param param
+     * @return
+     */
+    public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param);
+
+
+
+}

+ 40 - 0
fs-service/src/main/java/com/fs/statistics/service/impl/StatisticsServiceImpl.java

@@ -0,0 +1,40 @@
+package com.fs.statistics.service.impl;
+
+import com.fs.course.dto.WatchLogDTO;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.mapper.StatisticsServiceMapper;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+import com.fs.statistics.service.IStatisticsService;
+import org.apache.commons.lang3.ObjectUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class StatisticsServiceImpl implements IStatisticsService {
+    @Autowired
+    private StatisticsServiceMapper statisticsServiceMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper FsCourseWatchLogMapper;
+
+
+    @Override
+    public WatchCourseStatisticsDTO queryWatchCourse(WatchCourseStatisticsParam param) {
+
+        List<WatchLogDTO> data = null;
+        // 七天
+        if(ObjectUtils.equals(param.getType(),0)) {
+            data = FsCourseWatchLogMapper.selectFsCourseWatchLog7Day(param.getQwExternalContactId());
+        } else if(ObjectUtils.equals(param.getType(),1)){
+            data = FsCourseWatchLogMapper.selectFsCourseWatchLog30DayByExtId(param.getQwExternalContactId());
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = new WatchCourseStatisticsDTO();
+        watchCourseStatisticsDTO.setData(data);
+
+        return watchCourseStatisticsDTO;
+    }
+}

+ 43 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactInfoMapper.xml

@@ -211,6 +211,49 @@
         where id = #{id}
     </update>
 
+    <update id="updateQwExternalContactInfoByExternalContactId">
+        update qw_external_contact_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="externalContactId != null">external_contact_id = #{externalContactId},</if>
+            <if test="name != null">name = #{name},</if>
+            <if test="sex != null">sex = #{sex},</if>
+            <if test="age != null">age = #{age},</if>
+            <if test="address != null">address = #{address},</if>
+            <if test="habits != null">habits = #{habits},</if>
+            <if test="illnessTime != null">illness_time = #{illnessTime},</if>
+            <if test="body != null">body = #{body},</if>
+            <if test="study != null">study = #{study},</if>
+            <if test="courseStatus != null">course_status = #{courseStatus},</if>
+            <if test="family != null">family = #{family},</if>
+            <if test="familyDisease != null">family_disease = #{familyDisease},</if>
+            <if test="talk != null">talk = #{talk},</if>
+            <if test="userType != null">user_type = #{userType},</if>
+            <if test="isSelf != null">is_self = #{isSelf},</if>
+            <if test="intensify != null">intensify = #{intensify},</if>
+            <if test="isCold != null">is_cold = #{isCold},</if>
+            <if test="coldBody != null">cold_body = #{coldBody},</if>
+            <if test="sweat != null">sweat = #{sweat},</if>
+            <if test="other != null">other = #{other},</if>
+            <if test="toilet != null">toilet = #{toilet},</if>
+            <if test="eat != null">eat = #{eat},</if>
+            <if test="menses != null">menses = #{menses},</if>
+            <if test="medicine != null">medicine = #{medicine},</if>
+            <if test="constitution != null">constitution = #{constitution},</if>
+            <if test="recommendMedicine != null">recommend_medicine = #{recommendMedicine},</if>
+            <if test="consultProduct != null">consult_product = #{consultProduct},</if>
+            <if test="isBuy != null">is_buy = #{isBuy},</if>
+            <if test="buyProduct != null">buy_product = #{buyProduct},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="disease != null">disease = #{disease},</if>
+            <if test="isLine != null">is_line = #{isLine},</if>
+            <if test="course != null">course = #{course},</if>
+            <if test="productTalk != null">product_talk = #{productTalk},</if>
+            <if test="diseaseTalk != null">disease_talk = #{diseaseTalk},</if>
+        </trim>
+        where external_contact_id = #{externalContactId}
+    </update>
+
     <delete id="deleteQwExternalContactInfoById" parameterType="Long">
         delete from qw_external_contact_info where id = #{id}
     </delete>

+ 106 - 0
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -322,4 +322,110 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
         GROUP BY user_id,external_user_id
     </select>
+
+    <select id="getCountAnswer" resultType="com.fs.qw.vo.ExternalContactDetailsVO">
+        SELECT
+        count( DISTINCT fs_course_answer_logs.log_id ) AS answerTime,
+        count( DISTINCT CASE WHEN fs_course_answer_logs.is_right = 1 THEN fs_course_answer_logs.log_id END ) AS answerRightTime,
+        is_right,
+        qw_external_contact.id,
+        qw_external_contact.external_user_id,
+        qw_external_contact.qw_user_id
+        FROM
+        fs_course_answer_logs
+        LEFT JOIN qw_user ON qw_user.id = fs_course_answer_logs.qw_user_id
+        LEFT JOIN qw_external_contact ON qw_external_contact.user_id = qw_user.qw_user_id
+        <where>
+            <if test="param.dateTag != null and param.dateTag !='' ">
+                <choose>
+                    <when test = "param.dateTag == '今天'">
+                        and to_days(fs_course_answer_logs.create_time) = to_days(now())
+                    </when>
+                    <when test = "param.dateTag == ' 昨天'">
+                        and to_days(now()) - to_days(fs_course_answer_logs.create_time) &lt;= 1
+                    </when>
+                    <when test = "param.dateTag == '前天'">
+                        and to_days(now()) - to_days(fs_course_answer_logs.create_time) &lt;= 2
+                    </when>
+                    <when test = "param.dateTag == '近七天'">
+                        and DATE_SUB(CURDATE(), INTERVAL 7 DAY) &lt;= date(fs_course_answer_logs.create_time)
+                    </when>
+                </choose>
+            </if>
+        </where>
+        GROUP BY
+        qw_external_contact.id
+        HAVING
+        qw_external_contact.id = #{param.contactId}
+    </select>
+
+    <select id="getCountRedPacket" resultType="com.fs.qw.vo.ExternalContactDetailsVO">
+        SELECT
+        count( DISTINCT log_id ) AS answerRedPacketTime,
+        ifnull(sum( amount ), 0) AS answerRedPacketAmount,
+        amount,
+        qw_external_contact.id,
+        qw_external_contact.external_user_id,
+        qw_external_contact.qw_user_id
+        FROM
+        fs_course_red_packet_log
+        LEFT JOIN qw_user ON qw_user.id = fs_course_red_packet_log.qw_user_id
+        LEFT JOIN qw_external_contact ON qw_external_contact.user_id = qw_user.qw_user_id
+        <where>
+            <if test="param.dateTag != null and param.dateTag !='' ">
+                <choose>
+                    <when test = "param.dateTag == '今天'">
+                        and to_days(fs_course_red_packet_log.create_time) = to_days(now())
+                    </when>
+                    <when test = "param.dateTag == ' 昨天'">
+                        and to_days(now()) - to_days(fs_course_red_packet_log.create_time) &lt;= 1
+                    </when>
+                    <when test = "param.dateTag == '前天'">
+                        and to_days(now()) - to_days(fs_course_red_packet_log.create_time) &lt;= 2
+                    </when>
+                    <when test = "param.dateTag == '近七天'">
+                        and DATE_SUB(CURDATE(), INTERVAL 7 DAY) &lt;= date(fs_course_red_packet_log.create_time)
+                    </when>
+                </choose>
+            </if>
+        </where>
+        GROUP BY
+        qw_external_contact.id
+        HAVING
+        qw_external_contact.id = #{param.contactId}
+    </select>
+
+    <select id="getCountCourseWatch" resultType="com.fs.qw.vo.ExternalContactDetailsVO">
+        SELECT
+        count( CASE WHEN fwl.log_type != 3 THEN fwl.log_id END ) AS courseWatchTime,
+        count( CASE WHEN fwl.log_type = 2 THEN fwl.log_id END ) AS courseCompleteTime,
+        GROUP_CONCAT( fwl.video_id ),
+        fwl.qw_external_contact_id,
+        fwl.qw_user_id
+        FROM
+        fs_course_watch_log fwl
+        <where>
+            <if test="param.dateTag != null and param.dateTag !='' ">
+                <choose>
+                    <when test = "param.dateTag == '今天'">
+                        and to_days(fwl.update_time) = to_days(now())
+                    </when>
+                    <when test = "param.dateTag == ' 昨天'">
+                        and to_days(now()) - to_days(fwl.update_time) &lt;= 1
+                    </when>
+                    <when test = "param.dateTag == '前天'">
+                        and to_days(now()) - to_days(fwl.update_time) &lt;= 2
+                    </when>
+                    <when test = "param.dateTag == '近七天'">
+                        and DATE_SUB(CURDATE(), INTERVAL 7 DAY) &lt;= date(fwl.update_time)
+                    </when>
+                </choose>
+            </if>
+        </where>
+        GROUP BY
+        fwl.qw_external_contact_id
+        having fwl.qw_external_contact_id = #{param.contactId}
+
+    </select>
+
 </mapper>

+ 12 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -214,4 +214,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{userId}
         </foreach>
     </select>
+
+
+    <select id="selectQwWorkTaskList" resultType="com.fs.qw.domain.QwWorkTask">
+        select
+            qwt.id,qwt.ext_id, qwt.qw_user_id, qwt.status, qwt.track_type, qwt.type, qwt.title, qwt.remark, qwt.score, qwt.sop_id,
+            qwt.company_id, qwt.company_user_id, qwt.duration, qwt.create_time, qwt.update_time
+        from qw_work_task qwt
+        where qwt.qw_user_id = #{userId}
+          and date(qwt.create_time) = #{date}
+          and qwt.status = 0
+    </select>
+
 </mapper>