浏览代码

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 4 周之前
父节点
当前提交
976e0d7193
共有 70 个文件被更改,包括 1567 次插入141 次删除
  1. 13 2
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java
  2. 36 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java
  3. 20 6
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseController.java
  4. 8 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  5. 0 6
      fs-admin/src/main/java/com/fs/course/controller/FsVideoResourceController.java
  6. 15 0
      fs-admin/src/main/java/com/fs/course/controller/PublicCourseWatchStatisticsController.java
  7. 10 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  8. 4 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  9. 22 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java
  10. 58 0
      fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java
  11. 34 4
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  12. 1 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  13. 2 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  14. 23 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java
  15. 0 1
      fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java
  16. 43 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseMarketingEvent.java
  17. 49 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseShareLog.java
  18. 63 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseMarketingEventMapper.java
  19. 64 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseShareLogMapper.java
  20. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchCommentMapper.java
  21. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  22. 6 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseStudyMapper.java
  23. 5 0
      fs-service/src/main/java/com/fs/course/mapper/PublicCourseWatchStatisticsMapper.java
  24. 4 0
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchCommentPageParam.java
  25. 5 1
      fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java
  26. 2 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseListUParam.java
  27. 61 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseMarketingEventService.java
  28. 61 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseShareLogService.java
  29. 1 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchCommentService.java
  30. 3 0
      fs-service/src/main/java/com/fs/course/service/IPublicCourseWatchStatisticsService.java
  31. 96 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseMarketingEventServiceImpl.java
  32. 49 23
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  33. 95 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseShareLogServiceImpl.java
  34. 6 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchCommentServiceImpl.java
  35. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseCategoryServiceImpl.java
  36. 55 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  37. 17 0
      fs-service/src/main/java/com/fs/course/service/impl/PublicCourseWatchStatisticsServiceImpl.java
  38. 40 0
      fs-service/src/main/java/com/fs/course/vo/CatalogUserStudyVO.java
  39. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCoursePublicAppVO.java
  40. 3 2
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java
  41. 15 9
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  42. 2 0
      fs-service/src/main/java/com/fs/his/dto/InquiryConfigDTO.java
  43. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsInquiryOrderMapper.java
  44. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsInquiryOrderService.java
  45. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  46. 6 6
      fs-service/src/main/java/com/fs/his/utils/RedisCacheUtil.java
  47. 12 0
      fs-service/src/main/java/com/fs/his/vo/InquirySubTypeVo.java
  48. 6 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCartScrmMapper.java
  49. 4 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  50. 1 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  51. 3 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  52. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java
  53. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  54. 23 1
      fs-service/src/main/java/com/fs/wx/order/service/ShippingService.java
  55. 79 0
      fs-service/src/main/resources/mapper/course/FsCourseMarketingEventMapper.xml
  56. 79 0
      fs-service/src/main/resources/mapper/course/FsCourseShareLogMapper.xml
  57. 54 0
      fs-service/src/main/resources/mapper/course/FsCourseWatchCommentMapper.xml
  58. 19 3
      fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml
  59. 32 8
      fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml
  60. 4 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  61. 54 30
      fs-service/src/main/resources/mapper/course/PublicCourseWatchStatisticsMapper.xml
  62. 10 0
      fs-service/src/main/resources/mapper/his/FsInquiryOrderMapper.xml
  63. 1 17
      fs-user-app-ai-chat/src/main/java/com/fs/framework/config/RedisConfig.java
  64. 43 0
      fs-user-app/src/main/java/com/fs/app/controller/CommonController.java
  65. 6 5
      fs-user-app/src/main/java/com/fs/app/controller/app/AppController.java
  66. 85 0
      fs-user-app/src/main/java/com/fs/app/controller/course/PublicCourseWatchStatisticsController.java
  67. 20 0
      fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java
  68. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/PayScrmController.java
  69. 2 1
      fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java
  70. 0 6
      fs-user-app/src/main/java/com/fs/framework/config/RedisConfig.java

+ 13 - 2
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCategoryController.java

@@ -90,6 +90,17 @@ public class FsUserCourseCategoryController extends BaseController
         return AjaxResult.success(list);
     }
 
+    /**
+     * 查询课堂分类列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:userCourseCategory:list')")
+    @GetMapping("/publicList")
+    public AjaxResult publicList(FsUserCourseCategory fsUserCourseCategory)
+    {
+        List<FsUserCourseCategory> list = fsUserCourseCategoryService.selectFsUserCourseCategoryList(fsUserCourseCategory);
+        return AjaxResult.success(list);
+    }
+
     /**
      * 导出课堂分类列表
      */
@@ -178,10 +189,10 @@ public class FsUserCourseCategoryController extends BaseController
                     continue;
                 }
                 if (fsUserCourseMapper.countCourseByCategoryId(cateId) > 0) {
-                    return AjaxResult.error("该分类下有关联课程,不能删除");
+                    return AjaxResult.error("该分类下有关联课程或者素材,不能删除");
                 }
                 if (fsVideoResourceMapper.countVideoResourceByCourseCategoryId(cateId) > 0) {
-                    return AjaxResult.error("该分类下有关联课程,不能删除");
+                    return AjaxResult.error("该分类下有关联课程或者素材,不能删除");
                 }
             }
         }

+ 36 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseCommentController.java

@@ -3,8 +3,10 @@ package com.fs.course.controller;
 import java.util.List;
 
 import com.fs.course.param.FsUserCourseCommentParam;
+import com.fs.course.param.FsCourseWatchCommentPageParam;
 import com.fs.course.vo.FsUserCourseCommentListVO;
 import com.fs.course.vo.FsUserCourseCommentVO;
+import com.fs.course.vo.FsCourseWatchCommentListVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -21,6 +23,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.enums.BusinessType;
 import com.fs.course.domain.FsUserCourseComment;
 import com.fs.course.service.IFsUserCourseCommentService;
+import com.fs.course.service.IFsCourseWatchCommentService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
 
@@ -37,6 +40,9 @@ public class FsUserCourseCommentController extends BaseController
     @Autowired
     private IFsUserCourseCommentService fsUserCourseCommentService;
 
+    @Autowired
+    private IFsCourseWatchCommentService fsCourseWatchCommentService;
+
     /**
      * 查询课堂评论列表
      */
@@ -49,6 +55,23 @@ public class FsUserCourseCommentController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询公域视频下的评论列表(给公域课页面复用)。
+     * <p>
+     * 这里复用的是 fs_course_watch_comment(看课评论)表的查询逻辑;
+     * 由 fs-service 的 Mapper 在 cateType=1(公域看课评论)时完成公域视频范围过滤。
+     * </p>
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:list')")
+    @GetMapping("/publicCourseVideoWatchComment/list")
+    public TableDataInfo publicCourseVideoWatchCommentList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam)
+    {
+        startPage();
+        // 前端传 cateType=1、isAll=true 时,Mapper 会自动限定“公域课程 -> 对应视频 -> 评论”范围
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentPublicList(fsCourseWatchCommentPageParam);
+        return getDataTable(list);
+    }
+
     /**
      * 导出课堂评论列表
      */
@@ -62,6 +85,19 @@ public class FsUserCourseCommentController extends BaseController
         return util.exportExcel(list, "课堂评论数据");
     }
 
+    /**
+     * 导出公域看课评论列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchComment:export')")
+    @Log(title = "公域看课评论", businessType = BusinessType.EXPORT)
+    @GetMapping("/publicCourseVideoWatchComment/export")
+    public AjaxResult exportPublicCourseWatchComment(FsCourseWatchCommentPageParam param)
+    {
+        List<FsCourseWatchCommentListVO> list = fsCourseWatchCommentService.selectFsCourseWatchCommentPublicList(param);
+        ExcelUtil<FsCourseWatchCommentListVO> util = new ExcelUtil<FsCourseWatchCommentListVO>(FsCourseWatchCommentListVO.class);
+        return util.exportExcel(list, "公域看课评论数据");
+    }
+
     /**
      * 获取课堂评论详细信息
      */

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

@@ -1,7 +1,9 @@
 package com.fs.course.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
@@ -11,6 +13,7 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.cache.PublicCourseAppCacheNames;
 import com.fs.course.config.CourseConfig;
@@ -22,14 +25,20 @@ import com.fs.course.vo.FsUserCourseListPVO;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.dto.StoreOrderProductDTO;
+import com.fs.hisStore.vo.FsStoreOrderItemExportZMVO;
 import com.fs.qw.param.FsUserCourseRedPageParam;
 import com.fs.sop.service.IQwSopTempService;
 import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.math.BigDecimal;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 课程Controller
@@ -76,6 +85,9 @@ public class FsUserCourseController extends BaseController {
         return getDataTable(list);
     }
 
+    @Value("${cloud_host.company_name}")
+    private String signProjectName;
+
     /**
      * 查询公域课程列表
      */
@@ -83,12 +95,14 @@ public class FsUserCourseController extends BaseController {
     @GetMapping("/publicList")
     public TableDataInfo publicList(FsUserCourse fsUserCourse) {
         startPage();
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        Long userId = loginUser.getUser().getUserId();
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-            fsUserCourse.setUserId(userId);
+        if(!"北京卓美".equals(signProjectName)){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            Long userId = loginUser.getUser().getUserId();
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+            if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
+                fsUserCourse.setUserId(userId);
+            }
         }
         List<FsUserCourseListPVO> list = fsUserCourseService.selectFsUserCourseListPVO(fsUserCourse);
         return getDataTable(list);

+ 8 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -248,6 +248,14 @@ public class FsUserCourseVideoController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/getPublicVideoListByCourseId")
+    public TableDataInfo getPublicVideoListByCourseId(FsUserCourseVideo fsUserCourseVideo)
+    {
+        startPage();
+        List<FsUserCourseVideo> list = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
+        return getDataTable(list);
+    }
+
     @GetMapping("/getVideoListByCourseIdAll")
     public TableDataInfo getVideoListByCourseIdAll(Long courseId)
     {

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

@@ -114,12 +114,6 @@ public class FsVideoResourceController extends BaseController {
             videoType = 1;
         }
         params.put("videoType", videoType);
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if (ObjectUtil.isNotEmpty(config.getIsBound()) && config.getIsBound()) {
-            params.put("userId", loginUser.getUser().getUserId());
-        }
         PageHelper.startPage(pageNum, pageSize);
         List<FsVideoResourceVO> list = fsVideoResourceService.selectPublicVideoResourceListByMap(params);
         return getDataTable(list);

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

@@ -8,12 +8,14 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
 import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -61,4 +63,17 @@ public class PublicCourseWatchStatisticsController extends BaseController {
         ExcelUtil<PublicCourseWatchStatCatalogVO> util = new ExcelUtil<>(PublicCourseWatchStatCatalogVO.class);
         return util.exportExcel(list, "公域看课统计-目录数据");
     }
+
+    /**
+     * 查询目录下用户学习记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:publicCourseWatchStat:list')")
+    @GetMapping("/catalog/userStudy")
+    public TableDataInfo catalogUserStudy(@RequestParam Long videoId,
+                                          @RequestParam(required = false) String nickName,
+                                          @RequestParam(required = false) String phone) {
+        startPage();
+        List<CatalogUserStudyVO> list = publicCourseWatchStatisticsService.listCatalogUserStudy(videoId, nickName, phone);
+        return getDataTable(list);
+    }
 }

+ 10 - 1
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -16,6 +16,7 @@ import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.uuid.IdUtils;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.his.domain.*;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
@@ -143,6 +144,10 @@ public class FsIntegralOrderController extends BaseController
         return sysRole;
     }
 
+    @Autowired
+    // 260506 积分订单没有接入erp 暂时这么处理 卓美 需要对订单进行发货处理
+    private CloudHostProper cloudHostProper;
+
     /**
      * 导出积分商品订单列表 卓美个性化导出
      */
@@ -167,7 +172,11 @@ public class FsIntegralOrderController extends BaseController
         for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
             if (!(sysRole.getIsCheckPhone()==1)){
                 // 加密手机号
-                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+
+                // 260506 积分订单没有接入erp 暂时这么处理 卓美 需要对订单进行发货处理
+                if (!"北京卓美".equals(cloudHostProper.getCompanyName())) {
+                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+                }
             }
             if (StringUtils.isNotEmpty(vo.getItemJson())) {
                 try {

+ 4 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -138,6 +138,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
                                 zmvo.setCompanyUserNickName(vo.getCompanyUserNickName());
                                 zmvo.setRefundTime(vo.getCreateTime());
                                 zmvo.setRefundMoney(vo.getRefundAmount());
+                                zmvo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                                        ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
                                 zmvo.setBankTransactionId(vo.getBankTransactionId());
                                 zmvo.setReasons(vo.getReasons());
                                 zmvo.setExplains(vo.getExplains());
@@ -159,6 +161,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
         }
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                    ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
         }
 
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);

+ 22 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java

@@ -6,8 +6,10 @@ 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.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
@@ -184,6 +186,26 @@ public class FsStoreProductPackageScrmController extends BaseController
         return toAjax(fsStoreProductPackageService.deleteFsStoreProductPackageByIds(packageIds));
     }
 
