Prechádzať zdrojové kódy

同步评论、投诉相关代码

Long 12 hodín pred
rodič
commit
b4eef74fa0
47 zmenil súbory, kde vykonal 2426 pridanie a 74 odobranie
  1. 120 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseWatchCommentController.java
  2. 102 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseComplaintRecordController.java
  3. 95 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseComplaintTypeController.java
  4. 59 21
      fs-admin/src/main/java/com/fs/web/controller/system/SysKeywordController.java
  5. 2 0
      fs-service-system/src/main/java/com/fs/course/config/CourseConfig.java
  6. 69 0
      fs-service-system/src/main/java/com/fs/course/domain/FsCourseWatchComment.java
  7. 56 0
      fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseComplaintRecord.java
  8. 29 0
      fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseComplaintType.java
  9. 8 6
      fs-service-system/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java
  10. 89 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java
  11. 70 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseComplaintRecordMapper.java
  12. 62 0
      fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseComplaintTypeMapper.java
  13. 29 0
      fs-service-system/src/main/java/com/fs/course/param/FsCourseWatchCommentListParam.java
  14. 42 0
      fs-service-system/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java
  15. 44 0
      fs-service-system/src/main/java/com/fs/course/param/FsCourseWatchCommentSaveParam.java
  16. 24 0
      fs-service-system/src/main/java/com/fs/course/param/UserCourseComplaintRecordParam.java
  17. 90 0
      fs-service-system/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java
  18. 72 0
      fs-service-system/src/main/java/com/fs/course/service/IFsUserCourseComplaintRecordService.java
  19. 70 0
      fs-service-system/src/main/java/com/fs/course/service/IFsUserCourseComplaintTypeService.java
  20. 172 0
      fs-service-system/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java
  21. 104 0
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintRecordServiceImpl.java
  22. 137 0
      fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintTypeServiceImpl.java
  23. 58 0
      fs-service-system/src/main/java/com/fs/course/vo/FsCourseWatchCommentListVO.java
  24. 54 0
      fs-service-system/src/main/java/com/fs/course/vo/FsCourseWatchCommentVO.java
  25. 43 0
      fs-service-system/src/main/java/com/fs/course/vo/FsUserCourseComplaintRecordPageListVO.java
  26. 28 0
      fs-service-system/src/main/java/com/fs/course/vo/FsUserCourseComplaintTypeListVO.java
  27. 2 0
      fs-service-system/src/main/java/com/fs/qw/domain/QwExternalContact.java
  28. 4 0
      fs-service-system/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  29. 8 0
      fs-service-system/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  30. 5 0
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  31. 4 0
      fs-service-system/src/main/java/com/fs/qw/vo/QwContactListVO.java
  32. 3 0
      fs-service-system/src/main/java/com/fs/sop/domain/QwSop.java
  33. 4 5
      fs-service-system/src/main/java/com/fs/system/domain/SysKeyword.java
  34. 13 9
      fs-service-system/src/main/java/com/fs/system/mapper/SysKeywordMapper.java
  35. 16 8
      fs-service-system/src/main/java/com/fs/system/service/ISysKeywordService.java
  36. 17 12
      fs-service-system/src/main/java/com/fs/system/service/impl/SysKeywordServiceImpl.java
  37. 178 0
      fs-service-system/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml
  38. 102 0
      fs-service-system/src/main/resources/mapper/course/FsUserCourseComplaintRecordMapper.xml
  39. 74 0
      fs-service-system/src/main/resources/mapper/course/FsUserCourseComplaintTypeMapper.xml
  40. 5 1
      fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml
  41. 4 0
      fs-service-system/src/main/resources/mapper/sop/QwSopMapper.xml
  42. 12 11
      fs-service-system/src/main/resources/mapper/system/SysKeywordMapper.xml
  43. 5 0
      fs-user-app/pom.xml
  44. 36 1
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  45. 48 0
      fs-user-app/src/main/java/com/fs/app/controller/UserCourseComplaintController.java
  46. 51 0
      fs-user-app/src/main/java/com/fs/app/websocket/bean/SendMsgVO.java
  47. 107 0
      fs-user-app/src/main/java/com/fs/app/websocket/service/WebSocketServer.java

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

