Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

三七 před 1 týdnem
rodič
revize
c41bfb0bdc
31 změnil soubory, kde provedl 2221 přidání a 18 odebrání
  1. 103 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchCommentController.java
  2. 64 0
      fs-admin/src/main/java/com/fs/qw/FsCourseTask.java
  3. 240 0
      fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java
  4. 17 0
      fs-company/src/main/java/com/fs/company/controller/qw/qw/QwQwWorkTaskController.java
  5. 1 0
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  6. 2 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  7. 54 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchComment.java
  8. 80 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java
  9. 20 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentListParam.java
  10. 32 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentSaveParam.java
  11. 87 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java
  12. 127 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java
  13. 38 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchCommentVO.java
  14. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  15. 19 0
      fs-service/src/main/java/com/fs/qw/mapper/QwWorkTaskMapper.java
  16. 23 0
      fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java
  17. 4 0
      fs-service/src/main/java/com/fs/qw/service/IQwWorkTaskService.java
  18. 3 3
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  19. 75 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java
  20. 22 0
      fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java
  21. 108 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml
  22. 394 0
      fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml
  23. 162 0
      fs-service/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml
  24. 60 0
      fs-service/src/main/resources/mapper/statis/FsStatisPeriodWatchMapper.xml
  25. 86 0
      fs-service/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml
  26. 161 0
      fs-service/src/main/resources/mapper/statis/FsStatsMemberDailyMapper.xml
  27. 4 1
      fs-user-app/pom.xml
  28. 42 0
      fs-user-app/src/main/java/com/fs/app/config/WebSocketConfig.java
  29. 46 14
      fs-user-app/src/main/java/com/fs/app/controller/CourseH5Controller.java
  30. 36 0
      fs-user-app/src/main/java/com/fs/websocket/bean/SendMsgVO.java
  31. 108 0
      fs-user-app/src/main/java/com/fs/websocket/service/WebSocketServer.java

+ 103 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchCommentController.java

@@ -0,0 +1,103 @@
+package com.fs.course.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.service.IFsCourseWatchCommentService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 看课评论Controller
+ * 
+ * @author fs
+ * @date 2025-05-26
+ */
+@RestController
+@RequestMapping("/course/courseWatchComment")
+public class FsCourseWatchCommentController extends BaseController
+{
+    @Autowired
+    private IFsCourseWatchCommentService fsCourseWatchCommentService;
+
+    /**
+     * 查询看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseWatchComment fsCourseWatchComment)
+    {
+        startPage();
+        List<FsCourseWatchComment> list = fsCourseWatchCommentService.selectFsCourseWatchCommentList(fsCourseWatchComment);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:export')")
+    @Log(title = "看课评论", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseWatchComment fsCourseWatchComment)
+    {
+        List<FsCourseWatchComment> list = fsCourseWatchCommentService.selectFsCourseWatchCommentList(fsCourseWatchComment);
+        ExcelUtil<FsCourseWatchComment> util = new ExcelUtil<FsCourseWatchComment>(FsCourseWatchComment.class);
+        return util.exportExcel(list, "看课评论数据");
+    }
+
+    /**
+     * 获取看课评论详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:query')")
+    @GetMapping(value = "/{commentId}")
+    public AjaxResult getInfo(@PathVariable("commentId") Long commentId)
+    {
+        return AjaxResult.success(fsCourseWatchCommentService.selectFsCourseWatchCommentByCommentId(commentId));
+    }
+
+    /**
+     * 新增看课评论
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:add')")
+    @Log(title = "看课评论", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseWatchComment fsCourseWatchComment)
+    {
+        return toAjax(fsCourseWatchCommentService.insertFsCourseWatchComment(fsCourseWatchComment));
+    }
+
+    /**
+     * 修改看课评论
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:edit')")
+    @Log(title = "看课评论", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseWatchComment fsCourseWatchComment)
+    {
+        return toAjax(fsCourseWatchCommentService.updateFsCourseWatchComment(fsCourseWatchComment));
+    }
+
+    /**
+     * 删除看课评论
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:remove')")
+    @Log(title = "看课评论", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{commentIds}")
+    public AjaxResult remove(@PathVariable Long[] commentIds)
+    {
+        return toAjax(fsCourseWatchCommentService.deleteFsCourseWatchCommentByCommentIds(commentIds));
+    }
+}

+ 64 - 0
fs-admin/src/main/java/com/fs/qw/FsCourseTask.java

@@ -0,0 +1,64 @@
+package com.fs.qw;
+
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.qw.service.IQwWorkTaskService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 后台统计相关 定时任务
+ */
+@Slf4j
+@Service("qwFsCourseTask")
+public class FsCourseTask {
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IQwWorkTaskService qwWorkTaskService;
+
+    /**
+     * 添加企微观看日志
+     * @throws Exception
+     */
+    public void addQwWatchLog() throws Exception
+    {
+        fsCourseWatchLogService.addCourseWatchLogDay();
+    }
+
+    /**
+     * 企微任务定时更新
+     * @throws Exception
+     */
+    public void qwWorkTask1() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByCourse4();
+        qwWorkTaskService.addQwWorkByCourseLastTime();
+    }
+    /**
+     * 企微待看课和先导课
+     * @throws Exception
+     */
+    public void qwWorkTask2() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByCourse();
+        qwWorkTaskService.addQwWorkByFirstCourse();
+    }
+    /**
+     * 用户大小转
+     * @throws Exception
+     */
+    public void qwWorkTask3() throws Exception
+    {
+        qwWorkTaskService.addQwWorkByConversionDay();
+    }
+    /**
+     * 删除过期数据
+     * @throws Exception
+     */
+    public void qwWorkTask4() throws Exception
+    {
+        qwWorkTaskService.delQwWorkTaskByOver();
+    }
+
+}

+ 240 - 0
fs-company/src/main/java/com/fs/company/controller/company/IndexStatisticsController.java

@@ -0,0 +1,240 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.ServletUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+import com.fs.statis.StatisticsRedisConstant;
+import com.fs.statis.dto.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.fs.statis.StatisticsRedisConstant.*;
+
+/**
+ * 首页-统计
+ */
+@RestController
+@RequestMapping("/index/statistics")
+public class IndexStatisticsController {
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 分析概览
+     */
+    @PostMapping("/analysisPreview")
+    public R analysisPreview(@RequestBody AnalysisPreviewQueryDTO param){
+        AnalysisPreviewDTO analysisPreviewDTO = null;
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+
+        if(userType == null) {
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCompanyId(loginUser.getCompany().getCompanyId());
+        analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,param.getCompanyId()));
+
+        return R.ok().put("data",analysisPreviewDTO);
+    }
+
+
+    /**
+     * 消费余额
+     */
+    @GetMapping("/rechargeComsumption")
+    public R rechargeComsumption(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE,companyId));
+        return R.ok().put("data", consumptionBalanceDataDTO);
+    }
+
+    /**
+     * 获取统计流量
+     * @return
+     */
+    @GetMapping("/trafficLog")
+    public R getTrafficLog(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        TrafficLogDTO trafficLogDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_TRAFFIC_LOG,companyId));
+        return R.ok().put("data",trafficLogDTO);
+    }
+
+    /**
+     * 观看趋势
+     */
+    @PostMapping("/watchEndPlayTrend")
+    public R watchEndPlayTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 经销商会员观看
+     */
+    @PostMapping("/deaMemberTopTen")
+    public R deaMemberTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+
+        if(type == null) {
+            type = 0;
+        }
+        if(userType == null){
+            userType = 0;
+        }
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,param.getCompanyId()));
+        if(deaMemberTopTenDTOS == null){
+            deaMemberTopTenDTOS = new ArrayList<>();
+        }
+        return R.ok().put("data", deaMemberTopTenDTOS);
+    }
+
+    /**
+     * 奖励金额top10
+     */
+    @PostMapping("/rewardMoneyTopTen")
+    public R rewardMoneyTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer dataType = param.getDataType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId()));
+        return R.ok().put("data", rewardMoneyTopTenDTOS);
+    }
+
+    /**
+     * 答题红包金额趋势图
+     */
+    @PostMapping("/rewardMoneyTrend")
+    public R rewardMoneyTrend(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId()));
+        return R.ok().put("data", rewardMoneyTrendDTOS);
+    }
+
+    /**
+     * 课程观看top10
+     */
+    @PostMapping("/watchCourseTopTen")
+    public R watchCourseTopTen(@RequestBody AnalysisPreviewQueryDTO param){
+        Integer type = param.getType();
+        String sort = param.getSort();
+        Integer statisticalType = param.getStatisticalType();
+        Integer userType = param.getUserType();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+
+        List<CourseStatsDTO> courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
+        return R.ok().put("data", courseStatsDTOS);
+    }
+
+    /**
+     * 数据概览
+     */
+    @GetMapping("/dealerAggregated")
+    public R dealerAggregated(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED,companyId));
+
+        return R.ok().put("data",dealerAggregatedDTO);
+    }
+
+    /**
+     * 短信余额
+     */
+    @GetMapping("/smsBalance")
+    public R smsBalance(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        Long smsBalance = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE,companyId));
+
+        return R.ok().put("data", smsBalance);
+    }
+
+
+    /**
+     * 授权信息
+     */
+    @GetMapping("/authorizationInfo")
+    public R authorizationInfo(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        AuthorizationInfoDTO authorizationInfoDTO = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO,companyId));
+
+        return R.ok().put("data", authorizationInfoDTO);
+    }
+
+
+    /**
+     * 当月订单数统计
+     * @return
+     */
+    @GetMapping("/thisMonthOrderCount")
+    public R thisMonthOrderCount(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        R result = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT,companyId));
+        return result;
+    }
+
+    /**
+     * 当月收益统计
+     * @return
+     */
+    @GetMapping("/thisMonthRecvCount")
+    public R thisMonthRecvCount(){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        R result = redisCache.getCacheObject(String.format("%s:%d",StatisticsRedisConstant.THIS_MONTH_RECV_COUNT,companyId));
+        return result;
+    }
+}

