Browse Source

会员列表看课统计修改

wangxy 2 days ago
parent
commit
f350587019

+ 85 - 1
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -5,6 +5,7 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.his.dto.UserConditionDTO;
 import com.fs.his.vo.FsCourseReportVO;
 import com.fs.his.vo.FsUserReportVO;
 import com.fs.his.vo.WatchLogReportVO;
@@ -626,7 +627,40 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      */
     List<FsCourseReportVO>  selectFsCourseReportVO(FsCourseWatchLogStatisticsListParam param);
 
-    List<FsUserReportVO>  selectFsUserReportVO(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 查询会员基础数据
+     * @return
+     */
+    List<FsUserReportVO> selectUserBaseInfo(FsCourseWatchLogStatisticsListParam fsCourseWatchLogStatisticsListParam);
+
+    /**
+     * 对应会员的看课数据
+     * @param
+     * @return
+     */
+    List<FsUserReportVO> selectWatchStatsByUserIds(@Param("userConditions") List<UserConditionDTO> userConditions, @Param("watchParam") FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 对应会员的积分数据
+     * @param userIds
+     * @return
+     */
+    List<FsUserReportVO> selectIntegralStatsByUserIds(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 对应会员的红包数据
+     * @param
+     * @return
+     */
+    List<FsUserReportVO> selectRedPacketStatsByUserIds(@Param("userConditions") List<UserConditionDTO> userConditions, @Param("watchParam") FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 对应会员的订单数据
+     * @param userIds
+     * @return
+     */
+    List<FsUserReportVO> selectOrderStatsByUserIds(@Param("userIds") List<Long> userIds);
 
 
     List<WatchLogReportVO>  selectWatchLogReportVO(FsCourseWatchLogStatisticsListParam param);
@@ -645,4 +679,54 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "</script>"
     })
     List<Long> getExContactIdsIdsByWatchLogIds(@Param("watchLogIds")List<Long> watchLogIds);
+
+    /**
+     * 销售端看课报表 用户维度基础数据
+     * @param param
+     * @return
+     */
+    List<WatchLogReportVO> selectUserBaseData(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售端看课报表 销售维度基础数据
+     * @param param
+     * @return
+     */
+    List<WatchLogReportVO>  selectSalesBaseData(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售端看课报表 公司维度基础数据
+     * @param param
+     * @return
+     */
+    List<WatchLogReportVO>  selectCompanyBaseData(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售端看课报表 看课统计明细数据
+     * @param userConditions
+     * @param param
+     * @return
+     */
+    List<WatchLogReportVO>  selectUserWatchDetails(@Param("userConditions") List<UserConditionDTO> userConditions, @Param("watchParam") FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 销售端看课报表 红包
+     * @param
+     * @return
+     */
+    List<WatchLogReportVO> selectRedPacketStats(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 订单
+     * @param userIds
+     * @return
+     */
+    List<WatchLogReportVO> selectOrderStats(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 答题
+     * @param userIds
+     * @return
+     */
+    List<WatchLogReportVO> selectAnswerStats(@Param("userIds") List<Long> userIds);
 }

+ 453 - 210
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -27,6 +27,7 @@ import com.fs.course.service.cache.IFsUserCourseVideoCacheService;
 import com.fs.course.vo.*;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
+import com.fs.his.dto.UserConditionDTO;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.PhoneUtil;
@@ -63,6 +64,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -70,6 +73,7 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -79,8 +83,7 @@ import java.util.stream.Collectors;
  * @date 2024-09-18
  */
 @Service
-public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMapper, FsCourseWatchLog> implements IFsCourseWatchLogService
-{
+public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMapper, FsCourseWatchLog> implements IFsCourseWatchLogService {
     private static final Logger log = LoggerFactory.getLogger(FsCourseWatchLogServiceImpl.class);
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
@@ -147,8 +150,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 短链课程看课记录
      */
     @Override
-    public FsCourseWatchLog selectFsCourseWatchLogByLogId(Long logId)
-    {
+    public FsCourseWatchLog selectFsCourseWatchLogByLogId(Long logId) {
         return fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(logId);
     }
 
@@ -159,8 +161,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 短链课程看课记录
      */
     @Override
-    public List<FsCourseWatchLog> selectFsCourseWatchLogList(FsCourseWatchLog fsCourseWatchLog)
-    {
+    public List<FsCourseWatchLog> selectFsCourseWatchLogList(FsCourseWatchLog fsCourseWatchLog) {
         return fsCourseWatchLogMapper.selectFsCourseWatchLogList(fsCourseWatchLog);
     }
 
@@ -182,8 +183,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 结果
      */
     @Override
-    public int insertFsCourseWatchLog(FsCourseWatchLog fsCourseWatchLog)
-    {
+    public int insertFsCourseWatchLog(FsCourseWatchLog fsCourseWatchLog) {
         fsCourseWatchLog.setCreateTime(DateUtils.getNowDate());
         return fsCourseWatchLogMapper.insertFsCourseWatchLog(fsCourseWatchLog);
     }
@@ -195,8 +195,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 结果
      */
     @Override
-    public int updateFsCourseWatchLog(FsCourseWatchLog fsCourseWatchLog)
-    {
+    public int updateFsCourseWatchLog(FsCourseWatchLog fsCourseWatchLog) {
         fsCourseWatchLog.setUpdateTime(DateUtils.getNowDate());
         return fsCourseWatchLogMapper.updateFsCourseWatchLog(fsCourseWatchLog);
     }
@@ -208,8 +207,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 结果
      */
     @Override
-    public int deleteFsCourseWatchLogByLogIds(Long[] logIds)
-    {
+    public int deleteFsCourseWatchLogByLogIds(Long[] logIds) {
         return fsCourseWatchLogMapper.deleteFsCourseWatchLogByLogIds(logIds);
     }
 
@@ -220,19 +218,18 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
      * @return 结果
      */
     @Override
-    public int deleteFsCourseWatchLogByLogId(Long logId)
-    {
+    public int deleteFsCourseWatchLogByLogId(Long logId) {
         return fsCourseWatchLogMapper.deleteFsCourseWatchLogByLogId(logId);
     }
 
     @Override
-    public FsCourseWatchLog getWatchCourseVideo(Long userId, Long videoId, String qwUserId,Long externalId) {
-        return fsCourseWatchLogMapper.getWatchCourseVideo(userId,videoId,qwUserId,externalId);
+    public FsCourseWatchLog getWatchCourseVideo(Long userId, Long videoId, String qwUserId, Long externalId) {
+        return fsCourseWatchLogMapper.getWatchCourseVideo(userId, videoId, qwUserId, externalId);
     }
 
     @Override
     public FsCourseWatchLog getWatchCourseVideoH5(Long videoId, String qwUserId, Long externalId) {
-        return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId,qwUserId,externalId);
+        return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId, qwUserId, externalId);
     }
 
     @Override
@@ -240,37 +237,37 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVONew(param);
         for (FsCourseWatchLogStatisticsListVO item : list) {
             // 项目名
-            if(ObjectUtils.isNotNull(item.getProject())){
+            if (ObjectUtils.isNotNull(item.getProject())) {
                 String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
-                if(StringUtils.isNotBlank(sysCourseProject)){
+                if (StringUtils.isNotBlank(sysCourseProject)) {
                     item.setProjectName(sysCourseProject);
                 }
             }
             // 课程名
-            if(ObjectUtils.isNotNull(item.getCourseId())) {
+            if (ObjectUtils.isNotNull(item.getCourseId())) {
                 String courseName = fsUserCourseCacheService.selectCourseNameByCourseId(item.getCourseId());
-                if(ObjectUtils.isNotNull(courseName)){
+                if (ObjectUtils.isNotNull(courseName)) {
                     item.setCourseName(courseName);
                 }
             }
             // 小节名
-            if(ObjectUtils.isNotNull(item.getVideoId())) {
+            if (ObjectUtils.isNotNull(item.getVideoId())) {
                 FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
-                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                if (ObjectUtils.isNotNull(fsUserCourseVideo)) {
                     item.setVideoName(fsUserCourseVideo.getTitle());
                 }
             }
             // 用户名
-            if(ObjectUtils.isNotNull(item.getUserId())) {
+            if (ObjectUtils.isNotNull(item.getUserId())) {
                 FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
-                if(ObjectUtils.isNotNull(fsUser)){
-                    item.setUserName(String.format("%s_%d",fsUser.getNickName(),fsUser.getUserId()));
+                if (ObjectUtils.isNotNull(fsUser)) {
+                    item.setUserName(String.format("%s_%d", fsUser.getNickName(), fsUser.getUserId()));
                 }
             }
             // 销售名
-            if(ObjectUtils.isNotNull(item.getCompanyUserId())) {
+            if (ObjectUtils.isNotNull(item.getCompanyUserId())) {
                 CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
-                if(ObjectUtils.isNotNull(companyUser)){
+                if (ObjectUtils.isNotNull(companyUser)) {
                     item.setCompanyUserName(String.format("%s_%d", companyUser.getUserName(), companyUser.getUserId()));
                 }
             }
@@ -284,7 +281,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
     public void addCourseWatchLogDayNew() {
 
         List<FsUserCourse> courses = fsUserCourseMapper.selectFsUserCourseAllCourse();
@@ -300,45 +297,45 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
                         // 获取进线时间
                         FsUser fsUser = fsUserService.selectFsUserById(fsCourseWatchLog.getUserId());
-                        if (fsUser!=null){
+                        if (fsUser != null) {
                             fsCourseWatchLog.setLineTime(fsUser.getCreateTime());
                         }
                         // 查询首次观看时间
                         Date date = fsCourseWatchLogMapper.queryFirstWatchDateLogByVideoId(fsCourseWatchLog.getUserId());
-                        if (date!=null){
+                        if (date != null) {
                             fsCourseWatchLog.setFirstTime(date);
                         }
 
                         Date firstTime = fsCourseWatchLog.getFirstTime();
-                        Long day=1L;
-                        if (fsCourseWatchLog.getLineTime()==null){
+                        Long day = 1L;
+                        if (fsCourseWatchLog.getLineTime() == null) {
                             continue;
                         }
                         //不是第一次 看课程
-                        if (firstTime!=null){
+                        if (firstTime != null) {
                             LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                             LocalDate currentDate = LocalDate.now();
                             day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
-                        }else {
+                        } else {
                             //是先导课
-                            if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
+                            if (fsUserCourseVideo.getIsFirst() != null && fsUserCourseVideo.getIsFirst() == 1) {
                                 // 如果存在第一次记录就跳过
                                 int count = qwWatchLogMapper.selectQwWatchLogIsFirstByUserId(fsCourseWatchLog.getUserId());
-                                if (count>0){
+                                if (count > 0) {
                                     continue;
                                 }
-                                day=0L;
+                                day = 0L;
                                 //不是先导课
                             }
                         }
                         HyWatchLog qwWatchLog = new HyWatchLog();
                         qwWatchLog.setExtId(fsCourseWatchLog.getQwExternalContactId());
                         qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
-                        if(fsCourseWatchLog.getQwUserId() != null) {
+                        if (fsCourseWatchLog.getQwUserId() != null) {
                             qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
                         }
                         qwWatchLog.setDay(day);
-                        qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
+                        qwWatchLog.setStatus(fsCourseWatchLog.getLogType() == 3 ? 0 : fsCourseWatchLog.getLogType() == 2 ? 2 : 1);
                         qwWatchLog.setProject(project);
                         qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
                         qwWatchLog.setCompanyId(fsCourseWatchLog.getCompanyId());
@@ -348,11 +345,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                         qwWatchLog.setVideoId(fsCourseWatchLog.getVideoId());
                         QwWatchLogs.add(qwWatchLog);
                     }
-                    if (!QwWatchLogs.isEmpty()){
+                    if (!QwWatchLogs.isEmpty()) {
                         hyWatchLogMapper.insertHyWatchLogBatch(QwWatchLogs);
                     }
-                }catch (Exception e){
-                    log.error("看课记录add异常:{}",course.getCourseId(),e);
+                } catch (Exception e) {
+                    log.error("看课记录add异常:{}", course.getCourseId(), e);
                     throw new RuntimeException(e);
                 }
 
@@ -385,8 +382,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             Long videoId = Long.parseLong(parts[4]);
             Long companyUserId = Long.parseLong(parts[5]);
             String durationStr = redisCache.getCacheObject(key);
-            if(durationStr==null){
-                log.error("key中数据为null:{}",key);
+            if (durationStr == null) {
+                log.error("key中数据为null:{}", key);
                 continue;  // 如果 Redis 中没有记录,跳过
             }
             Long duration = Long.valueOf(durationStr);
@@ -401,7 +398,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             Long videoDuration = 0L;
             try {
                 videoDuration = getFsUserVideoDuration(videoId);
-            }catch (Exception e){
+            } catch (Exception e) {
                 log.error("视频时长识别错误:{}", key);
                 continue;
             }
@@ -411,7 +408,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 if (percentage >= config.getAnswerRate()) {
                     watchLog.setLogType(2); // 设置状态为“已完成”checkFsUserWatchStatus
                     watchLog.setFinishTime(new Date());
-                    String heartbeatKey ="h5wxuser:watch:heartbeat:" + userId+ ":" + videoId + ":" + companyUserId;
+                    String heartbeatKey = "h5wxuser:watch:heartbeat:" + userId + ":" + videoId + ":" + companyUserId;
                     // 完课删除心跳记录
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
@@ -422,32 +419,34 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             //集合中增加
             logs.add(watchLog);
         }
-        batchUpdateFsUserCourseWatchLog(logs,100);
+        batchUpdateFsUserCourseWatchLog(logs, 100);
 
-        if(CollectionUtils.isNotEmpty(finishedLogs)){
+        if (CollectionUtils.isNotEmpty(finishedLogs)) {
             fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
         }
     }
-    public Long getFsUserVideoDuration(Long videoId){
+
+    public Long getFsUserVideoDuration(Long videoId) {
         //将视频时长也存到redis
         String videoRedisKey = "h5wxuser:video:duration:" + videoId;
-        Long videoDuration=0L;
+        Long videoDuration = 0L;
         try {
             videoDuration = redisCache.getCacheObject(videoRedisKey);
-        }catch (Exception e){
+        } catch (Exception e) {
             String string = redisCache.getCacheObject(videoRedisKey);
-            videoDuration=Long.parseLong(string);
+            videoDuration = Long.parseLong(string);
             log.error("key中id为S:{}", videoDuration);
         }
 
 
-        if (videoDuration==null){
+        if (videoDuration == null) {
             FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
-            videoDuration=video.getDuration();
-            redisCache.setCacheObject(videoRedisKey,video.getDuration());
+            videoDuration = video.getDuration();
+            redisCache.setCacheObject(videoRedisKey, video.getDuration());
         }
         return videoDuration;
     }
+
     @Override
     public void checkFsUserWatchStatus() {
         log.info("WXH5-开始更新会员看课中断记录>>>>>");
@@ -478,14 +477,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 watchLog.setLogType(4);
                 // 从 Redis 中删除该记录
                 redisCache.deleteObject(key);
-            }else {
+            } else {
                 watchLog.setLogType(1);
                 watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
-        batchUpdateFsUserCourseWatchLog(logs,100);
-        if(CollectionUtils.isNotEmpty(watchingLogs)){
+        batchUpdateFsUserCourseWatchLog(logs, 100);
+        if (CollectionUtils.isNotEmpty(watchingLogs)) {
             fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
         }
     }
@@ -513,13 +512,13 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
-            Long videoId=null;
-            Long userId=null;
+            Long videoId = null;
+            Long userId = null;
             try {
                 String[] parts = key.split(":");
                 userId = Long.parseLong(parts[3]);
                 videoId = Long.parseLong(parts[4]);
-            }catch (Exception e){
+            } catch (Exception e) {
                 log.error("key中id为null:{}", key);
                 continue;
             }
@@ -541,12 +540,12 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             if (videoDuration != null && videoDuration != 0) {
                 boolean complete = false;
                 // 判断百分比
-                if(config.getCompletionMode() == 1 && config.getAnswerRate() != null){
+                if (config.getCompletionMode() == 1 && config.getAnswerRate() != null) {
                     long percentage = (duration * 100 / videoDuration);
                     complete = percentage >= config.getAnswerRate();
                 }
                 // 判断分钟数
-                if(config.getCompletionMode() == 2 && config.getMinutesNum() != null){
+                if (config.getCompletionMode() == 2 && config.getMinutesNum() != null) {
                     int i = config.getMinutesNum() * 60;
                     complete = videoDuration > i;
                 }
@@ -554,7 +553,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 if (complete) {
                     watchLog.setLogType(2); // 设置状态为“已完成”
                     watchLog.setFinishTime(new Date());
-                    String heartbeatKey ="h5OpenUser:watch:heartbeat:" + userId + ":" + videoId;
+                    String heartbeatKey = "h5OpenUser:watch:heartbeat:" + userId + ":" + videoId;
                     // 完课删除心跳记录
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
@@ -567,10 +566,10 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             logs.add(watchLog);
         }
 
-        batchUpdateFsCourseWatchLogIsOpen(logs,100);
+        batchUpdateFsCourseWatchLogIsOpen(logs, 100);
 
         // 完课打标签
-        if(CollectionUtils.isNotEmpty(finishedLogs)){
+        if (CollectionUtils.isNotEmpty(finishedLogs)) {
             fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
         }
     }
@@ -596,7 +595,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             try {
                 fsCourseWatchLogMapper.batchUpdateWatchLogIsOpen(batchList);
             } catch (Exception e) {
-                log.error("第 {} 批日志更新失败:{}",(i / batchSize) + 1,e.getMessage(),e);
+                log.error("第 {} 批日志更新失败:{}", (i / batchSize) + 1, e.getMessage(), e);
                 throw new RuntimeException(e);
             }
         }
@@ -639,9 +638,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     }
 
     @Override
-    public FsCourseWatchLog getWatchCourseLogVideoBySop(Long videoId,String qwUserId,Long externalId )
-    {
-        return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId,qwUserId,externalId);
+    public FsCourseWatchLog getWatchCourseLogVideoBySop(Long videoId, String qwUserId, Long externalId) {
+        return fsCourseWatchLogMapper.getWatchCourseLogVideoBySop(videoId, qwUserId, externalId);
     }
 
 
@@ -649,20 +647,20 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
 
         // 因为selectFsCourseWatchLogListVO 这个方法中查询了看课日志记录表 需要限制创建时间必传
-        if((StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())) &&
+        if ((StringUtils.isEmpty(param.getSTime()) || StringUtils.isEmpty(param.getETime())) &&
                 (StringUtils.isEmpty(param.getUpSTime()) || StringUtils.isEmpty(param.getUpETime())) &&
-                (StringUtils.isEmpty(param.getScheduleEndTime()) || StringUtils.isEmpty(param.getScheduleStartTime()))){
+                (StringUtils.isEmpty(param.getScheduleEndTime()) || StringUtils.isEmpty(param.getScheduleStartTime()))) {
             throw new RuntimeException("请输入创建时间或营期时间或更新时间其中一个");
         }
 
         // 待看课-未注册
-        if(ObjectUtil.equal(param.getLogType(),5)){
+        if (ObjectUtil.equal(param.getLogType(), 5)) {
             param.setLogType(3);
             param.setIsVip(0);
         }
 
         // 待看课-已注册
-        if(ObjectUtil.equal(param.getLogType(),6)){
+        if (ObjectUtil.equal(param.getLogType(), 6)) {
             param.setLogType(3);
             param.setIsVip(1);
         }
@@ -692,8 +690,8 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListByParam(FsCourseWatchLogListParam param) {
 
-        if (param != null){
-            if (param.getScheduleStartTime() != null && param.getScheduleEndTime() != null){
+        if (param != null) {
+            if (param.getScheduleStartTime() != null && param.getScheduleEndTime() != null) {
                 List<String> sopUserLogsVOS = sopUserLogsMapper.selectSopUserLogsByDate(param.getScheduleStartTime(), param.getScheduleEndTime());
                 param.setSopIds(sopUserLogsVOS);
             }
@@ -701,7 +699,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLogListVO> fsCourseWatchLogListVOS = fsCourseWatchLogMapper.selectFsCourseWatchLogListByParam(param);
 
 
-        return fsCourseWatchLogListVOS ;
+        return fsCourseWatchLogListVOS;
     }
 
     @Autowired
@@ -712,9 +710,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         FsCourseWatchLog finishLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(341170L);
         QwUser qwUser = qwUserMapper.selectQwUserById(Long.valueOf(finishLog.getQwUserId()));
         QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
-        FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(),finishLog.getVideoId());
+        FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(), finishLog.getVideoId());
         QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
-        if (finishTemp!=null){
+        if (finishTemp != null) {
             //        List<QwSopCourseFinishTempSetting> tempSettings = JSON.parseArray(finishTemp.getSetting(), QwSopCourseFinishTempSetting.class);
             // 设置时间格式
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@@ -738,7 +736,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             sopLogs.setExternalUserId(externalContact.getExternalUserId());
             sopLogs.setExternalUserName(externalContact.getName());
             sopLogs.setFsUserId(finishLog.getUserId());
-            List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(finishTemp.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
+            List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(finishTemp.getSetting(), QwSopCourseFinishTempSetting.Setting.class);
             setting.setSetting(list);
             sopLogs.setContentJson(JSON.toJSONString(setting));
 
@@ -746,7 +744,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
 
             //封装消息体
-            SendSopParamDetails sopParamDetailsB=new SendSopParamDetails();
+            SendSopParamDetails sopParamDetailsB = new SendSopParamDetails();
             sopParamDetailsB.setSopLogId(sopLogs.getId());
             sopParamDetailsB.setExternalUserId(sopLogs.getExternalUserId());
             sopParamDetailsB.setExternalUserName(sopLogs.getExternalUserName());
@@ -757,7 +755,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 //            //设置为已发送并更新
 //            finishLog.setSendFinishMsg(1);
 //            fsCourseWatchLogMapper.updateFsCourseWatchLog(finishLog);
-            sendSocket("sendMsg",JSONObject.toJSONString(sopParamDetailsB),qwUser.getAppKey());
+            sendSocket("sendMsg", JSONObject.toJSONString(sopParamDetailsB), qwUser.getAppKey());
 
         }
 
@@ -774,18 +772,18 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     }
 
 
-    public void sendSocket(String cmd,String message,String appKey){
-        MsgBean msgBean=new MsgBean();
+    public void sendSocket(String cmd, String message, String appKey) {
+        MsgBean msgBean = new MsgBean();
         msgBean.setCmd(cmd);
         msgBean.setData(message);
         msgBean.setKey(appKey);
-        JSONObject params=new JSONObject();
-        params.put("id",appKey);
-        params.put("message",JSONObject.toJSONString(msgBean));
+        JSONObject params = new JSONObject();
+        params.put("id", appKey);
+        params.put("message", JSONObject.toJSONString(msgBean));
         FsSysConfig config = configUtil.getSysConfig();
         String domainName = config.getHookUrl();
-        HttpRequest.post(domainName+"/app/qwmsg/receiveMsg")
-                .body(JSONObject.toJSONString(params),"application/json;charset=UTF-8")
+        HttpRequest.post(domainName + "/app/qwmsg/receiveMsg")
+                .body(JSONObject.toJSONString(params), "application/json;charset=UTF-8")
                 .execute().body();
     }
 
@@ -805,15 +803,15 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLog> finishedLogs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
-            Long qwUserId=null;
-            Long videoId=null;
-            Long externalId=null;
+            Long qwUserId = null;
+            Long videoId = null;
+            Long externalId = null;
             try {
                 String[] parts = key.split(":");
                 qwUserId = Long.parseLong(parts[3]);
                 externalId = Long.parseLong(parts[4]);
                 videoId = Long.parseLong(parts[5]);
-            }catch (Exception e){
+            } catch (Exception e) {
                 log.error("key中id为null:{}", key);
                 continue;
             }
@@ -835,7 +833,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             Long videoDuration = 0L;
             try {
                 videoDuration = getVideoDuration(videoId);
-            }catch (Exception e){
+            } catch (Exception e) {
                 log.error("视频时长识别错误:{}", key);
                 continue;
             }
@@ -843,12 +841,12 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             if (videoDuration != null && videoDuration != 0) {
                 boolean complete = false;
                 // 判断百分比
-                if(config.getCompletionMode() == 1 && config.getAnswerRate() != null){
+                if (config.getCompletionMode() == 1 && config.getAnswerRate() != null) {
                     long percentage = (duration * 100 / videoDuration);
                     complete = percentage >= config.getAnswerRate();
                 }
                 // 判断分钟数
-                if(config.getCompletionMode() == 2 && config.getMinutesNum() != null){
+                if (config.getCompletionMode() == 2 && config.getMinutesNum() != null) {
                     int i = config.getMinutesNum() * 60;
                     complete = videoDuration > i;
                 }
@@ -856,7 +854,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 if (complete) {
                     watchLog.setLogType(2); // 设置状态为“已完成”
                     watchLog.setFinishTime(new Date());
-                    String heartbeatKey ="h5user:watch:heartbeat:" + qwUserId+ ":" + externalId + ":" + videoId;
+                    String heartbeatKey = "h5user:watch:heartbeat:" + qwUserId + ":" + externalId + ":" + videoId;
                     // 完课删除心跳记录
                     redisCache.deleteObject(heartbeatKey);
                     // 完课删除看课时长记录
@@ -869,10 +867,10 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             logs.add(watchLog);
         }
 
-        batchUpdateFsCourseWatchLog(logs,100);
+        batchUpdateFsCourseWatchLog(logs, 100);
 
         // 完课打标签
-        if(CollectionUtils.isNotEmpty(finishedLogs)){
+        if (CollectionUtils.isNotEmpty(finishedLogs)) {
             fsTagUpdateService.onCourseWatchFinishedBatch(finishedLogs);
         }
     }
@@ -889,15 +887,15 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             //取key中数据
-            Long qwUserId=null;
-            Long videoId=null;
-            Long externalId=null;
+            Long qwUserId = null;
+            Long videoId = null;
+            Long externalId = null;
             try {
                 String[] parts = key.split(":");
                 qwUserId = Long.parseLong(parts[3]);
                 externalId = Long.parseLong(parts[4]);
                 videoId = Long.parseLong(parts[5]);
-            }catch (Exception e){
+            } catch (Exception e) {
                 log.error("key中id为null:{}", key);
                 continue;
             }
@@ -918,15 +916,15 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 watchLog.setLogType(4);
                 // 从 Redis 中删除该记录
                 redisCache.deleteObject(key);
-            }else {
+            } else {
                 watchLog.setLogType(1);
                 watchingLogs.add(watchLog);
             }
             logs.add(watchLog);
         }
-        batchUpdateFsCourseWatchLog(logs,100);
+        batchUpdateFsCourseWatchLog(logs, 100);
 
-        if(CollectionUtils.isNotEmpty(watchingLogs)){
+        if (CollectionUtils.isNotEmpty(watchingLogs)) {
             fsTagUpdateService.onCourseWatchingBatch(watchingLogs);
         }
     }
@@ -952,26 +950,26 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 List<FsQwCourseWatchLogVO> watchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByNoDayAndVoidIdByTime(fsUserCourseVideo.getVideoId(), DateUtil.formatLocalDateTime(start), DateUtil.formatLocalDateTime(end));
                 for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
                     Date firstTime = fsCourseWatchLog.getFirstTime();
-                    Long day=1L;
-                    if (fsCourseWatchLog.getLineTime()==null){
+                    Long day = 1L;
+                    if (fsCourseWatchLog.getLineTime() == null) {
                         continue;
                     }
                     //不是第一次 看课程
-                    if (firstTime!=null){
+                    if (firstTime != null) {
                         LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                         LocalDate currentDate = LocalDate.now();
                         day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
-                    }else {
+                    } else {
                         //是先导课
-                        if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
+                        if (fsUserCourseVideo.getIsFirst() != null && fsUserCourseVideo.getIsFirst() == 1) {
                             int count = qwWatchLogMapper.selectQwWatchLogIsFirst(fsCourseWatchLog.getQwExternalContactId());
-                            if (count>0){
+                            if (count > 0) {
                                 continue;
                             }
-                            day=0L;
+                            day = 0L;
                             //不是先导课
-                        }else {
-                            day=1L;
+                        } else {
+                            day = 1L;
                             QwExternalContact qwExternalContact = new QwExternalContact();
                             qwExternalContact.setId(fsCourseWatchLog.getQwExternalContactId());
                             qwExternalContact.setFirstTime(fsCourseWatchLog.getCreateTime());
@@ -983,18 +981,19 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
                     qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
                     qwWatchLog.setDay(day);
-                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
+                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType() == 3 ? 0 : fsCourseWatchLog.getLogType() == 2 ? 2 : 1);
                     qwWatchLog.setProject(project);
                     qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
                     QwWatchLogs.add(qwWatchLog);
                 }
-                if (!QwWatchLogs.isEmpty()){
+                if (!QwWatchLogs.isEmpty()) {
                     qwWatchLogMapper.insertQwWatchLogBatch(QwWatchLogs);
                 }
 
             }
         }
     }
+
     @Override
     public void addCourseWatchLogDay() {
 
@@ -1005,53 +1004,54 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             for (FsUserCourseVideo fsUserCourseVideo : fsUserCourseVideos) {
                 ArrayList<QwWatchLog> QwWatchLogs = new ArrayList<>();
                 List<FsQwCourseWatchLogVO> watchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByNoDayAndVoidId(fsUserCourseVideo.getVideoId());
-                    for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
-                        Date firstTime = fsCourseWatchLog.getFirstTime();
-                        Long day=1L;
-                        if (fsCourseWatchLog.getLineTime()==null){
-                            continue;
-                        }
-                        //不是第一次 看课程
-                        if (firstTime!=null){
-                            LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
-                            LocalDate currentDate = LocalDate.now();
-                            day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
-                        }else {
-                            //是先导课
-                            if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
-                                int count = qwWatchLogMapper.selectQwWatchLogIsFirst(fsCourseWatchLog.getQwExternalContactId());
-                                if (count>0){
-                                    continue;
-                                }
-                                day=0L;
-                            //不是先导课
-                            }else {
-                                day=1L;
-                                QwExternalContact qwExternalContact = new QwExternalContact();
-                                qwExternalContact.setId(fsCourseWatchLog.getQwExternalContactId());
-                                qwExternalContact.setFirstTime(fsCourseWatchLog.getCreateTime());
-                                qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
+                    Date firstTime = fsCourseWatchLog.getFirstTime();
+                    Long day = 1L;
+                    if (fsCourseWatchLog.getLineTime() == null) {
+                        continue;
+                    }
+                    //不是第一次 看课程
+                    if (firstTime != null) {
+                        LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+                        LocalDate currentDate = LocalDate.now();
+                        day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
+                    } else {
+                        //是先导课
+                        if (fsUserCourseVideo.getIsFirst() != null && fsUserCourseVideo.getIsFirst() == 1) {
+                            int count = qwWatchLogMapper.selectQwWatchLogIsFirst(fsCourseWatchLog.getQwExternalContactId());
+                            if (count > 0) {
+                                continue;
                             }
+                            day = 0L;
+                            //不是先导课
+                        } else {
+                            day = 1L;
+                            QwExternalContact qwExternalContact = new QwExternalContact();
+                            qwExternalContact.setId(fsCourseWatchLog.getQwExternalContactId());
+                            qwExternalContact.setFirstTime(fsCourseWatchLog.getCreateTime());
+                            qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
                         }
-                        QwWatchLog qwWatchLog = new QwWatchLog();
-                        qwWatchLog.setExtId(fsCourseWatchLog.getQwExternalContactId());
-                        qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
-                        qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
-                        qwWatchLog.setDay(day);
-                        qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
-                        qwWatchLog.setProject(project);
-                        qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
-                        QwWatchLogs.add(qwWatchLog);
-                    }
-                    if (!QwWatchLogs.isEmpty()){
-                        qwWatchLogMapper.insertQwWatchLogBatch(QwWatchLogs);
                     }
+                    QwWatchLog qwWatchLog = new QwWatchLog();
+                    qwWatchLog.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                    qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
+                    qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
+                    qwWatchLog.setDay(day);
+                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType() == 3 ? 0 : fsCourseWatchLog.getLogType() == 2 ? 2 : 1);
+                    qwWatchLog.setProject(project);
+                    qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
+                    QwWatchLogs.add(qwWatchLog);
+                }
+                if (!QwWatchLogs.isEmpty()) {
+                    qwWatchLogMapper.insertQwWatchLogBatch(QwWatchLogs);
+                }
 
             }
         }
 
 
     }
+
     @Override
     public void addCourseWatchLogDay2() {
 
@@ -1064,27 +1064,27 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                 List<FsQwCourseWatchLogVO> watchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogByNoDayAndVoidId2(fsUserCourseVideo.getVideoId());
                 for (FsQwCourseWatchLogVO fsCourseWatchLog : watchLogs) {
                     Date firstTime = fsCourseWatchLog.getFirstTime();
-                    Long day=1L;
-                    if (fsCourseWatchLog.getLineTime()==null){
+                    Long day = 1L;
+                    if (fsCourseWatchLog.getLineTime() == null) {
                         continue;
                     }
                     //不是第一次 看课程
-                    if (firstTime!=null){
+                    if (firstTime != null) {
                         LocalDate firstLocalDate = firstTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                         LocalDate currentDate = LocalDate.now();
                         day = ChronoUnit.DAYS.between(firstLocalDate, currentDate);
-                        day=day-1;
-                    }else {
+                        day = day - 1;
+                    } else {
                         //是先导课
-                        if (fsUserCourseVideo.getIsFirst()!=null&&fsUserCourseVideo.getIsFirst()==1){
+                        if (fsUserCourseVideo.getIsFirst() != null && fsUserCourseVideo.getIsFirst() == 1) {
                             int count = qwWatchLogMapper.selectQwWatchLogIsFirst(fsCourseWatchLog.getQwExternalContactId());
-                            if (count>0){
+                            if (count > 0) {
                                 continue;
                             }
-                            day=0L;
+                            day = 0L;
                             //不是先导课
-                        }else {
-                            day=1L;
+                        } else {
+                            day = 1L;
                             QwExternalContact qwExternalContact = new QwExternalContact();
                             qwExternalContact.setId(fsCourseWatchLog.getQwExternalContactId());
                             qwExternalContact.setFirstTime(fsCourseWatchLog.getCreateTime());
@@ -1096,12 +1096,12 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                     qwWatchLog.setLineTime(fsCourseWatchLog.getLineTime());
                     qwWatchLog.setQwUserId(Long.parseLong(fsCourseWatchLog.getQwUserId()));
                     qwWatchLog.setDay(day);
-                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType()==3?0:fsCourseWatchLog.getLogType()==2?2:1);
+                    qwWatchLog.setStatus(fsCourseWatchLog.getLogType() == 3 ? 0 : fsCourseWatchLog.getLogType() == 2 ? 2 : 1);
                     qwWatchLog.setProject(project);
                     qwWatchLog.setCreateTime(fsCourseWatchLog.getCreateTime());
                     QwWatchLogs.add(qwWatchLog);
                 }
-                if (!QwWatchLogs.isEmpty()){
+                if (!QwWatchLogs.isEmpty()) {
                     qwWatchLogMapper.insertQwWatchLogBatch(QwWatchLogs);
                 }
 
@@ -1112,35 +1112,35 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     }
 
 
-    public Long getVideoDuration(Long videoId){
+    public Long getVideoDuration(Long videoId) {
         //将视频时长也存到redis
         String videoRedisKey = "h5user:video:duration:" + videoId;
-        Long videoDuration=0L;
+        Long videoDuration = 0L;
         try {
             videoDuration = redisCache.getCacheObject(videoRedisKey);
-        }catch (Exception e){
+        } catch (Exception e) {
             String string = redisCache.getCacheObject(videoRedisKey);
-            videoDuration=Long.parseLong(string);
+            videoDuration = Long.parseLong(string);
             log.error("key中id为S:{}", videoDuration);
         }
 
 
-        if (videoDuration==null){
+        if (videoDuration == null) {
             FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
-            videoDuration=video.getDuration();
-            redisCache.setCacheObject(videoRedisKey,video.getDuration());
+            videoDuration = video.getDuration();
+            redisCache.setCacheObject(videoRedisKey, video.getDuration());
         }
         return videoDuration;
     }
 
-    public Long getVideoDurationIsOpen(Long videoId){
+    public Long getVideoDurationIsOpen(Long videoId) {
         //将视频时长也存到redis
         String videoRedisKey = "h5OpenUser:video:duration:" + videoId;
         Long videoDuration = redisCache.getCacheObject(videoRedisKey);
-        if (videoDuration==null){
+        if (videoDuration == null) {
             FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
-            videoDuration=video.getDuration();
-            redisCache.setCacheObject(videoRedisKey,video.getDuration());
+            videoDuration = video.getDuration();
+            redisCache.setCacheObject(videoRedisKey, video.getDuration());
         }
         return videoDuration;
     }
@@ -1167,7 +1167,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             try {
                 fsCourseWatchLogMapper.batchUpdateWatchLog(batchList);
             } catch (Exception e) {
-                log.error("第 {} 批日志更新失败:{}",(i / batchSize) + 1,e.getMessage(),e);
+                log.error("第 {} 批日志更新失败:{}", (i / batchSize) + 1, e.getMessage(), e);
                 throw new RuntimeException(e);
             }
         }
@@ -1181,22 +1181,21 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     }
 
 
-
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param) {
 
 
-        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+        if (param.getSendType() == 1 && param.getPeriodETime() != null && param.getPeriodSTime() != null) {
             List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
 
-            if (!periodIds.isEmpty()){
+            if (!periodIds.isEmpty()) {
                 List<Long> longs = userCoursePeriodService.selectFsUserCoursePeriodListByPeriodId(periodIds, param.getCompanyId());
-                if (!longs.isEmpty()){
+                if (!longs.isEmpty()) {
                     param.setPeriodIds(longs);
-                }else {
+                } else {
                     return new ArrayList<>();
                 }
-            }else {
+            } else {
                 return new ArrayList<>();
             }
 
@@ -1217,69 +1216,69 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
                         .findFirst()
                         .orElse("无营期名称");
                 item.setPeriodIdName(periodName);
-            }else {
+            } else {
                 item.setPeriodIdName("自动发课无营期名称");
             }
 
             // 项目
-            if(ObjectUtils.isNotNull(item.getProject())) {
+            if (ObjectUtils.isNotNull(item.getProject())) {
                 String sysCourseProject = DictUtils.getDictLabel("sys_course_project", String.valueOf(item.getProject()));
-                if(StringUtils.isNotBlank(sysCourseProject)){
+                if (StringUtils.isNotBlank(sysCourseProject)) {
                     item.setProjectName(sysCourseProject);
                 }
             }
             // 用户名
-            if(ObjectUtils.isNotNull(item.getUserId())) {
+            if (ObjectUtils.isNotNull(item.getUserId())) {
                 FsUser fsUser = fsUserCacheService.selectFsUserById(item.getUserId());
-                if(ObjectUtils.isNotNull(fsUser)){
-                    item.setExternalUserName(String.format("%s_%d",fsUser.getNickName(),fsUser.getUserId()));
+                if (ObjectUtils.isNotNull(fsUser)) {
+                    item.setExternalUserName(String.format("%s_%d", fsUser.getNickName(), fsUser.getUserId()));
                     item.setFsNickName(fsUser.getNickName());
                     item.setFsAvatar(fsUser.getAvatar());
                 }
             }
             // 公司名
-            if(ObjectUtils.isNotNull(item.getCompanyId())){
+            if (ObjectUtils.isNotNull(item.getCompanyId())) {
                 Company company = companyCacheService.selectCompanyById(Long.valueOf(item.getCompanyId()));
-                if(ObjectUtils.isNotNull(company)){
+                if (ObjectUtils.isNotNull(company)) {
                     item.setCompanyName(String.format("%s_%d", company.getCompanyName(), company.getCompanyId()));
                 }
             }
 
             // 销售名
-            if(ObjectUtils.isNotNull(item.getCompanyUserId())){
+            if (ObjectUtils.isNotNull(item.getCompanyUserId())) {
                 CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(item.getCompanyUserId());
-                if(ObjectUtils.isNotNull(companyUser)){
+                if (ObjectUtils.isNotNull(companyUser)) {
                     item.setCompanyUserName(String.format("%s_%d", companyUser.getNickName(), companyUser.getUserId()));
                 }
             }
 
             // 课程
-            if(ObjectUtils.isNotNull(item.getCourseId())){
+            if (ObjectUtils.isNotNull(item.getCourseId())) {
                 FsUserCourse course = fsUserCourseCacheService.selectFsUserCourseByCourseId(item.getCourseId());
-                if(ObjectUtils.isNotNull(course)){
+                if (ObjectUtils.isNotNull(course)) {
                     item.setCourseName(course.getCourseName());
                 }
             }
             // 小节
-            if(ObjectUtils.isNotNull(item.getVideoId())){
+            if (ObjectUtils.isNotNull(item.getVideoId())) {
                 FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoCacheService.selectFsUserCourseVideoByVideoId(item.getVideoId());
-                if(ObjectUtils.isNotNull(fsUserCourseVideo)){
+                if (ObjectUtils.isNotNull(fsUserCourseVideo)) {
                     item.setVideoName(fsUserCourseVideo.getTitle());
                 }
             }
 
             // 企微用户名
-            if(ObjectUtils.isNotNull(item.getQwUserId())){
+            if (ObjectUtils.isNotNull(item.getQwUserId())) {
                 String qwUserName = qwUserCacheService.queryQwUserNameByUserId(item.getQwUserId());
-                if(StringUtils.isNotBlank(qwUserName)){
+                if (StringUtils.isNotBlank(qwUserName)) {
                     item.setQwUserName(qwUserName);
                 }
             }
 
             // 企微外部联系人
-            if(ObjectUtils.isNotNull(item.getQwExternalContactId())){
+            if (ObjectUtils.isNotNull(item.getQwExternalContactId())) {
                 String qwExternalContactName = qwExternalContactCacheService.selectQwExternalContactById(Long.valueOf(item.getQwExternalContactId()));
-                if(StringUtils.isNotBlank(qwExternalContactName)){
+                if (StringUtils.isNotBlank(qwExternalContactName)) {
                     item.setExternalUserName(qwExternalContactName);
                 }
             }
@@ -1300,32 +1299,276 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
     @Override
     public List<FsUserReportVO> selectFsUserReportVO(FsCourseWatchLogStatisticsListParam param) {
-        if(StringUtils.isNotEmpty(param.getUserPhone())){
+        if (StringUtils.isNotEmpty(param.getUserPhone())) {
             //加密手机号
             param.setUserPhone(PhoneUtil.encryptPhone(param.getUserPhone()));
         }
-        return fsCourseWatchLogMapper.selectFsUserReportVO(param);
+        List<FsUserReportVO> userList = fsCourseWatchLogMapper.selectUserBaseInfo(param);
+        if (CollectionUtils.isEmpty(userList)) {
+            return Collections.emptyList();
+        }
+        List<UserConditionDTO> userCondition = userList.stream()
+                .map(user -> new UserConditionDTO(user.getUserId(), user.getCompanyUserId()))
+                .collect(Collectors.toList());
+
+        List<Long> userIds = userList.stream().map(FsUserReportVO::getUserId).collect(Collectors.toList());
+        // 3. 批量查询各项统计数据
+        Map<String, FsUserReportVO> watchStatsMap = fsCourseWatchLogMapper.selectWatchStatsByUserIds(userCondition, param)
+                .stream()
+                .collect(Collectors.toMap(
+                        stats -> buildKey(stats.getUserId(), stats.getCompanyUserId()),
+                        Function.identity()
+                ));
+
+        Map<Long, FsUserReportVO> integralStatsMap = fsCourseWatchLogMapper.selectIntegralStatsByUserIds(userIds)
+                .stream()
+                .collect(Collectors.toMap(FsUserReportVO::getUserId, Function.identity()));
+
+        Map<String, FsUserReportVO> redPacketStatsMap = fsCourseWatchLogMapper.selectRedPacketStatsByUserIds(userCondition, param)
+                .stream()
+                .collect(Collectors.toMap(
+                        stats -> buildKey(stats.getUserId(), stats.getCompanyUserId()),
+                        Function.identity()
+                ));
+
+        Map<Long, FsUserReportVO> orderStatsMap = fsCourseWatchLogMapper.selectOrderStatsByUserIds(userIds)
+                .stream()
+                .collect(Collectors.toMap(FsUserReportVO::getUserId, Function.identity()));
+        userList.forEach(user -> {
+            String key = buildKey(user.getUserId(), user.getCompanyUserId());
+            //看课统计
+            FsUserReportVO watchStats = watchStatsMap.get(key);
+            if (watchStats != null) {
+                user.setLastWatchTime(watchStats.getLastWatchTime());
+                user.setWatchCount(watchStats.getWatchCount());
+                user.setPeriodCount(watchStats.getPeriodCount());
+                user.setWatchStatus(watchStats.getWatchStatus());
+            }
+            //积分统计
+            FsUserReportVO fsUserReportVO = integralStatsMap.get(user.getUserId());
+            if (fsUserReportVO != null) {
+                user.setIntegral(fsUserReportVO.getIntegral());
+                user.setConsumedIntegral(fsUserReportVO.getConsumedIntegral());
+            }
+            //红包统计
+            FsUserReportVO redPacketStats = redPacketStatsMap.get(key);
+            if (redPacketStats != null) {
+                user.setReceivedAmount(redPacketStats.getReceivedAmount());
+            }
+            //订单统计
+            FsUserReportVO orderStats = orderStatsMap.get(user.getUserId());
+            if (orderStats != null) {
+                user.setOrderAmount(orderStats.getOrderAmount());
+            }
+        });
+        return userList;
     }
 
     @Override
     public List<WatchLogReportVO> selectWatchLogReportVO(FsCourseWatchLogStatisticsListParam param) {
-        if(StringUtils.isNotEmpty(param.getUserPhone())){
+        if (StringUtils.isNotEmpty(param.getUserPhone())) {
             //加密手机号
             param.setUserPhone(PhoneUtil.encryptPhone(param.getUserPhone()));
         }
-        return fsCourseWatchLogMapper.selectWatchLogReportVO(param);
+        // 获取基础数据
+        List<WatchLogReportVO> baseData = getBaseDataByDimension(param);
+        if (CollectionUtils.isEmpty(baseData)) {
+            return Collections.emptyList();
+        }
+        // 获取统计数据和组装结果
+        return assembleStatisticsData(baseData, param);
+    }
+
+
+    /**
+     * 根据维度获取基础数据
+     */
+    private List<WatchLogReportVO> getBaseDataByDimension(FsCourseWatchLogStatisticsListParam param) {
+        switch (param.getDimension()) {
+            case "user":
+                return fsCourseWatchLogMapper.selectUserBaseData(param);
+            case "sales":
+                return fsCourseWatchLogMapper.selectSalesBaseData(param);
+            case "company":
+                return fsCourseWatchLogMapper.selectCompanyBaseData(param);
+            default:
+                return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 组装统计数据
+     */
+    private List<WatchLogReportVO> assembleStatisticsData(List<WatchLogReportVO> baseData, FsCourseWatchLogStatisticsListParam param) {
+        // 准备查询条件
+        List<UserConditionDTO> userCondition = baseData.stream()
+                .map(user -> new UserConditionDTO(user.getUserId(), user.getCompanyUserId()))
+                .collect(Collectors.toList());
+        List<Long> userIds = baseData.stream().map(WatchLogReportVO::getUserId).collect(Collectors.toList());
+
+        // 批量查询统计数据
+        Map<String, WatchLogReportVO> watchMap = convertWatchStatsToMap(
+                fsCourseWatchLogMapper.selectUserWatchDetails(userCondition, param)
+        );
+        Map<Long, WatchLogReportVO> redPacketMap = convertRedPacketToMap(
+                fsCourseWatchLogMapper.selectRedPacketStats(userIds)
+        );
+        Map<Long, WatchLogReportVO> orderMap = convertOrderToMap(
+                fsCourseWatchLogMapper.selectOrderStats(userIds)
+        );
+        Map<Long, WatchLogReportVO> answerMap = convertAnswerToMap(
+                fsCourseWatchLogMapper.selectAnswerStats(userIds)
+        );
+
+        // 组装数据
+        for (WatchLogReportVO item : baseData) {
+            String key = buildKey(item.getUserId(), item.getCompanyUserId());
+
+            // 看课数据
+            WatchLogReportVO watchStats = watchMap.get(key);
+            if (watchStats != null) {
+                setWatchData(item, watchStats, param.getDimension());
+            }
+
+            // 红包数据
+            WatchLogReportVO redPacketStats = redPacketMap.get(item.getUserId());
+            if (redPacketStats != null) {
+                item.setRedPacketAmount(redPacketStats.getRedPacketAmount());
+            }
+
+            // 订单数据
+            WatchLogReportVO order = orderMap.get(item.getUserId());
+            if (order != null) {
+                item.setHistoryOrderCount(order.getHistoryOrderCount());
+            }
+
+            // 答题数据
+            WatchLogReportVO answer = answerMap.get(item.getUserId());
+            if (answer != null) {
+                setAnswerData(item, answer, param.getDimension());
+            }
+        }
+
+        return baseData;
+    }
+
+    /**
+     * 设置看课数据(根据不同维度设置不同字段)
+     */
+    private void setWatchData(WatchLogReportVO target, WatchLogReportVO source, String dimension) {
+        target.setTrainingCampName(source.getTrainingCampName());
+        target.setPeriodName(source.getPeriodName());
+        target.setVideoTitle(source.getVideoTitle());
+
+        if ("user".equals(dimension)) {
+            target.setWatchStatus(source.getWatchStatus());
+            target.setDuration(source.getDuration());
+            target.setCourseTime(source.getCourseTime());
+            target.setFinishTime(source.getFinishTime());
+        } else {
+            target.setFinishedCount(source.getFinishedCount());
+            target.setUnfinishedCount(source.getUnfinishedCount());
+            target.setCompletionRate(calculateCompletionRate(source));
+            target.setNotWatchedCount(source.getNotWatchedCount());
+        }
+    }
+
+    /**
+     * 设置答题数据(根据不同维度设置不同字段)
+     */
+    private void setAnswerData(WatchLogReportVO target, WatchLogReportVO source, String dimension) {
+        if ("user".equals(dimension)) {
+            target.setAnswerStatus(source.getAnswerStatus());
+        } else {
+            target.setNotAnsweredCount(source.getNotAnsweredCount());
+        }
+    }
+
+
+    /**
+     * 看课数据转Map
+     */
+    public Map<String, WatchLogReportVO> convertWatchStatsToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        stats -> buildKey(stats.getUserId(), stats.getCompanyUserId()),
+                        Function.identity()
+                ));
+    }
+
+    /**
+     * 红包数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertRedPacketToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getUserId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    /**
+     * 订单数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertOrderToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getUserId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    /**
+     * 答题数据转Map
+     */
+    public Map<Long, WatchLogReportVO> convertAnswerToMap(List<WatchLogReportVO> list) {
+        if (list == null || list.isEmpty()) {
+            return new HashMap<>();
+        }
+        return list.stream()
+                .collect(Collectors.toMap(
+                        WatchLogReportVO::getUserId,
+                        Function.identity(),
+                        (existing, replacement) -> existing // 当出现重复键时,保留第一个值
+                ));
+    }
+
+    private String buildKey(Long userId, Long companyUserId) {
+        return userId + "_" + companyUserId;
+    }
+
+    /**
+     * 计算完成率
+     */
+    private BigDecimal calculateCompletionRate(WatchLogReportVO watchStats) {
+        if (watchStats.getTotalLogCount() == 0) {
+            return BigDecimal.ZERO;
+        }
+        return BigDecimal.valueOf(watchStats.getFinishedCount() * 100.0 / watchStats.getTotalLogCount())
+                .setScale(2, RoundingMode.HALF_UP);
     }
 
     /**
      * 根据看课记录id获取所有的外部联系人ids
+     *
      * @param watchLogIds
      * @return
      */
     @Override
-    public List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds){
+    public List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds) {
         return fsCourseWatchLogMapper.getExContactIdsIdsByWatchLogIds(watchLogIds);
     }
 
 
-
 }

+ 15 - 0
fs-service/src/main/java/com/fs/his/dto/UserConditionDTO.java

@@ -0,0 +1,15 @@
+package com.fs.his.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class UserConditionDTO {
+
+    private Long userId;
+
+    private Long companyUserId;
+
+
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/vo/FsUserReportVO.java

@@ -1,5 +1,6 @@
 package com.fs.his.vo;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -37,6 +38,7 @@ public class FsUserReportVO {
     /**
      * 最近看课时间
      */
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private  Date lastWatchTime;
 
 
@@ -89,5 +91,11 @@ public class FsUserReportVO {
     /**
      * 首次进线时间
      */
+    @JsonFormat(pattern = "yyyy-MM-dd")
     private  Date firstTime;
+
+    /**
+     *  销售id
+     */
+    private  long companyUserId;
 }

+ 10 - 0
fs-service/src/main/java/com/fs/his/vo/WatchLogReportVO.java

@@ -119,4 +119,14 @@ public class WatchLogReportVO {
      * 历史订单数
      */
     private  Integer historyOrderCount;
+
+    /**
+     * 销售id
+     */
+    private  Long companyUserId;
+
+    /**
+     * 总观看人数
+     */
+    private  Long totalLogCount;
 }

+ 310 - 172
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -1136,177 +1136,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY COALESCE(watch.today_access_count, 0) DESC
 
     </select>
-    <select id="selectFsUserReportVO" resultType="com.fs.his.vo.FsUserReportVO">
-        WITH
-        watch_stats AS (
-        SELECT
-        user_id,
-        MAX(CASE WHEN log_type = 1 THEN create_time END) AS last_watch_time,
-        COUNT(DISTINCT CASE WHEN log_type = 1 THEN log_id END) AS watch_count,
-        COUNT(DISTINCT CASE WHEN log_type = 3 THEN log_id END) AS absent_count,
-        (SELECT log_type FROM fs_course_watch_log w2
-        WHERE w2.user_id = w1.user_id
-        ORDER BY create_time DESC LIMIT 1) AS last_watch_status
-        FROM fs_course_watch_log w1
-        <where>
-            <if test="sTime != null and eTime != null">
-                AND w1.create_time BETWEEN #{sTime} AND #{eTime}
-            </if>
-            <if test="periodId != null">
-                AND w1.period_id = #{periodId}
-            </if>
-            <if test="trainingCampId != null">
-                AND w1.period_id IN (
-                SELECT period_id FROM fs_user_course_period
-                WHERE training_camp_id = #{trainingCampId}
-                )
-            </if>
-        </where>
-        GROUP BY user_id
-        ),
-        period_stats AS (
-        SELECT
-        wl.user_id,
-        COUNT(DISTINCT ucp.period_id) AS period_count
-        FROM fs_course_watch_log wl
-        INNER JOIN fs_user_course_period ucp ON wl.period_id = ucp.period_id AND ucp.del_flag = 0
-        <where>
-            <if test="periodId != null">
-                AND ucp.period_id = #{periodId}
-            </if>
-            <if test="trainingCampId != null">
-                AND ucp.training_camp_id = #{trainingCampId}
-            </if>
-        </where>
-        GROUP BY wl.user_id
-        ),
-        integral_stats AS (
-        SELECT
-        user_id,
-        SUM(integral) AS total_integral,
-        ABS(SUM(CASE WHEN integral &lt; 0 THEN integral ELSE 0 END)) AS consumed_integral
-        FROM fs_user_integral_logs
-        <where>
-            <if test="sTime != null and eTime != null">
-                AND create_time BETWEEN #{sTime} AND #{eTime}
-            </if>
-        </where>
-        GROUP BY user_id
-        ),
-        redpacket_stats AS (
-        SELECT
-        user_id,
-        SUM(amount) AS received_amount
-        FROM fs_course_red_packet_log
-        WHERE status = 1
-        <if test="sTime != null and eTime != null">
-            AND create_time BETWEEN #{sTime} AND #{eTime}
-        </if>
-        GROUP BY user_id
-        ),
-        order_stats AS (
-        SELECT
-        user_id,
-        SUM(pay_money) as order_amount
-        FROM fs_package_order
-        WHERE `status` = 3
-        <if test="sTime != null and eTime != null">
-            AND create_time BETWEEN #{sTime} AND #{eTime}
-        </if>
-        GROUP BY user_id
-        )
-
-        SELECT
-        u.user_id userId,
-        u.nick_name AS nickName,
-        CASE
-        WHEN u.`status` = '1' THEN '正常'
-        WHEN u.`status` = '2' THEN '禁止'
-        ELSE '无'
-        END AS status,
-        cu.nick_name AS companyUserName,
-        c.company_name AS companyName,
-        u.register_date AS registerDate,
-        MIN(u.register_date) as firstTime,
-        ws.last_watch_time AS lastWatchTime,
-        COALESCE(ws.watch_count, 0) AS watchCount,
-        COALESCE(ws.absent_count, 0) AS absentCount,
-        CASE
-        WHEN ws.last_watch_status = '1' THEN '看课中'
-        WHEN ws.last_watch_status = '2' THEN '完课'
-        WHEN ws.last_watch_status = '3' THEN '待看课'
-        WHEN ws.last_watch_status = '4' THEN '看课中断'
-        ELSE '无'
-        END AS watchStatus,
-        COALESCE(ps.period_count, 0) AS periodCount,
-        COALESCE(integral.consumed_integral, 0) AS consumedIntegral,
-        COALESCE(integral.total_integral, 0) as Integral,
-        COALESCE(rs.received_amount, 0) AS receivedAmount,
-        COALESCE(os.order_amount, 0) as orderAmount
-        <if test="dimension == 'dept'">
-            ,cd.dept_name AS deptName
-        </if>
-        FROM fs_user u
-        LEFT JOIN fs_user_company_user cuu ON u.user_id = cuu.user_id
-        LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id
-        LEFT JOIN company c ON cuu.company_id = c.company_id
-        <if test="dimension == 'dept'">
-            LEFT JOIN company_dept cd ON c.company_id = cd.company_id
-        </if>
-        <!-- 通过看课记录关联营期和训练营 -->
-        LEFT JOIN fs_course_watch_log watch_log ON u.user_id = watch_log.user_id
-        LEFT JOIN fs_user_course_period cp ON watch_log.period_id = cp.period_id
-        LEFT JOIN fs_user_course_training_camp camp ON cp.training_camp_id = camp.training_camp_id
-        LEFT JOIN watch_stats ws ON u.user_id = ws.user_id
-        LEFT JOIN period_stats ps ON u.user_id = ps.user_id
-        LEFT JOIN integral_stats integral ON u.user_id = integral.user_id
-        LEFT JOIN redpacket_stats rs ON u.user_id = rs.user_id
-        LEFT JOIN order_stats os ON u.user_id = os.user_id
-        <where>
-            u.user_id IS NOT NULL
-
-            <!-- 销售公司条件 -->
-            <if test="companyId != null and companyId != ''">
-                AND c.company_id = #{companyId}
-            </if>
-
-            <if test="project != null">
-                AND cuu.project_id = #{project}
-            </if>
-
-            <!-- 训练营条件 -->
-            <if test="trainingCampId != null">
-                AND camp.training_camp_id = #{trainingCampId}
-            </if>
-
-            <!-- 营期条件 -->
-            <if test="periodId != null">
-                AND cp.period_id = #{periodId}
-            </if>
-
-            <!-- 时间范围 - 使用BETWEEN -->
-            <if test="sTime != null and eTime != null">
-                AND u.register_date BETWEEN #{sTime} AND #{eTime}
-            </if>
-
-            <!-- 会员条件 -->
-            <if test="userId != null">
-                AND u.user_id = #{userId}
-            </if>
-            <if test="userPhone != null and userPhone != ''">
-                AND u.phone LIKE CONCAT('%', #{userPhone}, '%')
-            </if>
-            <if test="nickName != null and nickName != ''">
-                AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
-            </if>
-            <!-- 部门条件 -->
-            <if test="deptId != null">
-                AND cd.dept_id = #{deptId}
-            </if>
-        </where>
-        GROUP BY u.user_id
-        ORDER BY u.register_date DESC
-    </select>
     <select id="selectWatchLogReportVO" resultType="com.fs.his.vo.WatchLogReportVO">
         <choose>
             <when test="dimension == 'user'">
@@ -1563,6 +1392,315 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </choose>
 
 
+    </select>
+    <select id="selectUserBaseInfo" resultType="com.fs.his.vo.FsUserReportVO">
+        SELECT distinct u.user_id AS userId,
+        u.nick_name AS nickName,
+        CASE
+        WHEN u.`status` = '1' THEN '正常'
+        WHEN u.`status` = '2' THEN '禁止'
+        ELSE '无'
+        END AS status,
+        u.register_date AS registerDate,
+        u.phone,
+        cu.nick_name AS companyUserName,
+        cuu.company_user_id as companyUserId,
+        c.company_name AS companyName
+        <if test='dimension == "dept"'>
+            ,cd.dept_name AS deptName
+        </if>
+        FROM fs_user u
+        inner JOIN fs_user_company_user cuu ON u.user_id = cuu.user_id
+        <if test="sTime != null and eTime != null">
+            left join fs_package_order po on po.user_id=u.user_id
+        </if>
+        LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id
+        LEFT JOIN company c ON cuu.company_id = c.company_id
+        <if test='dimension == "dept"'>
+            LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
+        </if>
+        <if test="trainingCampId != null or periodId != null ">
+            Left Join  fs_course_watch_log watch on watch.user_id= cuu.user_id and watch.project=cuu.project_id
+            left join  fs_user_course_period period on watch.period_id=period.period_id and period.del_flag=0
+            left join  fs_user_course_training_camp camp on period.training_camp_id= camp.training_camp_id and camp.del_flag=0
+        </if>
+        WHERE u.user_id IS NOT NULL
+        <if test="userId != null">
+            AND u.user_id = #{userId}
+        </if>
+        <if test="userPhone != null and userPhone != ''">
+            AND u.phone LIKE CONCAT('%', #{userPhone}, '%')
+        </if>
+        <if test="nickName != null and nickName != ''">
+            AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+        </if>
+        <!-- 部门条件 -->
+        <if test="deptId != null">
+            AND cd.dept_id = #{deptId}
+        </if>
+        <!-- 销售公司条件 -->
+        <if test="companyId != null and companyId != ''">
+            AND c.company_id = #{companyId}
+        </if>
+
+        <if test="project != null">
+            AND cuu.project_id = #{project}
+        </if>
+
+        <!-- 训练营条件 -->
+        <if test="trainingCampId != null">
+            AND camp.training_camp_id = #{trainingCampId}
+        </if>
+
+        <!-- 营期条件 -->
+        <if test="periodId != null">
+            AND period.period_id = #{periodId}
+        </if>
+
+        <!-- 时间范围 - 使用BETWEEN -->
+        <if test="sTime != null and eTime != null">
+            AND po.create_time BETWEEN #{sTime} AND #{eTime}
+        </if>
+    </select>
+    <select id="selectWatchStatsByUserIds" resultType="com.fs.his.vo.FsUserReportVO">
+        SELECT
+            user_id AS userId,
+            company_user_id AS companyUserId,
+            MAX(
+                    COALESCE ( update_time, create_time )) AS lastWatchTime,
+            COUNT( log_id ) AS watchCount,
+            COUNT( DISTINCT period_id ) AS periodCount,
+            CASE
+                ( SELECT log_type FROM fs_course_watch_log w2 WHERE w2.user_id = w1.user_id ORDER BY create_time DESC LIMIT 1 )
+		WHEN '1' THEN
+		'看课中'
+		WHEN '2' THEN
+		'完课'
+		WHEN '3' THEN
+		'待看课'
+		WHEN '4' THEN
+		'看课中断' ELSE '无'
+        END AS watchStatus
+FROM
+	fs_course_watch_log w1
+        WHERE (user_id, company_user_id) IN
+        <foreach collection="userConditions" item="condition" open="(" close=")" separator=",">
+            (#{condition.userId}, #{condition.companyUserId})
+        </foreach>
+        <if test="watchParam.project != null">
+            AND w1.project = #{watchParam.project}
+        </if>
+        <if test="watchParam.periodId != null">
+            AND w1.period_id = #{watchParam.periodId}
+        </if>
+        GROUP BY user_id,company_user_id
+    </select>
+    <select id="selectIntegralStatsByUserIds" resultType="com.fs.his.vo.FsUserReportVO">
+        SELECT
+        user_id AS userId,
+        SUM(integral) AS Integral,
+        ABS(SUM(CASE WHEN integral &lt; 0 THEN integral ELSE 0 END)) AS consumedIntegral
+        FROM fs_user_integral_logs
+        WHERE user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        GROUP BY user_id
+    </select>
+    <select id="selectRedPacketStatsByUserIds" resultType="com.fs.his.vo.FsUserReportVO">
+        SELECT
+        user_id AS userId,
+        company_user_id AS companyUserId,
+        SUM(amount) AS receivedAmount
+        FROM fs_course_red_packet_log
+        WHERE status = 1
+         and (user_id, company_user_id) IN
+        <foreach collection="userConditions" item="condition" open="(" close=")" separator=",">
+            (#{condition.userId}, #{condition.companyUserId})
+        </foreach>
+        <if test="watchParam.periodId != null">
+            AND period_id = #{watchParam.periodId}
+        </if>
+        GROUP BY user_id,company_user_id
+    </select>
+    <select id="selectOrderStatsByUserIds" resultType="com.fs.his.vo.FsUserReportVO">
+        SELECT
+        user_id AS userId,
+        SUM(pay_money) AS orderAmount
+        FROM fs_package_order
+        WHERE status = 3
+        AND user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+        GROUP BY user_id
+    </select>
+    <select id="selectUserBaseData" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT DISTINCT
+        u.user_id AS userId,
+        u.nick_name AS nickName,
+        u.status AS userStatus,
+        u.register_date AS registerDate,
+        cuu.company_user_id AS companyUserId,
+        cu.nick_name AS salesName,
+        c.company_name AS salesCompany,
+        dept.dept_name AS salesDept
+        FROM fs_user u
+        LEFT JOIN fs_user_company_user cuu ON u.user_id = cuu.user_id
+        LEFT JOIN company_user cu ON cuu.company_user_id = cu.user_id
+        LEFT JOIN company c ON cuu.company_id = c.company_id
+        LEFT JOIN company_dept dept ON cu.dept_id = dept.dept_id
+        <if test="(trainingCampId != null and trainingCampId != '') or (periodId != null and periodId != '')">
+            LEFT JOIN fs_course_watch_log watch ON watch.user_id = u.user_id
+            LEFT JOIN fs_user_course_period cp ON watch.period_id = cp.period_id
+            AND cp.del_flag = 0
+            LEFT JOIN fs_user_course_training_camp camp ON cp.training_camp_id = camp.training_camp_id and camp.del_flag=0
+        </if>
+        WHERE u.user_id IS NOT NULL
+        <include refid="commonConditions"/>
+        ORDER BY u.register_date DESC
+    </select>
+    <select id="selectSalesBaseData" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT DISTINCT
+        cu.user_id AS companyUserId,
+        cu.nick_name AS salesName,
+        u.user_id AS userId,
+        cd.dept_name AS salesDept,
+        c.company_name AS salesCompany,
+        COUNT(DISTINCT cuu.user_id) AS userCount,
+        COUNT(DISTINCT CASE WHEN u.status = '1' THEN u.user_id END) AS onlineUserCount
+        FROM company_user cu
+        LEFT JOIN company c ON cu.company_id = c.company_id
+        LEFT JOIN company_dept cd ON cu.dept_id = cd.dept_id
+        LEFT JOIN fs_user_company_user cuu ON cu.user_id = cuu.company_user_id
+        LEFT JOIN fs_user u ON cuu.user_id = u.user_id
+        <if test="(trainingCampId != null and trainingCampId != '') or (periodId != null and periodId != '')">
+            LEFT JOIN fs_course_watch_log watch ON watch.user_id = u.user_id
+            LEFT JOIN fs_user_course_period cp ON watch.period_id = cp.period_id
+            AND cp.del_flag = 0
+            LEFT JOIN fs_user_course_training_camp camp ON cp.training_camp_id = camp.training_camp_id and camp.del_flag=0
+        </if>
+        WHERE cu.user_id IS NOT NULL
+        <include refid="commonConditions"/>
+        GROUP BY cu.user_id, cu.nick_name, cd.dept_name, c.company_name
+        ORDER BY userCount DESC
+    </select>
+    <select id="selectCompanyBaseData" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT DISTINCT
+        c.company_id AS companyId,
+        c.company_name AS salesCompany,
+        cd.dept_id AS deptId,
+        u.user_id AS userId,
+        cd.dept_name AS salesDept,
+        cu.user_id AS companyUserId,
+        COUNT(DISTINCT cu.user_id) AS salesCount,
+        COUNT(DISTINCT cuu.user_id) AS userCount,
+        COUNT(DISTINCT CASE WHEN u.status = '1' THEN cuu.user_id END) AS onlineUserCount
+        FROM company c
+        LEFT JOIN company_dept cd ON c.company_id = cd.company_id
+        LEFT JOIN company_user cu ON cd.dept_id = cu.dept_id
+        LEFT JOIN fs_user_company_user cuu ON cu.user_id = cuu.company_user_id
+        LEFT JOIN fs_user u ON cuu.user_id = u.user_id
+        <if test="(trainingCampId != null and trainingCampId != '') or (periodId != null and periodId != '')">
+            LEFT JOIN fs_course_watch_log watch ON watch.user_id = u.user_id
+            LEFT JOIN fs_user_course_period cp ON watch.period_id = cp.period_id
+            AND cp.del_flag = 0
+            LEFT JOIN fs_user_course_training_camp camp ON cp.training_camp_id = camp.training_camp_id and camp.del_flag=0
+        </if>
+        WHERE c.company_id IS NOT NULL
+        <include refid="commonConditions"/>
+        GROUP BY c.company_id, c.company_name, cd.dept_id, cd.dept_name
+        ORDER BY salesCount DESC, userCount DESC
+    </select>
+    <select id="selectUserWatchDetails" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        wl.user_id AS userId,
+        wl.company_user_id as companyUserId,
+        camp.training_camp_name trainingCampName,
+        cp.period_name periodName ,
+        cv.title videoTitle ,
+        CASE
+        WHEN wl.log_type = '1' THEN '看课中'
+        WHEN wl.log_type = '2' THEN '完课'
+        WHEN wl.log_type = '3' THEN '待看课'
+        WHEN wl.log_type = '4' THEN '看课中断'
+        ELSE '无'
+        END AS watchStatus,
+        wl.duration,
+        wl.create_time courseTime,
+        wl.finish_time finishTime,
+        COUNT(DISTINCT log_id) AS totalLogCount,
+        COUNT(DISTINCT CASE WHEN wl.log_type = '2' THEN wl.log_id END) AS finishedCount,
+        COUNT(DISTINCT CASE WHEN wl.log_type IN ('1', '3', '4') THEN wl.log_id END) AS unfinishedCount,
+        (SELECT COUNT(DISTINCT concat(uc.userId, '_', uc.companyUserId))
+        FROM (
+        <foreach collection="userConditions" item="condition"  separator="UNION ALL">
+            SELECT #{condition.userId} AS userId, #{condition.companyUserId} AS companyUserId
+        </foreach>
+        ) uc
+        ) - COUNT(DISTINCT concat(wl.user_id, '_', wl.company_user_id)) AS notWatchedCount
+        FROM fs_course_watch_log wl
+        LEFT JOIN fs_user_course_video cv ON wl.video_id = cv.video_id
+        LEFT JOIN fs_user_course_period cp ON wl.period_id = cp.period_id AND cp.del_flag = 0
+        LEFT JOIN fs_user_course_training_camp camp ON cp.training_camp_id = camp.training_camp_id
+        WHERE wl.send_type = 1
+        and (wl.user_id, wl.company_user_id) IN
+        <foreach collection="userConditions" item="condition" open="(" close=")" separator=",">
+            (#{condition.userId}, #{condition.companyUserId})
+        </foreach>
+        <if test="watchParam.trainingCampId != null">
+            AND camp.training_camp_id = #{watchParam.trainingCampId}
+        </if>
+        <if test="watchParam.periodId != null">
+            AND cp.period_id = #{watchParam.periodId}
+        </if>
+        <if test="watchParam.sTime != null and watchParam.eTime != null">
+            AND wl.create_time BETWEEN #{watchParam.sTime} AND #{watchParam.eTime}
+        </if>
+    </select>
+    <select id="selectRedPacketStats" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        wl.user_id AS userId,
+        SUM(rp.amount) AS totalAmount
+        FROM fs_course_watch_log wl
+        JOIN fs_course_red_packet_log rp ON wl.log_id = rp.watch_log_id
+        WHERE wl.send_type = 1
+        AND wl.user_id IN
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+        GROUP BY wl.user_id
+    </select>
+    <select id="selectOrderStats" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        user_id AS userId,
+        COUNT(DISTINCT CASE WHEN status = 3 THEN order_id END) AS historyOrderCount
+        FROM fs_package_order
+        WHERE user_id IN
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+        GROUP BY user_id
+    </select>
+    <select id="selectAnswerStats" resultType="com.fs.his.vo.WatchLogReportVO">
+        SELECT
+        u.user_id AS userId,
+        CASE WHEN l.log_id IS NOT NULL THEN '已答题' ELSE '未答题' END AS answerStatus,
+        (SELECT COUNT(1)
+        FROM fs_user
+        WHERE user_id IN
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+        AND user_id NOT IN (SELECT user_id FROM fs_course_answer_logs)
+        ) AS notAnsweredCount
+        FROM fs_user u
+        LEFT JOIN fs_course_answer_logs l ON u.user_id = l.user_id
+        WHERE u.user_id IN
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+        GROUP BY u.user_id, l.log_id;
     </select>
     <sql id="commonConditions">
         <!-- 销售公司 -->
@@ -1597,7 +1735,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         <!-- 时间范围 -->
         <if test="sTime != null and eTime != null">
-            AND watch_log.create_time BETWEEN #{startTime} AND #{endTime}
+            AND watch.create_time BETWEEN #{startTime} AND #{endTime}
         </if>
 
         <!-- 会员ID -->