@@ -0,0 +1,120 @@
+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.domain.R;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.param.FsCourseWatchCommentPageParam;
+import com.fs.course.service.IFsCourseWatchCommentService;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
+import com.fs.qw.service.IQwExternalContactService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 看课评论Controller
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+@RestController
+@RequestMapping("/course/courseWatchComment")
+public class FsCourseWatchCommentController extends BaseController
+{
+    @Autowired
+    private IFsCourseWatchCommentService fsCourseWatchCommentService;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    /**
+     * 查询看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:list')")
+    @GetMapping("/list")
+    public R list(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam)
+    {
+//        startPage();
+        PageHelper.startPage(fsCourseWatchCommentPageParam.getPageNum(), fsCourseWatchCommentPageParam.getPageSize());
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentList(fsCourseWatchCommentPageParam);
+        PageInfo<FsCourseWatchCommentListVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("rows", pageInfo);
+    }
+
+    /**
+     * 导出看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:export')")
+    @Log(title = "看课评论", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam)
+    {
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentList(fsCourseWatchCommentPageParam);
+        ExcelUtil<FsCourseWatchCommentListVO> util = new ExcelUtil<FsCourseWatchCommentListVO>(FsCourseWatchCommentListVO.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));
+    }
+
+    @Log(title = "手动拉黑外部联系人用户", businessType = BusinessType.UPDATE)
+    @PutMapping("/addBlack")
+    public R addBlack(Integer commentStatus, Long fsUserId)
+    {
+        int i = qwExternalContactService.updateQwExternalContactByFsUserId(commentStatus, fsUserId);
+        if (i > 0){
+            return R.ok();
+        } else {
+            return R.error();
+        }
+    }
+
+}

+ 102 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseComplaintRecordController.java

@@ -0,0 +1,102 @@
+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.domain.R;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsUserCourseComplaintRecord;
+import com.fs.course.service.IFsUserCourseComplaintRecordService;
+import com.fs.course.vo.FsUserCourseComplaintRecordPageListVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 看课投诉记录Controller
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@RestController
+@RequestMapping("/course/userCourseComplaintRecord")
+public class FsUserCourseComplaintRecordController extends BaseController
+{
+    @Autowired
+    private IFsUserCourseComplaintRecordService fsUserCourseComplaintRecordService;
+
+    /**
+     * 查询看课投诉记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:list')")
+    @GetMapping("/list")
+    public R list(FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+//        startPage();
+        PageHelper.startPage(fsUserCourseComplaintRecord.getPageNum(), fsUserCourseComplaintRecord.getPageSize());
+        List<FsUserCourseComplaintRecordPageListVO> list = fsUserCourseComplaintRecordService.selectFsUserCourseComplaintRecordList(fsUserCourseComplaintRecord);
+        PageInfo<FsUserCourseComplaintRecordPageListVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("rows", pageInfo);
+    }
+
+    /**
+     * 导出看课投诉记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:export')")
+    @Log(title = "看课投诉记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        List<FsUserCourseComplaintRecordPageListVO> list = fsUserCourseComplaintRecordService.selectFsUserCourseComplaintRecordList(fsUserCourseComplaintRecord);
+        ExcelUtil<FsUserCourseComplaintRecordPageListVO> util = new ExcelUtil<>(FsUserCourseComplaintRecordPageListVO.class);
+        return util.exportExcel(list, "看课投诉记录");
+    }
+
+    /**
+     * 获取看课投诉记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:query')")
+    @GetMapping(value = "/{recordId}")
+    public AjaxResult getInfo(@PathVariable("recordId") Long recordId)
+    {
+        return AjaxResult.success(fsUserCourseComplaintRecordService.selectFsUserCourseComplaintRecordByRecordId(recordId));
+    }
+
+    /**
+     * 新增看课投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:add')")
+    @Log(title = "看课投诉记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        return toAjax(fsUserCourseComplaintRecordService.insertFsUserCourseComplaintRecord(fsUserCourseComplaintRecord));
+    }
+
+    /**
+     * 修改看课投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:edit')")
+    @Log(title = "看课投诉记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        return toAjax(fsUserCourseComplaintRecordService.updateFsUserCourseComplaintRecord(fsUserCourseComplaintRecord));
+    }
+
+    /**
+     * 删除看课投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintRecord:remove')")
+    @Log(title = "看课投诉记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{recordIds}")
+    public AjaxResult remove(@PathVariable Long[] recordIds)
+    {
+        return toAjax(fsUserCourseComplaintRecordService.deleteFsUserCourseComplaintRecordByRecordIds(recordIds));
+    }
+}

+ 95 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseComplaintTypeController.java

@@ -0,0 +1,95 @@
+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.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsUserCourseComplaintType;
+import com.fs.course.service.IFsUserCourseComplaintTypeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 看课投诉类型Controller
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@RestController
+@RequestMapping("/course/userCourseComplaintType")
+public class FsUserCourseComplaintTypeController extends BaseController
+{
+    @Autowired
+    private IFsUserCourseComplaintTypeService fsUserCourseComplaintTypeService;
+
+    /**
+     * 查询看课投诉类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:list')")
+    @GetMapping("/list")
+    public AjaxResult list(FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        List<FsUserCourseComplaintType> list = fsUserCourseComplaintTypeService.selectFsUserCourseComplaintTypeList(fsUserCourseComplaintType);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 导出看课投诉类型列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:export')")
+    @Log(title = "看课投诉类型", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        List<FsUserCourseComplaintType> list = fsUserCourseComplaintTypeService.selectFsUserCourseComplaintTypeList(fsUserCourseComplaintType);
+        ExcelUtil<FsUserCourseComplaintType> util = new ExcelUtil<FsUserCourseComplaintType>(FsUserCourseComplaintType.class);
+        return util.exportExcel(list, "看课投诉类型数据");
+    }
+
+    /**
+     * 获取看课投诉类型详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:query')")
+    @GetMapping(value = "/{complaintTypeId}")
+    public AjaxResult getInfo(@PathVariable("complaintTypeId") Long complaintTypeId)
+    {
+        return AjaxResult.success(fsUserCourseComplaintTypeService.selectFsUserCourseComplaintTypeByComplaintTypeId(complaintTypeId));
+    }
+
+    /**
+     * 新增看课投诉类型
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:add')")
+    @Log(title = "看课投诉类型", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        return toAjax(fsUserCourseComplaintTypeService.insertFsUserCourseComplaintType(fsUserCourseComplaintType));
+    }
+
+    /**
+     * 修改看课投诉类型
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:edit')")
+    @Log(title = "看课投诉类型", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        return toAjax(fsUserCourseComplaintTypeService.updateFsUserCourseComplaintType(fsUserCourseComplaintType));
+    }
+
+    /**
+     * 删除看课投诉类型
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseComplaintType:remove')")
+    @Log(title = "看课投诉类型", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{complaintTypeIds}")
+    public AjaxResult remove(@PathVariable Long[] complaintTypeIds)
+    {
+        return toAjax(fsUserCourseComplaintTypeService.deleteFsUserCourseComplaintTypeByComplaintTypeIds(complaintTypeIds));
+    }
+}

+ 59 - 21
fs-admin/src/main/java/com/fs/web/controller/system/SysKeywordController.java

@@ -1,29 +1,27 @@
 package com.fs.web.controller.system;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import com.fs.common.core.domain.R;
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
-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.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.system.domain.SysKeyword;
 import com.fs.system.service.ISysKeywordService;
-import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.common.core.page.TableDataInfo;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 系统关键字Controller
@@ -38,6 +36,14 @@ public class SysKeywordController extends BaseController
     @Autowired
     private ISysKeywordService sysKeywordService;
 
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    public RedisTemplate<String,String> redisTemplate;
+
+    private static final String REDIS_KEY = "sys:keywords";
+
     /**
      * 查询系统关键字列表
      */
@@ -87,7 +93,10 @@ public class SysKeywordController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody SysKeyword sysKeyword)
     {
-        return toAjax(sysKeywordService.insertSysKeyword(sysKeyword));
+        int i = sysKeywordService.insertSysKeyword(sysKeyword);
+        // 缓存
+        redisTemplate.opsForSet().add(REDIS_KEY, sysKeyword.getKeyword());
+        return toAjax(i);
     }
 
     /**
@@ -98,7 +107,14 @@ public class SysKeywordController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody SysKeyword sysKeyword)
     {
-        return toAjax(sysKeywordService.updateSysKeyword(sysKeyword));
+        //获取之前的数据
+        SysKeyword sysKeywordOld = sysKeywordService.selectSysKeywordById(sysKeyword.getKeywordId());
+        String keywordOld = sysKeywordOld.getKeyword();
+        int i = sysKeywordService.updateSysKeyword(sysKeyword);
+        // 更新缓存
+        redisTemplate.opsForSet().remove(REDIS_KEY, keywordOld);
+        redisTemplate.opsForSet().add(REDIS_KEY, sysKeyword.getKeyword());
+        return toAjax(i);
     }
 
     /**
@@ -109,6 +125,28 @@ public class SysKeywordController extends BaseController
 	@DeleteMapping("/{keywordIds}")
     public AjaxResult remove(@PathVariable Long[] keywordIds)
     {
-        return toAjax(sysKeywordService.deleteSysKeywordByIds(keywordIds));
+        List<SysKeyword> sysKeywords = sysKeywordService.selectSysKeywordByIds(keywordIds);
+        int i = sysKeywordService.deleteSysKeywordByIds(keywordIds);
+        if (!CollectionUtils.isEmpty(sysKeywords)) {
+            redisTemplate.opsForSet().remove(REDIS_KEY, sysKeywords.stream().map(SysKeyword::getKeyword).toArray(String[]::new));
+        }
+        return toAjax(i);
+    }
+
+    /**
+     * 启动加载全部关键字到缓存
+     */
+    @PostConstruct
+    public void initKeywords() {
+        SysKeyword sysKeywordParam = new SysKeyword();
+        List<SysKeyword> sysKeywords = sysKeywordService.selectSysKeywordList(sysKeywordParam);
+        List<String> keywords = sysKeywords.stream()
+                .map(SysKeyword::getKeyword)
+                .collect(Collectors.toList());
+
+        if (!keywords.isEmpty()) {
+            redisTemplate.opsForSet().add(REDIS_KEY, keywords.toArray(new String[0]));
+        }
+        System.out.println("加载全部关键字到缓存");
     }
 }

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

@@ -29,6 +29,8 @@ public class CourseConfig implements Serializable {
     private List<DisabledTimeVo> disabledTimeList;//充值手续费百分比
     private String companyUserQRCode;// 默认客服二维码图片
     private String courseLogo;//课程Logo
+    private Integer openCommentStatus; //开启评论/弹幕
+    private Integer viewCommentNum; // 查看历史评论数量
 
     @Data
     public static class DisabledTimeVo{

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

@@ -0,0 +1,69 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+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;
+
+    @Excel(name = "时间点,记录发送消息的视频播放时间点")
+    private Integer time;
+
+    @ApiModelProperty(value = "字体大小")
+    @Excel(name = "字体大小")
+    private String fontSize;
+
+    @ApiModelProperty(value = "展示模式")
+    @Excel(name = "展示模式")
+    private String mode;
+
+    @ApiModelProperty(value = "字体颜色")
+    @Excel(name = "字体颜色")
+    private String color;
+
+}

+ 56 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseComplaintRecord.java

@@ -0,0 +1,56 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 看课投诉记录对象 fs_user_course_complaint_record
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserCourseComplaintRecord extends BaseEntity{
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    @TableField(exist = false)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "用户昵称")
+    @TableField(exist = false)
+    private String nickName;
+
+    /** 投诉记录id */
+    private Long recordId;
+
+    /** 用户id,关联fs_user */
+    @Excel(name = "用户id,关联fs_user")
+    private Long userId;
+
+    /** 投诉类型id */
+    @Excel(name = "投诉类型id")
+    private Long complaintTypeId;
+
+    /** 投诉内容 */
+    @Excel(name = "投诉内容")
+    private String complaintContent;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 视频小节id */
+    @Excel(name = "视频小节id")
+    private Long videoId;
+
+
+}

+ 29 - 0
fs-service-system/src/main/java/com/fs/course/domain/FsUserCourseComplaintType.java

@@ -0,0 +1,29 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.TreeEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 看课投诉类型对象 fs_user_course_complaint_type
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserCourseComplaintType extends TreeEntity{
+
+    /** 投诉类型id */
+    private Long complaintTypeId;
+
+    /** 投诉类型名称 */
+    @Excel(name = "投诉类型名称")
+    private String complaintTypeName;
+
+    /** 级别(目前只有两级) */
+    @Excel(name = "级别", readConverterExp = "目=前只有两级")
+    private Integer typeLevel;
+
+}

+ 8 - 6
fs-service-system/src/main/java/com/fs/course/domain/FsUserCoursePeriod.java