+ 17 - 0
fs-company/src/main/java/com/fs/company/controller/qw/qw/QwQwWorkTaskController.java

@@ -14,6 +14,7 @@ import com.fs.framework.service.TokenService;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -58,6 +59,22 @@ public class QwQwWorkTaskController extends BaseController
         return getDataTable(list);
     }
 
+
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwWorkTaskListParam qwWorkTask)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        qwWorkTask.setCompanyId(loginUser.getCompany().getCompanyId());
+        if (qwWorkTask.getSTime()==null||qwWorkTask.getETime()==null){
+            return new TableDataInfo();
+        }
+        List<QwWorkTaskAllListVO> list = qwWorkTaskService.selectQwWorkTaskAllListVO(qwWorkTask);
+
+        return getDataTable(list);
+    }
+
+
     /**
      * 导出企微任务看板列表
      */

+ 1 - 0
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -229,6 +229,7 @@ public class qwTask {
         }
     }
 
+
     // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
     private void processAndInsertQwSopLogs(List<QwSopLogsDoSendListTVO> logsByJsApiNotExtId) {
         // 定义批量插入的大小

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

@@ -30,6 +30,8 @@ public class CourseConfig implements Serializable {
     private List<DisabledTimeVo> disabledTimeList;//充值手续费百分比
     private Integer completionMode; // 完课模式
     private Integer minutesNum; //多少分钟算完课
+    private Integer isOpenComment; //是否开启评论
+    private Integer viewCommentNum; // 查看历史评论数量
 
 
     @Data

+ 54 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseWatchComment.java

@@ -0,0 +1,54 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 看课评论对象 fs_course_watch_comment
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseWatchComment extends BaseEntity{
+
+    /** 评论id */
+    private Long commentId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 用户类型,1-管理员,2-用户 */
+    @Excel(name = "用户类型,1-管理员,2-用户")
+    private Integer userType;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 视频id */
+    @Excel(name = "视频id")
+    private Long videoId;
+
+    /** 评论类型 1:评论,2:回复 */
+    @Excel(name = "评论类型 1:评论,2:回复")
+    private Integer type;
+
+    /** 父评论id */
+    @Excel(name = "父评论id")
+    private Long parentId;
+
+    /** 评论内容 */
+    @Excel(name = "评论内容")
+    private String content;
+
+    /** 是否是撤回的消息,1-是,0-否 */
+    @Excel(name = "是否是撤回的消息,1-是,0-否")
+    private Integer isRevoke;
+
+
+}

+ 80 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java

@@ -0,0 +1,80 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.param.FsCourseWatchCommentListParam;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+import org.apache.ibatis.annotations.Update;
+
+/**
+ * 看课评论Mapper接口
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+public interface FsCourseWatchCommentMapper extends BaseMapper<FsCourseWatchComment>{
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 看课评论集合
+     */
+    List<FsCourseWatchComment> selectFsCourseWatchCommentList(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 新增看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    int insertFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 修改看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    int updateFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 删除看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 结果
+     */
+    int deleteFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 批量删除看课评论
+     *
+     * @param commentIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseWatchCommentByCommentIds(Long[] commentIds);
+
+    /**
+     * 撤销评论
+     * @param commentId 评论id
+     * @return int,1-是,0-否
+     */
+    @Update("update fs_course_watch_comment set is_revoke = 1 where comment_id = #{commentId}")
+    int revokeH5CourseWatchComment(Long commentId);
+
+    /**
+     * h5查询评论列表
+     * @param param 入参
+     * @return list
+     */
+    List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param);
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentListParam.java

@@ -0,0 +1,20 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "评论列表查询参数")
+public class FsCourseWatchCommentListParam{
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "不传,通过配置获取")
+    private Integer listNum;
+
+}

+ 32 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentSaveParam.java

@@ -0,0 +1,32 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 保存看课评论入参
+ */
+@Data
+@ApiModel(value = "保存看课评论入参")
+public class FsCourseWatchCommentSaveParam{
+
+    @ApiModelProperty(value = "用户id,不传")
+    private Long userId;
+
+    @ApiModelProperty(value = "用户类型,1-管理员,2-用户")
+    private Integer userType;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "评论类型 1:评论,2:回复,目前没有回复功能,默认传1就行了")
+    private Integer type;
+
+    @ApiModelProperty(value = "评论内容")
+    private String content;
+
+}

+ 87 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java

@@ -0,0 +1,87 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.param.FsCourseWatchCommentListParam;
+import com.fs.course.param.FsCourseWatchCommentSaveParam;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+
+/**
+ * 看课评论Service接口
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+public interface IFsCourseWatchCommentService extends IService<FsCourseWatchComment>{
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 看课评论集合
+     */
+    List<FsCourseWatchComment> selectFsCourseWatchCommentList(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 新增看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    int insertFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 修改看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    int updateFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment);
+
+    /**
+     * 批量删除看课评论
+     *
+     * @param commentIds 需要删除的看课评论主键集合
+     * @return 结果
+     */
+    int deleteFsCourseWatchCommentByCommentIds(Long[] commentIds);
+
+    /**
+     * 删除看课评论信息
+     *
+     * @param commentId 看课评论主键
+     * @return 结果
+     */
+    int deleteFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 保存h5的评论数据
+     * @param param 入参
+     * @return
+     */
+    R saveH5CourseWatchComment(FsCourseWatchCommentSaveParam param);
+
+    /**
+     * 撤销评论
+     * @param commentId 评论id
+     * @return R
+     */
+    R revokeH5CourseWatchComment(Long commentId);
+
+    /**
+     * h5查询评论列表
+     * @param param 入参
+     * @return list
+     */
+    List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param);
+
+}

+ 127 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java

