Переглянути джерело

卓美,公寓课相关提交,小程序接口查询接口、(注意同步一下测试库结构)

yjwang 2 днів тому
батько
коміт
1061001e88
36 змінених файлів з 963 додано та 11 видалено
  1. 3 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchCommentController.java
  2. 6 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  3. 22 4
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  4. 64 0
      fs-admin/src/main/java/com/fs/course/controller/PublicCourseWatchStatisticsController.java
  5. 1 1
      fs-company/src/main/java/com/fs/FsCompanyApplication.java
  6. 4 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchComment.java
  7. 21 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourse.java
  8. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseCategory.java
  9. 3 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java
  11. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseCategoryMapper.java
  12. 9 1
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  13. 18 0
      fs-service/src/main/java/com/fs/course/mapper/PublicCourseWatchStatisticsMapper.java
  14. 4 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java
  15. 3 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentSaveParam.java
  16. 35 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java
  17. 38 0
      fs-service/src/main/java/com/fs/course/param/FsUserCoursePublicAppQueryParam.java
  18. 25 0
      fs-service/src/main/java/com/fs/course/param/PublicCourseWatchStatQueryParam.java
  19. 6 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseCategoryService.java
  20. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  21. 17 0
      fs-service/src/main/java/com/fs/course/service/IPublicCourseWatchStatisticsService.java
  22. 3 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java
  23. 13 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java
  24. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  25. 66 0
      fs-service/src/main/java/com/fs/course/service/impl/PublicCourseWatchStatisticsServiceImpl.java
  26. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchCommentListVO.java
  27. 56 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCoursePublicAppVO.java
  28. 5 0
      fs-service/src/main/java/com/fs/course/vo/FsVideoResourceVO.java
  29. 61 0
      fs-service/src/main/java/com/fs/course/vo/PublicCourseWatchStatCatalogVO.java
  30. 55 0
      fs-service/src/main/java/com/fs/course/vo/PublicCourseWatchStatCourseVO.java
  31. 16 1
      fs-service/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml
  32. 6 1
      fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml
  33. 126 3
      fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml
  34. 10 0
      fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml
  35. 203 0
      fs-service/src/main/resources/mapper/course/PublicCourseWatchStatisticsMapper.xml
  36. 37 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

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

@@ -96,6 +96,9 @@ public class FsCourseWatchCommentController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody FsCourseWatchComment fsCourseWatchComment)
     {
+        if (fsCourseWatchComment.getCateType() == null) {
+            fsCourseWatchComment.setCateType(0);
+        }
         return toAjax(fsCourseWatchCommentService.insertFsCourseWatchComment(fsCourseWatchComment));
     }
 

+ 6 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java