@@ -1,16 +1,15 @@
 package com.fs.course.domain;
 
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.util.Date;
-import java.util.List;
-
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+
 /**
  * 会员营期对象 fs_user_course_period
  *
@@ -101,4 +100,7 @@ public class FsUserCoursePeriod
     private Integer maxViewNum;
 
     private String courseLogo;
+
+    @Excel(name = "开启评论或者弹幕,1-开启评论;2-开启弹幕;3-都关闭")
+    private Integer openCommentStatus;
 }

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

@@ -0,0 +1,89 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.param.FsCourseWatchCommentListParam;
+import com.fs.course.param.FsCourseWatchCommentPageParam;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+import org.apache.ibatis.annotations.Update;
+
+import java.util.List;
+
+/**
+ * 看课评论Mapper接口
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+public interface FsCourseWatchCommentMapper extends BaseMapper<FsCourseWatchComment>{
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchCommentPageParam 看课评论
+     * @return 看课评论集合
+     */
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
+
+    /**
+     * 新增看课评论
+     *
+     * @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);
+
+    /**
+     * 随机获取200条弹幕
+     * @param param 入参
+     * @return list
+     */
+    List<FsCourseWatchCommentVO> selectH5CourseWatchCommentsRound(FsCourseWatchCommentListParam param);
+}

+ 70 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseComplaintRecordMapper.java

@@ -0,0 +1,70 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserCourseComplaintRecord;
+import com.fs.course.vo.FsUserCourseComplaintRecordPageListVO;
+
+import java.util.List;
+
+/**
+ * 看课投诉记录Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+public interface FsUserCourseComplaintRecordMapper extends BaseMapper<FsUserCourseComplaintRecord>{
+    /**
+     * 查询看课投诉记录
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 看课投诉记录
+     */
+    FsUserCourseComplaintRecord selectFsUserCourseComplaintRecordByRecordId(Long recordId);
+
+    /**
+     * 查询看课投诉记录列表
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 看课投诉记录集合
+     */
+    List<FsUserCourseComplaintRecord> selectFsUserCourseComplaintRecordList(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 查询看课分页列表
+     * @param fsUserCourseComplaintRecord 记录
+     * @return list
+     */
+    List<FsUserCourseComplaintRecordPageListVO> selectFsUserCourseComplaintRecordPageList(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 新增看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    int insertFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 修改看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    int updateFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 删除看课投诉记录
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintRecordByRecordId(Long recordId);
+
+    /**
+     * 批量删除看课投诉记录
+     *
+     * @param recordIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintRecordByRecordIds(Long[] recordIds);
+}

+ 62 - 0
fs-service-system/src/main/java/com/fs/course/mapper/FsUserCourseComplaintTypeMapper.java

@@ -0,0 +1,62 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsUserCourseComplaintType;
+
+import java.util.List;
+
+/**
+ * 看课投诉类型Mapper接口
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+public interface FsUserCourseComplaintTypeMapper extends BaseMapper<FsUserCourseComplaintType>{
+    /**
+     * 查询看课投诉类型
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 看课投诉类型
+     */
+    FsUserCourseComplaintType selectFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId);
+
+    /**
+     * 查询看课投诉类型列表
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 看课投诉类型集合
+     */
+    List<FsUserCourseComplaintType> selectFsUserCourseComplaintTypeList(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 新增看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    int insertFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 修改看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    int updateFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 删除看课投诉类型
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId);
+
+    /**
+     * 批量删除看课投诉类型
+     *
+     * @param complaintTypeIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintTypeByComplaintTypeIds(Long[] complaintTypeIds);
+}

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

@@ -0,0 +1,29 @@
+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;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "1-评论/2-弹幕")
+    private Integer openCommentStatus;
+
+}

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

@@ -0,0 +1,42 @@
+package com.fs.course.param;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 看课评论对象 fs_course_watch_comment
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseWatchCommentPageParam extends BaseEntity{
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "用户名称")
+    private String nickName;
+
+    @ApiModelProperty(value = "模糊搜索")
+    private String keywords;
+
+    @ApiModelProperty(value = "是否全部")
+    private Boolean isAll = false;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 视频id */
+    @Excel(name = "视频id")
+    private Long videoId;
+
+}

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

@@ -0,0 +1,44 @@
+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;
+
+    @ApiModelProperty(value = "时间(用于弹幕记录时间)")
+    private Integer time;
+
+    @ApiModelProperty(value = "字体大小")
+    private String fontSize;
+
+    @ApiModelProperty(value = "展示模式")
+    private String mode;
+
+    @ApiModelProperty(value = "字体颜色")
+    private String color;
+
+}

+ 24 - 0
fs-service-system/src/main/java/com/fs/course/param/UserCourseComplaintRecordParam.java

@@ -0,0 +1,24 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class UserCourseComplaintRecordParam {
+
+    @ApiModelProperty(value = "用户id,不传")
+    private Long userId;
+
+    @ApiModelProperty(value = "投诉类型id")
+    private Long complaintTypeId;
+
+    @ApiModelProperty(value = "投诉内容(暂时没有,只是保留这个字段)")
+    private String complaintContent;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频小节id")
+    private Long videoId;
+
+}

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

@@ -0,0 +1,90 @@
+package com.fs.course.service;
+
+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.FsCourseWatchCommentPageParam;
+import com.fs.course.param.FsCourseWatchCommentSaveParam;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+
+import java.util.List;
+
+/**
+ * 看课评论Service接口
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+public interface IFsCourseWatchCommentService extends IService<FsCourseWatchComment>{
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId);
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchCommentPageParam 看课评论
+     * @return 看课评论集合
+     */
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
+
+    /**
+     * 新增看课评论
+     *
+     * @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);
+
+    /**
+     * 保存评论/弹幕数据
+     * @param param 入参
+     * @return
+     */
+    R saveH5CourseWatchComment(FsCourseWatchCommentSaveParam param);
+
+    /**
+     * 撤销评论
+     * @param commentId 评论id
+     * @return R
+     */
+    R revokeH5CourseWatchComment(Long commentId);
+
+    /**
+     * 查询评论列表
+     * @param param 入参
+     * @return list
+     */
+    List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param);
+
+}

+ 72 - 0
fs-service-system/src/main/java/com/fs/course/service/IFsUserCourseComplaintRecordService.java

@@ -0,0 +1,72 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsUserCourseComplaintRecord;
+import com.fs.course.param.UserCourseComplaintRecordParam;
+import com.fs.course.vo.FsUserCourseComplaintRecordPageListVO;
+
+import java.util.List;
+
+/**
+ * 看课投诉记录Service接口
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+public interface IFsUserCourseComplaintRecordService extends IService<FsUserCourseComplaintRecord>{
+    /**
+     * 查询看课投诉记录
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 看课投诉记录
+     */
+    FsUserCourseComplaintRecord selectFsUserCourseComplaintRecordByRecordId(Long recordId);
+
+    /**
+     * 查询看课投诉记录列表
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 看课投诉记录集合
+     */
+    List<FsUserCourseComplaintRecordPageListVO> selectFsUserCourseComplaintRecordList(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 新增看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    int insertFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 修改看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    int updateFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord);
+
+    /**
+     * 批量删除看课投诉记录
+     *
+     * @param recordIds 需要删除的看课投诉记录主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintRecordByRecordIds(Long[] recordIds);
+
+    /**
+     * 删除看课投诉记录信息
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintRecordByRecordId(Long recordId);
+
+    /**
+     * 提交投诉记录
+     * @param userCourseComplaintRecordParam 入参
+     * @return int
+     */
+    int submitRecord(UserCourseComplaintRecordParam userCourseComplaintRecordParam);
+
+}

+ 70 - 0
fs-service-system/src/main/java/com/fs/course/service/IFsUserCourseComplaintTypeService.java

@@ -0,0 +1,70 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsUserCourseComplaintType;
+import com.fs.course.vo.FsUserCourseComplaintTypeListVO;
+
+import java.util.List;
+
+/**
+ * 看课投诉类型Service接口
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+public interface IFsUserCourseComplaintTypeService extends IService<FsUserCourseComplaintType>{
+    /**
+     * 查询看课投诉类型
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 看课投诉类型
+     */
+    FsUserCourseComplaintType selectFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId);
+
+    /**
+     * 查询看课投诉类型列表
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 看课投诉类型集合
+     */
+    List<FsUserCourseComplaintType> selectFsUserCourseComplaintTypeList(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 新增看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    int insertFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 修改看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    int updateFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType);
+
+    /**
+     * 批量删除看课投诉类型
+     *
+     * @param complaintTypeIds 需要删除的看课投诉类型主键集合
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintTypeByComplaintTypeIds(Long[] complaintTypeIds);
+
+    /**
+     * 删除看课投诉类型信息
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 结果
+     */
+    int deleteFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId);
+
+    /**
+     * 获取投诉类型树结构
+     * @return list
+     */
+    List<FsUserCourseComplaintTypeListVO> getAllComplaintTypeTree();
+
+}

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