@@ -0,0 +1,127 @@
+package com.fs.course.service.impl;
+
+import java.util.List;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.param.FsCourseWatchCommentListParam;
+import com.fs.course.param.FsCourseWatchCommentSaveParam;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import com.fs.course.mapper.FsCourseWatchCommentMapper;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.service.IFsCourseWatchCommentService;
+
+/**
+ * 看课评论Service业务层处理
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+@Service
+public class FsCourseWatchCommentServiceImpl extends ServiceImpl<FsCourseWatchCommentMapper, FsCourseWatchComment> implements IFsCourseWatchCommentService {
+
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    @Override
+    public FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId)
+    {
+        return baseMapper.selectFsCourseWatchCommentByCommentId(commentId);
+    }
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 看课评论
+     */
+    @Override
+    public List<FsCourseWatchComment> selectFsCourseWatchCommentList(FsCourseWatchComment fsCourseWatchComment)
+    {
+        return baseMapper.selectFsCourseWatchCommentList(fsCourseWatchComment);
+    }
+
+    /**
+     * 新增看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment)
+    {
+        fsCourseWatchComment.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsCourseWatchComment(fsCourseWatchComment);
+    }
+
+    /**
+     * 修改看课评论
+     *
+     * @param fsCourseWatchComment 看课评论
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseWatchComment(FsCourseWatchComment fsCourseWatchComment)
+    {
+        fsCourseWatchComment.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsCourseWatchComment(fsCourseWatchComment);
+    }
+
+    /**
+     * 批量删除看课评论
+     *
+     * @param commentIds 需要删除的看课评论主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseWatchCommentByCommentIds(Long[] commentIds)
+    {
+        return baseMapper.deleteFsCourseWatchCommentByCommentIds(commentIds);
+    }
+
+    /**
+     * 删除看课评论信息
+     *
+     * @param commentId 看课评论主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseWatchCommentByCommentId(Long commentId)
+    {
+        return baseMapper.deleteFsCourseWatchCommentByCommentId(commentId);
+    }
+
+    @Override
+    public R saveH5CourseWatchComment(FsCourseWatchCommentSaveParam param) {
+        FsCourseWatchComment fsCourseWatchComment = new FsCourseWatchComment();
+        BeanUtils.copyProperties(param, fsCourseWatchComment);
+        fsCourseWatchComment.setCreateTime(DateUtils.getNowDate());
+        int i = baseMapper.insertFsCourseWatchComment(fsCourseWatchComment);
+        if (i > 0){
+            return R.ok();
+        } else {
+            return R.error();
+        }
+    }
+
+    @Override
+    public R revokeH5CourseWatchComment(Long commentId) {
+        int i = baseMapper.revokeH5CourseWatchComment(commentId);
+        if (i > 0){
+            return R.ok();
+        } else {
+            return R.error();
+        }
+    }
+
+    @Override
+    public List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param) {
+        return baseMapper.selectH5CourseWatchComments(param);
+    }
+
+}

+ 38 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchCommentVO.java

@@ -0,0 +1,38 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.util.Date;
+
+@Data
+@ApiModel(value = "看课评论列表")
+public class FsCourseWatchCommentVO{
+
+    @ApiModelProperty(value = "评论id")
+    private Long commentId;
+
+    @ApiModelProperty(value = "用户id")
+    private Long userId;
+
+    @ApiModelProperty(value = "用户类型,1-管理员,2-用户")
+    private Integer userType;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "评论类型")
+    private Integer type;
+
+    @ApiModelProperty(value = "评论内容")
+    private String content;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+}

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

@@ -363,4 +363,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     @Select("select is_repeat from qw_external_contact where user_id=${userId} limit 1")
     Integer selectQwIsRepeat(Long id);
+
+    @Select("select * from qw_external_contact where fs_user_id = #{fsUserId}")
+    List<QwExternalContact> selectQwExternalContactListVOByfsUserId(Long fsUserId);
 }

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

@@ -3,6 +3,7 @@ package com.fs.qw.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -98,4 +99,22 @@ public interface QwWorkTaskMapper extends BaseMapper<QwWorkTask>{
             "</script>"
     })
     void updateQwWorkTaskStatus(@Param("overIds")List<Long> overIds);
+
+    @Select({"<script> " +
+            "select t.qw_user_id,qw.qw_user_name,ANY_VALUE(cu.nick_name) companyUserName,DATE(t.create_time) createTime,\n" +
+            "\t\tSUM(CASE WHEN t.status = 0 THEN 1 ELSE 0 END) AS status0,\n" +
+            "    SUM(CASE WHEN t.status = 1 THEN 1 ELSE 0 END) AS status1,\n" +
+            "    SUM(CASE WHEN t.status = 2 THEN 1 ELSE 0 END) AS status2,\n" +
+            "    SUM(CASE WHEN t.status = 3 THEN 1 ELSE 0 END) AS status3\n" +
+            "\t\tfrom qw_work_task t  LEFT JOIN qw_user qw ON qw.id = t.qw_user_id LEFT JOIN company_user cu on cu.user_id=t.company_user_id  where  t.company_id=#{companyId} " +
+            "    <if test=\"companyUserId != null \"> and t.company_user_id = #{companyUserId}</if>\n" +
+            "    <if test=\"companyUserName != null and companyUserName != ''\"> and cu.nick_name = #{companyUserName}</if>\n" +
+            "    <if test=\"qwUserName != null and qwUserName != ''\"> and qw.qw_user_name = #{qwUserName}</if>\n" +
+            " " +
+            "    <if test=\"sTime != null \">  and DATE(t.create_time) &gt;= DATE(#{sTime})</if>\n" +
+            "    <if test=\"eTime != null \">  and DATE(t.create_time) &lt;= DATE(#{eTime})</if>\n" +
+            " " +
+            " GROUP BY t.qw_user_id,DATE(t.create_time) "+
+            "</script>"})
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/qw/param/QwWorkTaskListParam.java

@@ -1,8 +1,12 @@
 package com.fs.qw.param;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.util.Date;
+
 @Data
 public class QwWorkTaskListParam {
     private Long id;
@@ -40,4 +44,23 @@ public class QwWorkTaskListParam {
     private Long companyUserId;
 
     private String title;
+
+    private String companyUserName;
+
+
+
+    private Long deptId;
+
+    private String qwUserName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date eTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date sTime;
+
+    private Long pageNum;
+    private Long pageSize;
+
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/qw/service/IQwWorkTaskService.java

@@ -3,6 +3,7 @@ package com.fs.qw.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.qw.domain.QwWorkTask;
 import com.fs.qw.param.QwWorkTaskListParam;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 
 import java.util.List;
@@ -75,4 +76,7 @@ public interface IQwWorkTaskService extends IService<QwWorkTask>{
 
     void delQwWorkTaskByOver();
 
+    void addQwWorkByCourseLastTime();
+
+    List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask);
 }

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

@@ -4121,8 +4121,8 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         logger.info("开始同步");
         for (QwOptionsVO qwOptionsVO : qwOptionsVOS) {
             String corpId = qwOptionsVO.getCorpId();
-            System.out.println("同步的"+corpId);
-            logger.info("同步的"+corpId);
+//            System.out.println("同步的"+corpId);
+//            logger.info("同步的"+corpId);
             executor.execute(() ->  qwExternalContactSyncByCorpId(corpId));
         }
         executor.shutdown();
@@ -4738,7 +4738,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         logger.info("开始外部联系人存redis");
         List<QwUser> qwUser = qwUserMapper.getQwUserAllKey();
         for (QwUser user : qwUser) {
-            logger.info("外部联系人存redis:"+user.getId());
+//            logger.info("外部联系人存redis:"+user.getId());
             QwExternalContactListResult contactListResult = qwApiService.getExternalcontactList(user.getQwUserId(), user.getCorpId());
             if (contactListResult.getErrcode() == 0) {
                 List<String> externalUserid = contactListResult.getExternal_userid();

+ 75 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwWorkTaskServiceImpl.java

@@ -12,6 +12,7 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwWorkTaskMapper;
 import com.fs.qw.param.QwWorkTaskListParam;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.qw.vo.QwWorkTaskAllListVO;
 import com.fs.qw.vo.QwWorkTaskListVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -20,6 +21,7 @@ import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
@@ -319,6 +321,79 @@ public class QwWorkTaskServiceImpl extends ServiceImpl<QwWorkTaskMapper, QwWorkT
 
     }
 
+    @Override
+    public void addQwWorkByCourseLastTime() {
+        List<QwSop> qwSops = qwSopMapper.selectQwSopByIsRating();
+        List<Long> extIds = qwWorkTaskMapper.selectQwWorkTaskByType();
+        SimpleDateFormat sdf = new SimpleDateFormat("HHmm"); // 24小时制,如 1100
+        String timeStr = sdf.format(new Date());
+        int lastTime = Integer.parseInt(timeStr);
+        Set<Long> extIdSet = new HashSet<>(extIds);
+        LocalDate today = LocalDate.now();
+        for (QwSop qwSop : qwSops) {
+            if (qwSop.getCourseDay()==null){
+                continue;
+            }
+            Integer courseDay=qwSop.getCourseDay()-1;
+            List<SopUserLogsInfo> qwSopLogs = sopUserLogsInfoMapper.selectSopUserLogsInfoBySopId(qwSop.getId());
+            if (qwSopLogs==null || qwSopLogs.isEmpty()) {
+                continue;
+            }
+
+            List<FsCourseWatchLogTaskVO> fsCourseWatchLogs = courseWatchLogMapper.selectFsCourseWatchLogByDaySopId3LastTime(qwSop.getId(),lastTime);
+            if (fsCourseWatchLogs==null || fsCourseWatchLogs.isEmpty()) {
+                continue;
+            }
+            List<QwWorkTask> qwWorkTasks = new ArrayList<>();
+            for (SopUserLogsInfo qwSopLog : qwSopLogs) {
+                Map<Long, FsCourseWatchLogTaskVO> map = fsCourseWatchLogs.stream()
+                        .collect(Collectors.toMap(FsCourseWatchLogTaskVO::getQwExternalContactId, data -> data,(oldValue, newValue) -> newValue ));
+                FsCourseWatchLogTaskVO fsCourseWatchLog = map.get(qwSopLog.getExternalId());
+
+                if (fsCourseWatchLog == null) {
+                    continue;
+                }
+                if (extIdSet.contains(fsCourseWatchLog.getQwExternalContactId())) {
+                    continue;
+                }
+                String createTime = qwSopLog.getCreateTime();
+                LocalDate createDate = LocalDate.parse(createTime.substring(0, 10), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+
+                Integer day = (Math.toIntExact(ChronoUnit.DAYS.between(createDate, today))) + 1 - courseDay;
+                if (day<=7){
+                    continue;
+                }
+                Integer score = getQwWorkCourseScore(fsCourseWatchLog.getLogType(), day,fsCourseWatchLog.getLevel());
+                if (score==0){
+                    continue;
+                }
+                QwWorkTask qwWorkTask = new QwWorkTask();
+                qwWorkTask.setCreateTime(DateUtils.getNowDate());
+                qwWorkTask.setExtId(fsCourseWatchLog.getQwExternalContactId());
+                qwWorkTask.setCompanyId(fsCourseWatchLog.getCompanyId());
+                qwWorkTask.setCompanyUserId(fsCourseWatchLog.getCompanyUserId());
+                qwWorkTask.setSopId(qwSop.getId());
+                qwWorkTask.setQwUserId(fsCourseWatchLog.getQwUserId());
+                qwWorkTask.setType(2);
+                qwWorkTask.setStatus(0);
+                qwWorkTask.setTitle("第"+day+"天"+"待看课");
+                qwWorkTask.setScore(score);
+                qwWorkTasks.add(qwWorkTask);
+
+            }
+            if (!qwWorkTasks.isEmpty()){
+
+                qwWorkTaskMapper.insertQwWorkTaskBatch(qwWorkTasks);
+            }
+
+        }
+    }
+
+    @Override
+    public List<QwWorkTaskAllListVO> selectQwWorkTaskAllListVO(QwWorkTaskListParam qwWorkTask) {
+        return qwWorkTaskMapper.selectQwWorkTaskAllListVO(qwWorkTask);
+    }
+
     private void addQwWorkTask(LocalDate today, Integer day, QwSop qwSop, String title,Map<Integer, Integer> map) {
         if (day>7){
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

+ 22 - 0
fs-service/src/main/java/com/fs/qw/vo/QwWorkTaskAllListVO.java

@@ -0,0 +1,22 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+@Data
+public class QwWorkTaskAllListVO {
+    /** 外部联系人id */
+
+    private String qwUserName;
+
+    private String companyUserName;
+
+    private Integer status0;
+
+    private Integer status1;
+
+    private Integer status2;
+
+    private Integer status3;
+
+    private String createTime;
+}