+    /**
+     * 一键复制商品组合套餐
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:add')")
+    @Log(title = "商品组合套餐", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{packageId}")
+    public AjaxResult copyPackage(@PathVariable("packageId") Long packageId)
+    {
+        FsStoreProductPackageScrm source = fsStoreProductPackageService.selectFsStoreProductPackageById(packageId);
+        if (source == null) {
+            return AjaxResult.error("未找到对应的套餐信息");
+        }
+        // 清空主键,让数据库自增生成新ID
+        source.setPackageId(null);
+        // 标题加"-副本"后缀
+        source.setTitle(source.getTitle() + "-副本");
+        // 设置当前登录用户的公司和部门
+        return toAjax(fsStoreProductPackageService.insertFsStoreProductPackage(source));
+    }
+
 
     @GetMapping("/listBySearch")
     public R listBySearCh(FsStoreProductPackageScrm productPackage)

+ 58 - 0
fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java

@@ -1,7 +1,9 @@
 package com.fs.user.controller;
 
+import com.fs.common.annotation.Log;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
@@ -14,6 +16,7 @@ import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
@@ -139,6 +142,7 @@ public class FsUserIntegralController {
      */
     @ApiOperation("添加积分")
     @PostMapping("/add")
+    @PreAuthorize("@ss.hasPermi('user:integral:add')")
     public R addIntegral(@RequestBody AddIntegralParam param) {
         Long userId = param.getUserId();
         Integer logType = param.getLogType();
@@ -183,4 +187,58 @@ public class FsUserIntegralController {
 
         return R.ok("添加积分成功");
     }
+
+    /**
+     * 校验用户是否存在
+     */
+    @ApiOperation("校验用户是否存在")
+    @GetMapping("/checkUser/{userId}")
+    public R checkUser(@PathVariable Long userId) {
+        FsUser user = userService.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        return R.ok().put("nickName", user.getNickName()).put("phone", user.getPhone()).put("integral", user.getIntegral());
+    }
+
+    /**
+     * 扣除积分
+     */
+    @ApiOperation("扣除积分")
+    @Log(title = "扣除积分", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('user:integral:deduct')")
+    @PostMapping("/deduct")
+    public R deductIntegral(@RequestBody AddIntegralParam param) {
+        Long userId = param.getUserId();
+        Integer logType = param.getLogType();
+        Long integral = param.getIntegral();
+        if (integral == null || integral <= 0) {
+            return R.error("扣除积分必须大于0");
+        }
+        // 查询用户信息
+        FsUser user = userService.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+        // 计算新的积分余额
+        Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+        if (currentIntegral < integral) {
+            return R.error("用户积分不足,当前积分:" + currentIntegral);
+        }
+        Long newIntegral = currentIntegral - integral;
+        // 更新用户积分(传入负数表示扣除)
+        userService.increaseIntegral(Collections.singletonList(user.getUserId()), -integral);
+        // 创建积分记录
+        Date now = new Date();
+        FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+        integralLogs.setUserId(userId);
+        integralLogs.setLogType(logType);
+        integralLogs.setIntegral(-integral);
+        integralLogs.setBalance(newIntegral);
+        integralLogs.setCreateTime(now);
+        integralLogs.setUpdateTime(now);
+        integralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+        return R.ok("扣除积分成功");
+    }
 }

+ 34 - 4
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -3,10 +3,7 @@ package com.fs.common.core.redis;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.BoundSetOperations;
-import org.springframework.data.redis.core.HashOperations;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.*;
 import org.springframework.stereotype.Component;
 
 /**
@@ -228,6 +225,39 @@ public class RedisCache
         return redisTemplate.keys(pattern);
     }
 
+    /**
+     * 使用SCAN命令遍历匹配的key,避免阻塞主线程
+     *
+     * @param pattern 匹配模式(如 "cacheName::*")
+     * @return 匹配的key集合
+     */
+    public Set<String> scan(final String pattern)
+    {
+        return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            Set<String> keys = new HashSet<>();
+            Cursor<byte[]> cursor = connection.keyCommands().scan(
+                    ScanOptions.scanOptions()
+                            .match(pattern)
+                            .count(1000)
+                            .build()
+            );
+
+            try {
+                while (cursor.hasNext()) {
+                    keys.add(new String(cursor.next()));
+                }
+            } finally {
+                try {
+                    cursor.close();
+                } catch (Exception e) {
+                    // 忽略关闭异常
+                }
+            }
+
+            return keys;
+        });
+    }
+
 
     public Boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
         return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -71,7 +71,7 @@ public class LiveController extends BaseController
     {
         startPage();
         List<Live> list = new ArrayList<>();
-        if (CloudHostUtils.hasCloudHostName("济世百康") ) {
+        if (CloudHostUtils.hasCloudHostName("济世百康","蒙牛") ) {
             //直播也发
             list = liveService.listToLiveNoEndNew(live);
         }else{

+ 2 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -110,6 +110,8 @@ public class FsStoreAfterSalesScrmController extends BaseController
         List<FsStoreAfterSalesVO> list = fsStoreAfterSalesService.selectFsStoreAfterSalesListVO(fsStoreAfterSales);
         for (FsStoreAfterSalesVO vo : list){
             vo.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+            vo.setRefundType(vo.getRefundAmount() != null && vo.getPayPrice() != null
+                    ? (vo.getRefundAmount().compareTo(vo.getPayPrice()) < 0 ? "部分退款" : "全额退款") : "");
         }
         ExcelUtil<FsStoreAfterSalesVO> util = new ExcelUtil<FsStoreAfterSalesVO>(FsStoreAfterSalesVO.class);
         return util.exportExcel(list, "storeAfterSales");

+ 23 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreProductPackageScrmController.java

@@ -156,6 +156,29 @@ public class FsStoreProductPackageScrmController extends BaseController
         return toAjax(fsStoreProductPackageService.updateFsStoreProductPackage(fsStoreProductPackage));
     }
 
+    /**
+     * 一键复制商品组合套餐
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeProductPackage:add')")
+    @Log(title = "商品组合套餐", businessType = BusinessType.INSERT)
+    @PostMapping("/copy/{packageId}")
+    public AjaxResult copyPackage(@PathVariable("packageId") Long packageId)
+    {
+        FsStoreProductPackageScrm source = fsStoreProductPackageService.selectFsStoreProductPackageById(packageId);
+        if (source == null) {
+            return AjaxResult.error("未找到对应的套餐信息");
+        }
+        // 清空主键,让数据库自增生成新ID
+        source.setPackageId(null);
+        // 标题加"-副本"后缀
+        source.setTitle(source.getTitle() + "-副本");
+        // 设置当前登录用户的公司和部门
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        source.setCompanyId(loginUser.getCompany().getCompanyId());
+        source.setDeptId(loginUser.getUser().getDeptId());
+        return toAjax(fsStoreProductPackageService.insertFsStoreProductPackage(source));
+    }
+    
     /**
      * 删除商品组合套餐
      */

+ 0 - 1
fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 

+ 43 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseMarketingEvent.java

@@ -0,0 +1,43 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+/**
+ * 课程曝光/点击埋点对象 fs_course_marketing_event
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Data
+public class FsCourseMarketingEvent extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键 */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 小节id,课程级事件可为空 */
+    @Excel(name = "小节id")
+    private Long videoId;
+
+    /** 0-曝光 1-点击 */
+    @Excel(name = "事件类型", readConverterExp = "0=曝光,1=点击")
+    private Integer eventType;
+
+    /** 0-首页推荐 1-课程中心 */
+    @Excel(name = "曝光位置", readConverterExp = "0=首页推荐,1=课程中心")
+    private Integer exposurePosition;
+}

+ 49 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseShareLog.java

@@ -0,0 +1,49 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 课程分享记录对象 fs_course_share_log
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Data
+public class FsCourseShareLog implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 小节id */
+    @Excel(name = "小节id")
+    private Long videoId;
+
+    /** 分享类型 */
+    @Excel(name = "分享类型")
+    private Integer shareType;
+
+    /** 曝光位置 0-首页推荐 1-课程中心 */
+    @Excel(name = "曝光位置", readConverterExp = "0=首页推荐,1=课程中心")
+    private Integer exposurePosition;
+
+    /** 创建时间 */
+    @Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 63 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseMarketingEventMapper.java

@@ -0,0 +1,63 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseMarketingEvent;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 课程曝光/点击埋点Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Repository
+public interface FsCourseMarketingEventMapper
+{
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id);
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点集合
+     */
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 删除课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventById(Long id);
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventByIds(Long[] ids);
+}

+ 64 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseShareLogMapper.java

@@ -0,0 +1,64 @@
+package com.fs.course.mapper;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseShareLog;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 课程分享记录Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Repository
+public interface FsCourseShareLogMapper
+{
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    public FsCourseShareLog selectFsCourseShareLogById(Long id);
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录集合
+     */
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 删除课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogById(Long id);
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogByIds(Long[] ids);
+}

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

@@ -85,4 +85,6 @@ public interface FsCourseWatchCommentMapper extends BaseMapper<FsCourseWatchComm
      * @return list
      */
     List<FsCourseWatchCommentVO> selectH5CourseWatchCommentsRound(FsCourseWatchCommentListParam param);
+
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
 }

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

@@ -370,4 +370,14 @@ public interface FsUserCourseMapper
     @Select("SELECT COUNT(1) FROM fs_user_course WHERE IFNULL(is_del,0) = 0 AND (cate_id = #{cateId} OR sub_cate_id = #{cateId})")
     int countCourseByCategoryId(@Param("cateId") Long cateId);
 
+    /**
+     * 查询同分类下推荐位排序值重复的课程(首页课程顶部推荐位)
+     */
+    List<FsUserCourse> selectDuplicateRecHomeCourseTopSort(@Param("cateId") Long cateId, @Param("subCateId") Long subCateId, @Param("recHomeCourseTopSort") Integer recHomeCourseTopSort, @Param("excludeCourseId") Long excludeCourseId);
+
+    /**
+     * 查询同分类下推荐位排序值重复的课程(首页长视频瀑布流)
+     */
+    List<FsUserCourse> selectDuplicateRecHomeLongVideoSort(@Param("cateId") Long cateId, @Param("subCateId") Long subCateId, @Param("recHomeLongVideoSort") Integer recHomeLongVideoSort, @Param("excludeCourseId") Long excludeCourseId);
+
 }

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