@@ -0,0 +1,172 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseWatchComment;
+import com.fs.course.mapper.FsCourseWatchCommentMapper;
+import com.fs.course.param.FsCourseWatchCommentListParam;
+import com.fs.course.param.FsCourseWatchCommentPageParam;
+import com.fs.course.param.FsCourseWatchCommentSaveParam;
+import com.fs.course.service.IFsCourseWatchCommentService;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
+import com.fs.course.vo.FsCourseWatchCommentVO;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.system.domain.SysKeyword;
+import com.fs.system.mapper.SysKeywordMapper;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 看课评论Service业务层处理
+ *
+ * @author fs
+ * @date 2025-05-26
+ */
+@Service
+public class FsCourseWatchCommentServiceImpl extends ServiceImpl<FsCourseWatchCommentMapper, FsCourseWatchComment> implements IFsCourseWatchCommentService {
+
+    private static final String REDIS_KEY = "sys:keywords";
+
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    public RedisTemplate<String,String> redisTemplate;
+
+    @Autowired
+    private SysKeywordMapper mapper;
+
+    /**
+     * 查询看课评论
+     *
+     * @param commentId 看课评论主键
+     * @return 看课评论
+     */
+    @Override
+    public FsCourseWatchComment selectFsCourseWatchCommentByCommentId(Long commentId)
+    {
+        return baseMapper.selectFsCourseWatchCommentByCommentId(commentId);
+    }
+
+    /**
+     * 查询看课评论列表
+     *
+     * @param fsCourseWatchCommentPageParam 看课评论
+     * @return 看课评论
+     */
+    @Override
+    public List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam)
+    {
+        return baseMapper.selectFsCourseWatchCommentList(fsCourseWatchCommentPageParam);
+    }
+
+    /**
+     * 新增看课评论
+     *
+     * @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) {
+        // 需求:查询是否包含关键字,包含则不保存,并且需要将用户标记为黑名单;
+        Set<String>  keywords = redisTemplate.opsForSet().members(REDIS_KEY);
+        if(keywords == null || keywords.isEmpty()){
+            SysKeyword sysKeywordParam = new SysKeyword();
+            List<SysKeyword> sysKeywords = mapper.selectSysKeywordList(sysKeywordParam);
+            keywords = sysKeywords.stream().map(SysKeyword::getKeyword).collect(Collectors.toSet());
+        }
+        if(!keywords.isEmpty()){
+            for (String keyword : keywords) {
+                if (param.getContent().contains(keyword)) {
+                    //标记用户为黑名单,并且不保存数据
+                    qwExternalContactMapper.updateQwExternalContactByFsUserId(1, param.getUserId());
+                    return R.ok().put("status", false);
+                }
+            }
+        }
+        FsCourseWatchComment fsCourseWatchComment = new FsCourseWatchComment();
+        BeanUtils.copyProperties(param, fsCourseWatchComment);
+        fsCourseWatchComment.setCreateTime(DateUtils.getNowDate());
+        int i = baseMapper.insertFsCourseWatchComment(fsCourseWatchComment);
+        if (i > 0) {
+            return R.ok().put("status", true);
+        } 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) {
+        if(param.getOpenCommentStatus() != null && 2 == param.getOpenCommentStatus()){
+            return baseMapper.selectH5CourseWatchCommentsRound(param);
+        } else {
+            return baseMapper.selectH5CourseWatchComments(param);
+        }
+    }
+
+}

+ 104 - 0
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintRecordServiceImpl.java

@@ -0,0 +1,104 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsUserCourseComplaintRecord;
+import com.fs.course.mapper.FsUserCourseComplaintRecordMapper;
+import com.fs.course.param.UserCourseComplaintRecordParam;
+import com.fs.course.service.IFsUserCourseComplaintRecordService;
+import com.fs.course.vo.FsUserCourseComplaintRecordPageListVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 看课投诉记录Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@Service
+public class FsUserCourseComplaintRecordServiceImpl extends ServiceImpl<FsUserCourseComplaintRecordMapper, FsUserCourseComplaintRecord> implements IFsUserCourseComplaintRecordService {
+
+    /**
+     * 查询看课投诉记录
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 看课投诉记录
+     */
+    @Override
+    public FsUserCourseComplaintRecord selectFsUserCourseComplaintRecordByRecordId(Long recordId)
+    {
+        return baseMapper.selectFsUserCourseComplaintRecordByRecordId(recordId);
+    }
+
+    /**
+     * 查询看课投诉记录列表
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 看课投诉记录
+     */
+    @Override
+    public List<FsUserCourseComplaintRecordPageListVO> selectFsUserCourseComplaintRecordList(FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        return baseMapper.selectFsUserCourseComplaintRecordPageList(fsUserCourseComplaintRecord);
+    }
+
+    /**
+     * 新增看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        fsUserCourseComplaintRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserCourseComplaintRecord(fsUserCourseComplaintRecord);
+    }
+
+    /**
+     * 修改看课投诉记录
+     *
+     * @param fsUserCourseComplaintRecord 看课投诉记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserCourseComplaintRecord(FsUserCourseComplaintRecord fsUserCourseComplaintRecord)
+    {
+        return baseMapper.updateFsUserCourseComplaintRecord(fsUserCourseComplaintRecord);
+    }
+
+    /**
+     * 批量删除看课投诉记录
+     *
+     * @param recordIds 需要删除的看课投诉记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseComplaintRecordByRecordIds(Long[] recordIds)
+    {
+        return baseMapper.deleteFsUserCourseComplaintRecordByRecordIds(recordIds);
+    }
+
+    /**
+     * 删除看课投诉记录信息
+     *
+     * @param recordId 看课投诉记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseComplaintRecordByRecordId(Long recordId)
+    {
+        return baseMapper.deleteFsUserCourseComplaintRecordByRecordId(recordId);
+    }
+
+    @Override
+    public int submitRecord(UserCourseComplaintRecordParam userCourseComplaintRecordParam) {
+        FsUserCourseComplaintRecord fsUserCourseComplaintRecord = new FsUserCourseComplaintRecord();
+        BeanUtils.copyProperties(userCourseComplaintRecordParam, fsUserCourseComplaintRecord);
+        fsUserCourseComplaintRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserCourseComplaintRecord(fsUserCourseComplaintRecord);
+    }
+}

+ 137 - 0
fs-service-system/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintTypeServiceImpl.java

@@ -0,0 +1,137 @@
+package com.fs.course.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsUserCourseComplaintType;
+import com.fs.course.mapper.FsUserCourseComplaintTypeMapper;
+import com.fs.course.service.IFsUserCourseComplaintTypeService;
+import com.fs.course.vo.FsUserCourseComplaintTypeListVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 看课投诉类型Service业务层处理
+ *
+ * @author fs
+ * @date 2025-06-04
+ */
+@Service
+public class FsUserCourseComplaintTypeServiceImpl extends ServiceImpl<FsUserCourseComplaintTypeMapper, FsUserCourseComplaintType> implements IFsUserCourseComplaintTypeService {
+
+    /**
+     * 查询看课投诉类型
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 看课投诉类型
+     */
+    @Override
+    public FsUserCourseComplaintType selectFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId)
+    {
+        return baseMapper.selectFsUserCourseComplaintTypeByComplaintTypeId(complaintTypeId);
+    }
+
+    /**
+     * 查询看课投诉类型列表
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 看课投诉类型
+     */
+    @Override
+    public List<FsUserCourseComplaintType> selectFsUserCourseComplaintTypeList(FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        return baseMapper.selectFsUserCourseComplaintTypeList(fsUserCourseComplaintType);
+    }
+
+    /**
+     * 新增看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    @Override
+    public int insertFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        fsUserCourseComplaintType.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsUserCourseComplaintType(fsUserCourseComplaintType);
+    }
+
+    /**
+     * 修改看课投诉类型
+     *
+     * @param fsUserCourseComplaintType 看课投诉类型
+     * @return 结果
+     */
+    @Override
+    public int updateFsUserCourseComplaintType(FsUserCourseComplaintType fsUserCourseComplaintType)
+    {
+        fsUserCourseComplaintType.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsUserCourseComplaintType(fsUserCourseComplaintType);
+    }
+
+    /**
+     * 批量删除看课投诉类型
+     *
+     * @param complaintTypeIds 需要删除的看课投诉类型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseComplaintTypeByComplaintTypeIds(Long[] complaintTypeIds)
+    {
+        return baseMapper.deleteFsUserCourseComplaintTypeByComplaintTypeIds(complaintTypeIds);
+    }
+
+    /**
+     * 删除看课投诉类型信息
+     *
+     * @param complaintTypeId 看课投诉类型主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsUserCourseComplaintTypeByComplaintTypeId(Long complaintTypeId)
+    {
+        return baseMapper.deleteFsUserCourseComplaintTypeByComplaintTypeId(complaintTypeId);
+    }
+
+    @Override
+    public List<FsUserCourseComplaintTypeListVO> getAllComplaintTypeTree() {
+        FsUserCourseComplaintType fsUserCourseComplaintType = new FsUserCourseComplaintType();
+        List<FsUserCourseComplaintType> list = baseMapper.selectFsUserCourseComplaintTypeList(fsUserCourseComplaintType);
+        List<FsUserCourseComplaintTypeListVO> collect = list.stream().map(v -> {
+            FsUserCourseComplaintTypeListVO listVO = new FsUserCourseComplaintTypeListVO();
+            BeanUtils.copyProperties(v, listVO);
+            return listVO;
+        }).collect(Collectors.toList());
+
+        //构造树列表
+        return this.buildTree(collect);
+    }
+
+    private List<FsUserCourseComplaintTypeListVO> buildTree(List<FsUserCourseComplaintTypeListVO> complaintTypeList) {
+        List<FsUserCourseComplaintTypeListVO> complaintTypeVOList = new ArrayList<>();
+        Map<Long, FsUserCourseComplaintTypeListVO> complaintTypeMap = new HashMap<>();
+
+        for (FsUserCourseComplaintTypeListVO complaintTypeVO : complaintTypeList) {
+            complaintTypeVO.setChildrenType(new ArrayList<>());
+            complaintTypeMap.put(complaintTypeVO.getComplaintTypeId(), complaintTypeVO);
+        }
+
+        // 构造树列表
+        for (FsUserCourseComplaintTypeListVO complaintTypeVO : complaintTypeList) {
+            if(complaintTypeVO.getParentId() == null || complaintTypeVO.getParentId() == 0){
+                complaintTypeVOList.add(complaintTypeVO);
+            } else {
+                FsUserCourseComplaintTypeListVO complaintTypeListVOChildren = complaintTypeMap.get(complaintTypeVO.getParentId());
+                if(complaintTypeListVOChildren != null){
+                    complaintTypeListVOChildren.getChildrenType().add(complaintTypeVO);
+                }
+            }
+        }
+        return complaintTypeVOList;
+    }
+}

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

@@ -0,0 +1,58 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@ApiModel(value = "评论列表-管理端")
+public class FsCourseWatchCommentListVO {
+
+    @ApiModelProperty(value = "评论id")
+    private Long commentId;
+
+    @ApiModelProperty(value = "用户id")
+    private Long userId;
+
+    @Excel(name = "用户名称")
+    @ApiModelProperty(value = "用户名称")
+    private String nickName;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频id")
+    private Long videoId;
+
+    @Excel(name = "评论内容", width = 120)
+    @ApiModelProperty(value = "评论内容")
+    private String content;
+
+    @Excel(name = "评论时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "评论时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @ApiModelProperty(value = "时间(用于弹幕记录时间)")
+    private Integer time;
+
+    @ApiModelProperty(value = "字体大小")
+    private String fontSize;
+
+    @ApiModelProperty(value = "展示模式")
+    private String mode;
+
+    @ApiModelProperty(value = "字体颜色")
+    private String color;
+
+    @ApiModelProperty(value = "课程名称")
+    private String courseName;
+
+    @ApiModelProperty(value = "小节名称")
+    private String title;
+
+}

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

@@ -0,0 +1,54 @@
+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 = "用户名称")
+    private String nickName;
+
+    @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;
+
+    @ApiModelProperty(value = "时间(用于弹幕记录时间)")
+    private Integer time;
+
+    @ApiModelProperty(value = "字体大小")
+    private String fontSize;
+
+    @ApiModelProperty(value = "展示模式")
+    private String mode;
+
+    @ApiModelProperty(value = "字体颜色")
+    private String color;
+
+}

+ 43 - 0
fs-service-system/src/main/java/com/fs/course/vo/FsUserCourseComplaintRecordPageListVO.java

@@ -0,0 +1,43 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsUserCourseComplaintRecordPageListVO extends BaseEntity{
+
+    /** 投诉记录id */
+    private Long recordId;
+
+    /** 用户id */
+    private Long userId;
+
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @Excel(name = "投诉类型")
+    private String complaintTypeName;
+
+//    @Excel(name = "投诉内容")
+    private String complaintContent;
+
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    @Excel(name = "小节名称")
+    private String title;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @Excel(name = "看课状态")
+    private String status;
+
+}