+ 108 - 0
fs-service/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsCourseWatchCommentMapper">
+
+    <resultMap type="FsCourseWatchComment" id="FsCourseWatchCommentResult">
+        <result property="commentId"    column="comment_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="userType"    column="user_type"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="type"    column="type"    />
+        <result property="parentId"    column="parent_id"    />
+        <result property="content"    column="content"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="isRevoke"    column="is_revoke"    />
+    </resultMap>
+
+    <sql id="selectFsCourseWatchCommentVo">
+        select comment_id, user_id, user_type, course_id, video_id, type, parent_id, content, create_time, update_time, is_revoke from fs_course_watch_comment
+    </sql>
+
+    <select id="selectFsCourseWatchCommentList" parameterType="FsCourseWatchComment" resultMap="FsCourseWatchCommentResult">
+        <include refid="selectFsCourseWatchCommentVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="userType != null "> and user_type = #{userType}</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="type != null "> and type = #{type}</if>
+            <if test="parentId != null "> and parent_id = #{parentId}</if>
+            <if test="content != null  and content != ''"> and content = #{content}</if>
+            <if test="isRevoke != null "> and is_revoke = #{isRevoke}</if>
+        </where>
+    </select>
+
+    <select id="selectFsCourseWatchCommentByCommentId" parameterType="Long" resultMap="FsCourseWatchCommentResult">
+        <include refid="selectFsCourseWatchCommentVo"/>
+        where comment_id = #{commentId}
+    </select>
+
+    <insert id="insertFsCourseWatchComment" parameterType="FsCourseWatchComment" useGeneratedKeys="true" keyProperty="commentId">
+        insert into fs_course_watch_comment
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="userType != null">user_type,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="type != null">type,</if>
+            <if test="parentId != null">parent_id,</if>
+            <if test="content != null">content,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="isRevoke != null">is_revoke,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="userType != null">#{userType},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="type != null">#{type},</if>
+            <if test="parentId != null">#{parentId},</if>
+            <if test="content != null">#{content},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="isRevoke != null">#{isRevoke},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCourseWatchComment" parameterType="FsCourseWatchComment">
+        update fs_course_watch_comment
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="userType != null">user_type = #{userType},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="type != null">type = #{type},</if>
+            <if test="parentId != null">parent_id = #{parentId},</if>
+            <if test="content != null">content = #{content},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="isRevoke != null">is_revoke = #{isRevoke},</if>
+        </trim>
+        where comment_id = #{commentId}
+    </update>
+
+    <delete id="deleteFsCourseWatchCommentByCommentId" parameterType="Long">
+        delete from fs_course_watch_comment where comment_id = #{commentId}
+    </delete>
+
+    <delete id="deleteFsCourseWatchCommentByCommentIds" parameterType="String">
+        delete from fs_course_watch_comment where comment_id in
+        <foreach item="commentId" collection="array" open="(" separator="," close=")">
+            #{commentId}
+        </foreach>
+    </delete>
+
+    <select id="selectH5CourseWatchComments" resultType="com.fs.course.vo.FsCourseWatchCommentVO">
+        select comment_id, user_id, user_type, course_id, video_id, type, content, create_time from fs_course_watch_comment
+        <where>
+           and is_revoke = 0
+        </where>
+        order by create_time desc
+    </select>
+
+</mapper>

+ 394 - 0
fs-service/src/main/resources/mapper/statis/ConsumptionBalanceMapper.xml