@@ -71,6 +71,12 @@ public interface FsUserCourseStudyMapper
             "<if test = ' maps.userId  '> " +
             "and s.user_id = #{maps.userId} " +
             "</if>" +
+            "<if test = ' maps.courseType != null and maps.courseType == 1 '> " +
+            "and c.cate_id = (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1) " +
+            "</if>" +
+            "<if test = ' maps.courseType == null  '> " +
+            "and c.cate_id != (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1) " +
+            "</if>" +
             " order by s.study_id  "+
             "</script>"})
     List<FsUserCourseStudyListUVO> selectFsUserCourseStudyListUVO(@Param("maps")FsUserCourseListUParam param);

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

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 import org.apache.ibatis.annotations.Param;
@@ -15,4 +16,8 @@ public interface PublicCourseWatchStatisticsMapper {
     List<PublicCourseWatchStatCourseVO> selectCourseDayStatList(@Param("q") PublicCourseWatchStatQueryParam q);
 
     List<PublicCourseWatchStatCatalogVO> selectCatalogStatList(@Param("q") PublicCourseWatchStatQueryParam q);
+
+    List<CatalogUserStudyVO> selectCatalogUserStudyList(@Param("videoId") Long videoId,
+                                                       @Param("nickName") String nickName,
+                                                       @Param("phone") String phone);
 }

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

@@ -43,4 +43,8 @@ public class FsCourseWatchCommentPageParam extends BaseEntity{
     @ApiModelProperty(value = "分类类型:0-评论,1-公域看课评论")
     private Integer cateType;
 
+    /** 弹幕状态:0-正常,1-屏蔽 */
+    @ApiModelProperty(value = "弹幕状态:0-正常,1-屏蔽")
+    private Integer status;
+
 }

+ 5 - 1
fs-service/src/main/java/com/fs/course/param/FsUserCourseCategoryAppQueryParam.java

@@ -39,6 +39,9 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
     @ApiModelProperty(value = "原乡行标签:不传或0=只统计/展示下挂课程中无「原乡行」标签的;1=只统计/展示含「原乡行」标签的课程", example = "0")
     private Integer yxxTag;
 
+    @ApiModelProperty(value = "首页排序 1 是首页,后端添加标签排序,0不是首页", example = "0")
+    private Integer homePage;
+
     /**
      * 与 {@link com.fs.course.service.impl.FsUserCourseCategoryServiceImpl#selectFsUserCourseCategoryAppList}
      * 中默认入参(null 补全)一致,供缓存 key 使用。
@@ -48,7 +51,8 @@ public class FsUserCourseCategoryAppQueryParam implements Serializable {
         int ps = pageSize != null ? pageSize : 10;
         int ct = cateType != null ? cateType : 1;
         int yxx = yxxTag != null ? yxxTag : 0;
-        return pn + "|" + ps + "|" + n(cateName) + "|" + n(pid) + "|" + n(isShow) + "|" + ct + "|" + yxx;
+        int home = homePage != null ? homePage : 0;
+        return pn + "|" + ps + "|" + n(cateName) + "|" + n(pid) + "|" + n(isShow) + "|" + ct + "|" + yxx  + "|" + home;
     }
 
     private static String n(Object o) {

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseListUParam.java

@@ -22,6 +22,8 @@ public class FsUserCourseListUParam  implements Serializable {
 
     Integer isBest;
 
+    Integer courseType;
+
     @ApiModelProperty(value = "页码,默认为1")
     private Integer pageNum =1;
     @ApiModelProperty(value = "页大小,默认为10")

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

@@ -0,0 +1,61 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseMarketingEvent;
+
+/**
+ * 课程曝光/点击埋点Service接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+public interface IFsCourseMarketingEventService
+{
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id);
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点集合
+     */
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent);
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的课程曝光/点击埋点主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventByIds(Long[] ids);
+
+    /**
+     * 删除课程曝光/点击埋点信息
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    public int deleteFsCourseMarketingEventById(Long id);
+}

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

@@ -0,0 +1,61 @@
+package com.fs.course.service;
+
+import java.util.List;
+import com.fs.course.domain.FsCourseShareLog;
+
+/**
+ * 课程分享记录Service接口
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+public interface IFsCourseShareLogService
+{
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    public FsCourseShareLog selectFsCourseShareLogById(Long id);
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录集合
+     */
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog);
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的课程分享记录主键集合
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogByIds(Long[] ids);
+
+    /**
+     * 删除课程分享记录信息
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    public int deleteFsCourseShareLogById(Long id);
+}

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

@@ -86,4 +86,5 @@ public interface IFsCourseWatchCommentService extends IService<FsCourseWatchComm
      */
     List<FsCourseWatchCommentVO> selectH5CourseWatchComments(FsCourseWatchCommentListParam param);
 
+    List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam);
 }

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

@@ -1,6 +1,7 @@
 package com.fs.course.service;
 
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
 
@@ -14,4 +15,6 @@ public interface IPublicCourseWatchStatisticsService {
     List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param);
 
     List<PublicCourseWatchStatCatalogVO> listCatalogStat(PublicCourseWatchStatQueryParam param);
+
+    List<CatalogUserStudyVO> listCatalogUserStudy(Long videoId, String nickName, String phone);
 }

+ 96 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseMarketingEventServiceImpl.java

@@ -0,0 +1,96 @@
+package com.fs.course.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseMarketingEvent;
+import com.fs.course.mapper.FsCourseMarketingEventMapper;
+import com.fs.course.service.IFsCourseMarketingEventService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 课程曝光/点击埋点Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Service
+public class FsCourseMarketingEventServiceImpl implements IFsCourseMarketingEventService
+{
+    @Autowired
+    private FsCourseMarketingEventMapper fsCourseMarketingEventMapper;
+
+    /**
+     * 查询课程曝光/点击埋点
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 课程曝光/点击埋点
+     */
+    @Override
+    public FsCourseMarketingEvent selectFsCourseMarketingEventById(Long id)
+    {
+        return fsCourseMarketingEventMapper.selectFsCourseMarketingEventById(id);
+    }
+
+    /**
+     * 查询课程曝光/点击埋点列表
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 课程曝光/点击埋点
+     */
+    @Override
+    public List<FsCourseMarketingEvent> selectFsCourseMarketingEventList(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        return fsCourseMarketingEventMapper.selectFsCourseMarketingEventList(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 新增课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        fsCourseMarketingEvent.setCreateTime(DateUtils.getNowDate());
+        return fsCourseMarketingEventMapper.insertFsCourseMarketingEvent(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 修改课程曝光/点击埋点
+     *
+     * @param fsCourseMarketingEvent 课程曝光/点击埋点
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseMarketingEvent(FsCourseMarketingEvent fsCourseMarketingEvent)
+    {
+        fsCourseMarketingEvent.setUpdateTime(DateUtils.getNowDate());
+        return fsCourseMarketingEventMapper.updateFsCourseMarketingEvent(fsCourseMarketingEvent);
+    }
+
+    /**
+     * 批量删除课程曝光/点击埋点
+     *
+     * @param ids 需要删除的课程曝光/点击埋点主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseMarketingEventByIds(Long[] ids)
+    {
+        return fsCourseMarketingEventMapper.deleteFsCourseMarketingEventByIds(ids);
+    }
+
+    /**
+     * 删除课程曝光/点击埋点信息
+     *
+     * @param id 课程曝光/点击埋点主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseMarketingEventById(Long id)
+    {
+        return fsCourseMarketingEventMapper.deleteFsCourseMarketingEventById(id);
+    }
+}

+ 49 - 23
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -645,17 +645,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (param.getLinkType() != null && param.getLinkType() == 1) {
             errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(param.getVideoId(), param.getUserId(), null, null);
         } else {
-            FsCourseWatchLog courseWatchLog = resolveCourseWatchLogForApp(
-                    param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-            if (courseWatchLog == null) {
+            if (studyRow == null) {
                 return R.error("无看课记录");
             }
-            if (courseWatchLog.getLogType() == null || courseWatchLog.getLogType() != 2) {
-                return R.error("未完课");
-            }
-            logId = courseWatchLog.getLogId();
+            logId = Long.valueOf(studyRow.getLogId());
             errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(
-                    param.getVideoId(), param.getUserId(), param.getQwUserId(), courseWatchLog.getProject());
+                    param.getVideoId(), param.getUserId(), param.getQwUserId(), null);
         }
 
         if (errorCount >= APP_USER_ANSWER_MAX_ATTEMPTS) {
@@ -663,8 +658,12 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         }
         int remainCount = APP_USER_ANSWER_MAX_ATTEMPTS - errorCount - 1;
 
+
         List<FsCourseQuestionBank> questions = param.getQuestions();
         if (questions != null && !questions.isEmpty()) {
+//            String json = configService.selectConfigByKey("course.config");
+//            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+//            boolean skipAnswerValidation = CourseConfigUserAnswerExpose.skipValidateAnswerOnSubmit(json);
 //            if (skipAnswerValidation) {
 //                thisRightCount = questions.size();
 //            } else {
@@ -721,6 +720,43 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         if (thisRightCount == questionCount) {
             logs.setIsRight(1);
             courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+
+            // 答题成功自动发放积分
+            try {
+                FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+                if (video != null && video.getIntegralReward() != null && video.getIntegralReward() > 0) {
+                    String businessId = resolveAnswerIntegralBusinessId(param.getVideoId(), param.getPeriodId());
+                    FsUserIntegralLogs existed = fsUserIntegralLogsMapper.selectAnswerRewardIntegralLog(param.getUserId(), businessId);
+                    if (existed == null) {
+                        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+                        if (user != null) {
+                            long baseIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+                            long reward = video.getIntegralReward().longValue();
+                            long newBalance = baseIntegral + reward;
+
+                            FsUser upd = new FsUser();
+                            upd.setUserId(param.getUserId());
+                            upd.setIntegral(newBalance);
+                            fsUserMapper.updateFsUser(upd);
+
+                            FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+                            integralLogs.setIntegral(reward);
+                            integralLogs.setUserId(param.getUserId());
+                            integralLogs.setBalance(newBalance);
+                            integralLogs.setLogType(FsUserIntegralLogTypeEnum.TYPE_17.getValue());
+                            integralLogs.setBusinessId(businessId);
+                            integralLogs.setRemark(FsUserIntegralLogTypeEnum.TYPE_17.getDesc());
+                            integralLogs.setCreateTime(new Date());
+                            fsUserIntegralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+                            return R.ok("答题成功").put("integral", video.getIntegralReward()).put("balance", newBalance);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+
+            }
+
             return R.ok("答题成功");
         }
         logs.setIsRight(0);
@@ -775,14 +811,10 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             if (param.getLinkType() != null && param.getLinkType() == 1) {
                 rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideo(param.getVideoId(), param.getUserId(), null);
             } else {
-                FsCourseWatchLog watchLog = resolveCourseWatchLogForApp(
-                        param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-                if (watchLog == null) {
+                if (studyRow == null) {
                     return R.error("无看课记录");
                 }
-                if (watchLog.getLogType() == null || watchLog.getLogType() != 2) {
-                    return R.error("未完课");
-                }
+
                 if (param.getPeriodId() != null) {
                     rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideoWithPeriodId(
                             param.getVideoId(), param.getUserId(), param.getQwUserId(), param.getPeriodId());
@@ -875,21 +907,15 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
             return vo;
         }
 
-        FsCourseWatchLog watchLog = resolveCourseWatchLogForApp(
-                param.getVideoId(), param.getUserId(), param.getCompanyUserId(), param.getPeriodId());
-        if (watchLog == null) {
+
+        if (studyRow == null) {
             vo.setStatus(-2);
             vo.setStatusText("无看课记录");
             return vo;
         }
-        if (watchLog.getLogType() == null || watchLog.getLogType() != 2) {
-            vo.setStatus(-3);
-            vo.setStatusText("未完课");
-            return vo;
-        }
 
         int errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideoToday(
-                param.getVideoId(), param.getUserId(), param.getQwUserId(), watchLog.getProject());
+                param.getVideoId(), param.getUserId(), param.getQwUserId(), null);
         vo.setErrorCount(errorCount);
         fillProgressByErrorCount(vo, errorCount);
         return vo;

+ 95 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseShareLogServiceImpl.java

@@ -0,0 +1,95 @@
+package com.fs.course.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.domain.FsCourseShareLog;
+import com.fs.course.mapper.FsCourseShareLogMapper;
+import com.fs.course.service.IFsCourseShareLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 课程分享记录Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-29
+ */
+@Service
+public class FsCourseShareLogServiceImpl implements IFsCourseShareLogService
+{
+    @Autowired
+    private FsCourseShareLogMapper fsCourseShareLogMapper;
+
+    /**
+     * 查询课程分享记录
+     *
+     * @param id 课程分享记录主键
+     * @return 课程分享记录
+     */
+    @Override
+    public FsCourseShareLog selectFsCourseShareLogById(Long id)
+    {
+        return fsCourseShareLogMapper.selectFsCourseShareLogById(id);
+    }
+
+    /**
+     * 查询课程分享记录列表
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 课程分享记录
+     */
+    @Override
+    public List<FsCourseShareLog> selectFsCourseShareLogList(FsCourseShareLog fsCourseShareLog)
+    {
+        return fsCourseShareLogMapper.selectFsCourseShareLogList(fsCourseShareLog);
+    }
+
+    /**
+     * 新增课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseShareLog(FsCourseShareLog fsCourseShareLog)
+    {
+        fsCourseShareLog.setCreateTime(DateUtils.getNowDate());
+        return fsCourseShareLogMapper.insertFsCourseShareLog(fsCourseShareLog);
+    }
+
+    /**
+     * 修改课程分享记录
+     *
+     * @param fsCourseShareLog 课程分享记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseShareLog(FsCourseShareLog fsCourseShareLog)
+    {
+        return fsCourseShareLogMapper.updateFsCourseShareLog(fsCourseShareLog);
+    }
+
+    /**
+     * 批量删除课程分享记录
+     *
+     * @param ids 需要删除的课程分享记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseShareLogByIds(Long[] ids)
+    {
+        return fsCourseShareLogMapper.deleteFsCourseShareLogByIds(ids);
+    }
+
+    /**
+     * 删除课程分享记录信息
+     *
+     * @param id 课程分享记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseShareLogById(Long id)
+    {
+        return fsCourseShareLogMapper.deleteFsCourseShareLogById(id);
+    }
+}

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

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -172,4 +173,9 @@ public class FsCourseWatchCommentServiceImpl extends ServiceImpl<FsCourseWatchCo
         }
     }
 
+    @Override
+    public List<FsCourseWatchCommentListVO> selectFsCourseWatchCommentPublicList(FsCourseWatchCommentPageParam fsCourseWatchCommentPageParam) {
+        return baseMapper.selectFsCourseWatchCommentPublicList(fsCourseWatchCommentPageParam);
+    }
+
 }

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

@@ -73,7 +73,7 @@ public class FsUserCourseCategoryServiceImpl implements IFsUserCourseCategorySer
 //    @Cacheable(
 //            value = PublicCourseAppCacheNames.CATEGORY_APP_LIST,
 //            key = "#param == null ? 'default' : #param.appListCacheKey()")
-    @Cacheable(value = "publicCourseCategoryApp", key = "#param")
+    @Cacheable(value = "publicCourseCategoryApp", key = "#param.appListCacheKey()")
     public PageInfo<FsUserCourseCategory> selectFsUserCourseCategoryAppPage(FsUserCourseCategoryAppQueryParam param) {
         if (param == null) {
             param = new FsUserCourseCategoryAppQueryParam();

+ 55 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -14,6 +14,8 @@ import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.common.utils.RedisUtil;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.CloudHostUtils;
@@ -43,6 +45,7 @@ import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.service.IFsUserNewTaskService;
+import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.service.IQwCompanyService;
@@ -131,6 +134,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     @Autowired
     private AsyncUploadQwCourseImageService asyncUploadQwCourseImageService;
     @Autowired
@@ -147,6 +153,49 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     private static final String appRealLink = "/appcourse/pages_course/videovip?course=";
     public static final String appShortLink = "/courseH5/pages_course/videovip?s=";
 
+    @Autowired
+    private RedisCacheUtil redisCacheUtil;
+
+    /**
+     * 校验同分类下推荐位排序值是否重复(仅非北京卓美客户校验)
+     * @param fsUserCourse 课程对象
+     * @param excludeCourseId 排除的课程ID(修改时排除自身,新增时传null)
+     */
+    private void validateDuplicateSort(FsUserCourse fsUserCourse, Long excludeCourseId) {
+        if (!"北京卓美".equals(cloudHostProper.getCompanyName())) {
+            return;
+        }
+        Long cateId = fsUserCourse.getCateId();
+        Long subCateId = fsUserCourse.getSubCateId();
+
+        // 校验首页课程顶部推荐位排序值重复
+        if (fsUserCourse.getRecHomeCourseTopSort() != null) {
+            List<FsUserCourse> duplicateTop = fsUserCourseMapper.selectDuplicateRecHomeCourseTopSort(
+                    cateId, subCateId, fsUserCourse.getRecHomeCourseTopSort(), excludeCourseId);
+            if (!duplicateTop.isEmpty()) {
+                String duplicateNames = duplicateTop.stream()
+                        .map(FsUserCourse::getCourseName)
+                        .collect(java.util.stream.Collectors.joining("、"));
+                throw new com.fs.common.exception.CustomException(
+                        "首页课程顶部推荐位重复:" + duplicateNames);
+            }
+        }
+
+        // 校验首页长视频瀑布流排序值重复
+        if (fsUserCourse.getRecHomeLongVideoSort() != null) {
+            List<FsUserCourse> duplicateLong = fsUserCourseMapper.selectDuplicateRecHomeLongVideoSort(
+                    cateId, subCateId, fsUserCourse.getRecHomeLongVideoSort(), excludeCourseId);
+            if (duplicateLong != null && !duplicateLong.isEmpty()) {
+                String duplicateNames = duplicateLong.stream()
+                        .map(FsUserCourse::getCourseName)
+                        .collect(java.util.stream.Collectors.joining("、"));
+                throw new com.fs.common.exception.CustomException(
+                        "首页长视频瀑布流重复:" + duplicateNames);
+            }
+        }
+        redisCacheUtil.delSpringCacheAllByName(PublicCourseAppCacheNames.CATEGORY_APP_LIST);
+    }
+
     /**
      * 查询课程
      *
@@ -185,6 +234,8 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
         }
 
+        // 校验推荐位排序值重复
+        validateDuplicateSort(fsUserCourse, null);
 
         fsUserCourse.setCreateTime(DateUtils.getNowDate());
         return  fsUserCourseMapper.insertFsUserCourse(fsUserCourse);
@@ -203,6 +254,9 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             asyncUploadQwCourseImageService.uploadQwCourseImage(fsUserCourse);
         }
 
+        // 校验推荐位排序值重复
+        validateDuplicateSort(fsUserCourse, fsUserCourse.getCourseId());
+
         fsUserCourse.setUpdateTime(DateUtils.getNowDate());
         return fsUserCourseMapper.updateFsUserCourse(fsUserCourse);
     }
@@ -294,9 +348,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     }
 
     @Override
-    @Cacheable(
-            value = PublicCourseAppCacheNames.COURSE_PUBLIC_APP_LIST,
-            key = "#param == null ? 'default' : #param.appListCacheKey()")
+    @Cacheable(value = "publicCoursePublicApp", key = "#param.appListCacheKey()")
     public PageInfo<FsUserCoursePublicAppVO> selectFsUserCoursePublicAppPage(FsUserCoursePublicAppQueryParam param) {
         if (param == null) {
             param = new FsUserCoursePublicAppQueryParam();

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

@@ -4,8 +4,11 @@ import com.fs.common.utils.StringUtils;
 import com.fs.course.mapper.PublicCourseWatchStatisticsMapper;
 import com.fs.course.param.PublicCourseWatchStatQueryParam;
 import com.fs.course.service.IPublicCourseWatchStatisticsService;
+import com.fs.course.vo.CatalogUserStudyVO;
 import com.fs.course.vo.PublicCourseWatchStatCatalogVO;
 import com.fs.course.vo.PublicCourseWatchStatCourseVO;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -22,6 +25,9 @@ public class PublicCourseWatchStatisticsServiceImpl implements IPublicCourseWatc
     @Autowired
     private PublicCourseWatchStatisticsMapper publicCourseWatchStatisticsMapper;
 
+    @Autowired
+    private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
+
     @Override
     public List<PublicCourseWatchStatCourseVO> listCourseDayStat(PublicCourseWatchStatQueryParam param) {
         fillDefaultDateRange(param);
@@ -52,6 +58,17 @@ public class PublicCourseWatchStatisticsServiceImpl implements IPublicCourseWatc
         return publicCourseWatchStatisticsMapper.selectCatalogStatList(param);
     }
 
+    @Override
+    public List<CatalogUserStudyVO> listCatalogUserStudy(Long videoId, String nickName, String phone) {
+        List<CatalogUserStudyVO> list = publicCourseWatchStatisticsMapper.selectCatalogUserStudyList(videoId, nickName, phone);
+        for (CatalogUserStudyVO vo : list) {
+            // 查询用户是否领取过积分(log_type=17 答题积分,business_id = videoId)
+            FsUserIntegralLogs integralLog = fsUserIntegralLogsMapper.selectAnswerRewardIntegralLog(vo.getUserId(), String.valueOf(videoId));
+            vo.setIntegralClaimed(integralLog != null ? "是" : "否");
+        }
+        return list;
+    }
+
     private void fillDefaultDateRange(PublicCourseWatchStatQueryParam param) {
         if (param == null) {
             return;

+ 40 - 0
fs-service/src/main/java/com/fs/course/vo/CatalogUserStudyVO.java

@@ -0,0 +1,40 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 目录用户学习记录VO
+ */
+@Data
+@ApiModel(value = "目录用户学习记录")
+public class CatalogUserStudyVO {
+
+    @ApiModelProperty(value = "用户ID")
+    private Long userId;
+
+    @ApiModelProperty(value = "微信昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "累计学习时长(秒)")
+    private Long totalDuration;
+
+    @ApiModelProperty(value = "首次看课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date firstWatchTime;
+
+    @ApiModelProperty(value = "最近看课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastWatchTime;
+
+    @ApiModelProperty(value = "是否领取积分")
+    private String integralClaimed;
+}

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

@@ -36,6 +36,9 @@ public class FsUserCoursePublicAppVO implements Serializable {
     @ApiModelProperty("看课人数(看课记录表 send_type=1 下去重 user_id)")
     private Long watchUserCount;
 
+    @ApiModelProperty("看课默认人数")
+    private Long views;
+
     @ApiModelProperty("首页课程顶部推荐位:0否 1是")
     private Integer recHomeCourseTopEnabled;
     @ApiModelProperty("首页顶部推荐方式:1插入 2替换")

+ 3 - 2
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java

@@ -1,13 +1,14 @@
 package com.fs.fastGpt.domain;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import lombok.Data;
 
 @Data
 public class FastGptChatConversation {
     private JSONObject userInfo;
-    private JSONObject aiInfo;
-    private JSONObject history;
+    private String aiInfo;
+    private JSONArray history;
     private String isRepository;
     private String userContent;
     private String aiContent;

+ 15 - 9
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -652,10 +652,10 @@ public class AiHookServiceImpl implements AiHookService {
                 if (result.isLongText()){
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
-                    //发送图片消息
-                    sendImgMsg(contentKh,sender,uid,serverId);
 
                     if(isNewVersion){
+                        //发送图片消息
+                        sendImgMsg(contentKh,sender,uid,serverId);
                         sendAiMsg(content,sender,uid,serverId);
                     } else {
                         if (type==16){
@@ -677,10 +677,10 @@ public class AiHookServiceImpl implements AiHookService {
                     List<String> countList = countString(content);
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
-                    //发送图片消息
-                    sendImgMsg(contentKh,sender,uid,serverId);
                     for (String msg : countList) {
                         if(isNewVersion){
+                            //发送图片消息
+                            sendImgMsg(contentKh,sender,uid,serverId);
                             sendAiMsg(msg,sender,uid,serverId);
                         } else {
                             if (type==16){
@@ -1706,9 +1706,12 @@ public class AiHookServiceImpl implements AiHookService {
     private void addPromptWordNew(List<ChatParam.Message> messageList,String count,Long extId,FastGptRole role,FastGptChatSession fastGptChatSession){
 
         FastGptChatConversation conversation = new FastGptChatConversation();
-        conversation.setAiInfo(new com.alibaba.fastjson.JSONObject());
         conversation.setUserInfo(new com.alibaba.fastjson.JSONObject());
-        conversation.setHistory(new com.alibaba.fastjson.JSONObject());
+        conversation.setHistory(new com.alibaba.fastjson.JSONArray());
+
+        if(role.getReminderWords() != null && !role.getReminderWords().isEmpty()){
+            conversation.setAiInfo(role.getReminderWords());
+        }
 
         //组装客户信息
         String sessionUserInfo = fastGptChatSession.getUserInfo();
@@ -1739,7 +1742,7 @@ public class AiHookServiceImpl implements AiHookService {
 
         List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionIdAndExtId(fastGptChatSession.getSessionId(),extId);
         if (!msgs.isEmpty()){
-            com.alibaba.fastjson.JSONObject history = conversation.getHistory();
+            com.alibaba.fastjson.JSONArray historyArray = new com.alibaba.fastjson.JSONArray();
             Collections.reverse(msgs);
             msgs.remove(msgs.size() - 1);
             for (FastGptChatMsg msg : msgs) {
@@ -1750,9 +1753,12 @@ public class AiHookServiceImpl implements AiHookService {
                         continue;
                     }
                 }
-                history.put(sendType==1?"user":"ai",content);
+                com.alibaba.fastjson.JSONObject msgObj = new com.alibaba.fastjson.JSONObject();
+                msgObj.put("role", sendType==1?"user":"ai");
+                msgObj.put("content", content);
+                historyArray.add(msgObj);
             }
-            conversation.setHistory(history);
+            conversation.setHistory(historyArray);
         }
 
         if (count!=null&& !count.isEmpty()){

+ 2 - 0
fs-service/src/main/java/com/fs/his/dto/InquiryConfigDTO.java

@@ -20,4 +20,6 @@ public class InquiryConfigDTO implements Serializable {
     private BigDecimal companyPrice;
     private BigDecimal companyPrescribePrice;
     private String inquirySubType;
+    private String followMsg;
+    private String prescribeMsg;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsInquiryOrderMapper.java

@@ -415,4 +415,6 @@ public interface FsInquiryOrderMapper
             "create_time," +
             "update_time FROM fs_inquiry_order_hs_log WHERE book_no = #{bookNo}")
     InquiryOrderHsLog selectHsLogByBookNo(@Param("bookNo") String bookNo);
+
+    Long countByInquirySubTypeAndCreateTime(@Param("inquirySubType")Integer inquirySubType,@Param("date") String date);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/IFsInquiryOrderService.java

@@ -133,4 +133,7 @@ public interface IFsInquiryOrderService
     R getWxaCodeInquiryOrderUnLimitR(Long orderId);
 
     void closeOrder(Long orderId);
+
+    Long countByInquirySubTypeAndCreateTime(Integer inquirySubType, String date);
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java

@@ -2114,5 +2114,10 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
         fsInquiryOrderMapper.closeOrder(orderId);
     }
 
+    @Override
+    public Long countByInquirySubTypeAndCreateTime(Integer inquirySubType, String date) {
+        return fsInquiryOrderMapper.countByInquirySubTypeAndCreateTime(inquirySubType,date);
+    }
+
 
 }

+ 6 - 6
fs-service/src/main/java/com/fs/his/utils/RedisCacheUtil.java

@@ -1,14 +1,11 @@
 package com.fs.his.utils;
 
 import com.fs.common.core.redis.RedisCache;
-import org.apache.ibatis.annotations.Select;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import javax.crypto.Cipher;
-import javax.crypto.spec.SecretKeySpec;
-import java.util.Base64;
 import java.util.Collection;
+import java.util.Set;
 @Service
 public class RedisCacheUtil {
     @Autowired
@@ -32,9 +29,12 @@ public class RedisCacheUtil {
     }
 
     /**
-     * 清除某个 Spring {@link org.springframework.cache.annotation.Cacheable} 名称下的全部 Redis 项(同前缀 "cacheName::")
+     * 使用SCAN扫描并清除某个 Spring {@link org.springframework.cache.annotation.Cacheable} 名称下的全部 Redis 项(同前缀 "cacheName::")
      */
     public void delSpringCacheAllByName(String cacheName) {
-        delRedisKey(cacheName + "::");
+        Set<String> keys = redisCache.scan(cacheName + "::*");
+        if (keys != null && !keys.isEmpty()) {
+            redisCache.deleteObject(keys);
+        }
     }
 }

+ 12 - 0
fs-service/src/main/java/com/fs/his/vo/InquirySubTypeVo.java

@@ -0,0 +1,12 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+@Data
+public class InquirySubTypeVo {
+    private String lable;
+    private Integer value;
+    private Long num; //限制数量
+    private Long actualNum; //实际数量
+    private Integer isCanBuy; //是否可以下单 0:不可以,1:可以
+}

+ 6 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreCartScrmMapper.java

@@ -106,8 +106,13 @@ public interface FsStoreCartScrmMapper
     void updateIsPay(String cartIds);
 
 
-    @Select("select ifnull(sum(c.cart_num),0) from fs_store_cart_scrm c inner join fs_store_product_scrm p on p.product_id=c.product_id inner join fs_store_product_attr_value_scrm v on v.id=c.product_attr_value_id where c.is_pay=0 and c.is_del=0 and c.is_buy=0 and p.is_show=1 and p.is_del=0 and c.user_id= #{userId}")
+    @Select("select ifnull(sum(c.cart_num),0) from fs_store_cart_scrm c " +
+            "inner join fs_store_product_scrm p on p.product_id=c.product_id " +
+            "inner join fs_store_product_attr_value_scrm v " +
+            "on v.id=c.product_attr_value_id " +
+            "where c.is_pay=0 and c.is_del=0 and c.is_buy=0 and p.is_show=1 and p.is_del=0 and c.user_id= #{userId}")
     Integer selectFsStoreCartCountByUserId(long userId);
+
     @Select({"<script> " +
             "select ifnull(sum(c.cart_num),0) from fs_store_cart_scrm c  " +
             "where c.is_pay=0 and c.is_del=0 and c.is_buy=0 " +

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -722,6 +722,10 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         boolean mapEmpty = orderItemMap.isEmpty();
         if (null != fsStoreAfterSalesVOS && !fsStoreAfterSalesVOS.isEmpty()) {
             for (FsStoreAfterSalesVO item : fsStoreAfterSalesVOS) {
+                // 计算退款类型:退款金额<实付金额为部分退款,等于为全额退款
+                if (item.getRefundAmount() != null && item.getPayPrice() != null) {
+                    item.setRefundType(item.getRefundAmount().compareTo(item.getPayPrice()) < 0 ? "部分退款" : "全额退款");
+                }
                 if(!mapEmpty && orderItemMap.containsKey(item.getOrderId())){
                     List<FsStoreOrderItemVO> orderItems = orderItemMap.get(item.getOrderId());
                     for (FsStoreOrderItemVO orderItem : orderItems) {

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -547,6 +547,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
             }catch (Exception e){
                 logger.info("该单 {} 推送到物流失败!",fsWxExpressTask);
+                logger.info("该单 {} 推送到物流失败!",e.getMessage());
                 fsWxExpressTask.setRetryCount(fsWxExpressTask.getRetryCount() +1);
                 fsWxExpressTask.setStatus(3);
 

+ 3 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java

@@ -1188,7 +1188,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         // 创建记录 TODO 根据type创建支付
         FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
         if (payOrderParam.getBusinessType().getPrefix().equals("live")) {
-            LiveOrderPayment liveOrderPayment = createLiveStorePayment(payConfig, user, payOrderParam);
+            LiveOrderPayment liveOrderPayment = createLiveStorePayment(payConfig, user, payOrderParam,merchantAppConfig.getMerchantId());
             BeanUtils.copyProperties(liveOrderPayment, storePayment);
         } else {
             storePayment = createStorePaymentScrm(payConfig, user, payOrderParam,merchantAppConfig.getMerchantId());
@@ -1206,7 +1206,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
      * @param payOrderParam
      * @return
      */
-    private LiveOrderPayment createLiveStorePayment(FsPayConfig payConfig, FsUserScrm user, PayOrderParam payOrderParam) {
+    private LiveOrderPayment createLiveStorePayment(FsPayConfig payConfig, FsUserScrm user, PayOrderParam payOrderParam,String merchantId) {
         String payCode = OrderCodeUtils.getOrderSn();
         if (StringUtils.isEmpty(payCode)) {
             throw new CustomException("订单生成失败,请重试");
@@ -1228,6 +1228,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
         storePayment.setStoreId(payOrderParam.getStoreId());
         storePayment.setUserId(user.getUserId());
         storePayment.setBusinessId(payOrderParam.getOrderId().toString());
+        storePayment.setMerConfigId(Long.valueOf(merchantId));
         // 设置openId(如果是微信支付)
         if (isWechatPayment(payOrderParam.getPaymentMethod())) {
             storePayment.setOpenId(getOpenIdForPaymentMethod(user, payOrderParam.getPaymentMethod(), payConfig));

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreAfterSalesVO.java

@@ -150,6 +150,9 @@ public class FsStoreAfterSalesVO implements Serializable
     private Long reasonId1;
 
     private Long reasonId2;
+    @Excel(name = "退款类型")
+    private String refundType;
+
     @Excel(name ="售后原因一级")
     @TableField(exist = false)
     private String reasonValue1;

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java

@@ -142,6 +142,9 @@ public class FsStoreOrderItemExportRefundZMVO implements Serializable  {
     @Excel(name = "说明",sort = 260)
     private String explains;
 
+    @Excel(name ="退款类型",sort = 245)
+    private String refundType;
+
     @Excel(name ="售后原因一级")
     @TableField(exist = false)
     private String reasonValue1;

+ 23 - 1
fs-service/src/main/java/com/fs/wx/order/service/ShippingService.java

@@ -24,6 +24,7 @@ public class ShippingService {
 
     private final WeChatApiConfig weChatApiConfig;
 
+
     public ShippingService(WeChatApiConfig weChatApiConfig) {
         this.weChatApiConfig = weChatApiConfig;
     }
@@ -83,8 +84,29 @@ public class ShippingService {
                 if (!weChatApiResponse.isSuccess()) {
                     log.warn("微信接口返回业务错误: code={}, message={}", weChatApiResponse.getErrcode(), weChatApiResponse.getErrmsg());
                     if(ObjectUtil.equal(weChatApiResponse.getErrcode(),40001)) {
+                        log.info("token缓存失效,强制刷新token并重试...");
                         accessToken = weChatAuthService.getAccessToken(true);
-                        log.info("token缓存失效,清除token,等待下次执行...");
+                        if (accessToken != null) {
+                            String retryUrl = UriComponentsBuilder.fromHttpUrl(weChatApiConfig.getUploadShippingInfoUrl())
+                                    .queryParam("access_token", accessToken)
+                                    .toUriString();
+                            try {
+                                HttpResponse retryResponse = HttpUtil.createPost(retryUrl)
+                                        .header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
+                                        .body(requestBodyJson)
+                                        .timeout(10000)
+                                        .execute();
+                                try {
+                                    WeChatApiResponse retryResult = JSONUtil.toBean(retryResponse.body(), WeChatApiResponse.class);
+                                    log.info("重试微信接口响应: errcode={}, errmsg={}", retryResult.getErrcode(), retryResult.getErrmsg());
+                                    return retryResult;
+                                } finally {
+                                    retryResponse.close();
+                                }
+                            } catch (Exception retryEx) {
+                                log.error("重试调用微信接口失败", retryEx);
+                            }
+                        }
                     }
                 }
                 return weChatApiResponse;

+ 79 - 0
fs-service/src/main/resources/mapper/course/FsCourseMarketingEventMapper.xml

@@ -0,0 +1,79 @@
+<?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.FsCourseMarketingEventMapper">
+
+    <resultMap id="FsCourseMarketingEventResult" type="com.fs.course.domain.FsCourseMarketingEvent">
+        <id property="id" column="id"/>
+        <result property="userId" column="user_id"/>
+        <result property="courseId" column="course_id"/>
+        <result property="videoId" column="video_id"/>
+        <result property="eventType" column="event_type"/>
+        <result property="exposurePosition" column="exposure_position"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <sql id="selectFsCourseMarketingEventVo">
+        select id, user_id, course_id, video_id, event_type, exposure_position, create_time
+        from fs_course_marketing_event
+    </sql>
+
+    <select id="selectFsCourseMarketingEventById" parameterType="Long" resultMap="FsCourseMarketingEventResult">
+        <include refid="selectFsCourseMarketingEventVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectFsCourseMarketingEventList" parameterType="com.fs.course.domain.FsCourseMarketingEvent" resultMap="FsCourseMarketingEventResult">
+        <include refid="selectFsCourseMarketingEventVo"/>
+        <where>
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="courseId != null"> and course_id = #{courseId}</if>
+            <if test="videoId != null"> and video_id = #{videoId}</if>
+            <if test="eventType != null"> and event_type = #{eventType}</if>
+            <if test="exposurePosition != null"> and exposure_position = #{exposurePosition}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <insert id="insertFsCourseMarketingEvent" parameterType="com.fs.course.domain.FsCourseMarketingEvent" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_course_marketing_event (
+            user_id,
+            course_id,
+            video_id,
+            event_type,
+            exposure_position,
+            create_time
+        ) values (
+            #{userId},
+            #{courseId},
+            #{videoId},
+            #{eventType},
+            #{exposurePosition},
+            #{createTime}
+        )
+    </insert>
+
+    <update id="updateFsCourseMarketingEvent" parameterType="com.fs.course.domain.FsCourseMarketingEvent">
+        update fs_course_marketing_event
+        <set>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="eventType != null">event_type = #{eventType},</if>
+            <if test="exposurePosition != null">exposure_position = #{exposurePosition},</if>
+        </set>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsCourseMarketingEventById" parameterType="Long">
+        delete from fs_course_marketing_event where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCourseMarketingEventByIds" parameterType="Long">
+        delete from fs_course_marketing_event where id in
+        <foreach collection="array" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

+ 79 - 0
fs-service/src/main/resources/mapper/course/FsCourseShareLogMapper.xml

@@ -0,0 +1,79 @@
+<?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.FsCourseShareLogMapper">
+
+    <resultMap id="FsCourseShareLogResult" type="com.fs.course.domain.FsCourseShareLog">
+        <id property="id" column="id"/>
+        <result property="userId" column="user_id"/>
+        <result property="courseId" column="course_id"/>
+        <result property="videoId" column="video_id"/>
+        <result property="shareType" column="share_type"/>
+        <result property="exposurePosition" column="exposure_position"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <sql id="selectFsCourseShareLogVo">
+        select id, user_id, course_id, video_id, share_type, exposure_position, create_time
+        from fs_course_share_log
+    </sql>
+
+    <select id="selectFsCourseShareLogById" parameterType="Long" resultMap="FsCourseShareLogResult">
+        <include refid="selectFsCourseShareLogVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectFsCourseShareLogList" parameterType="com.fs.course.domain.FsCourseShareLog" resultMap="FsCourseShareLogResult">
+        <include refid="selectFsCourseShareLogVo"/>
+        <where>
+            <if test="userId != null"> and user_id = #{userId}</if>
+            <if test="courseId != null"> and course_id = #{courseId}</if>
+            <if test="videoId != null"> and video_id = #{videoId}</if>
+            <if test="shareType != null"> and share_type = #{shareType}</if>
+            <if test="exposurePosition != null"> and exposure_position = #{exposurePosition}</if>
+        </where>
+        order by id desc
+    </select>
+
+    <insert id="insertFsCourseShareLog" parameterType="com.fs.course.domain.FsCourseShareLog" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_course_share_log (
+            user_id,
+            course_id,
+            video_id,
+            share_type,
+            exposure_position,
+            create_time
+        ) values (
+            #{userId},
+            #{courseId},
+            #{videoId},
+            #{shareType},
+            #{exposurePosition},
+            #{createTime}
+        )
+    </insert>
+
+    <update id="updateFsCourseShareLog" parameterType="com.fs.course.domain.FsCourseShareLog">
+        update fs_course_share_log
+        <set>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="shareType != null">share_type = #{shareType},</if>
+            <if test="exposurePosition != null">exposure_position = #{exposurePosition},</if>
+        </set>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsCourseShareLogById" parameterType="Long">
+        delete from fs_course_share_log where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCourseShareLogByIds" parameterType="Long">
+        delete from fs_course_share_log where id in
+        <foreach collection="array" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>

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

@@ -80,6 +80,60 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
+    <!-- 查询公域课程的评论列表(用于公域课评论页) -->
+    <select id="selectFsCourseWatchCommentPublicList" resultType="com.fs.course.vo.FsCourseWatchCommentListVO">
+        SELECT
+            c.comment_id,
+            c.user_id,
+            c.course_id,
+            c.type,
+            c.parent_id,
+            c.content,
+            c.reply_count,
+            c.likes,
+            c.to_user_id,
+            c.create_time,
+            c.update_time,
+            u.nick_name,
+            COALESCE(uc.title, uc.course_name) AS course_name
+        FROM fs_user_course_comment c
+        INNER JOIN fs_user_course uc ON uc.course_id = c.course_id
+        LEFT JOIN fs_user u ON u.user_id = c.user_id
+        <where>
+            c.is_del = 0
+            <if test="courseId != null"> AND c.course_id = #{courseId}</if>
+            <if test="nickName != null and nickName != ''"> AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')</if>
+            <if test="status != null"> AND c.status = #{status}</if>
+            <if test="keywords != null and keywords != '' and isAll != null and isAll == true">
+                AND (
+                    u.nick_name LIKE CONCAT('%',#{keywords},'%')
+                    OR uc.course_name LIKE CONCAT('%',#{keywords},'%')
+                    OR uc.title LIKE CONCAT('%',#{keywords},'%')
+                )
+            </if>
+            <!-- 公域课程筛选:非私域 + 未删除 -->
+            AND IFNULL(uc.is_private, 0) = 0
+            AND EXISTS (
+                SELECT 1
+                FROM fs_user_course_category pc
+                WHERE pc.cate_id = uc.cate_id
+                  AND pc.cate_type = 1
+                  AND IFNULL(pc.is_del, 0) = 0
+            )
+            AND (
+                uc.sub_cate_id IS NULL
+                OR EXISTS (
+                    SELECT 1
+                    FROM fs_user_course_category sc
+                    WHERE sc.cate_id = uc.sub_cate_id
+                      AND sc.cate_type = 1
+                      AND IFNULL(sc.is_del, 0) = 0
+                )
+            )
+        </where>
+        ORDER BY c.comment_id DESC
+    </select>
+
     <select id="selectFsCourseWatchCommentByCommentId" parameterType="Long" resultMap="FsCourseWatchCommentResult">
         <include refid="selectFsCourseWatchCommentVo"/>
         where comment_id = #{commentId}

+ 19 - 3
fs-service/src/main/resources/mapper/course/FsUserCourseCategoryMapper.xml

@@ -38,13 +38,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          pid 有值:只查该一级下的二级;pid 无值:仅保留在公域课 fs_user_course 中 sub_cate_id 有上架使用 的二级 -->
     <select id="selectFsUserCourseCategoryAppList" parameterType="com.fs.course.param.FsUserCourseCategoryAppQueryParam" resultType="com.fs.course.domain.FsUserCourseCategory">
         SELECT
-            c.cate_id, c.pid, c.cate_name, c.sort, c.is_show, c.create_time, c.update_time, c.is_del, c.cate_type
+         distinct c.cate_id, c.pid, c.cate_name, c.sort, c.is_show, c.create_time, c.update_time, c.is_del, c.cate_type
         FROM fs_user_course_category c
-        INNER JOIN fs_user_course_category p ON p.cate_id = c.pid
+        LEFT JOIN fs_user_course_category p ON p.cate_id = c.pid
+        <if test="homePage != null and homePage == 1">
+            left JOIN  fs_user_course d on c.cate_id = d.sub_cate_id
+        </if>
         <where>
             c.is_del = 0
             AND p.is_del = 0
             AND p.pid = 0
+                <if test="yxxTag != null and yxxTag == 1 and homePage != null and homePage == 1">
+                    and d.rec_home_course_top_enabled = 1
+                </if>
+                <if test="yxxTag == null and homePage != null and homePage == 1">
+                    and d.rec_home_long_video_enabled = 1
+                </if>
             <choose>
                 <when test="yxxTag != null and yxxTag == 1">
                     AND c.pid = (select cate_id from fs_user_course_category WHERE cate_name like '%央广原乡行%' AND cate_type = 1 limit 1)
@@ -104,7 +113,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 )
             </if>
         </where>
-        ORDER BY c.sort ASC, c.cate_id ASC
+        ORDER BY
+        <if test="yxxTag != null and yxxTag == 1 and homePage != null and homePage == 1">
+            ifnull(d.rec_home_course_top_sort,9999),
+        </if>
+        <if test="yxxTag == null and homePage != null and homePage == 1">
+            ifnull(d.rec_home_long_video_sort,9999),
+        </if>
+        c.sort ASC, c.cate_id ASC
     </select>
 
     <select id="selectFsUserCourseCategoryByCateId" parameterType="Long" resultMap="FsUserCourseCategoryResult">

+ 32 - 8
fs-service/src/main/resources/mapper/course/FsUserCourseMapper.xml

@@ -339,12 +339,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="recHomeCourseTopEnabled != null">rec_home_course_top_enabled = #{recHomeCourseTopEnabled},</if>
             <if test="recHomeCourseTopMode != null">rec_home_course_top_mode = #{recHomeCourseTopMode},</if>
             <if test="recHomeCourseTopSort != null">rec_home_course_top_sort = #{recHomeCourseTopSort},</if>
+            <if test="recHomeCourseTopSort == null">rec_home_course_top_sort = null,</if>
             <if test="recMallHomeEnabled != null">rec_mall_home_enabled = #{recMallHomeEnabled},</if>
             <if test="recMallHomeMode != null">rec_mall_home_mode = #{recMallHomeMode},</if>
             <if test="recMallHomeSort != null">rec_mall_home_sort = #{recMallHomeSort},</if>
+            <if test="recMallHomeSort == null">rec_mall_home_sort = null,</if>
             <if test="recHomeLongVideoEnabled != null">rec_home_long_video_enabled = #{recHomeLongVideoEnabled},</if>
             <if test="recHomeLongVideoMode != null">rec_home_long_video_mode = #{recHomeLongVideoMode},</if>
             <if test="recHomeLongVideoSort != null">rec_home_long_video_sort = #{recHomeLongVideoSort},</if>
+            <if test="recHomeLongVideoSort == null">rec_home_long_video_sort = null,</if>
         </trim>
         where course_id = #{courseId}
     </update>
@@ -389,7 +392,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             c.description AS courseDesc,
             c.img_url AS imgUrl,
             c.second_img AS secondImg,
-            IFNULL(wl_stat.watch_uv, 0) AS watchUserCount,
+            IFNULL(c.views, 0) AS watchUserCount,
             IFNULL(c.rec_home_course_top_enabled, 0) AS recHomeCourseTopEnabled,
             c.rec_home_course_top_mode AS recHomeCourseTopMode,
             c.rec_home_course_top_sort AS recHomeCourseTopSort,
@@ -400,12 +403,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             c.rec_home_long_video_mode AS recHomeLongVideoMode,
             c.rec_home_long_video_sort AS recHomeLongVideoSort
         FROM fs_user_course c
-        LEFT JOIN (
-            SELECT course_id, COUNT(DISTINCT user_id) AS watch_uv
-            FROM fs_course_watch_log
-            WHERE send_type = 1
-            GROUP BY course_id
-        ) wl_stat ON wl_stat.course_id = c.course_id
         WHERE c.is_del = 0
         AND c.is_show = 1
         AND c.is_private = 0
@@ -464,6 +461,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="q.recommendSlot != null and q.recommendSlot == 3">
             IFNULL(c.rec_home_long_video_sort, 999999) ASC,
         </if>
-        c.sort ASC, c.course_id DESC
+        c.update_time desc,c.sort ASC, c.course_id DESC
+    </select>
+
+    <!-- 查询同分类下首页课程顶部推荐位排序值重复的课程 -->
+    <select id="selectDuplicateRecHomeCourseTopSort" resultType="com.fs.course.domain.FsUserCourse">
+        SELECT course_id, course_name FROM fs_user_course
+        WHERE is_del = 0
+        <if test="cateId != null">
+            AND cate_id = #{cateId}
+        </if>
+        <if test="cateId == null">
+            AND cate_id IS NULL
+        </if>
+        AND rec_home_course_top_sort = #{recHomeCourseTopSort}
+        <if test="excludeCourseId != null">
+            AND course_id != #{excludeCourseId}
+        </if>
+    </select>
+
+    <!-- 查询同分类下首页长视频瀑布流排序值重复的课程 -->
+    <select id="selectDuplicateRecHomeLongVideoSort" resultType="com.fs.course.domain.FsUserCourse">
+        SELECT course_id, course_name FROM fs_user_course
+        WHERE is_del = 0
+        AND cate_id = #{cateId}
+        AND rec_home_long_video_sort = #{recHomeLongVideoSort}
+        <if test="excludeCourseId != null">
+            AND course_id != #{excludeCourseId}
+        </if>
     </select>
 </mapper>

+ 4 - 0
fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml

@@ -305,6 +305,7 @@
         fcpd.period_id,
         fcp.period_name,
         fcpd.id,
+        if(fav.id is null, 0, 1) as isFavorite,
         if(ccut.start_date_time is null, fcpd.start_date_time, ccut.start_date_time) as startDateTime,
         if(ccut.end_date_time is null, fcpd.end_date_time, ccut.end_date_time) as endDateTime,
         course.project as projectId,
@@ -317,6 +318,9 @@
         AND ccut.course_id = fcpd.course_id
         AND ccut.video_id = fcpd.video_id
         AND ccut.company_user_id = #{companyUserId}
+        left join fs_company_user_course_favorite fav
+            on fav.period_id = fcpd.period_id
+            and fav.company_user_id = #{companyUserId}
         where course.is_del = 0 and fcp.del_flag = 0 and fcpd.del_flag = 0
         AND FIND_IN_SET(#{companyId}, fcp.company_id)
         <if test="periodId != null and periodId !='' ">

+ 54 - 30
fs-service/src/main/resources/mapper/course/PublicCourseWatchStatisticsMapper.xml

@@ -23,8 +23,8 @@
 
     <select id="selectCourseDayStatList" resultType="com.fs.course.vo.PublicCourseWatchStatCourseVO">
         SELECT
-            x.stat_date AS statDate,
-            x.course_id AS courseId,
+            m.stat_date AS statDate,
+            m.course_id AS courseId,
             COALESCE(uc.title, uc.course_name) AS courseName,
             pc.cate_name AS rootCateName,
             sc.cate_name AS subCateName,
@@ -42,20 +42,23 @@
                 WHEN IFNULL(m.exposure_pv, 0) &gt; 0 THEN ROUND(100.0 * IFNULL(m.click_pv, 0) / m.exposure_pv, 2)
                 ELSE 0
             END AS clickRate,
-            x.watch_uv AS watchUv,
-            x.finish_uv AS finishUv
+            IFNULL(w.watch_uv, 0) AS watchUv,
+            IFNULL(w.finish_uv, 0) AS finishUv
         FROM (
             SELECT
-                DATE(wl.create_time) AS stat_date,
-                wl.course_id AS course_id,
-                COUNT(DISTINCT wl.user_id) AS watch_uv,
-                COUNT(DISTINCT CASE WHEN wl.log_type = 2 THEN wl.user_id END) AS finish_uv
-            FROM fs_course_watch_log wl
-            INNER JOIN fs_user_course uc ON uc.course_id = wl.course_id
+                DATE(e.create_time) AS stat_date,
+                e.course_id AS course_id,
+                SUM(CASE WHEN e.event_type = 0 THEN 1 ELSE 0 END) AS exposure_pv,
+                COUNT(DISTINCT CASE WHEN e.event_type = 0 THEN e.user_id END) AS exposure_uv,
+                SUM(CASE WHEN e.event_type = 1 THEN 1 ELSE 0 END) AS click_pv,
+                COUNT(DISTINCT CASE WHEN e.event_type = 1 THEN e.user_id END) AS click_uv,
+                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 0 THEN 1 ELSE 0 END) AS has_home,
+                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 1 THEN 1 ELSE 0 END) AS has_center
+            FROM fs_course_marketing_event e
+            LEFT JOIN fs_user_course uc ON uc.course_id = e.course_id
             <include refid="publicCourseWhereCourse"/>
-            WHERE wl.send_type = 1
-              AND wl.create_time &gt;= #{q.beginDate}
-              AND wl.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            WHERE e.create_time &gt;= #{q.beginDate}
+              AND e.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
             <if test="q.keywords != null and q.keywords != ''">
                 AND (
                     uc.title LIKE CONCAT('%', #{q.keywords}, '%')
@@ -68,27 +71,23 @@
             <if test="q.subCateId != null">
                 AND uc.sub_cate_id = #{q.subCateId}
             </if>
-            GROUP BY DATE(wl.create_time), wl.course_id
-        ) x
-        INNER JOIN fs_user_course uc ON uc.course_id = x.course_id
+            GROUP BY DATE(e.create_time), e.course_id
+        ) m
+        LEFT JOIN fs_user_course uc ON uc.course_id = m.course_id
         LEFT JOIN fs_user_course_category pc ON pc.cate_id = uc.cate_id AND IFNULL(pc.is_del, 0) = 0
         LEFT JOIN fs_user_course_category sc ON sc.cate_id = uc.sub_cate_id AND IFNULL(sc.is_del, 0) = 0
         LEFT JOIN (
             SELECT
-                DATE(e.create_time) AS dt,
-                e.course_id AS course_id,
-                SUM(CASE WHEN e.event_type = 0 THEN 1 ELSE 0 END) AS exposure_pv,
-                COUNT(DISTINCT CASE WHEN e.event_type = 0 THEN e.user_id END) AS exposure_uv,
-                SUM(CASE WHEN e.event_type = 1 THEN 1 ELSE 0 END) AS click_pv,
-                COUNT(DISTINCT CASE WHEN e.event_type = 1 THEN e.user_id END) AS click_uv,
-                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 0 THEN 1 ELSE 0 END) AS has_home,
-                MAX(CASE WHEN e.event_type = 0 AND e.exposure_position = 1 THEN 1 ELSE 0 END) AS has_center
-            FROM fs_course_marketing_event e
-            WHERE e.create_time &gt;= #{q.beginDate}
-              AND e.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
-            GROUP BY DATE(e.create_time), e.course_id
-        ) m ON m.dt = x.stat_date AND m.course_id = x.course_id
-        ORDER BY x.stat_date DESC, x.course_id DESC
+                DATE(wl.create_time) AS stat_date,
+                wl.course_id AS course_id,
+                COUNT(DISTINCT wl.user_id) AS watch_uv,
+                COUNT(DISTINCT CASE WHEN wl.log_type = 2 THEN wl.user_id END) AS finish_uv
+            FROM fs_course_watch_log wl
+            WHERE wl.create_time &gt;= #{q.beginDate}
+              AND wl.create_time &lt; DATE_ADD(#{q.endDate}, INTERVAL 1 DAY)
+            GROUP BY DATE(wl.create_time), wl.course_id
+        ) w ON w.stat_date = m.stat_date AND w.course_id = m.course_id
+        ORDER BY m.stat_date DESC, m.course_id DESC
     </select>
 
     <select id="selectCatalogStatList" resultType="com.fs.course.vo.PublicCourseWatchStatCatalogVO">
@@ -200,4 +199,29 @@
             sc.cate_name
         ORDER BY v.video_id DESC
     </select>
+
+    <!-- 查询目录下用户学习记录 -->
+    <select id="selectCatalogUserStudyList" resultType="com.fs.course.vo.CatalogUserStudyVO">
+        SELECT
+            wl.user_id AS userId,
+            u.nick_name AS nickName,
+            u.phone AS phone,
+            COALESCE(SUM(CASE WHEN COALESCE(wl.duration, 0) &gt; 0 THEN wl.duration ELSE 0 END), 0) AS totalDuration,
+            MIN(wl.create_time) AS firstWatchTime,
+            MAX(wl.create_time) AS lastWatchTime
+        FROM fs_course_watch_log wl
+        LEFT JOIN fs_user u ON u.user_id = wl.user_id
+        <where>
+            wl.video_id = #{videoId}
+            AND wl.user_id IS NOT NULL
+            <if test="nickName != null and nickName != ''">
+                AND u.nick_name LIKE CONCAT('%', #{nickName}, '%')
+            </if>
+            <if test="phone != null and phone != ''">
+                AND u.phone LIKE CONCAT('%', #{phone}, '%')
+            </if>
+        </where>
+        GROUP BY wl.user_id, u.nick_name, u.phone
+        ORDER BY wl.user_id DESC
+    </select>
 </mapper>

+ 10 - 0
fs-service/src/main/resources/mapper/his/FsInquiryOrderMapper.xml

@@ -344,4 +344,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         and type = 1
     </select>
 
+
+    <select id="countByInquirySubTypeAndCreateTime" resultType="java.lang.Long">
+        SELECT IFNULL(COUNT(1), 0)
+        FROM fs_inquiry_order
+        WHERE
+            inquiry_sub_type = #{inquirySubType}
+          AND `status` IN (2,3)
+          AND create_time >= CONCAT(#{date}, ' 00:00:00')
+          AND create_time &lt; DATE_ADD(CONCAT(#{date}, ' 00:00:00'), INTERVAL 1 DAY)
+    </select>
 </mapper>

+ 1 - 17
fs-user-app-ai-chat/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -5,35 +5,19 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.cache.concurrent.ConcurrentMapCache;
-import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
-import org.springframework.cache.interceptor.KeyGenerator;
-import org.springframework.cache.interceptor.SimpleKeyGenerator;
-import org.springframework.cache.support.SimpleCacheManager;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.cache.RedisCacheConfiguration;
-import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Arrays;
 
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
+;
 
 /**
  * redis配置

+ 43 - 0
fs-user-app/src/main/java/com/fs/app/controller/CommonController.java

@@ -26,6 +26,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.file.OssException;
+import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.file.FileUploadUtils;
 import com.fs.common.utils.http.HttpUtils;
 import com.fs.common.utils.sign.Md5Utils;
@@ -44,10 +45,12 @@ import com.fs.framework.config.ServerConfig;
 import com.fs.his.config.FsSmsConfig;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.*;
+import com.fs.his.dto.InquiryConfigDTO;
 import com.fs.his.param.FsInquiryOrderFinishParam;
 import com.fs.his.service.*;
 
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.vo.InquirySubTypeVo;
 import com.fs.hisStore.mapper.FsWechatTemplateScrmMapper;
 import com.fs.hisStore.service.IFsWechatTemplateScrmService;
 import com.fs.im.dto.*;
@@ -352,6 +355,46 @@ public class CommonController {
 
 	}
 
+	@GetMapping(value = "/getInquiryConfig")
+	@ApiOperation("获取问诊配置")
+	public R getInquiryConfig()
+	{
+		String json=configService.selectConfigByKey("his.inquiryConfig");
+		InquiryConfigDTO configDTO= JSONUtil.toBean(json, InquiryConfigDTO.class);
+		Map<String, Object> result = new HashMap<>();
+		result.put("prices",configDTO.getPrices());
+		result.put("isAutoPrescribeAudit",configDTO.getIsAutoPrescribeAudit());
+		result.put("unPayCancelTime",configDTO.getUnPayCancelTime());
+		result.put("unReceiveCancelTime",configDTO.getUnReceiveCancelTime());
+		result.put("companyPrice",configDTO.getCompanyPrice());
+		result.put("companyPrescribePrice",configDTO.getCompanyPrescribePrice());
+		result.put("followMsg",configDTO.getFollowMsg());
+		result.put("prescribeMsg",configDTO.getPrescribeMsg());
+		result.put("inquirySubType",configDTO.getInquirySubType());
+		String inquirySubType = configDTO.getInquirySubType();
+		if (StringUtils.isNotBlank(inquirySubType)){
+			//查询现已下单数量
+			List<InquirySubTypeVo> list = JSON.parseArray(inquirySubType, InquirySubTypeVo.class);
+			if (list!=null&& !list.isEmpty()){
+				for (InquirySubTypeVo inquiryConfigDTO : list) {
+					Long num = inquiryConfigDTO.getNum();
+					Integer value = inquiryConfigDTO.getValue();
+					inquiryConfigDTO.setIsCanBuy(1);
+					if (num!=null && num > 0 && value != null){
+						Long actualNum =
+								inquiryOrderService.countByInquirySubTypeAndCreateTime(value, DateUtils.getDate());
+						if (actualNum!=null && actualNum > 0 && actualNum >= num){
+							inquiryConfigDTO.setIsCanBuy(0);
+						}
+					}
+				}
+				result.put("inquirySubType",list);
+			}
+		}
+		return  R.ok().put("data",JSON.toJSONString(result));
+
+	}
+
 
 	@PostMapping("uploadOSS")
 	public R uploadOSS(@RequestParam("file") MultipartFile file) throws Exception

+ 6 - 5
fs-user-app/src/main/java/com/fs/app/controller/app/AppController.java

@@ -14,10 +14,7 @@ import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @description: APP 相关接口
@@ -57,8 +54,12 @@ public class AppController extends AppBaseController {
     @RepeatSubmit
     @ApiOperation("支付宝支付")
     @PostMapping("/aliPayment")
-    public R aliPayment(@Validated @RequestBody FsIntegralOrderDoPayParam param) {
+    public R aliPayment(@Validated @RequestBody FsIntegralOrderDoPayParam param,
+                        @RequestHeader(value = "AppId", required = false) String AppId) {
         param.setUserId(Long.parseLong(getUserId()));
+        if (AppId != null && !AppId.isEmpty()) {
+            param.setAppId(AppId);
+        }
         return fsStoreOrderScrmService.payment(param, PaymentMethodEnum.ALIPAY);
     }
 

+ 85 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/PublicCourseWatchStatisticsController.java

@@ -0,0 +1,85 @@
+package com.fs.app.controller.course;
+
+import com.fs.app.annotation.Login;
+import com.fs.app.controller.AppBaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsCourseMarketingEvent;
+import com.fs.course.domain.FsCourseShareLog;
+import com.fs.course.service.IFsCourseMarketingEventService;
+import com.fs.course.service.IFsCourseShareLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 公域看课数据统计(独立 Controller)
+ */
+@Api("公域看课数据统计")
+@RestController
+@RequestMapping("/course/publicCourseWatchStat")
+public class PublicCourseWatchStatisticsController extends AppBaseController {
+
+    @Autowired
+    private IFsCourseMarketingEventService fsCourseMarketingEventService;
+
+    @Autowired
+    private IFsCourseShareLogService fsCourseShareLogService;
+
+    /**
+     * 课程曝光/点击埋点上报
+     */
+//    @Login
+    @ApiOperation("课程曝光/点击埋点上报")
+    @PostMapping("/marketingEvent")
+    public R marketingEvent(@RequestBody FsCourseMarketingEvent param) {
+        String userId = "112";
+        if (StringUtils.isEmpty(userId)) {
+            return R.error("用户未登录");
+        }
+        if (param.getCourseId() == null) {
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId() == null) {
+            return R.error("小节id不能为空");
+        }
+        if (param.getEventType() == null) {
+            return R.error("事件类型不能为空");
+        }
+        if (param.getExposurePosition() == null) {
+            return R.error("曝光位置不能为空");
+        }
+        param.setUserId(Long.parseLong(userId));
+        int result = fsCourseMarketingEventService.insertFsCourseMarketingEvent(param);
+        return result > 0 ? R.ok() : R.error();
+    }
+
+    /**
+     * 课程分享记录上报
+     */
+//    @Login
+    @ApiOperation("课程分享记录上报")
+    @PostMapping("/shareLog")
+    public R shareLog(@RequestBody FsCourseShareLog param) {
+        String userId = "112";
+        if (StringUtils.isEmpty(userId)) {
+            return R.error("用户未登录");
+        }
+        if (param.getCourseId() == null) {
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId() == null) {
+            return R.error("小节id不能为空");
+        }
+        if (param.getShareType() == null) {
+            return R.error("分享类型不能为空");
+        }
+        if (param.getExposurePosition() == null) {
+            return R.error("曝光位置不能为空");
+        }
+        param.setUserId(Long.parseLong(userId));
+        int result = fsCourseShareLogService.insertFsCourseShareLog(param);
+        return result > 0 ? R.ok() : R.error();
+    }
+}

+ 20 - 0
fs-user-app/src/main/java/com/fs/app/controller/store/CourseCommentScrmController.java

@@ -5,6 +5,7 @@ import com.fs.app.controller.AppBaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.domain.FsUserCourseComment;
 import com.fs.course.domain.FsUserCourseCommentLike;
 import com.fs.course.mapper.FsUserCourseMapper;
@@ -15,6 +16,7 @@ import com.fs.course.service.IFsUserCourseCommentLikeService;
 import com.fs.course.service.IFsUserCourseCommentService;
 import com.fs.course.vo.FsUserCourseCommentListUVO;
 import com.fs.course.vo.FsUserCourseCommentReplyListUVO;
+import com.fs.sensitive.ProductionWordFilter;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -45,6 +47,13 @@ public class CourseCommentScrmController extends AppBaseController
 
     @Autowired
     private FsUserCourseMapper fsUserCourseMapper;
+
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
+    @Autowired
+    private ProductionWordFilter productionWordFilter;
+
     /**
      * 查询课堂评论列表
      */
@@ -87,6 +96,17 @@ public class CourseCommentScrmController extends AppBaseController
     @PostMapping("/addComment")
     public R add(@RequestBody FsUserCourseCommentAddParam param)
     {
+        String content = param.getContent();
+        if ("北京卓美".equals(cloudHostProper.getCompanyName())) {
+            if (content == null) {
+                content = "";
+            }
+            String filtered = productionWordFilter.filter(content).getFilteredText();
+            if (StringUtils.isEmpty(filtered)) {
+                return R.error("评论内容无效,请重新输入");
+            }
+            param.setContent(filtered);
+        }
         FsUserCourseComment fsUserCourseComment = new FsUserCourseComment();
         fsUserCourseComment.setUserId(Long.parseLong(getUserId()));
         fsUserCourseComment.setCourseId(param.getCourseId());

+ 1 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/PayScrmController.java

@@ -82,7 +82,7 @@ public class PayScrmController {
             switch (order[0]) {
                 case "store":
                 case "live":
-                    if (CloudHostUtils.hasCloudHostName("济世百康") ) {
+                    if (CloudHostUtils.hasCloudHostName("济世百康","蒙牛") ) {
                         liveOrderService.payConfirm(1,null,order[1], o.getHf_seq_id(),o.getOut_trans_id(),o.getParty_order_id());
                         break;
                     }

+ 2 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

@@ -336,7 +336,8 @@ public class ProductScrmController extends AppBaseController {
     @GetMapping("/getCartCount")
     public R getCartCount(FsStoreCartCountParam param){
         param.setUserId(Long.parseLong(getUserId()));
-        Integer count=cartService.selectFsStoreCartCount(param);
+//        Integer count=cartService.selectFsStoreCartCount(param);
+        Integer count=cartService.selectFsStoreCartCountByUserId(Long.parseLong(getUserId()));
         return R.ok().put("data", count);
     }
 

+ 0 - 6
fs-user-app/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -5,23 +5,17 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
-import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.cache.RedisCacheConfiguration;
-import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 import java.math.BigDecimal;
-import java.time.Duration;
 
 /**
  * redis配置