+ 28 - 0
fs-service-system/src/main/java/com/fs/course/vo/FsUserCourseComplaintTypeListVO.java

@@ -0,0 +1,28 @@
+package com.fs.course.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@ApiModel(value = "类型列表返回类")
+@Data
+public class FsUserCourseComplaintTypeListVO {
+
+    @ApiModelProperty(value = "投诉类型id")
+    private Long complaintTypeId;
+
+    @ApiModelProperty(value = "投诉类型名称")
+    private String complaintTypeName;
+
+    @ApiModelProperty(value = "级别,目前只有两级")
+    private Integer typeLevel;
+
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "子类型")
+    private List<FsUserCourseComplaintTypeListVO> childrenType;
+
+}

+ 2 - 0
fs-service-system/src/main/java/com/fs/qw/domain/QwExternalContact.java

@@ -124,4 +124,6 @@ public class QwExternalContact extends BaseEntity
     private Integer lastWatchTime;
     private Integer isRepeat;
 
+    //看课评论状态,1-拉黑;0-正常
+    private Integer commentStatus;
 }

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

@@ -392,4 +392,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Select("SELECT id,external_user_id,name,avatar,remark,description,fs_user_id FROM qw_external_contact " +
             " WHERE id = #{qwExternalContactId}")
     QwExternalContact getQwExternalContactDetailsById(Long qwExternalContactId);
+
+    @Update("update qw_external_contact set comment_status = #{commentStatus} where fs_user_id = #{fsUserId}")
+    int updateQwExternalContactByFsUserId(@Param("commentStatus") Integer commentStatus, @Param("fsUserId")Long fsUserId);
+
 }

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

@@ -214,4 +214,12 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      * @return QwExternalContact
      */
     QwExternalContact getQwExternalContactDetailsById(Long qwExternalContactId);
+
+    /**
+     * 根据userid修改外部联系人评论状态
+     * @param commentStatus 评论状态
+     * @param fsUserId 用户id
+     * @return
+     */
+    int updateQwExternalContactByFsUserId(Integer commentStatus, Long fsUserId);
 }

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

@@ -210,6 +210,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         }
     }
 
+    @Override
+    public int updateQwExternalContactByFsUserId(Integer commentStatus, Long fsUserId) {
+        return qwExternalContactMapper.updateQwExternalContactByFsUserId(commentStatus, fsUserId);
+    }
+
     /**
      * 查询企业微信客户
      *

+ 4 - 0
fs-service-system/src/main/java/com/fs/qw/vo/QwContactListVO.java

@@ -20,4 +20,8 @@ public class QwContactListVO {
     private String type;
     // 外部联系人ID
     private String extId;
+    // 是否黑粉
+    private Boolean isBlack;
+    // 是否重粉
+    private Boolean isRepeat;
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/sop/domain/QwSop.java

@@ -127,4 +127,7 @@ public class QwSop implements Serializable {
     // 群聊ID
     private String chatId;
 
+    @Excel(name = "开启评论或者弹幕,1-开启评论;2-开启弹幕;3-都关闭")
+    private Integer openCommentStatus;
+
 }

+ 4 - 5
fs-service-system/src/main/java/com/fs/system/domain/SysKeyword.java

@@ -1,10 +1,9 @@
 package com.fs.system.domain;
 
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.fs.common.annotation.Excel;
-import lombok.Data;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 /**
@@ -30,9 +29,9 @@ public class SysKeyword extends BaseEntity{
     @Excel(name = "关键字")
     private String keyword;
 
-    /** 类型:1-看课弹幕; */
-    @Excel(name = "类型:1-看课弹幕;")
-    private Long type;
+//    /** 类型:1-看课弹幕; */
+//    @Excel(name = "类型:1-看课弹幕;")
+//    private Integer type;
 
     /** 所属公司 */
     @Excel(name = "所属公司")