@@ -0,0 +1,394 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.ConsumptionBalanceMapper">
+
+
+    <select id="rechargeConsumption" resultType="com.fs.statis.dto.ConsumptionBalanceDataDTO">
+        select
+            14371277 AS balance,
+            103100 AS today_comsumption,
+            195280 AS yesterday_comsumption
+    </select>
+    <select id="dealerAggregated" resultType="com.fs.statis.dto.DealerAggregatedDTO">
+        SELECT
+                (SELECT COUNT(*) FROM COMPANY) AS dealder_count,
+                (SELECT COUNT(*) FROM COMPANY_USER) AS group_mgr_count,
+                (SELECT COUNT(*) FROM FS_USER) AS member_count,
+                (SELECT COUNT(*) FROM FS_USER WHERE STATUS=1) AS normal_num,
+                (SELECT COUNT(*) FROM FS_USER WHERE STATUS=0) AS black_num,
+                (select COUNT(*) FROM qw_user) AS qw_member_num
+    </select>
+    <select id="analysisPreview" resultType="com.fs.statis.dto.AnalysisPreviewDTO">
+        -- 观看人数
+        select (select count(*) from fs_course_watch_log where create_time between #{startTime} and #{endTime} group by user_id) as watch_count,
+-- 完播人数
+               (select count(*) from fs_course_watch_log where finish_time is not null and create_time between #{startTime} and #{endTime} group by user_id)
+                                                                                                                                                     as completed_user_count,
+-- 观看次数
+               (select count(*) from fs_course_watch_log where create_time between #{startTime} and #{endTime}) as watch_count,
+-- 完播次数
+               (select count(*) from fs_course_watch_log where create_time between #{startTime} and #{endTime}) as completed_count,
+-- 答题人数
+               (select count(*) from fs_course_answer_logs where create_time between #{startTime} and #{endTime}) as answer_member_count,
+-- 正确人数
+               (select count(*) from fs_course_answer_logs where is_right=1 AND create_time between #{startTime} and #{endTime}) as correct_user_count,
+-- 答题红包个数
+               (select 0) as reward_type,
+               (select 0) as reward_money
+    </select>
+    <select id="queryWatchCount" resultType="java.lang.Long">
+        select count(log_id) from fs_course_watch_log
+        <where>
+            <if test="userType != null">
+                and send_type=${userType}
+            </if>
+            <if test="startTime != null and endTime != null">
+                and create_time between #{startTime} and #{endTime}
+            </if>
+            <if test="companyId != null">
+                and company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="queryCompletedUserCount" resultType="java.lang.Long">
+        select count(DISTINCT user_id) from fs_course_watch_log
+        <where>
+            finish_time is not null
+            <if test="userType != null">
+                and send_type=${userType}
+            </if>
+            <if test="startTime != null and endTime != null">
+                and create_time between #{startTime} and #{endTime}
+            </if>
+            <if test="companyId != null">
+                and company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="queryWatchUserCount" resultType="java.lang.Long">
+        select count(distinct user_id) from fs_course_watch_log
+        <where>
+            <if test="userType != null">
+                and send_type=${userType}
+            </if>
+            <if test="startTime != null and endTime != null">
+                and create_time between #{startTime} and #{endTime}
+            </if>
+            <if test="companyId != null">
+                and company_id = #{companyId}
+            </if>
+
+        </where>
+    </select>
+    <select id="queryCompletedCount" resultType="java.lang.Long">
+        SELECT COUNT(log_id) FROM fs_course_watch_log
+         <where>
+             finish_time IS NOT NULL
+             <if test="startTime != null and endTime != null">
+                 AND create_time BETWEEN #{startTime} AND #{endTime}
+             </if>
+             <if test="userType != null">
+                 and send_type=${userType}
+             </if>
+             <if test="companyId != null">
+                 and company_id = #{companyId}
+             </if>
+         </where>
+    </select>
+    <select id="queryAnswerMemberCount" resultType="java.lang.Long">
+        SELECT COUNT(DISTINCT user_id) FROM fs_course_answer_logs
+       <where>
+           <if test="startTime != null and endTime != null">
+               create_time BETWEEN #{startTime} AND #{endTime}
+           </if>
+           <if test="companyId != null">
+               and company_id = #{companyId}
+           </if>
+       </where>
+    </select>
+    <select id="queryCorrectUserCount" resultType="java.lang.Long">
+        SELECT COUNT(DISTINCT user_id) FROM fs_course_answer_logs
+        <where>
+            is_right = 1
+            <if test="startTime != null and endTime != null">
+                and create_time BETWEEN #{startTime} AND #{endTime}
+            </if>
+            <if test="companyId != null">
+                and company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="queryRewardCount" resultType="java.lang.Long">
+        select count(*) from company_red_package_logs rpl
+            left join fs_course_watch_log log
+            on rpl.watch_log_id=log.log_id
+        <where>
+            rpl.operate_type=1
+            <if test="startTime != null and endTime != null">
+                and rpl.create_time BETWEEN #{startTime} AND #{endTime}
+            </if>
+            <if test="userType != null">
+                and log.send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                and log.company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="queryRewardMoney" resultType="java.math.BigDecimal">
+        select sum(up_money) from company_red_package_logs rpl
+        left join fs_course_watch_log log
+        on rpl.watch_log_id=log.log_id
+        <where>
+            rpl.operate_type=1
+            <if test="startTime != null and endTime != null">
+                and rpl.create_time BETWEEN #{startTime} AND #{endTime}
+            </if>
+            <if test="userType != null">
+                and log.send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                and log.company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="smsBalance" resultType="java.lang.Long">
+        select sum(remain_sms_count) from company_sms
+    </select>
+    <select id="smsBalanceCompany" resultType="java.lang.Long">
+        select sum(remain_sms_count) from company_sms
+        <where>
+            <if test="companyId != null">
+                company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+    <select id="authorizationInfo" resultType="com.fs.statis.dto.AuthorizationInfoDTO">
+        select version_limit,today_watch_user_count from fs_statistics_index limit 1
+    </select>
+    <select id="watchEndPlayTrend" resultType="com.fs.statis.dto.WatchEndPlayTrendDTO">
+        SELECT
+--             今日/昨日 小时
+            <if test="type == 0 or type == 1">
+                DATE_FORMAT(create_time, '%H') AS start_date,
+            </if>
+--                 本周/本月/上月 天
+            <if test="type == 2 or type == 3 or type == 4">
+                DATE_FORMAT(create_time, '%Y-%m-%d') AS start_date,
+            </if>
+        COUNT(DISTINCT user_id) AS watch_user_count,
+        COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS completed_user_count
+        FROM
+        fs_course_watch_log
+        <where>
+            <if test="startTime != null">
+                create_time <![CDATA[>=]]> #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND create_time <![CDATA[<]]> #{endTime}
+            </if>
+            <if test="userType != null">
+                AND send_type = ${userType}
+            </if>
+        </where>
+        GROUP BY
+        start_date
+        ORDER BY
+        start_date
+    </select>
+    <select id="deaMemberTopTen" resultType="com.fs.statis.dto.DeaMemberTopTenDTO">
+        SELECT
+        company_id,
+        <if test="statisticalType == 0">
+            count(DISTINCT user_id) AS watch_user_count
+        </if>
+        <if test="statisticalType == 1">
+            COUNT(DISTINCT CASE WHEN log_type = 2 THEN user_id END) AS watch_user_count
+        </if>
+        FROM
+        fs_course_watch_log
+        <where>
+            <if test="startTime != null">
+                create_time <![CDATA[>=]]> #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND create_time <![CDATA[<]]> #{endTime}
+            </if>
+            <if test="userType != null">
+                AND send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                AND company_id = ${companyId}
+            </if>
+        </where>
+        GROUP BY company_id
+        limit 10
+    </select>
+    <select id="watchCourseTopTen" resultType="com.fs.statis.dto.CourseStatsDTO">
+        SELECT
+        w.course_id AS course_id,
+        COUNT(DISTINCT w.user_id) AS watch_user_count,
+        COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END) AS completed_user_count,
+        COUNT(DISTINCT a.user_id) AS answer_user_count,
+        COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END) AS correct_user_count
+        FROM
+        fs_course_watch_log w
+        LEFT JOIN
+        fs_course_answer_logs a ON w.video_id = a.video_id AND w.user_id = a.user_id
+        <where>
+            <if test="startTime != null">
+                w.create_time <![CDATA[>=]]> #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND w.create_time <![CDATA[<]]> #{endTime}
+            </if>
+            <if test="userType != null">
+                AND send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                AND w.company_id = ${companyId}
+            </if>
+        </where>
+        GROUP BY
+        w.course_id
+        ORDER BY
+            -- 观看人数
+            <if test="statisticalType == 0">
+                COUNT(DISTINCT w.user_id)
+            </if>
+            <if test="statisticalType == 1">
+                COUNT(DISTINCT CASE WHEN w.log_type = 2 THEN w.user_id END)
+            </if>
+            <if test="statisticalType == 2">
+                COUNT(DISTINCT a.user_id)
+            </if>
+            <if test="statisticalType == 3">
+                COUNT(DISTINCT CASE WHEN a.is_right = 1 THEN a.user_id END)
+            </if>
+        ${sort}
+        LIMIT 10
+    </select>
+    <select id="rewardMoneyTopTen" resultType="com.fs.statis.dto.RewardMoneyTopTenDTO">
+        SELECT
+            -- 按公司
+            <if test="dataType == 0">
+                rpl.company_id as company_id,
+            </if>
+            -- 按课程
+            <if test="dataType == 1">
+                rpl.course_id as course_id,
+            </if>
+            SUM(rpl.money) as rewardMoney
+        FROM
+            company_red_package_logs rpl
+        left join fs_course_watch_log log
+        on rpl.watch_log_id=log.log_id
+        <where>
+            rpl.operate_type = 1
+            AND rpl.status = 1
+            <if test="startTime != null">
+                AND rpl.create_time <![CDATA[>=]]> #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND rpl.create_time <![CDATA[<]]> #{endTime}
+            </if>
+            <if test="userType != null">
+                and log.send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                and log.company_id = ${companyId}
+            </if>
+        </where>
+        GROUP BY
+            <if test="dataType == 0">
+                rpl.company_id
+            </if>
+            <if test="dataType == 1">
+                rpl.course_id
+            </if>
+        ORDER BY
+            rewardMoney DESC
+            LIMIT 10
+    </select>
+    <select id="rewardMoneyTrendDTO" resultType="com.fs.statis.dto.RewardMoneyTrendDTO">
+        select
+        --             今日/昨日 小时
+        <if test="type == 0 or type == 1">
+            DATE_FORMAT(rpl.create_time, '%H') AS start_date,
+        </if>
+        --                 本周/本月/上月 天
+        <if test="type == 2 or type == 3 or type == 4">
+            DATE_FORMAT(rpl.create_time, '%Y-%m-%d') AS start_date,
+        </if>
+               SUM(rpl.money) as rewardMoney
+        from company_red_package_logs rpl
+        left join fs_course_watch_log log
+        on rpl.watch_log_id=log.log_id
+        <where>
+            <if test="startTime != null">
+                rpl.create_time <![CDATA[>=]]> #{startTime}
+            </if>
+            <if test="endTime != null">
+                AND rpl.create_time <![CDATA[<]]> #{endTime}
+            </if>
+            <if test="userType != null">
+                and log.send_type = ${userType}
+            </if>
+            <if test="companyId != null">
+                and log.company_id = ${companyId}
+            </if>
+            AND rpl.operate_type = 1
+            AND rpl.status = 1
+        </where>
+        group by start_date
+    </select>
+    <select id="getCurrentBalance" resultType="java.math.BigDecimal">
+        select sum(money) from company
+    </select>
+    <select id="dealerAggregatedCompanyId" resultType="com.fs.statis.dto.DealerAggregatedDTO">
+        SELECT
+                1 AS dealder_count,
+                (SELECT COUNT(*) FROM COMPANY_USER
+                                 <where>
+                                     <if test="companyId != null">
+                                         company_id = #{companyId}
+                                     </if>
+                                 </where>
+                                 ) AS group_mgr_count,
+                (SELECT COUNT(*) FROM FS_USER
+                                <where>
+                                    <if test="companyId != null">
+                                        company_id = #{companyId}
+                                    </if>
+                                </where>
+                                ) AS member_count,
+                (SELECT COUNT(*) FROM FS_USER
+                                <where>
+                                    STATUS=1
+                                    <if test="companyId != null">
+                                        AND company_id = #{companyId}
+                                    </if>
+                                </where>
+                                ) AS normal_num,
+                (SELECT COUNT(*) FROM FS_USER
+                                <where>
+                                    STATUS=0
+                                    <if test="companyId != null">
+                                        AND company_id = #{companyId}
+                                    </if>
+                                </where>
+                ) AS black_num,
+                (select COUNT(*) FROM qw_user) AS qw_member_num
+    </select>
+    <select id="getCurrentBalanceCompanyId" resultType="java.math.BigDecimal">
+        select sum(money) from company
+        <where>
+            <if test="companyId != null">
+                company_id = #{companyId}
+            </if>
+        </where>
+    </select>
+
+</mapper>

+ 162 - 0
fs-service/src/main/resources/mapper/statis/FsStatisEveryDayMapper.xml

@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisEveryDayWatchMapper">
+
+    <!-- 结果集映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisEveryDayWatch">
+        <id column="id" jdbcType="INTEGER" property="id" />
+        <result column="data_date" jdbcType="TIMESTAMP" property="dataDate" />
+        <result column="period_id" jdbcType="INTEGER" property="periodId" />
+        <result column="period_num" jdbcType="INTEGER" property="periodNum" />
+        <result column="not_reg_num" jdbcType="INTEGER" property="notRegNum" />
+        <result column="registered_num" jdbcType="INTEGER" property="registeredNum" />
+        <result column="reg_rate" jdbcType="REAL" property="regRate" /> <!-- Use REAL or FLOAT for float type -->
+        <result column="completed_rate" jdbcType="REAL" property="completedRate" />
+        <result column="offline_total" jdbcType="INTEGER" property="offlineTotal" />
+        <result column="offline_not_reg_num" jdbcType="INTEGER" property="offlineNotRegNum" />
+        <result column="offline_not_watch_num" jdbcType="INTEGER" property="offlineNotWatchNum" />
+        <result column="online_total" jdbcType="INTEGER" property="onlineTotal" />
+        <result column="online_rate" jdbcType="REAL" property="onlineRate" />
+        <result column="online_completed_rate" jdbcType="REAL" property="onlineCompletedRate" />
+        <result column="online_not_comp_rate_num" jdbcType="INTEGER" property="onlineNotCompRateNum" />
+        <result column="online_completed_num" jdbcType="INTEGER" property="onlineCompletedNum" />
+        <result column="company_user_id" jdbcType="INTEGER" property="companyUserId" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, data_date, period_id, period_num, not_reg_num, registered_num, reg_rate,
+    completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num,
+    online_total, online_rate, online_completed_rate, online_not_comp_rate_num,
+    online_completed_num, company_user_id
+    </sql>
+
+    <!-- 根据主键查询 -->
+    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_every_day_watch
+        where id = #{id,jdbcType=INTEGER}
+    </select>
+
+    <!-- 根据主键删除 -->
+    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
+        delete from fs_statis_every_day_watch
+        where id = #{id,jdbcType=INTEGER}
+    </delete>
+
+    <!-- 选择性插入记录 (只插入非空字段) -->
+    <insert id="insertSelective" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_statis_every_day_watch
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="dataDate != null">data_date,</if>
+            <if test="periodId != null">period_id,</if>
+            <if test="periodNum != null">period_num,</if>
+            <if test="notRegNum != null">not_reg_num,</if>
+            <if test="registeredNum != null">registered_num,</if>
+            <if test="regRate != null">reg_rate,</if>
+            <if test="completedRate != null">completed_rate,</if>
+            <if test="offlineTotal != null">offline_total,</if>
+            <if test="offlineNotRegNum != null">offline_not_reg_num,</if>
+            <if test="offlineNotWatchNum != null">offline_not_watch_num,</if>
+            <if test="onlineTotal != null">online_total,</if>
+            <if test="onlineRate != null">online_rate,</if>
+            <if test="onlineCompletedRate != null">online_completed_rate,</if>
+            <if test="onlineNotCompRateNum != null">online_not_comp_rate_num,</if>
+            <if test="onlineCompletedNum != null">online_completed_num,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="dataDate != null">#{dataDate,jdbcType=TIMESTAMP},</if>
+            <if test="periodId != null">#{periodId,jdbcType=INTEGER},</if>
+            <if test="periodNum != null">#{periodNum,jdbcType=INTEGER},</if>
+            <if test="notRegNum != null">#{notRegNum,jdbcType=INTEGER},</if>
+            <if test="registeredNum != null">#{registeredNum,jdbcType=INTEGER},</if>
+            <if test="regRate != null">#{regRate,jdbcType=REAL},</if>
+            <if test="completedRate != null">#{completedRate,jdbcType=REAL},</if>
+            <if test="offlineTotal != null">#{offlineTotal,jdbcType=INTEGER},</if>
+            <if test="offlineNotRegNum != null">#{offlineNotRegNum,jdbcType=INTEGER},</if>
+            <if test="offlineNotWatchNum != null">#{offlineNotWatchNum,jdbcType=INTEGER},</if>
+            <if test="onlineTotal != null">#{onlineTotal,jdbcType=INTEGER},</if>
+            <if test="onlineRate != null">#{onlineRate,jdbcType=REAL},</if>
+            <if test="onlineCompletedRate != null">#{onlineCompletedRate,jdbcType=REAL},</if>
+            <if test="onlineNotCompRateNum != null">#{onlineNotCompRateNum,jdbcType=INTEGER},</if>
+            <if test="onlineCompletedNum != null">#{onlineCompletedNum,jdbcType=INTEGER},</if>
+            <if test="companyUserId != null">#{companyUserId,jdbcType=INTEGER},</if>
+        </trim>
+    </insert>
+
+    <!-- 根据主键选择性更新 (只更新非空字段) -->
+    <update id="updateByPrimaryKeySelective" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch">
+        update fs_statis_every_day_watch
+        <set>
+            <if test="dataDate != null">data_date = #{dataDate,jdbcType=TIMESTAMP},</if>
+            <if test="periodId != null">period_id = #{periodId,jdbcType=INTEGER},</if>
+            <if test="periodNum != null">period_num = #{periodNum,jdbcType=INTEGER},</if>
+            <if test="notRegNum != null">not_reg_num = #{notRegNum,jdbcType=INTEGER},</if>
+            <if test="registeredNum != null">registered_num = #{registeredNum,jdbcType=INTEGER},</if>
+            <if test="regRate != null">reg_rate = #{regRate,jdbcType=REAL},</if>
+            <if test="completedRate != null">completed_rate = #{completedRate,jdbcType=REAL},</if>
+            <if test="offlineTotal != null">offline_total = #{offlineTotal,jdbcType=INTEGER},</if>
+            <if test="offlineNotRegNum != null">offline_not_reg_num = #{offlineNotRegNum,jdbcType=INTEGER},</if>
+            <if test="offlineNotWatchNum != null">offline_not_watch_num = #{offlineNotWatchNum,jdbcType=INTEGER},</if>
+            <if test="onlineTotal != null">online_total = #{onlineTotal,jdbcType=INTEGER},</if>
+            <if test="onlineRate != null">online_rate = #{onlineRate,jdbcType=REAL},</if>
+            <if test="onlineCompletedRate != null">online_completed_rate = #{onlineCompletedRate,jdbcType=REAL},</if>
+            <if test="onlineNotCompRateNum != null">online_not_comp_rate_num = #{onlineNotCompRateNum,jdbcType=INTEGER},</if>
+            <if test="onlineCompletedNum != null">online_completed_num = #{onlineCompletedNum,jdbcType=INTEGER},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId,jdbcType=INTEGER},</if>
+        </set>
+        where id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- 根据主键更新 (所有字段) -->
+    <update id="updateByPrimaryKey" parameterType="com.fs.statis.domain.FsStatisEveryDayWatch">
+        update fs_statis_every_day_watch
+        set data_date = #{dataDate,jdbcType=TIMESTAMP},
+            period_id = #{periodId,jdbcType=INTEGER},
+            period_num = #{periodNum,jdbcType=INTEGER},
+            not_reg_num = #{notRegNum,jdbcType=INTEGER},
+            registered_num = #{registeredNum,jdbcType=INTEGER},
+            reg_rate = #{regRate,jdbcType=REAL},
+            completed_rate = #{completedRate,jdbcType=REAL},
+            offline_total = #{offlineTotal,jdbcType=INTEGER},
+            offline_not_reg_num = #{offlineNotRegNum,jdbcType=INTEGER},
+            offline_not_watch_num = #{offlineNotWatchNum,jdbcType=INTEGER},
+            online_total = #{onlineTotal,jdbcType=INTEGER},
+            online_rate = #{onlineRate,jdbcType=REAL},
+            online_completed_rate = #{onlineCompletedRate,jdbcType=REAL},
+            online_not_comp_rate_num = #{onlineNotCompRateNum,jdbcType=INTEGER},
+            online_completed_num = #{onlineCompletedNum,jdbcType=INTEGER},
+            company_user_id = #{companyUserId,jdbcType=INTEGER}
+        where id = #{id,jdbcType=INTEGER}
+    </update>
+
+    <!-- 查询所有记录 -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List" />
+        from fs_statis_every_day_watch
+    </select>
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        select * from fs_statis_every_day_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+</mapper>

+ 60 - 0
fs-service/src/main/resources/mapper/statis/FsStatisPeriodWatchMapper.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisPeriodWatchMapper">
+
+    <!-- 通用查询结果映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisPeriodWatch">
+        <id column="id" property="id" jdbcType="INTEGER" />
+        <result column="period_id" property="periodId" jdbcType="INTEGER" />
+        <result column="period_num" property="periodNum" jdbcType="INTEGER" />
+        <result column="not_reg_num" property="notRegNum" jdbcType="INTEGER" />
+        <result column="registered_num" property="registeredNum" jdbcType="INTEGER" />
+        <result column="reg_rate" property="regRate" jdbcType="DECIMAL" />
+        <result column="watch_completed_rate" property="watchCompletedRate" jdbcType="DECIMAL" />
+        <result column="offline_total" property="offlineTotal" jdbcType="INTEGER" />
+        <result column="offline_not_reg_num" property="offlineNotRegNum" jdbcType="INTEGER" />
+        <result column="offline_not_watch_num" property="offlineNotWatchNum" jdbcType="INTEGER" />
+        <result column="online_total" property="onlineTotal" jdbcType="INTEGER" />
+        <result column="online_rate" property="onlineRate" jdbcType="DECIMAL" />
+        <result column="online_watch_completed_rate" property="onlineWatchCompletedRate" jdbcType="DECIMAL" />
+        <result column="online_watch_not_completed" property="onlineWatchNotCompleted" jdbcType="INTEGER" />
+        <result column="online_watch_completed" property="onlineWatchCompleted" jdbcType="INTEGER" />
+        <result column="data_date" property="dataDate" jdbcType="DATE" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, period_id, period_num, not_reg_num, registered_num, reg_rate,
+        watch_completed_rate, offline_total, offline_not_reg_num, offline_not_watch_num,
+        online_total, online_rate, online_watch_completed_rate, online_watch_not_completed,
+        online_watch_completed, data_date
+    </sql>
+
+    <!-- 查询所有数据 -->
+    <select id="selectAll" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM fs_statis_period_watch
+    </select>
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisPeriodWatch">
+        select * from fs_statis_period_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+</mapper>

+ 86 - 0
fs-service/src/main/resources/mapper/statis/FsStatisSalerWatchMapper.xml

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatisSalerWatchMapper"> <!-- 请替换为你的Mapper接口的完整路径 -->
+
+    <!-- 结果映射 -->
+    <resultMap id="BaseResultMap" type="com.fs.statis.domain.FsStatisSalerWatch"> <!-- 请替换为你的Entity的完整路径 -->
+        <id column="id" property="id" jdbcType="INTEGER"/>
+        <result column="dept_id" property="deptId" jdbcType="INTEGER"/>
+        <result column="company_user_id" property="companyUserId" jdbcType="INTEGER"/>
+        <result column="train_camp_num" property="trainCampNum" jdbcType="INTEGER"/>
+        <result column="not_registered_num" property="notRegisteredNum" jdbcType="INTEGER"/>
+        <result column="registered_num" property="registeredNum" jdbcType="INTEGER"/>
+        <result column="reg_rate" property="regRate" jdbcType="FLOAT"/>
+        <result column="finished_rate" property="finishedRate" jdbcType="FLOAT"/>
+        <result column="offline_total" property="offlineTotal" jdbcType="INTEGER"/>
+        <result column="offline_not_part" property="offlineNotPart" jdbcType="INTEGER"/> <!-- 对应修正后的 offlineNotPart -->
+        <result column="offline_not_watched" property="offlineNotWatched" jdbcType="INTEGER"/>
+        <result column="online_total" property="onlineTotal" jdbcType="INTEGER"/>
+        <result column="online_online_rate" property="onlineOnlineRate" jdbcType="FLOAT"/>
+        <result column="online_playback_comple_rate" property="onlinePlaybackCompleRate" jdbcType="FLOAT"/>
+        <result column="online_incomplete_playback" property="onlineIncompletePlayback" jdbcType="INTEGER"/>
+        <result column="online_complete_playback" property="onlineCompletePlayback" jdbcType="INTEGER"/>
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, dept_id, company_user_id, train_camp_num, not_registered_num, registered_num,
+        reg_rate, finished_rate, offline_total, offline_not_part, offline_not_watched,
+        online_total, online_online_rate, online_playback_comple_rate,
+        online_incomplete_playback, online_complete_playback
+    </sql>
+
+    <select id="queryList" resultType="com.fs.statis.domain.FsStatisSalerWatch">
+        select * from fs_statis_saler_watch
+        <where>
+            <if test="userIds != null and userIds.length > 0">
+                AND company_user_id IN
+                 <foreach collection="userIds" open="(" close=")" separator="," item="item">
+                     ${item}
+                </foreach>
+            </if>
+            <if test="periodList != null and periodList.length > 0">
+                AND period_id IN
+                <foreach collection="periodList" open="(" close=")" separator="," item="item">
+                    ${item}
+                </foreach>
+            </if>
+            <if test="startDate != null and endDate != null">
+                AND data_date BETWEEN #{startDate} AND #{endDate}
+            </if>
+        </where>
+    </select>
+
+    <insert id="batchSave">
+        INSERT INTO fs_statis_saler_watch (
+            dept_id, company_user_id, train_camp_num,
+            not_registered_num, registered_num, reg_rate,
+            finished_rate, offline_total, offline_not_part,
+            offline_not_watched, online_total, online_online_rate,
+            online_playback_comple_rate, online_incomplete_playback,
+            online_complete_playback,period_id,data_date
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.deptId,jdbcType=INTEGER},
+                #{item.companyUserId,jdbcType=INTEGER},
+                #{item.trainCampNum,jdbcType=INTEGER},
+                #{item.notRegisteredNum,jdbcType=INTEGER},
+                #{item.registeredNum,jdbcType=INTEGER},
+                #{item.regRate,jdbcType=FLOAT},
+                #{item.finishedRate,jdbcType=FLOAT},
+                #{item.offlineTotal,jdbcType=INTEGER},
+                #{item.offlineNotPart,jdbcType=INTEGER},
+                #{item.offlineNotWatched,jdbcType=INTEGER},
+                #{item.onlineTotal,jdbcType=INTEGER},
+                #{item.onlineOnlineRate,jdbcType=FLOAT},
+                #{item.onlinePlaybackCompleRate,jdbcType=FLOAT},
+                #{item.onlineIncompletePlayback,jdbcType=INTEGER},
+                #{item.onlineCompletePlayback,jdbcType=INTEGER},
+                #{item.periodId,jdbcType=INTEGER},
+                #{item.dataDate,jdbcType=VARCHAR}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 161 - 0
fs-service/src/main/resources/mapper/statis/FsStatsMemberDailyMapper.xml

@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.statis.mapper.FsStatsMemberDailyMapper">
+
+    <update id="refreshMemberDailyData">
+        insert into fs_stats_member_daily
+            (
+            stat_date,user_id,nick_name,real_name,phone,tag,company_group_id,company_id,company_name,company_user_id,company_user_name,
+            train_camp_id,train_camp_name,period_id,period_name,course_id,course_name,video_id,video_name,is_over,watch_count,watch_duration,
+            answer_count,answer_correct_count,red_packet_count,red_packet_amount
+            )
+        select
+            #{date}					    as stat_date,
+            u.user_id				    as user_id,
+            u.nickname				    as nick_name,
+            u.real_name				    as real_name,
+            u.phone					    as phone,
+            concat_ws(',', t.tag) 	    as tag,
+            null					    as company_group_id,
+            c.company_id			    as company_id,
+            c.company_name			    as company_name,
+            cu.user_id				    as company_user_id,
+            cu.nick_name			    as company_user_name,
+            uctc.training_camp_id 	    as train_camp_id,
+            uctc.training_camp_name	    as train_camp_name,
+            ucp.period_id			    as period_id,
+            ucp.period_name			    as period_name,
+            uc.course_id			    as course_id,
+            uc.course_name			    as course_name,
+            ucv.video_id			    as video_id,
+            ucv.title				    as video_name,
+            max(case when cwl.log_type = 2 then 1 else 0 end)
+                                        as is_over,
+            count(cwl.log_id)		    as watch_count,
+            sum(cwl.duration)		    as watch_duration,
+            count(cal.log_id)		    as answer_count,
+            count(case when cal.is_right = 1 then cal.log_id end)
+                                        as answer_correct_count,
+            count(crpl.log_id)		    as red_packet_count,
+            ifnull(sum(crpl.amount),0)	as red_packet_amount
+        from fs_course_watch_log cwl
+        inner join fs_user u 									on u.user_id = cwl.user_id
+        left join company_tag_user ctu 							on ctu.company_id = cwl.company_id and ctu.company_user_id = cwl.company_user_id and ctu.user_id = cwl.user_id
+        left join company_tag t 								on FIND_IN_SET(t.tag_id, ctu.tag_ids)
+        left join fs_user_course_period ucp 				    on ucp.period_id = cwl.period_id
+        left join fs_user_course_training_camp uctc             on uctc.training_camp_id = ucp.training_camp_id
+        left join company c 									on c.company_id = cwl.company_id
+        left join company_user cu 								on cu.company_id = cwl.company_id and cu.user_id = cwl.company_user_id
+        left join fs_user_course uc 							on uc.course_id = cwl.course_id
+        left join fs_user_course_video ucv 					    on ucv.course_id = cwl.course_id and ucv.video_id = cwl.video_id
+        left join fs_course_answer_logs cal 				    on cal.watch_log_id = cwl.log_id
+        left join fs_course_red_packet_log crpl 		        on crpl.watch_log_id = cwl.log_id
+        <![CDATA[
+        where cwl.create_time >= #{date} and cwl.create_time < date_add(#{date}, interval 1 day)
+        ]]>
+        group by
+            cwl.user_id,
+            cwl.company_id,
+            cwl.company_user_id,
+            ucp.training_camp_id,
+            cwl.period_id,
+            cwl.course_id,
+            cwl.video_id
+        on duplicate key update
+            nick_name = values(nick_name),
+            real_name = values(real_name),
+            phone = values(phone),
+            tag = values(tag),
+            company_name = values(company_name),
+            company_user_name = values(company_user_name),
+            train_camp_name = values(train_camp_name),
+            period_name = values(period_name),
+            course_name = values(course_name),
+            video_name = values(video_name),
+            is_over = values(is_over),
+            watch_count = values(watch_count),
+            watch_duration = values(watch_duration),
+            answer_count = values(answer_count),
+            answer_correct_count = values(answer_correct_count),
+            red_packet_count = values(red_packet_count),
+            red_packet_amount = values(red_packet_amount)
+    </update>
+
+    <select id="selectDailyData" resultType="com.fs.statis.vo.FsStatsMemberDailyVO">
+        select
+            <choose>
+                <when test="params.type == 1">
+                    fsmd.stat_date,
+                </when>
+                <otherwise>
+                    date_format(fsmd.stat_date, '%Y-%m') as stat_date,
+                </otherwise>
+            </choose>
+            fsmd.user_id,
+            fsmd.nick_name,
+            fsmd.real_name,
+            fsmd.phone,
+            fsmd.tag,
+            fsmd.company_id,
+            fsmd.company_name,
+            fsmd.company_user_id,
+            fsmd.company_user_name,
+            count(fsmd.id) as count,
+            sum(fsmd.is_over) as overCount,
+            sum(fsmd.watch_count) as watchCount,
+            sum(fsmd.watch_duration) as watchDuration,
+            sum(fsmd.answer_count) as answerCount,
+            sum(fsmd.answer_correct_count) as anserCorrectCount,
+            sum(fsmd.red_packet_count) as redPacketCount,
+            sum(fsmd.red_packet_amount) redPacketAmount
+        from fs_stats_member_daily fsmd
+        <where>
+            <if test="params.startDate != null">
+                and fsmd.stat_date >= #{params.startDate}
+            </if>
+            <if test="params.endDate != null">
+            <![CDATA[
+                and fsmd.stat_date < #{params.endDate}
+            ]]>
+            </if>
+            <if test="params.companyId != null">
+                and fsmd.company_id = #{params.companyId}
+            </if>
+            <if test="params.companyUserId != null">
+                and fsmd.company_user_id = #{params.companyUserId}
+            </if>
+            <if test="params.userId != null">
+                and fsmd.user_id = #{params.userId}
+            </if>
+            <if test="params.phone != null and params.phone != ''">
+                and fsmd.phone like concat('%', #{params.phone}, '%')
+            </if>
+            <if test="params.trainCampId != null">
+                and fsmd.train_camp_id = #{params.trainCampId}
+            </if>
+            <if test="params.periodId != null">
+                and fsmd.period_id = #{params.periodId}
+            </if>
+            <if test="params.courseId != null">
+                and fsmd.course_id = #{params.courseId}
+            </if>
+            <if test="params.videoId != null">
+                and fsmd.video_id = #{params.videoId}
+            </if>
+        </where>
+        group by
+            <choose>
+                <when test="params.type == 1">
+                    stat_date,
+                </when>
+                <otherwise>
+                    date_format(stat_date, '%Y-%m'),
+                </otherwise>
+            </choose>
+            user_id,
+            company_id,
+            company_user_id
+    </select>
+</mapper>

+ 4 - 1
fs-user-app/pom.xml

@@ -15,7 +15,10 @@
     </description>
 
     <dependencies>
-
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
         <!-- spring-boot-devtools -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 42 - 0
fs-user-app/src/main/java/com/fs/app/config/WebSocketConfig.java

@@ -0,0 +1,42 @@
+package com.fs.app.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
+
+@Configuration
+public class WebSocketConfig {
+    /**
+     * 自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
+     *
+     * @return
+     */
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+    /**
+     * 通信文本消息和二进制缓存区大小
+     * 避免对接 第三方 报文过大时,Websocket 1009 错误
+     *
+     * @return
+     */
+
+    @Bean
+    public ServletServerContainerFactoryBean createWebSocketContainer() {
+        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
+        // 设置文本消息缓冲区大小
+        container.setMaxTextMessageBufferSize(10240000);
+        // 设置二进制消息缓冲区大小
+        container.setMaxBinaryMessageBufferSize(10240000);
+        // 设置最大会话空闲超时时间(单位:毫秒)
+        container.setMaxSessionIdleTimeout(20 * 60000L); // 15分钟
+        // 设置异步发送超时时间(单位:毫秒)
+        container.setAsyncSendTimeout(300 * 1000L);
+        return container;
+    }
+
+}

+ 46 - 14
fs-user-app/src/main/java/com/fs/app/controller/CourseH5Controller.java

@@ -3,37 +3,26 @@ package com.fs.app.controller;
 
 import cn.hutool.json.JSONUtil;
 import com.fs.app.annotation.Login;
-import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
-import com.fs.common.utils.DateUtils;
-import com.fs.common.utils.ServletUtils;
-import com.fs.common.utils.StringUtils;
-import com.fs.core.utils.OrderCodeUtils;
+import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.param.*;
 import com.fs.course.service.*;
-import com.fs.course.service.impl.TencentCloudCosService;
 import com.fs.course.vo.*;
-import com.fs.his.vo.OptionsVO;
 import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
-import io.jsonwebtoken.Claims;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import lombok.Synchronized;
+import io.swagger.annotations.ApiParam;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
-import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.*;
+import java.util.List;
 
 @Api("h5课堂接口")
 @RestController
@@ -54,6 +43,9 @@ public class CourseH5Controller extends  AppBaseController{
     @Autowired
     private IFsCourseWatchLogService courseWatchLogService;
 
+    @Autowired
+    private IFsCourseWatchCommentService courseWatchCommentService;
+
 
     @ApiOperation("h5课程简介")
     @GetMapping("/getH5CourseByVideoId")
@@ -135,4 +127,44 @@ public class CourseH5Controller extends  AppBaseController{
         logger.error("zyp \n【h5看课中途报错】:{}",msg);
     }
 
+    @Login
+    @ApiOperation("保存评论数据")
+    @PostMapping("/saveMsg")
+    public R saveMsg(@RequestBody FsCourseWatchCommentSaveParam param)
+    {
+//        param.setUserId(Long.parseLong(getUserId()));
+        return courseWatchCommentService.saveH5CourseWatchComment(param);
+    }
+
+    @Login
+    @ApiOperation("撤销评论")
+    @PutMapping("/revokeMsg")
+    public R revokeMsg(@ApiParam(value = "评论id", required = true) @RequestParam Long commentId)
+    {
+        return courseWatchCommentService.revokeH5CourseWatchComment(commentId);
+    }
+
+    @ApiOperation("获取历史评论数据")
+    @GetMapping("/getComments")
+    public ResponseResult<PageInfo<FsCourseWatchCommentVO>> getCourseWatchComments(@ApiParam(value = "页码", required = true) @RequestParam Integer pageNum,
+                                                 @ApiParam(value = "每页大小", required = true) @RequestParam Integer pageSize)
+    {
+        //获取配置信息中需要查询的数据条数
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        FsCourseWatchCommentListParam param = new FsCourseWatchCommentListParam();
+        param.setPageNum(pageNum);
+        param.setPageSize(pageSize);
+        param.setListNum(config.getViewCommentNum() != null &&  config.getViewCommentNum() != 0 ? config.getViewCommentNum() : 200);
+
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsCourseWatchCommentVO> list = courseWatchCommentService.selectH5CourseWatchComments(param);
+        PageInfo<FsCourseWatchCommentVO> pageInfo = new PageInfo<>(list);
+        return ResponseResult.ok(pageInfo);
+    }
+
+
+
+
 }

+ 36 - 0
fs-user-app/src/main/java/com/fs/websocket/bean/SendMsgVO.java

@@ -0,0 +1,36 @@
+package com.fs.websocket.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+//@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendMsgVO {
+
+    @ApiModelProperty("用户id")
+    private Long userId;
+
+    @ApiModelProperty("用户类型,1-管理员,2-用户")
+    private Integer userType;
+
+    @ApiModelProperty("课程id")
+    private Long courseId;
+
+    @ApiModelProperty("视频id")
+    private Long videoId;
+
+    @ApiModelProperty("评论类型 1:评论,2:回复")
+    private Integer type;
+
+    @ApiModelProperty("消息代码,heartbeat-检测心跳;sendMsg-发送消息")
+    private String cmd;
+
+    @ApiModelProperty("消息内容")
+    private String msg;
+
+
+}

+ 108 - 0
fs-user-app/src/main/java/com/fs/websocket/service/WebSocketServer.java

@@ -0,0 +1,108 @@
+package com.fs.websocket.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.StringUtils;
+import com.fs.websocket.bean.SendMsgVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@ServerEndpoint("/app/webSocket/{userId}")
+@Component
+@Slf4j
+public class WebSocketServer {
+
+    //concurrent包的线程安全,用来存放每个客户端对应的WebSocketServer的会话对象
+    private static final ConcurrentHashMap<Long, Session> sessionPools = new ConcurrentHashMap<>();
+//    private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
+
+    //分发消息
+    public void sendMessageToAll(String message) throws IOException {
+        Collection<Session> sessions = sessionPools.values();
+        if(!CollectionUtils.isEmpty(sessions)){
+            for (Session session : sessions) {
+                System.out.println("发送数据:" + message);
+                session.getBasicRemote().sendText(message);
+                log.info("分发消息结束,人数,{},消息内容,{}",  sessionPools.size(), message);
+            }
+        }
+    }
+
+    //指定用户发送消息
+    public static void sendMessage(Session session, String message) throws IOException {
+        if(session != null){
+            synchronized (session) {
+                log.info("发送数据:{}", message);
+                session.getBasicRemote().sendText(message);
+            }
+        }
+    }
+
+    //建立连接成功调用
+    @OnOpen
+    public void onOpen(Session session,  @PathParam(value = "userId") Long userId) {
+        Map<String, String> params = getParams(session);
+        if(userId == null || !params.containsKey("userId")){
+            throw new BaseException("用户信息错误");
+        }
+        sessionPools.put(userId, session);
+        System.out.println("已成功加入webSocket连接!当前人数为,"+ sessionPools.size());
+        log.info("{}已成功加入webSocket连接!当前人数为,{}", userId, sessionPools.size());
+    }
+
+
+    //收到客户端信息
+    @OnMessage
+    public void onMessage(String message) throws IOException {
+        SendMsgVO msg = JSONObject.parseObject(message, SendMsgVO.class);
+        Session session;
+        try {
+            switch (msg.getCmd()) {
+                case "heartbeat":
+                    session = sessionPools.get(msg.getUserId());
+                    sendMessage(session, JSONObject.toJSONString(msg));
+                    break;
+                case "sendMsg":
+                    // 分发消息
+                    sendMessageToAll(JSONObject.toJSONString(R.ok().put("data", msg)));
+                    break;
+            }
+        } catch (Exception e) {
+            log.error("webSocket消息发送错误,{}", e.getMessage());
+        }
+
+    }
+
+    //关闭连接时调用
+    @OnClose
+    public void onClose(Session session) {
+        Map<String, String> params = getParams(session);
+        long userId = Long.parseLong(params.get("userId"));
+        sessionPools.remove(userId);
+        log.info("{}已断开webSocket连接!当前人数为,{}", userId, sessionPools.size());
+    }
+
+    //错误时调用
+    @OnError
+    public void onError(Session session, Throwable throwable) {
+        log.error("webSocket连接错误,{}", throwable.getMessage());
+        throwable.printStackTrace();
+    }
+
+    private Map<String, String> getParams(Session session){
+        return session.getRequestParameterMap().entrySet().stream()
+                .filter(e -> e.getValue() != null && !e.getValue().isEmpty() && StringUtils.isNotEmpty(e.getValue().get(0)))
+                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
+    }
+}