@@ -163,6 +163,9 @@ public class FsUserCourseController extends BaseController {
         if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
+        if (fsUserCourse.getIsPrivate() == null) {
+            fsUserCourse.setIsPrivate(1);
+        }
         fsUserCourseService.insertFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
 
@@ -183,6 +186,9 @@ public class FsUserCourseController extends BaseController {
         if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
             fsUserCourse.setUserId(userId);
         }
+        if (fsUserCourse.getIsPrivate() == null) {
+            fsUserCourse.setIsPrivate(0);
+        }
         fsUserCourseService.insertFsUserCourse(fsUserCourse);
         redisCacheUtil.delRedisKey("getCourseList");
 

+ 22 - 4
fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
@@ -65,6 +66,7 @@ public class FsVideoResourceController extends BaseController {
                               @RequestParam(required = false) String fileName,
                               @RequestParam(required = false) Integer typeId,
                               @RequestParam(required = false) Integer typeSubId,
+                              @RequestParam(required = false) Integer videoType,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
                               @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String, Object> params = new HashMap<>();
@@ -72,6 +74,10 @@ public class FsVideoResourceController extends BaseController {
         params.put("fileName", fileName);
         params.put("typeId", typeId);
         params.put("typeSubId", typeSubId);
+        if (videoType == null) {
+            videoType = 0;
+        }
+        params.put("videoType", videoType);
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
@@ -111,6 +117,9 @@ public class FsVideoResourceController extends BaseController {
         if (com.fs.common.utils.StringUtils.isBlank(fsVideoResource.getDisplayType())) {
             fsVideoResource.setDisplayType("landscape");
         }
+        if (fsVideoResource.getVideoType() == null) {
+            fsVideoResource.setVideoType(0);
+        }
 
         fsVideoResource.setCreateTime(LocalDateTime.now());
         boolean save = fsVideoResourceService.save(fsVideoResource);
@@ -172,7 +181,8 @@ public class FsVideoResourceController extends BaseController {
     @PostMapping("/batchUpdateClass")
     public AjaxResult batchUpdateClass(@RequestParam("typeId") Long typeId,
                                        @RequestParam("typeSubId") Long typeSubId,
-                                       @RequestParam("ids") String ids) {
+                                       @RequestParam("ids") String ids,
+                                       @RequestParam(value = "videoType", required = false) Integer videoType) {
         if (typeId == null || typeId <= 0) {
             return AjaxResult.error("请选择分类");
         }
@@ -186,13 +196,18 @@ public class FsVideoResourceController extends BaseController {
         // 将ids字符串分割为ID列表
         List<String> idList = Arrays.asList(ids.split(","));
 
-        // 创建更新条件
-        Wrapper<FsVideoResource> updateWrapper = Wrappers.<FsVideoResource>lambdaUpdate()
+        LambdaUpdateWrapper<FsVideoResource> updateWrapper = Wrappers.<FsVideoResource>lambdaUpdate()
                 .set(FsVideoResource::getTypeId, typeId)
                 .set(FsVideoResource::getTypeSubId, typeSubId)
                 .in(FsVideoResource::getId, idList.stream().map(Long::valueOf).collect(Collectors.toList()));
+        if (videoType != null) {
+            if (videoType == 0) {
+                updateWrapper.apply("(video_type = 0 or video_type is null)");
+            } else {
+                updateWrapper.eq(FsVideoResource::getVideoType, videoType);
+            }
+        }
 
-        // 执行批量更新
         fsVideoResourceService.update(updateWrapper);
         return AjaxResult.success();
     }
@@ -215,6 +230,9 @@ public class FsVideoResourceController extends BaseController {
             if (com.fs.common.utils.StringUtils.isBlank(v.getDisplayType())) {
                 v.setDisplayType("landscape");
             }
+            if (v.getVideoType() == null) {
+                v.setVideoType(0);
+            }
             if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
                 v.setUserId(userId);
             }

+ 64 - 0
fs-admin/src/main/java/com/fs/course/controller/PublicCourseWatchStatisticsController.java

@@ -0,0 +1,64 @@
+package com.fs.course.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
+import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 公域看课数据统计(独立 Controller)
+ */
+@RestController
+@RequestMapping("/course/publicCourseWatchStat")
+public class PublicCourseWatchStatisticsController extends BaseController {
+
+    @Autowired
+    private IPublicCourseWatchStatisticsService publicCourseWatchStatisticsService;
+
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:list')")
+    @GetMapping("/courseDay/list")
+    public TableDataInfo courseDayList(PublicCourseWatchStatQueryParam param) {
+        startPage();
+        List<PublicCourseWatchStatCourseVO> list = publicCourseWatchStatisticsService.listCourseDayStat(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:export')")
+    @Log(title = "公域看课统计-课程数据", businessType = BusinessType.EXPORT)
+    @GetMapping("/courseDay/export")
+    public AjaxResult courseDayExport(PublicCourseWatchStatQueryParam param) {
+        List<PublicCourseWatchStatCourseVO> list = publicCourseWatchStatisticsService.listCourseDayStat(param);
+        ExcelUtil<PublicCourseWatchStatCourseVO> util = new ExcelUtil<>(PublicCourseWatchStatCourseVO.class);
+        return util.exportExcel(list, "公域看课统计-课程数据");
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:list')")
+    @GetMapping("/catalog/list")
+    public TableDataInfo catalogList(PublicCourseWatchStatQueryParam param) {
+        startPage();
+        List<PublicCourseWatchStatCatalogVO> list = publicCourseWatchStatisticsService.listCatalogStat(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:export')")
+    @Log(title = "公域看课统计-目录数据", businessType = BusinessType.EXPORT)
+    @GetMapping("/catalog/export")
+    public AjaxResult catalogExport(PublicCourseWatchStatQueryParam param) {
+        List<PublicCourseWatchStatCatalogVO> list = publicCourseWatchStatisticsService.listCatalogStat(param);
+        ExcelUtil<PublicCourseWatchStatCatalogVO> util = new ExcelUtil<>(PublicCourseWatchStatCatalogVO.class);
+        return util.exportExcel(list, "公域看课统计-目录数据");
+    }
+}

+ 1 - 1
fs-company/src/main/java/com/fs/FsCompanyApplication.java

@@ -11,7 +11,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  */
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 @EnableTransactionManagement
-@EnableAsync
+//@EnableAsync
 public class FsCompanyApplication
 {
     public static void main(String[] args)

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

@@ -67,4 +67,8 @@ public class FsCourseWatchComment extends BaseEntity{
     @Excel(name = "字体颜色")
     private String color;
 
+    /** 分类类型:0-评论(课程),1-公域看课评论 */
+    @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论")
+    private Integer cateType;
+
 }

+ 21 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourse.java

@@ -157,4 +157,25 @@ public class FsUserCourse extends BaseEntity
      */
     private String configJson;
 
+    /** 首页课程顶部推荐位:0否 1是 */
+    private Integer recHomeCourseTopEnabled;
+    /** 首页顶部推荐方式:1插入 2替换 */
+    private Integer recHomeCourseTopMode;
+    /** 首页顶部推荐位置序号 */
+    private Integer recHomeCourseTopSort;
+
+    /** 商城首页推荐位:0否 1是 */
+    private Integer recMallHomeEnabled;
+    /** 商城首页推荐方式:1插入 2替换 */
+    private Integer recMallHomeMode;
+    /** 商城首页推荐位置序号 */
+    private Integer recMallHomeSort;
+
+    /** 首页长视频瀑布流:0否 1是 */
+    private Integer recHomeLongVideoEnabled;
+    /** 长视频瀑布流方式:1插入 2替换 */
+    private Integer recHomeLongVideoMode;
+    /** 长视频瀑布流位置序号 */
+    private Integer recHomeLongVideoSort;
+
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseCategory.java

@@ -40,6 +40,9 @@ public class FsUserCourseCategory extends BaseEntity
 
     private String pic;
 
+    /** 分类类型:0-普通课程分类 1-公域课程分类 */
+    @Excel(name = "分类类型")
+    private Integer cateType;
 
     private Long userId;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java

@@ -130,6 +130,9 @@ public class FsUserCourseVideo extends BaseEntity
 
     private String vid;
 
+    /** 看课奖励类型(展示文案,可配置) */
+    private String rewardType;
+
     @TableField(exist = false)
     private Integer showProduct; //1不展示疗法,0展示疗法
 

+ 5 - 0
fs-service/src/main/java/com/fs/course/domain/FsVideoResource.java

@@ -109,4 +109,9 @@ public class FsVideoResource {
      * 视频展示类型:landscape-横屏,portrait-竖屏,默认横屏
      */
     private String displayType;
+
+    /**
+     * 视频资源类型:0-视频资源,1-公域课视频资源
+     */
+    private Integer videoType;
 }

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

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import com.fs.course.domain.FsUserCourseCategory;
+import com.fs.course.param.FsUserCourseCategoryAppQueryParam;
 import com.fs.his.vo.OptionsVO;
 import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
@@ -33,6 +34,11 @@ public interface FsUserCourseCategoryMapper
      */
     public List<FsUserCourseCategory> selectFsUserCourseCategoryList(FsUserCourseCategory fsUserCourseCategory);
 
+    /**
+     * 小程序端:课程分类分页列表(稳定排序,供 PageHelper 使用)
+     */
+    List<FsUserCourseCategory> selectFsUserCourseCategoryAppList(FsUserCourseCategoryAppQueryParam param);
+
     /**
      * 新增课堂分类
      *

+ 9 - 1
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -10,6 +10,7 @@ import com.fs.course.domain.FsUserCourse;
 import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.FsUserCourseAddStudyCourseParam;
 import com.fs.course.param.FsUserCourseListUParam;
+import com.fs.course.param.FsUserCoursePublicAppQueryParam;
 import com.fs.course.param.FsUserCourseParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.vo.*;
@@ -131,6 +132,10 @@ public interface FsUserCourseMapper
             "</script>"})
     List<FsUserCourseListUVO> selectFsUserCourseListUVO(@Param("maps") FsUserCourseListUParam param);
 
+    /**
+     * 小程序:公域课程分页(联表统计看课人数)
+     */
+    List<FsUserCoursePublicAppVO> selectFsUserCoursePublicAppList(@Param("q") FsUserCoursePublicAppQueryParam param);
 
     @Select({"<script> " +
             "select c.*,cc.cate_name,ucc.cate_name as sub_cate_name from fs_user_course  c " +
@@ -153,7 +158,10 @@ public interface FsUserCourseMapper
             "and c.course_name like concat('%', #{maps.courseName}, '%') " +
             "</if>" +
             "<if test = ' maps.isPrivate !=null '> " +
-            "and c.is_private = #{maps.isPrivate} " +
+            " and <choose>" +
+            "<when test='maps.isPrivate == 1'>(c.is_private = 1 or c.is_private is null)</when>" +
+            "<otherwise>c.is_private = #{maps.isPrivate}</otherwise>" +
+            "</choose> " +
             "</if>" +
             "<if test = ' maps.isShow !=null '> " +
             "and c.is_show = #{maps.isShow} " +

+ 18 - 0
fs-service/src/main/java/com/fs/course/mapper/PublicCourseWatchStatisticsMapper.java

@@ -0,0 +1,18 @@
+package com.fs.course.mapper;
+
+import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
+import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 公域看课统计(独立 Mapper,勿与 fs_course_watch_log 业务混用)
+ */
+public interface PublicCourseWatchStatisticsMapper {
+
+    List<PublicCourseWatchStatCourseVO> selectCourseDayStatList(@Param("q") PublicCourseWatchStatQueryParam q);
+
+    List<PublicCourseWatchStatCatalogVO> selectCatalogStatList(@Param("q") PublicCourseWatchStatQueryParam q);
+}

+ 4 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java

@@ -39,4 +39,8 @@ public class FsCourseWatchCommentPageParam extends BaseEntity{
     @Excel(name = "视频id")
     private Long videoId;
 
+    /** 分类类型:0-评论(课程),1-公域看课评论 */
+    @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论")
+    private Integer cateType;
+
 }

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

@@ -41,4 +41,7 @@ public class FsCourseWatchCommentSaveParam{
     @ApiModelProperty(value = "字体颜色")
     private String color;
 
+    @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论,默认0")
+    private Integer cateType;
+
 }

+ 35 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java

@@ -0,0 +1,35 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序端课程分类分页查询参数(默认公域课分类 cateType=1)
+ */
+@Data
+@ApiModel("小程序-课程分类分页查询参数")
+public class FsUserCourseCategoryAppQueryParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "页码,默认1", example = "1")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "每页条数,默认10", example = "10")
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "分类名称(模糊)")
+    private String cateName;
+
+    @ApiModelProperty(value = "一级分类ID(父级 pid);只返回该一级下的二级;不传则返回所有「有公域课使用」的二级")
+    private Long pid;
+
+    @ApiModelProperty(value = "是否显示:1显示 0隐藏;不传表示不限")
+    private Integer isShow;
+
+    @ApiModelProperty(value = "分类类型:1=公域课程分类,0=普通;不传时接口默认按1(公域)查询")
+    private Integer cateType;
+}

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

@@ -0,0 +1,38 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序端公域课程分页查询(与课程分类联动:传 cateId / subCateId)
+ */
+@Data
+@ApiModel("小程序-公域课程分页查询参数")
+public class FsUserCoursePublicAppQueryParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "页码,默认1", example = "1")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "每页条数,默认10", example = "10")
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "一级分类ID(与课程分类接口联动)")
+    private Long cateId;
+
+    @ApiModelProperty(value = "二级分类ID")
+    private Long subCateId;
+
+    @ApiModelProperty(value = "关键词(匹配课程标题/名称)")
+    private String keyword;
+
+    /**
+     * 推荐位筛选:不传=全部公域课;1=首页课程顶部推荐位已勾选;2=商城首页推荐位已勾选;3=首页长视频瀑布流已勾选
+     */
+    @ApiModelProperty(value = "推荐位:1首页顶部 2商城首页 3首页长视频瀑布流;不传则不限")
+    private Integer recommendSlot;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/course/param/PublicCourseWatchStatQueryParam.java

@@ -0,0 +1,25 @@
+package com.fs.course.param;
+
+import lombok.Data;
+
+/**
+ * 公域看课统计查询参数
+ */
+@Data
+public class PublicCourseWatchStatQueryParam {
+
+    /** 课程名称 / 章节名称 模糊 */
+    private String keywords;
+
+    /** 一级分类 */
+    private Long cateId;
+
+    /** 二级分类 */
+    private Long subCateId;
+
+    /** 开始日期 yyyy-MM-dd */
+    private String beginDate;
+
+    /** 结束日期 yyyy-MM-dd(含当天) */
+    private String endDate;
+}

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

@@ -5,6 +5,7 @@ import java.util.Map;
 
 import com.fs.course.domain.FsUserCourseCategory;
 import com.fs.course.dto.FsCourseCategoryImportDTO;
+import com.fs.course.param.FsUserCourseCategoryAppQueryParam;
 import com.fs.his.vo.OptionsVO;
 
 /**
@@ -31,6 +32,11 @@ public interface IFsUserCourseCategoryService
      */
     public List<FsUserCourseCategory> selectFsUserCourseCategoryList(FsUserCourseCategory fsUserCourseCategory);
 
+    /**
+     * 小程序端:课程分类分页列表(默认公域 cateType=1,仅查未删除)
+     */
+    List<FsUserCourseCategory> selectFsUserCourseCategoryAppList(FsUserCourseCategoryAppQueryParam param);
+
     /**
      * 新增课堂分类
      *

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

@@ -84,6 +84,11 @@ public interface IFsUserCourseService {
 
     List<FsUserCourseListUVO> selectFsUserCourseListUVO(FsUserCourseListUParam param);
 
+    /**
+     * 小程序:公域课程分页(含看课人数,与分类联动)
+     */
+    List<FsUserCoursePublicAppVO> selectFsUserCoursePublicAppList(FsUserCoursePublicAppQueryParam param);
+
     List<OptionsVO> selectFsUserCourseAllList();
 
     List<FsUserCourseListPVO> selectFsUserCourseListPVO(FsUserCourse param);

+ 17 - 0
fs-service/src/main/java/com/fs/course/service/IPublicCourseWatchStatisticsService.java

@@ -0,0 +1,17 @@
+package com.fs.course.service;
+
+import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
+import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+
+import java.util.List;
+
+/**
+ * 公域看课统计
+ */
+public interface IPublicCourseWatchStatisticsService {
+
+    List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param);
+
+    List<PublicCourseWatchStatCatalogVO> listCatalogStat(PublicCourseWatchStatQueryParam param);
+}

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

@@ -141,6 +141,9 @@ public class FsCourseWatchCommentServiceImpl extends ServiceImpl<FsCourseWatchCo
         }
         FsCourseWatchComment fsCourseWatchComment = new FsCourseWatchComment();
         BeanUtils.copyProperties(param, fsCourseWatchComment);
+        if (fsCourseWatchComment.getCateType() == null) {
+            fsCourseWatchComment.setCateType(0);
+        }
         fsCourseWatchComment.setCreateTime(DateUtils.getNowDate());
         int i = baseMapper.insertFsCourseWatchComment(fsCourseWatchComment);
         if (i > 0) {

+ 13 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java

@@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsUserCourseCategoryMapper;
 import com.fs.course.domain.FsUserCourseCategory;
+import com.fs.course.param.FsUserCourseCategoryAppQueryParam;
 import com.fs.course.service.IFsUserCourseCategoryService;
 
 /**
@@ -52,6 +53,18 @@ public class FsUserCourseCategoryServiceImpl implements IFsUserCourseCategorySer
         return fsUserCourseCategoryMapper.selectFsUserCourseCategoryList(fsUserCourseCategory);
     }
 
+    @Override
+    public List<FsUserCourseCategory> selectFsUserCourseCategoryAppList(FsUserCourseCategoryAppQueryParam param)
+    {
+        if (param == null) {
+            param = new FsUserCourseCategoryAppQueryParam();
+        }
+        if (param.getCateType() == null) {
+            param.setCateType(1);
+        }
+        return fsUserCourseCategoryMapper.selectFsUserCourseCategoryAppList(param);
+    }
+
     /**
      * 新增课堂分类
      *

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

@@ -283,6 +283,11 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return fsUserCourseMapper.selectFsUserCourseListUVO(param);
     }
 
+    @Override
+    public List<FsUserCoursePublicAppVO> selectFsUserCoursePublicAppList(FsUserCoursePublicAppQueryParam param) {
+        return fsUserCourseMapper.selectFsUserCoursePublicAppList(param);
+    }
+
     @Override
     public List<FsUserCourseListUVO> selectFsUserCourseCommentListUVO(FsUserCourseListUParam param) {
         return fsUserCourseMapper.selectFsUserCourseCommentListUVO(param);

+ 66 - 0
fs-service/src/main/java/com/fs/course/service/impl/PublicCourseWatchStatisticsServiceImpl.java

@@ -0,0 +1,66 @@
+package com.fs.course.service.impl;
+
+import com.fs.common.utils.StringUtils;
+import com.fs.course.mapper.PublicCourseWatchStatisticsMapper;
+import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
+import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@Service
+public class PublicCourseWatchStatisticsServiceImpl implements IPublicCourseWatchStatisticsService {
+
+    private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    @Autowired
+    private PublicCourseWatchStatisticsMapper publicCourseWatchStatisticsMapper;
+
+    @Override
+    public List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param) {
+        fillDefaultDateRange(param);
+        List<PublicCourseWatchStatCourseVO> list = publicCourseWatchStatisticsMapper.selectCourseDayStatList(param);
+        for (PublicCourseWatchStatCourseVO vo : list) {
+            if (vo.getClickRate() == null) {
+                vo.setClickRate(BigDecimal.ZERO);
+            }
+            if (vo.getExposurePv() == null) {
+                vo.setExposurePv(0L);
+            }
+            if (vo.getExposureUv() == null) {
+                vo.setExposureUv(0L);
+            }
+            if (vo.getClickPv() == null) {
+                vo.setClickPv(0L);
+            }
+            if (vo.getClickUv() == null) {
+                vo.setClickUv(0L);
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public List<PublicCourseWatchStatCatalogVO> listCatalogStat(PublicCourseWatchStatQueryParam param) {
+        fillDefaultDateRange(param);
+        return publicCourseWatchStatisticsMapper.selectCatalogStatList(param);
+    }
+
+    private void fillDefaultDateRange(PublicCourseWatchStatQueryParam param) {
+        if (param == null) {
+            return;
+        }
+        if (StringUtils.isEmpty(param.getEndDate())) {
+            param.setEndDate(LocalDate.now().format(DAY));
+        }
+        if (StringUtils.isEmpty(param.getBeginDate())) {
+            param.setBeginDate(LocalDate.now().minusDays(30).format(DAY));
+        }
+    }
+}

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchCommentListVO.java

@@ -55,4 +55,7 @@ public class FsCourseWatchCommentListVO {
     @ApiModelProperty(value = "小节名称")
     private String title;
 
+    @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论")
+    private Integer cateType;
+
 }

+ 56 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCoursePublicAppVO.java

@@ -0,0 +1,56 @@
+package com.fs.course.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序端公域课程列表(含看课人数)
+ */
+@Data
+@ApiModel("小程序-公域课程项")
+public class FsUserCoursePublicAppVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("课程ID")
+    private Long courseId;
+
+    @ApiModelProperty("展示标题(优先 title)")
+    private String courseTitle;
+
+    @ApiModelProperty("课程名称")
+    private String courseName;
+
+    @ApiModelProperty("课程封面")
+    private String imgUrl;
+
+    @ApiModelProperty("小封面")
+    private String secondImg;
+
+    @ApiModelProperty("看课人数(看课记录表 send_type=1 下去重 user_id)")
+    private Long watchUserCount;
+
+    @ApiModelProperty("首页课程顶部推荐位:0否 1是")
+    private Integer recHomeCourseTopEnabled;
+    @ApiModelProperty("首页顶部推荐方式:1插入 2替换")
+    private Integer recHomeCourseTopMode;
+    @ApiModelProperty("首页顶部推荐位置序号")
+    private Integer recHomeCourseTopSort;
+
+    @ApiModelProperty("商城首页推荐位:0否 1是")
+    private Integer recMallHomeEnabled;
+    @ApiModelProperty("商城首页推荐方式:1插入 2替换")
+    private Integer recMallHomeMode;
+    @ApiModelProperty("商城首页推荐位置序号")
+    private Integer recMallHomeSort;
+
+    @ApiModelProperty("首页长视频瀑布流:0否 1是")
+    private Integer recHomeLongVideoEnabled;
+    @ApiModelProperty("长视频瀑布流方式:1插入 2替换")
+    private Integer recHomeLongVideoMode;
+    @ApiModelProperty("长视频瀑布流位置序号")
+    private Integer recHomeLongVideoSort;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/course/vo/FsVideoResourceVO.java

@@ -92,4 +92,9 @@ public class FsVideoResourceVO {
      * 视频展示类型:landscape-横屏,portrait-竖屏
      */
     private String displayType;
+
+    /**
+     * 视频资源类型:0-视频资源,1-公域课视频资源
+     */
+    private Integer videoType;
 }

+ 61 - 0
fs-service/src/main/java/com/fs/course/vo/PublicCourseWatchStatCatalogVO.java

@@ -0,0 +1,61 @@
+package com.fs.course.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 公域看课统计 - 目录(小节)维度
+ */
+@Data
+public class PublicCourseWatchStatCatalogVO {
+
+    @Excel(name = "目录ID")
+    private Long videoId;
+
+    @Excel(name = "目录名称")
+    private String catalogName;
+
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    @Excel(name = "课程分类")
+    private String catePath;
+
+    @Excel(name = "浏览量(PV)")
+    private Long pv;
+
+    @Excel(name = "观看人数(UV)")
+    private Long uv;
+
+    @Excel(name = "完课人数")
+    private Long finishUv;
+
+    @Excel(name = "评论数")
+    private Long commentCount;
+
+    @Excel(name = "完课率%")
+    private BigDecimal finishRate;
+
+    /** 平均观看时长(秒) */
+    private Long avgWatchSeconds;
+
+    @Excel(name = "平均学习时长")
+    private String avgWatchDuration;
+
+    @Excel(name = "看课奖励类型")
+    private String rewardType;
+
+    @Excel(name = "答题人数")
+    private Long answerUv;
+
+    @Excel(name = "领取积分人数")
+    private Long integralReceiveUv;
+
+    @Excel(name = "分享私聊数")
+    private Long sharePrivateCount;
+
+    @Excel(name = "分享朋友圈数")
+    private Long shareTimelineCount;
+}

+ 55 - 0
fs-service/src/main/java/com/fs/course/vo/PublicCourseWatchStatCourseVO.java

@@ -0,0 +1,55 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 公域看课统计 - 按日 + 课程
+ */
+@Data
+public class PublicCourseWatchStatCourseVO {
+
+    @Excel(name = "时间", width = 12, dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date statDate;
+
+    @Excel(name = "课程ID")
+    private Long courseId;
+
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    @Excel(name = "课程分类")
+    private String rootCateName;
+
+    @Excel(name = "子分类")
+    private String subCateName;
+
+    @Excel(name = "曝光位置")
+    private String exposurePositionLabel;
+
+    @Excel(name = "曝光次数")
+    private Long exposurePv;
+
+    @Excel(name = "曝光人数")
+    private Long exposureUv;
+
+    @Excel(name = "点击次数")
+    private Long clickPv;
+
+    @Excel(name = "点击人数")
+    private Long clickUv;
+
+    @Excel(name = "点击率%")
+    private BigDecimal clickRate;
+
+    @Excel(name = "课程观看人数")
+    private Long watchUv;
+
+    @Excel(name = "课程完课人数")
+    private Long finishUv;
+}

+ 16 - 1
fs-service/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml

@@ -20,11 +20,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="fontSize"    column="font_size"    />
         <result property="mode"    column="mode"    />
         <result property="color"    column="color"    />
+        <result property="cateType"    column="cate_type"    />
     </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, `time`,
-               font_size, mode, color from fs_course_watch_comment
+               font_size, mode, color, cate_type from fs_course_watch_comment
     </sql>
 
     <select id="selectFsCourseWatchCommentList" resultType="com.fs.course.vo.FsCourseWatchCommentListVO">
@@ -44,6 +45,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         fs_course_watch_comment.font_size,
         fs_course_watch_comment.`mode`,
         fs_course_watch_comment.color,
+        fs_course_watch_comment.cate_type,
         fs_user.nick_name
         <if test="isAll != null and isAll == true ">
             ,fs_user_course.course_name, fs_user_course_video.title
@@ -65,6 +67,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 or  fs_user_course_video.title LIKE concat('%',#{keywords},'%')
                 )
             </if>
+            <if test="cateType != null">
+                <choose>
+                    <when test="cateType == 0">
+                        and (fs_course_watch_comment.cate_type = 0 or fs_course_watch_comment.cate_type is null)
+                    </when>
+                    <otherwise>
+                        and fs_course_watch_comment.cate_type = #{cateType}
+                    </otherwise>
+                </choose>
+            </if>
         </where>
     </select>
 
@@ -90,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="fontSize != null">font_size,</if>
             <if test="mode != null">mode,</if>
             <if test="color != null">color,</if>
+            <if test="cateType != null">cate_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -106,6 +119,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="fontSize != null">#{fontSize},</if>
             <if test="mode != null">#{mode},</if>
             <if test="color != null">#{color},</if>
+            <if test="cateType != null">#{cateType},</if>
          </trim>
     </insert>
 
@@ -126,6 +140,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="fontSize != null">font_size = #{fontSize},</if>
             <if test="mode != null">mode = #{mode},</if>
             <if test="color != null">color = #{color},</if>
+            <if test="cateType != null">cate_type = #{cateType},</if>
         </trim>
         where comment_id = #{commentId}
     </update>

+ 6 - 1
fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml

@@ -13,11 +13,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="updateTime"    column="update_time"    />
         <result property="isDel"    column="is_del"    />
+        <result property="cateType"    column="cate_type"    />
         <result property="userId"    column="user_id"    />
     </resultMap>
 
     <sql id="selectFsUserCourseCategoryVo">
-        select cate_id, pid, cate_name, sort, is_show, create_time, update_time, is_del from fs_user_course_category
+        select cate_id, pid, cate_name, sort, is_show, create_time, update_time, is_del, cate_type from fs_user_course_category
     </sql>
 
     <select id="selectFsUserCourseCategoryList" parameterType="FsUserCourseCategory" resultMap="FsUserCourseCategoryResult">
@@ -28,6 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="sort != null "> and sort = #{sort}</if>
             <if test="isShow != null "> and is_show = #{isShow}</if>
             <if test="isDel != null  and isDel != ''"> and is_del = #{isDel}</if>
+            <if test="cateType != null "> and cate_type = #{cateType}</if>
             <if test="userId != null "> and user_id = #{userId}</if>
         </where>
     </select>
@@ -55,6 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time,</if>
             <if test="updateTime != null">update_time,</if>
             <if test="isDel != null">is_del,</if>
+            <if test="cateType != null">cate_type,</if>
             <if test="userId != null">user_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -65,6 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">#{createTime},</if>
             <if test="updateTime != null">#{updateTime},</if>
             <if test="isDel != null">#{isDel},</if>
+            <if test="cateType != null">#{cateType},</if>
             <if test="userId != null">#{userId},</if>
          </trim>
     </insert>
@@ -79,6 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="isDel != null">is_del = #{isDel},</if>
+            <if test="cateType != null">cate_type = #{cateType},</if>
         </trim>
         where cate_id = #{cateId}
     </update>

+ 126 - 3
fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml

@@ -44,10 +44,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="secondImg"    column="second_img"    />
         <result property="companyIds"    column="company_ids"    />
         <result property="configJson"    column="config_json"    />
+        <result property="recHomeCourseTopEnabled" column="rec_home_course_top_enabled"/>
+        <result property="recHomeCourseTopMode" column="rec_home_course_top_mode"/>
+        <result property="recHomeCourseTopSort" column="rec_home_course_top_sort"/>
+        <result property="recMallHomeEnabled" column="rec_mall_home_enabled"/>
+        <result property="recMallHomeMode" column="rec_mall_home_mode"/>
+        <result property="recMallHomeSort" column="rec_mall_home_sort"/>
+        <result property="recHomeLongVideoEnabled" column="rec_home_long_video_enabled"/>
+        <result property="recHomeLongVideoMode" column="rec_home_long_video_mode"/>
+        <result property="recHomeLongVideoSort" column="rec_home_long_video_sort"/>
     </resultMap>
 
     <sql id="selectFsUserCourseVo">
-        select course_id,is_private,company_ids,is_next,talent_id,second_img,is_del, cate_id,sub_cate_id, course_name, title, img_url, sort, create_time, update_time, status, is_vip, is_hot, is_show, views, duration, description, hot_ranking, integral, price, sell_price, project, tags, likes, favorite_num, shares, is_auto_play, is_fast, is_best, is_tui, hot_num, is_integral, course_type, config_json from fs_user_course
+        select course_id,is_private,company_ids,is_next,talent_id,second_img,is_del, cate_id,sub_cate_id, course_name, title, img_url, sort, create_time, update_time, status, is_vip, is_hot, is_show, views, duration, description, hot_ranking, integral, price, sell_price, project, tags, likes, favorite_num, shares, is_auto_play, is_fast, is_best, is_tui, hot_num, is_integral, course_type, config_json,
+        rec_home_course_top_enabled, rec_home_course_top_mode, rec_home_course_top_sort,
+        rec_mall_home_enabled, rec_mall_home_mode, rec_mall_home_sort,
+        rec_home_long_video_enabled, rec_home_long_video_mode, rec_home_long_video_sort
+        from fs_user_course
     </sql>
 
     <select id="selectFsUserCourseList" parameterType="FsUserCourse" resultMap="FsUserCourseResult">
@@ -84,7 +97,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="courseType != null "> and course_type = #{courseType}</if>
             <if test="talentId != null "> and talent_id = #{talentId}</if>
             <if test="isNext != null "> and is_next = #{isNext}</if>
-            <if test="isPrivate != null "> and is_private = #{isPrivate}</if>
+            <if test="isPrivate != null ">
+                <choose>
+                    <when test="isPrivate == 1"> and (is_private = 1 or is_private is null)</when>
+                    <otherwise> and is_private = #{isPrivate}</otherwise>
+                </choose>
+            </if>
             <if test="userId != null "> and user_id = #{userId}</if>
             <if test="configJson != null "> and config_json = #{configJson}</if>
         </where>
@@ -215,6 +233,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyIds != null">company_ids,</if>
             <if test="userId != null">user_id,</if>
             <if test="configJson != null">config_json,</if>
+            <if test="recHomeCourseTopEnabled != null">rec_home_course_top_enabled,</if>
+            <if test="recHomeCourseTopMode != null">rec_home_course_top_mode,</if>
+            <if test="recHomeCourseTopSort != null">rec_home_course_top_sort,</if>
+            <if test="recMallHomeEnabled != null">rec_mall_home_enabled,</if>
+            <if test="recMallHomeMode != null">rec_mall_home_mode,</if>
+            <if test="recMallHomeSort != null">rec_mall_home_sort,</if>
+            <if test="recHomeLongVideoEnabled != null">rec_home_long_video_enabled,</if>
+            <if test="recHomeLongVideoMode != null">rec_home_long_video_mode,</if>
+            <if test="recHomeLongVideoSort != null">rec_home_long_video_sort,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="cateId != null">#{cateId},</if>
@@ -255,7 +282,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="secondImg != null">#{secondImg},</if>
             <if test="companyIds != null">#{companyIds},</if>
             <if test="userId != null">#{userId},</if>
-            <if test="configJson != null">config_json = #{configJson},</if>
+            <if test="configJson != null">#{configJson},</if>
+            <if test="recHomeCourseTopEnabled != null">#{recHomeCourseTopEnabled},</if>
+            <if test="recHomeCourseTopMode != null">#{recHomeCourseTopMode},</if>
+            <if test="recHomeCourseTopSort != null">#{recHomeCourseTopSort},</if>
+            <if test="recMallHomeEnabled != null">#{recMallHomeEnabled},</if>
+            <if test="recMallHomeMode != null">#{recMallHomeMode},</if>
+            <if test="recMallHomeSort != null">#{recMallHomeSort},</if>
+            <if test="recHomeLongVideoEnabled != null">#{recHomeLongVideoEnabled},</if>
+            <if test="recHomeLongVideoMode != null">#{recHomeLongVideoMode},</if>
+            <if test="recHomeLongVideoSort != null">#{recHomeLongVideoSort},</if>
         </trim>
     </insert>
 
@@ -299,6 +335,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isPrivate != null">is_private = #{isPrivate},</if>
             <if test="secondImg != null">second_img = #{secondImg},</if>
             <if test="companyIds != null">company_ids = #{companyIds},</if>
+            <if test="configJson != null">config_json = #{configJson},</if>
+            <if test="recHomeCourseTopEnabled != null">rec_home_course_top_enabled = #{recHomeCourseTopEnabled},</if>
+            <if test="recHomeCourseTopMode != null">rec_home_course_top_mode = #{recHomeCourseTopMode},</if>
+            <if test="recHomeCourseTopSort != null">rec_home_course_top_sort = #{recHomeCourseTopSort},</if>
+            <if test="recMallHomeEnabled != null">rec_mall_home_enabled = #{recMallHomeEnabled},</if>
+            <if test="recMallHomeMode != null">rec_mall_home_mode = #{recMallHomeMode},</if>
+            <if test="recMallHomeSort != null">rec_mall_home_sort = #{recMallHomeSort},</if>
+            <if test="recHomeLongVideoEnabled != null">rec_home_long_video_enabled = #{recHomeLongVideoEnabled},</if>
+            <if test="recHomeLongVideoMode != null">rec_home_long_video_mode = #{recHomeLongVideoMode},</if>
+            <if test="recHomeLongVideoSort != null">rec_home_long_video_sort = #{recHomeLongVideoSort},</if>
         </trim>
         where course_id = #{courseId}
     </update>
@@ -333,4 +379,81 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 <!--        ORDER BY-->
 <!--        c.course_id-->
 <!--    </select>-->
+
+    <!-- 小程序公域课程列表:公域分类 + 非公域课 + 看课人数(去重 user_id, send_type=1) + 推荐位字段 -->
+    <select id="selectFsUserCoursePublicAppList" resultType="com.fs.course.vo.FsUserCoursePublicAppVO">
+        SELECT
+            c.course_id AS courseId,
+            COALESCE(c.title, c.course_name) AS courseTitle,
+            c.course_name AS courseName,
+            c.img_url AS imgUrl,
+            c.second_img AS secondImg,
+            IFNULL(wl_stat.watch_uv, 0) AS watchUserCount,
+            IFNULL(c.rec_home_course_top_enabled, 0) AS recHomeCourseTopEnabled,
+            c.rec_home_course_top_mode AS recHomeCourseTopMode,
+            c.rec_home_course_top_sort AS recHomeCourseTopSort,
+            IFNULL(c.rec_mall_home_enabled, 0) AS recMallHomeEnabled,
+            c.rec_mall_home_mode AS recMallHomeMode,
+            c.rec_mall_home_sort AS recMallHomeSort,
+            IFNULL(c.rec_home_long_video_enabled, 0) AS recHomeLongVideoEnabled,
+            c.rec_home_long_video_mode AS recHomeLongVideoMode,
+            c.rec_home_long_video_sort AS recHomeLongVideoSort
+        FROM fs_user_course c
+        LEFT JOIN (
+            SELECT course_id, COUNT(DISTINCT user_id) AS watch_uv
+            FROM fs_course_watch_log
+            WHERE send_type = 1
+            GROUP BY course_id
+        ) wl_stat ON wl_stat.course_id = c.course_id
+        WHERE IFNULL(c.is_del, 0) = 0
+          AND IFNULL(c.is_show, 0) = 1
+          AND IFNULL(c.is_private, 0) = 0
+          AND EXISTS (
+                SELECT 1 FROM fs_user_course_category pc2
+                WHERE pc2.cate_id = c.cate_id AND pc2.cate_type = 1 AND IFNULL(pc2.is_del, 0) = 0
+            )
+          AND (
+                c.sub_cate_id IS NULL
+                OR EXISTS (
+                    SELECT 1 FROM fs_user_course_category sc2
+                    WHERE sc2.cate_id = c.sub_cate_id AND sc2.cate_type = 1 AND IFNULL(sc2.is_del, 0) = 0
+                )
+            )
+        <if test="q.cateId != null and q.subCateId != null">
+            AND c.cate_id = #{q.cateId}
+            AND c.sub_cate_id = #{q.subCateId}
+        </if>
+        <if test="q.subCateId != null and q.cateId == null">
+            AND c.sub_cate_id = #{q.subCateId}
+        </if>
+        <if test="q.subCateId == null and q.cateId != null">
+            AND (c.cate_id = #{q.cateId} OR c.sub_cate_id = #{q.cateId})
+        </if>
+        <if test="q.keyword != null and q.keyword != ''">
+            AND (
+                c.title LIKE CONCAT('%', #{q.keyword}, '%')
+                OR c.course_name LIKE CONCAT('%', #{q.keyword}, '%')
+            )
+        </if>
+        <if test="q.recommendSlot != null and q.recommendSlot == 1">
+            AND IFNULL(c.rec_home_course_top_enabled, 0) = 1
+        </if>
+        <if test="q.recommendSlot != null and q.recommendSlot == 2">
+            AND IFNULL(c.rec_mall_home_enabled, 0) = 1
+        </if>
+        <if test="q.recommendSlot != null and q.recommendSlot == 3">
+            AND IFNULL(c.rec_home_long_video_enabled, 0) = 1
+        </if>
+        ORDER BY
+        <if test="q.recommendSlot != null and q.recommendSlot == 1">
+            IFNULL(c.rec_home_course_top_sort, 999999) ASC,
+        </if>
+        <if test="q.recommendSlot != null and q.recommendSlot == 2">
+            IFNULL(c.rec_mall_home_sort, 999999) ASC,
+        </if>
+        <if test="q.recommendSlot != null and q.recommendSlot == 3">
+            IFNULL(c.rec_home_long_video_sort, 999999) ASC,
+        </if>
+        c.sort ASC, c.course_id DESC
+    </select>
 </mapper>

+ 10 - 0
fs-service/src/main/resources/mapper/course/FsVideoResourceMapper.xml

@@ -24,6 +24,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="params.typeSubId != null">
             and rr.type_sub_id = #{params.typeSubId}
         </if>
+        <if test="params.videoType != null">
+            <choose>
+                <when test="params.videoType == 0">
+                    and (rr.video_type = 0 or rr.video_type is null)
+                </when>
+                <otherwise>
+                    and rr.video_type = #{params.videoType}
+                </otherwise>
+            </choose>
+        </if>
         order by rr.sort,rr.id desc
     </select>
 

+ 203 - 0
fs-service/src/main/resources/mapper/course/PublicCourseWatchStatisticsMapper.xml

@@ -0,0 +1,203 @@
+<?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.PublicCourseWatchStatisticsMapper">
+
+    <!-- 公域课:非私域 + 一级分类为公域分类;若有二级分类须同为公域分类 -->
+    <sql id="publicCourseWhereCourse">
+        AND IFNULL(uc.is_del, 0) = 0
+        AND IFNULL(uc.is_private, 0) = 0
+        AND EXISTS (
+            SELECT 1 FROM fs_user_course_category pc2
+            WHERE pc2.cate_id = uc.cate_id AND pc2.cate_type = 1 AND IFNULL(pc2.is_del, 0) = 0
+        )
+        AND (
+            uc.sub_cate_id IS NULL
+            OR EXISTS (
+                SELECT 1 FROM fs_user_course_category sc2
+                WHERE sc2.cate_id = uc.sub_cate_id AND sc2.cate_type = 1 AND IFNULL(sc2.is_del, 0) = 0
+            )
+        )
+    </sql>
+
+    <select id="selectCourseDayStatList" resultType="com.fs.course.vo.PublicCourseWatchStatCourseVO">
+        SELECT
+            x.stat_date AS statDate,
+            x.course_id AS courseId,
+            COALESCE(uc.title, uc.course_name) AS courseName,
+            pc.cate_name AS rootCateName,
+            sc.cate_name AS subCateName,
+            CASE
+                WHEN IFNULL(m.has_home, 0) = 1 AND IFNULL(m.has_center, 0) = 1 THEN '首页推荐/课程中心'
+                WHEN IFNULL(m.has_home, 0) = 1 THEN '首页推荐'
+                WHEN IFNULL(m.has_center, 0) = 1 THEN '课程中心'
+                ELSE '-'
+            END AS exposurePositionLabel,
+            IFNULL(m.exposure_pv, 0) AS exposurePv,
+            IFNULL(m.exposure_uv, 0) AS exposureUv,
+            IFNULL(m.click_pv, 0) AS clickPv,
+            IFNULL(m.click_uv, 0) AS clickUv,
+            CASE
+                WHEN IFNULL(m.exposure_pv, 0) &gt; 0 THEN ROUND(100.0 * IFNULL(m.click_pv, 0) / m.exposure_pv, 2)
+                ELSE 0
+            END AS clickRate,
+            x.watch_uv AS watchUv,
+            x.finish_uv AS finishUv
+        FROM (
+            SELECT
+                DATE(wl.create_time) AS stat_date,
+                wl.course_id AS course_id,
+                COUNT(DISTINCT wl.user_id) AS watch_uv,
+                COUNT(DISTINCT CASE WHEN wl.log_type = 2 THEN wl.user_id END) AS finish_uv
+            FROM fs_course_watch_log wl
+            INNER JOIN fs_user_course uc ON uc.course_id = wl.course_id
+            <include refid="publicCourseWhereCourse"/>
+            WHERE wl.send_type = 1
+              AND wl.create_time &gt;= #{q.beginDate}
+              AND wl.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            <if test="q.keywords != null and q.keywords != ''">
+                AND (
+                    uc.title LIKE CONCAT('%', #{q.keywords}, '%')
+                    OR uc.course_name LIKE CONCAT('%', #{q.keywords}, '%')
+                )
+            </if>
+            <if test="q.cateId != null">
+                AND uc.cate_id = #{q.cateId}
+            </if>
+            <if test="q.subCateId != null">
+                AND uc.sub_cate_id = #{q.subCateId}
+            </if>
+            GROUP BY DATE(wl.create_time), wl.course_id
+        ) x
+        INNER JOIN fs_user_course uc ON uc.course_id = x.course_id
+        LEFT JOIN fs_user_course_category pc ON pc.cate_id = uc.cate_id AND IFNULL(pc.is_del, 0) = 0
+        LEFT JOIN fs_user_course_category sc ON sc.cate_id = uc.sub_cate_id AND IFNULL(sc.is_del, 0) = 0
+        LEFT JOIN (
+            SELECT
+                DATE(e.create_time) AS dt,
+                e.course_id AS course_id,
+                SUM(CASE WHEN e.event_type = 0 THEN 1 ELSE 0 END) AS exposure_pv,
+                COUNT(DISTINCT CASE WHEN e.event_type = 0 THEN e.user_id END) AS exposure_uv,
+                SUM(CASE WHEN e.event_type = 1 THEN 1 ELSE 0 END) AS click_pv,
+                COUNT(DISTINCT CASE WHEN e.event_type = 1 THEN e.user_id END) AS click_uv,
+                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 0 THEN 1 ELSE 0 END) AS has_home,
+                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 1 THEN 1 ELSE 0 END) AS has_center
+            FROM fs_course_marketing_event e
+            WHERE e.create_time &gt;= #{q.beginDate}
+              AND e.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            GROUP BY DATE(e.create_time), e.course_id
+        ) m ON m.dt = x.stat_date AND m.course_id = x.course_id
+        ORDER BY x.stat_date DESC, x.course_id DESC
+    </select>
+
+    <select id="selectCatalogStatList" resultType="com.fs.course.vo.PublicCourseWatchStatCatalogVO">
+        SELECT
+            v.video_id AS videoId,
+            v.title AS catalogName,
+            COALESCE(uc.title, uc.course_name) AS courseName,
+            CASE
+                WHEN sc.cate_name IS NULL OR sc.cate_name = '' THEN pc.cate_name
+                ELSE CONCAT(pc.cate_name, '/', sc.cate_name)
+            END AS catePath,
+            COUNT(wl.log_id) AS pv,
+            COUNT(DISTINCT wl.user_id) AS uv,
+            COUNT(DISTINCT CASE WHEN wl.log_type = 2 THEN wl.user_id END) AS finishUv,
+            (
+                SELECT COUNT(*)
+                FROM fs_course_watch_comment c
+                WHERE c.video_id = v.video_id
+                  AND IFNULL(c.is_revoke, 0) = 0
+                  AND c.cate_type = 1
+            ) AS commentCount,
+            CASE
+                WHEN COUNT(DISTINCT wl.user_id) &gt; 0 THEN
+                    ROUND(100.0 * COUNT(DISTINCT CASE WHEN wl.log_type = 2 THEN wl.user_id END) / COUNT(DISTINCT wl.user_id), 2)
+                ELSE 0
+            END AS finishRate,
+            CASE
+                WHEN COUNT(DISTINCT wl.user_id) &gt; 0 THEN
+                    ROUND(SUM(IFNULL(wl.duration, 0)) / COUNT(DISTINCT wl.user_id), 0)
+                ELSE 0
+            END AS avgWatchSeconds,
+            TIME_FORMAT(
+                SEC_TO_TIME(
+                    CASE
+                        WHEN COUNT(DISTINCT wl.user_id) &gt; 0 THEN ROUND(SUM(IFNULL(wl.duration, 0)) / COUNT(DISTINCT wl.user_id), 0)
+                        ELSE 0
+                    END
+                ),
+                '%H:%i:%s'
+            ) AS avgWatchDuration,
+            MAX(wl.reward_type) AS rewardType,
+            (
+                SELECT COUNT(DISTINCT cal.user_id)
+                FROM fs_course_answer_logs cal
+                WHERE cal.video_id = v.video_id
+                  AND cal.create_time &gt;= #{q.beginDate}
+                  AND cal.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            ) AS answerUv,
+            (
+                SELECT COUNT(DISTINCT il.user_id)
+                FROM fs_course_watch_log wl2
+                INNER JOIN fs_user_integral_logs il
+                    ON wl2.log_id = CAST(il.business_id AS UNSIGNED)
+                WHERE wl2.video_id = v.video_id
+                  AND wl2.send_type = 1
+                  AND wl2.create_time &gt;= #{q.beginDate}
+                  AND wl2.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+                  AND il.business_id IS NOT NULL AND TRIM(il.business_id) &lt;&gt; ''
+                  AND il.business_id REGEXP '^[0-9]+$'
+                  AND IFNULL(il.integral, 0) &gt; 0
+                  AND il.log_type = 17
+            ) AS integralReceiveUv,
+            (
+                SELECT COUNT(*)
+                FROM fs_course_share_log s
+                WHERE s.video_id = v.video_id
+                  AND s.share_type = 'private_chat'
+                  AND s.create_time &gt;= #{q.beginDate}
+                  AND s.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            ) AS sharePrivateCount,
+            (
+                SELECT COUNT(*)
+                FROM fs_course_share_log s
+                WHERE s.video_id = v.video_id
+                  AND s.share_type = 'timeline'
+                  AND s.create_time &gt;= #{q.beginDate}
+                  AND s.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            ) AS shareTimelineCount
+        FROM fs_user_course_video v
+        INNER JOIN fs_user_course uc ON uc.course_id = v.course_id
+        <include refid="publicCourseWhereCourse"/>
+        LEFT JOIN fs_user_course_category pc ON pc.cate_id = uc.cate_id AND IFNULL(pc.is_del, 0) = 0
+        LEFT JOIN fs_user_course_category sc ON sc.cate_id = uc.sub_cate_id AND IFNULL(sc.is_del, 0) = 0
+        LEFT JOIN fs_course_watch_log wl ON wl.video_id = v.video_id
+            AND wl.send_type = 1
+            AND wl.create_time &gt;= #{q.beginDate}
+            AND wl.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+        WHERE IFNULL(v.is_del, 0) = 0
+        <if test="q.keywords != null and q.keywords != ''">
+            AND (
+                uc.title LIKE CONCAT('%', #{q.keywords}, '%')
+                OR uc.course_name LIKE CONCAT('%', #{q.keywords}, '%')
+                OR v.title LIKE CONCAT('%', #{q.keywords}, '%')
+            )
+        </if>
+        <if test="q.cateId != null">
+            AND uc.cate_id = #{q.cateId}
+        </if>
+        <if test="q.subCateId != null">
+            AND uc.sub_cate_id = #{q.subCateId}
+        </if>
+        GROUP BY
+            v.video_id,
+            v.title,
+            uc.course_id,
+            uc.title,
+            uc.course_name,
+            pc.cate_name,
+            sc.cate_name
+        ORDER BY v.video_id DESC
+    </select>
+</mapper>

+ 37 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -81,6 +81,43 @@ public class CourseController extends  AppBaseController{
             return R.error("操作异常");
         }
     }
+
+    /**
+     * 小程序端:公域课程分类分页(默认 cateType=1,与后台公域课分类一致)
+     */
+    @ApiOperation(value = "小程序-课程分类分页", notes = "仅返回「二级分类」,且至少被一门公域课占用(sub_cate_id);一级父分类也需为公域。可选 pid=一级 cateId 收窄范围。排序 sort asc, cate_id asc。")
+    @GetMapping("/publicCourseCategory/list")
+    public R listPublicCourseCategory(FsUserCourseCategoryAppQueryParam param) {
+        if (param == null) {
+            param = new FsUserCourseCategoryAppQueryParam();
+        }
+        int pageNum = param.getPageNum() == null || param.getPageNum() < 1 ? 1 : param.getPageNum();
+        int pageSize = param.getPageSize() == null || param.getPageSize() < 1 ? 10 : param.getPageSize();
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCourseCategory> list = courseCategoryService.selectFsUserCourseCategoryAppList(param);
+        PageInfo<FsUserCourseCategory> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
+    /**
+     * 小程序端:公域课程分页(联表看课记录统计看课人数;与分类联动传 cateId / subCateId)
+     */
+    @ApiOperation(value = "小程序-公域课程分页", notes = "仅公域课;返回封面、标题、看课人数、推荐管理字段。看课人数=fs_course_watch_log send_type=1 去重 user_id。"
+            + "分类:同时传 cateId+subCateId 时按父子双条件;仅 subCateId 按二级;仅 cateId 时按一级或二级 id 匹配(cate_id 或 sub_cate_id)。"
+            + "推荐位 recommendSlot:1首页顶部 2商城首页 3长视频瀑布流,仅返回对应推荐位已勾选课程并按位置序号排序。")
+    @GetMapping("/publicCourse/list")
+    public R listPublicCourse(FsUserCoursePublicAppQueryParam param) {
+        if (param == null) {
+            param = new FsUserCoursePublicAppQueryParam();
+        }
+        int pageNum = param.getPageNum() == null || param.getPageNum() < 1 ? 1 : param.getPageNum();
+        int pageSize = param.getPageSize() == null || param.getPageSize() < 1 ? 10 : param.getPageSize();
+        PageHelper.startPage(pageNum, pageSize);
+        List<FsUserCoursePublicAppVO> list = courseService.selectFsUserCoursePublicAppList(param);
+        PageInfo<FsUserCoursePublicAppVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
 //    @Cacheable(value = "getCourseList",key = "#param" )
     @ApiOperation("课程列表")
     @GetMapping("/getCourseList")