+ 13 - 9
fs-service-system/src/main/java/com/fs/system/mapper/SysKeywordMapper.java

@@ -1,19 +1,21 @@
 package com.fs.system.mapper;
 
-import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.system.domain.SysKeyword;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * 系统关键字Mapper接口
- * 
+ *
  * @author fs
  * @date 2025-05-14
  */
 public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
     /**
      * 查询系统关键字
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 系统关键字
      */
@@ -21,7 +23,7 @@ public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
 
     /**
      * 查询系统关键字列表
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 系统关键字集合
      */
@@ -29,7 +31,7 @@ public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
 
     /**
      * 新增系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -37,7 +39,7 @@ public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
 
     /**
      * 修改系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -45,7 +47,7 @@ public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
 
     /**
      * 删除系统关键字
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 结果
      */
@@ -53,9 +55,11 @@ public interface SysKeywordMapper extends BaseMapper<SysKeyword>{
 
     /**
      * 批量删除系统关键字
-     * 
+     *
      * @param keywordIds 需要删除的数据主键集合
      * @return 结果
      */
-    int deleteSysKeywordByIds(Long[] keywordIds);
+    int deleteSysKeywordByIds(@Param("keywordIds") Long[] keywordIds);
+
+    List<SysKeyword> selectSysKeywordByIds(@Param("keywordIds") Long[] keywordIds);
 }

+ 16 - 8
fs-service-system/src/main/java/com/fs/system/service/ISysKeywordService.java

@@ -1,19 +1,20 @@
 package com.fs.system.service;
 
-import java.util.List;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.system.domain.SysKeyword;
 
+import java.util.List;
+
 /**
  * 系统关键字Service接口
- * 
+ *
  * @author fs
  * @date 2025-05-14
  */
 public interface ISysKeywordService extends IService<SysKeyword>{
     /**
      * 查询系统关键字
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 系统关键字
      */
@@ -21,7 +22,7 @@ public interface ISysKeywordService extends IService<SysKeyword>{
 
     /**
      * 查询系统关键字列表
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 系统关键字集合
      */
@@ -29,7 +30,7 @@ public interface ISysKeywordService extends IService<SysKeyword>{
 
     /**
      * 新增系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -37,7 +38,7 @@ public interface ISysKeywordService extends IService<SysKeyword>{
 
     /**
      * 修改系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -45,7 +46,7 @@ public interface ISysKeywordService extends IService<SysKeyword>{
 
     /**
      * 批量删除系统关键字
-     * 
+     *
      * @param keywordIds 需要删除的系统关键字主键集合
      * @return 结果
      */
@@ -53,9 +54,16 @@ public interface ISysKeywordService extends IService<SysKeyword>{
 
     /**
      * 删除系统关键字信息
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 结果
      */
     int deleteSysKeywordById(Long keywordId);
+
+    /**
+     * 查询指定ids的关键字
+     * @param keywordIds
+     * @return
+     */
+    List<SysKeyword> selectSysKeywordByIds(Long[] keywordIds);
 }

+ 17 - 12
fs-service-system/src/main/java/com/fs/system/service/impl/SysKeywordServiceImpl.java

@@ -1,17 +1,17 @@
 package com.fs.system.service.impl;
 
-import java.util.List;
-import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import com.fs.system.mapper.SysKeywordMapper;
+import com.fs.common.utils.DateUtils;
 import com.fs.system.domain.SysKeyword;
+import com.fs.system.mapper.SysKeywordMapper;
 import com.fs.system.service.ISysKeywordService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
 
 /**
  * 系统关键字Service业务层处理
- * 
+ *
  * @author fs
  * @date 2025-05-14
  */
@@ -20,7 +20,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 查询系统关键字
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 系统关键字
      */
@@ -32,7 +32,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 查询系统关键字列表
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 系统关键字
      */
@@ -44,7 +44,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 新增系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -57,7 +57,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 修改系统关键字
-     * 
+     *
      * @param sysKeyword 系统关键字
      * @return 结果
      */
@@ -70,7 +70,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 批量删除系统关键字
-     * 
+     *
      * @param keywordIds 需要删除的系统关键字主键
      * @return 结果
      */
@@ -82,7 +82,7 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
 
     /**
      * 删除系统关键字信息
-     * 
+     *
      * @param keywordId 系统关键字主键
      * @return 结果
      */
@@ -91,4 +91,9 @@ public class SysKeywordServiceImpl extends ServiceImpl<SysKeywordMapper, SysKeyw
     {
         return baseMapper.deleteSysKeywordById(keywordId);
     }
+
+    @Override
+    public List<SysKeyword> selectSysKeywordByIds(Long[] keywordIds) {
+        return baseMapper.selectSysKeywordByIds(keywordIds);
+    }
 }

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

@@ -0,0 +1,178 @@
+<?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"    />
+        <result property="time"    column="time"    />
+        <result property="fontSize"    column="font_size"    />
+        <result property="mode"    column="mode"    />
+        <result property="color"    column="color"    />
+    </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
+    </sql>
+
+    <select id="selectFsCourseWatchCommentList" resultType="com.fs.course.vo.FsCourseWatchCommentListVO">
+        SELECT
+        fs_course_watch_comment.comment_id,
+        fs_course_watch_comment.user_id,
+        fs_course_watch_comment.user_type,
+        fs_course_watch_comment.course_id,
+        fs_course_watch_comment.video_id,
+        fs_course_watch_comment.type,
+        fs_course_watch_comment.parent_id,
+        fs_course_watch_comment.content,
+        fs_course_watch_comment.create_time,
+        fs_course_watch_comment.update_time,
+        fs_course_watch_comment.is_revoke,
+        fs_course_watch_comment.`time`,
+        fs_course_watch_comment.font_size,
+        fs_course_watch_comment.`mode`,
+        fs_course_watch_comment.color,
+        fs_user.nick_name
+        <if test="isAll != null and isAll == true ">
+            ,fs_user_course.course_name, fs_user_course_video.title
+        </if>
+        FROM
+        fs_course_watch_comment
+        LEFT JOIN fs_user ON fs_user.user_id = fs_course_watch_comment.user_id
+        <if test="isAll != null and isAll == true">
+            LEFT JOIN fs_user_course on fs_user_course.course_id = fs_course_watch_comment.course_id
+            LEFT JOIN fs_user_course_video on fs_user_course_video.video_id = fs_course_watch_comment.video_id
+        </if>
+        <where>
+            <if test="courseId != null "> and fs_course_watch_comment.course_id = #{courseId}</if>
+            <if test="videoId != null "> and fs_course_watch_comment.video_id = #{videoId}</if>
+            <if test="nickName != null and nickName != '' ">and fs_user.nick_name like concat('%', #{nickName}, '%')</if>
+            <if test="isAll != null and isAll == true and keywords != null and keywords !='' ">
+                AND (fs_user.nickname LIKE concat('%',#{keywords},'%')
+                or  fs_user_course.course_name LIKE concat('%',#{keywords},'%')
+                or  fs_user_course_video.title LIKE concat('%',#{keywords},'%')
+                )
+            </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>
+            <if test="time != null">`time`,</if>
+            <if test="fontSize != null">font_size,</if>
+            <if test="mode != null">mode,</if>
+            <if test="color != null">color,</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>
+            <if test="time != null">#{time},</if>
+            <if test="fontSize != null">#{fontSize},</if>
+            <if test="mode != null">#{mode},</if>
+            <if test="color != null">#{color},</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>
+            <if test="time != null">`time` = #{time},</if>
+            <if test="fontSize != null">font_size = #{fontSize},</if>
+            <if test="mode != null">mode = #{mode},</if>
+            <if test="color != null">color = #{color},</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 cwc.comment_id, cwc.user_id, cwc.user_type, cwc.course_id, cwc.video_id, cwc.type, cwc.content, cwc.create_time,
+        fs_user.nick_name,cwc.time,cwc.font_size, cwc.mode, cwc.color from fs_course_watch_comment cwc
+        left join fs_user on fs_user.user_id = cwc.user_id
+        <where>
+           and cwc.is_revoke = 0
+            <if test="courseId != null">
+                and cwc.course_id = #{courseId}
+           </if>
+            <if test="videoId != null">
+                and cwc.video_id = #{videoId}
+            </if>
+        </where>
+        order by cwc.create_time desc
+    </select>
+
+    <select id="selectH5CourseWatchCommentsRound" resultType="com.fs.course.vo.FsCourseWatchCommentVO">
+        select * from (
+        select cwc.comment_id, cwc.user_id, cwc.user_type, cwc.course_id, cwc.video_id, cwc.type, cwc.content,
+        cwc.create_time,
+        fs_user.nick_name,cwc.time,cwc.font_size, cwc.mode, cwc.color from fs_course_watch_comment cwc
+        left join fs_user on fs_user.user_id = cwc.user_id
+        <where>
+            and cwc.is_revoke = 0
+            <if test="courseId != null">
+                and cwc.course_id = #{courseId}
+            </if>
+            <if test="videoId != null">
+                and cwc.video_id = #{videoId}
+            </if>
+        </where>
+        ORDER BY RAND() LIMIT #{listNum}
+        ) a
+    </select>
+</mapper>

+ 102 - 0
fs-service-system/src/main/resources/mapper/course/FsUserCourseComplaintRecordMapper.xml

@@ -0,0 +1,102 @@
+<?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.FsUserCourseComplaintRecordMapper">
+
+    <resultMap type="FsUserCourseComplaintRecord" id="FsUserCourseComplaintRecordResult">
+        <result property="recordId"    column="record_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="complaintTypeId"    column="complaint_type_id"    />
+        <result property="complaintContent"    column="complaint_content"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectFsUserCourseComplaintRecordVo">
+        select record_id, user_id, complaint_type_id, complaint_content, course_id, video_id, create_time from fs_user_course_complaint_record
+    </sql>
+
+    <select id="selectFsUserCourseComplaintRecordList" parameterType="FsUserCourseComplaintRecord" resultMap="FsUserCourseComplaintRecordResult">
+        <include refid="selectFsUserCourseComplaintRecordVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="complaintTypeId != null "> and complaint_type_id = #{complaintTypeId}</if>
+            <if test="complaintContent != null  and complaintContent != ''"> and complaint_content = #{complaintContent}</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCourseComplaintRecordPageList" resultType="com.fs.course.vo.FsUserCourseComplaintRecordPageListVO">
+        SELECT
+        cr.*,
+        ct.complaint_type_name,
+        uc.course_name,
+        ucv.title,
+        fs_user.nick_name,
+        if(ec.comment_status = 1,'已拉黑','正常') as status
+        FROM
+        fs_user_course_complaint_record cr
+        LEFT JOIN fs_user_course_complaint_type ct ON ct.complaint_type_id = cr.complaint_type_id
+        LEFT JOIN fs_user_course uc ON uc.course_id = cr.course_id
+        LEFT JOIN fs_user_course_video ucv ON ucv.video_id = cr.video_id
+        LEFT JOIN fs_user ON fs_user.user_id = cr.user_id
+        left join qw_external_contact ec on cr.user_id = ec.fs_user_id
+        <where>
+            <if test="nickName != null and nickName != '' ">
+                and fs_user.nick_name like concat('%', #{nickName}, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCourseComplaintRecordByRecordId" parameterType="Long" resultMap="FsUserCourseComplaintRecordResult">
+        <include refid="selectFsUserCourseComplaintRecordVo"/>
+        where record_id = #{recordId}
+    </select>
+
+    <insert id="insertFsUserCourseComplaintRecord" parameterType="FsUserCourseComplaintRecord" useGeneratedKeys="true" keyProperty="recordId">
+        insert into fs_user_course_complaint_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="complaintTypeId != null">complaint_type_id,</if>
+            <if test="complaintContent != null">complaint_content,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="complaintTypeId != null">#{complaintTypeId},</if>
+            <if test="complaintContent != null">#{complaintContent},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserCourseComplaintRecord" parameterType="FsUserCourseComplaintRecord">
+        update fs_user_course_complaint_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="complaintTypeId != null">complaint_type_id = #{complaintTypeId},</if>
+            <if test="complaintContent != null">complaint_content = #{complaintContent},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where record_id = #{recordId}
+    </update>
+
+    <delete id="deleteFsUserCourseComplaintRecordByRecordId" parameterType="Long">
+        delete from fs_user_course_complaint_record where record_id = #{recordId}
+    </delete>
+
+    <delete id="deleteFsUserCourseComplaintRecordByRecordIds" parameterType="String">
+        delete from fs_user_course_complaint_record where record_id in
+        <foreach item="recordId" collection="array" open="(" separator="," close=")">
+            #{recordId}
+        </foreach>
+    </delete>
+</mapper>

+ 74 - 0
fs-service-system/src/main/resources/mapper/course/FsUserCourseComplaintTypeMapper.xml

@@ -0,0 +1,74 @@
+<?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.FsUserCourseComplaintTypeMapper">
+
+    <resultMap type="FsUserCourseComplaintType" id="FsUserCourseComplaintTypeResult">
+        <result property="complaintTypeId"    column="complaint_type_id"    />
+        <result property="parentId"    column="parent_id"    />
+        <result property="complaintTypeName"    column="complaint_type_name"    />
+        <result property="typeLevel"    column="type_level"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsUserCourseComplaintTypeVo">
+        select complaint_type_id, parent_id, complaint_type_name, type_level, create_time, update_time from fs_user_course_complaint_type
+    </sql>
+
+    <select id="selectFsUserCourseComplaintTypeList" parameterType="FsUserCourseComplaintType" resultMap="FsUserCourseComplaintTypeResult">
+        <include refid="selectFsUserCourseComplaintTypeVo"/>
+        <where>
+            <if test="parentId != null "> and parent_id = #{parentId}</if>
+            <if test="complaintTypeName != null  and complaintTypeName != ''"> and complaint_type_name like concat('%', #{complaintTypeName}, '%')</if>
+            <if test="typeLevel != null "> and type_level = #{typeLevel}</if>
+        </where>
+    </select>
+
+    <select id="selectFsUserCourseComplaintTypeByComplaintTypeId" parameterType="Long" resultMap="FsUserCourseComplaintTypeResult">
+        <include refid="selectFsUserCourseComplaintTypeVo"/>
+        where complaint_type_id = #{complaintTypeId}
+    </select>
+
+    <insert id="insertFsUserCourseComplaintType" parameterType="FsUserCourseComplaintType" useGeneratedKeys="true" keyProperty="complaintTypeId">
+        insert into fs_user_course_complaint_type
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="parentId != null">parent_id,</if>
+            <if test="complaintTypeName != null">complaint_type_name,</if>
+            <if test="typeLevel != null">type_level,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="parentId != null">#{parentId},</if>
+            <if test="complaintTypeName != null">#{complaintTypeName},</if>
+            <if test="typeLevel != null">#{typeLevel},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsUserCourseComplaintType" parameterType="FsUserCourseComplaintType">
+        update fs_user_course_complaint_type
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="parentId != null">parent_id = #{parentId},</if>
+            <if test="complaintTypeName != null">complaint_type_name = #{complaintTypeName},</if>
+            <if test="typeLevel != null">type_level = #{typeLevel},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where complaint_type_id = #{complaintTypeId}
+    </update>
+
+    <delete id="deleteFsUserCourseComplaintTypeByComplaintTypeId" parameterType="Long">
+        delete from fs_user_course_complaint_type where complaint_type_id = #{complaintTypeId}
+    </delete>
+
+    <delete id="deleteFsUserCourseComplaintTypeByComplaintTypeIds" parameterType="String">
+        delete from fs_user_course_complaint_type where complaint_type_id in
+        <foreach item="complaintTypeId" collection="array" open="(" separator="," close=")">
+            #{complaintTypeId}
+        </foreach>
+    </delete>
+</mapper>

+ 5 - 1
fs-service-system/src/main/resources/mapper/course/FsUserCoursePeriodMapper.xml

@@ -19,6 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="periodEndTime"    column="period_end_time"    />
         <result property="maxViewNum"    column="max_view_num"    />
         <result property="courseLogo"    column="course_logo"    />
+        <result property="openCommentStatus"    column="open_comment_status"    />
     </resultMap>
 
     <sql id="selectFsUserCoursePeriodVo">
@@ -105,6 +106,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="lastJoinTime != null">last_join_time,</if>
             <if test="maxViewNum != null">max_view_num,</if>
             <if test="courseLogo != null">course_logo,</if>
+            <if test="openCommentStatus != null">open_comment_status,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="periodId != null">#{periodId},</if>
@@ -125,7 +127,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="lastJoinTime != null">#{lastJoinTime},</if>
             <if test="maxViewNum != null">#{maxViewNum},</if>
             <if test="courseLogo != null">#{courseLogo},</if>
-         </trim>
+            <if test="openCommentStatus != null">#{openCommentStatus},</if>
+        </trim>
     </insert>
 
     <update id="updateFsUserCoursePeriod" parameterType="FsUserCoursePeriod">
@@ -148,6 +151,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="lastJoinTime != null">last_join_time = #{lastJoinTime},</if>
             <if test="maxViewNum != null">max_view_num = #{maxViewNum},</if>
             <if test="courseLogo != null and courseLogo !=''">course_logo = #{courseLogo},</if>
+            <if test="openCommentStatus != null">open_comment_status = #{openCommentStatus},</if>
         </trim>
         where period_id = #{periodId}
     </update>

+ 4 - 0
fs-service-system/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -31,6 +31,7 @@
         <result property="isRating"    column="is_rating"    />
         <result property="courseDay"    column="course_day"    />
         <result property="filterMode"    column="filter_mode"    />
+        <result property="openCommentStatus"    column="open_comment_status"    />
     </resultMap>
 
     <sql id="selectQwSopVo">
@@ -279,6 +280,7 @@
             <if test="data.maxSend != null">max_send,</if>
             <if test="data.isRating != null">is_rating,</if>
             <if test="data.courseDay != null">course_day,</if>
+            <if test="data.openCommentStatus != null">open_comment_status,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="data.name != null">#{data.name},</if>
@@ -304,6 +306,7 @@
             <if test="data.maxSend != null">#{data.maxSend},</if>
             <if test="data.isRating != null">#{data.isRating},</if>
             <if test="data.courseDay != null">#{data.courseDay},</if>
+            <if test="data.openCommentStatus != null">#{data.openCommentStatus},</if>
         </trim>
     </insert>
 
@@ -444,6 +447,7 @@
             <if test="data.voice != null">voice = #{data.voice},</if>
             <if test="data.isRating != null">is_rating = #{data.isRating},</if>
             <if test="data.courseDay != null">course_day = #{data.courseDay},</if>
+            <if test="data.openCommentStatus != null">open_comment_status = #{data.openCommentStatus},</if>
         </trim>
         where id = #{data.id}
     </update>

+ 12 - 11
fs-service-system/src/main/resources/mapper/system/SysKeywordMapper.xml

@@ -7,30 +7,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <resultMap type="SysKeyword" id="SysKeywordResult">
         <result property="keywordId"    column="keyword_id"    />
         <result property="keyword"    column="keyword"    />
-        <result property="type"    column="type"    />
         <result property="companyId"    column="company_id"    />
         <result property="createTime"    column="create_time"    />
         <result property="updateTime"    column="update_time"    />
     </resultMap>
 
     <sql id="selectSysKeyword">
-        select keyword_id, keyword, type, company_id, create_time, update_time from sys_keyword
+        select keyword_id, keyword, company_id, create_time, update_time from sys_keyword
     </sql>
 
     <sql id="selectSysKeywordVo">
         SELECT
             keyword_id,
             keyword,
-            type,
             sys_keyword.company_id,
             sys_keyword.create_time,
             sys_keyword.update_time,
-            sys_dict_data.dict_label as typeName,
             company.company_name
         FROM
             sys_keyword
-                LEFT JOIN sys_dict_data ON sys_dict_data.dict_type = "keyword_type"
-                AND sys_dict_data.dict_value = sys_keyword.type
                 LEFT JOIN company ON company.company_id = sys_keyword.company_id
     </sql>
 
@@ -38,7 +33,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectSysKeywordVo"/>
         <where>
             <if test="keyword != null  and keyword != ''"> and keyword like concat('%', #{keyword}, '%')</if>
-            <if test="type != null "> and type = #{type}</if>
             <if test="companyId != null "> and sys_keyword.company_id = #{companyId}</if>
         </where>
     </select>
@@ -52,14 +46,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         insert into sys_keyword
         <trim prefix="(" suffix=")" suffixOverrides=",">
             <if test="keyword != null">keyword,</if>
-            <if test="type != null">type,</if>
             <if test="companyId != null">company_id,</if>
             <if test="createTime != null">create_time,</if>
             <if test="updateTime != null">update_time,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="keyword != null">#{keyword},</if>
-            <if test="type != null">#{type},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="createTime != null">#{createTime},</if>
             <if test="updateTime != null">#{updateTime},</if>
@@ -70,7 +62,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update sys_keyword
         <trim prefix="SET" suffixOverrides=",">
             <if test="keyword != null">keyword = #{keyword},</if>
-            <if test="type != null">type = #{type},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
@@ -84,8 +75,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <delete id="deleteSysKeywordByIds" parameterType="String">
         delete from sys_keyword where keyword_id in
-        <foreach item="keywordId" collection="array" open="(" separator="," close=")">
+        <foreach item="keywordId" collection="keywordIds" open="(" separator="," close=")">
             #{keywordId}
         </foreach>
     </delete>
+
+
+    <select id="selectSysKeywordByIds" parameterType="Long" resultMap="SysKeywordResult">
+        <include refid="selectSysKeyword"/>
+        where keyword_id in
+        <foreach item="keywordId" collection="keywordIds" open="(" separator="," close=")">
+            #{keywordId}
+        </foreach>
+    </select>
+
 </mapper>

+ 5 - 0
fs-user-app/pom.xml

@@ -117,6 +117,11 @@
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 36 - 1
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -8,7 +8,6 @@ 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.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.param.*;
@@ -22,6 +21,7 @@ import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import lombok.Synchronized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -76,6 +76,8 @@ public class CourseController extends  AppBaseController{
     private CachingOperationNameGenerator cachingOperationNameGenerator;
     @Autowired
     private IFsUserCoursePeriodService coursePeriodService;
+    @Autowired
+    private IFsCourseWatchCommentService courseWatchCommentService;
 
 
     @Cacheable(value="getCourseCate" )
@@ -504,4 +506,37 @@ public class CourseController extends  AppBaseController{
 //
 //        courseWatchLogService.scheduleBatchUpdateToDatabase();
 //    }
+
+    @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 R getCourseWatchComments(FsCourseWatchCommentListParam param)
+    {
+        //获取配置信息中需要查询的数据条数
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        param.setListNum(config.getViewCommentNum() != null &&  config.getViewCommentNum() != 0 ? config.getViewCommentNum() : 200);
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseWatchCommentVO> list = courseWatchCommentService.selectH5CourseWatchComments(param);
+        PageInfo<FsCourseWatchCommentVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data", pageInfo);
+    }
+
 }

+ 48 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserCourseComplaintController.java

@@ -0,0 +1,48 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.core.domain.R;
+import com.fs.course.param.UserCourseComplaintRecordParam;
+import com.fs.course.service.IFsUserCourseComplaintRecordService;
+import com.fs.course.service.IFsUserCourseComplaintTypeService;
+import com.fs.course.vo.FsUserCourseComplaintTypeListVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Api("看课投诉相关接口")
+@RestController
+@RequestMapping("/app/user/complaint")
+public class UserCourseComplaintController extends AppBaseController {
+
+    @Autowired
+    private IFsUserCourseComplaintTypeService fsUserCourseComplaintTypeService;
+
+    @Autowired
+    private IFsUserCourseComplaintRecordService fsUserCourseComplaintRecordService;
+
+    @Login
+    @ApiOperation("获取投诉类型")
+    @GetMapping("/getTypeTree")
+    public R getTypeTree() {
+        List<FsUserCourseComplaintTypeListVO> allComplaintTypeTree = fsUserCourseComplaintTypeService.getAllComplaintTypeTree();
+        return R.ok().put("data", allComplaintTypeTree);
+    }
+
+    @Login
+    @ApiOperation("提交反馈记录")
+    @PostMapping("/record")
+    public R submitRecord(@RequestBody UserCourseComplaintRecordParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        int i = fsUserCourseComplaintRecordService.submitRecord(param);
+        if (i > 0) {
+            return R.ok();
+        } else {
+            return R.error();
+        }
+    }
+
+}

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

@@ -0,0 +1,51 @@
+package com.fs.app.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(value = "用户名称")
+    private String nickName;
+
+    @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;
+
+    @ApiModelProperty(value = "时间(用于弹幕记录时间)")
+    private Integer time;
+
+    @ApiModelProperty(value = "字体大小")
+    private String fontSize;
+
+    @ApiModelProperty(value = "展示模式")
+    private String mode;
+
+    @ApiModelProperty(value = "字体颜色")
+    private String color;
+
+
+}

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

@@ -0,0 +1,107 @@
+package com.fs.app.websocket.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.app.websocket.bean.SendMsgVO;
+import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.StringUtils;
+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(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)));
+    }
+}