浏览代码

点播订单和看课倒计时

yuhongqi 3 天之前
父节点
当前提交
b9a85dfaf1
共有 23 个文件被更改,包括 389 次插入62 次删除
  1. 4 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  2. 21 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  3. 2 2
      fs-admin/src/main/resources/application.yml
  4. 4 0
      fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java
  5. 60 5
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  6. 41 0
      fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  7. 1 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  8. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  9. 27 0
      fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java
  10. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  11. 5 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  12. 9 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  13. 8 2
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java
  14. 10 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  15. 35 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java
  16. 3 1
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java
  17. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListQueryVO.java
  18. 7 7
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java
  19. 41 20
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  20. 8 16
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  21. 4 4
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml
  22. 6 3
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  23. 86 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

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

@@ -309,6 +309,8 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                 for (FsStoreOrderItemExportZMVO vo : zmvoList) {
                     if("2".equals(vo.getOrderType())){
                         vo.setOrderTypeStr("直播订单" );
+                    }else if ("3".equals(vo.getOrderType())){
+                        vo.setOrderTypeStr("点播订单" );
                     }else{
                         vo.setOrderTypeStr("商城订单" );
                     }
@@ -418,6 +420,8 @@ public class FsStoreHealthOrderScrmController extends BaseController {
                     for (FsStoreOrderItemExportZMVO vo : zmvoList) {
                         if ("2".equals(vo.getOrderType())) {
                             vo.setOrderTypeStr("直播订单");
+                        }else if ("3".equals(vo.getOrderType())){
+                            vo.setOrderTypeStr("点播订单" );
                         }else {
                             vo.setOrderTypeStr("商城订单");
                         }

+ 21 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -19,8 +19,12 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
 import com.fs.company.param.CompanyStoreOrderMoneyLogsListParam;
 import com.fs.company.service.ICompanyMoneyLogsService;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.CompanyStoreOrderMoneyLogsVO;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.erp.domain.ErpDeliverys;
@@ -150,6 +154,11 @@ public class FsStoreOrderScrmController extends BaseController {
     @Value("${cloud_host.company_name}")
     private String signProjectName;
 
+    @Autowired
+    private ICompanyUserService companyUserService;
+    @Autowired
+    private ICompanyService companyService;
+
     private IErpOrderService getErpService(){
         //判断是否开启erp
         IErpOrderService erpOrderService = null;
@@ -652,6 +661,18 @@ public class FsStoreOrderScrmController extends BaseController {
         if (user != null) {
             user.setPhone(ParseUtils.parsePhone(user.getPhone()));
         }
+
+        if (order.getCompanyUserId() != null) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
+            Company company = companyService.selectCompanyById(companyUser.getCompanyId());
+            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyName(company.getCompanyName());
+        } else if (order.getCompanyId() != null) {
+            Company company = companyService.selectCompanyById(order.getCompanyId());
+            order.setCompanyName(company.getCompanyName());
+        }
+
+
         FsStoreOrderItemScrm itemMap = new FsStoreOrderItemScrm();
         itemMap.setOrderId(order.getId());
         List<FsStoreOrderItemScrm> items = orderItemService.selectFsStoreOrderItemList(itemMap);

+ 2 - 2
fs-admin/src/main/resources/application.yml

@@ -4,11 +4,11 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: druid-ylrz
+    active: druid-bjzm-test
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz-test
 #    active: druid-sft
 #    active: druid-fby
-    active: dev
+#    active: dev
 

+ 4 - 0
fs-company/src/main/java/com/fs/company/controller/live/LiveDataController.java

@@ -1,6 +1,7 @@
 package com.fs.company.controller.live;
 
 import com.fs.common.annotation.Log;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
@@ -12,9 +13,11 @@ import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
 import com.fs.live.domain.LiveData;
+import com.fs.live.param.LiveDataCompanyParam;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.ColumnsConfigVo;
+import com.fs.live.vo.LiveDataCompanyVO;
 import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
@@ -24,6 +27,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 

+ 60 - 5
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -83,14 +83,18 @@ public class FsStoreOrderController extends BaseController
     private IFsStoreOrderItemScrmService orderItemScrmService;
 
     /**
-     * 查询直播订单列表(仅 fs_store_order_scrm 中 order_type=2)
-     * 分公司负责人(userType=00)可查公司下所有直播订单,否则仅能查自己的直播订单
+     * 查询直播/点播订单列表(fs_store_order_scrm 中 order_type=2 直播订单,order_type=3 点播订单)
+     * 如果前端传了 orderType,则按指定类型查询;如果没传(null),则查询所有直播和点播订单(orderType IN (2,3))
+     * 分公司负责人(userType=00)可查公司下所有订单,否则仅能查自己的订单
      */
     @PostMapping("/healthLiveList")
     public FsStoreOrderListAndStatisticsVo healthLiveList(@RequestBody com.fs.hisStore.param.FsStoreOrderParam param) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         param.setCompanyId(loginUser.getCompany().getCompanyId());
-        param.setOrderType(2);
+        // 如果前端传了 orderType,使用前端传的值;如果没传(null),设置 orderType = -1(特殊值,SQL 中会转换为查询 orderType IN (2,3))
+        if (param.getOrderType() == null) {
+            param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
+        }
         if (!"00".equals(loginUser.getUser().getUserType())) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
@@ -147,11 +151,14 @@ public class FsStoreOrderController extends BaseController
         return vo;
     }
 
-    /** 直播订单导出:筛选条件与 healthLiveList 一致(orderType=2 + 公司/负责人权限) */
+    /** 直播/点播订单导出:筛选条件与 healthLiveList 一致(支持按 orderType 筛选,不传则查询所有 + 公司/负责人权限) */
     private void applyHealthLiveFilter(com.fs.hisStore.param.FsStoreOrderParam param) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         param.setCompanyId(loginUser.getCompany().getCompanyId());
-        param.setOrderType(2);
+        // 如果前端传了 orderType,使用前端传的值;如果没传(null),设置 orderType = -1(特殊值,SQL 中会转换为查询 orderType IN (2,3))
+        if (param.getOrderType() == null) {
+            param.setOrderType(-1); // 特殊值,表示查询所有直播和点播订单
+        }
         if (!"00".equals(loginUser.getUser().getUserType())) {
             param.setCompanyUserId(loginUser.getUser().getUserId());
         } else {
@@ -212,6 +219,17 @@ public class FsStoreOrderController extends BaseController
                 if (vo.getUserAddress() != null) {
                     vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         String filter = param.getFilter();
@@ -243,6 +261,21 @@ public class FsStoreOrderController extends BaseController
             return AjaxResult.error("请筛选数据导出");
         }
         List<FsStoreOrderErpExportVO> list = fsStoreOrderScrmService.selectFsStoreOrderListVOByExport(param);
+        if (list != null) {
+            for (FsStoreOrderErpExportVO vo : list) {
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
+            }
+        }
         String filter = param.getFilter();
         ArrayList<String> filterList = new ArrayList<>();
         if (StringUtils.isNotBlank(filter)) {
@@ -296,6 +329,17 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);
@@ -335,6 +379,17 @@ public class FsStoreOrderController extends BaseController
                     vo.setCateName("");
                     vo.setBankTransactionId("");
                 }
+                // 设置订单类型中文显示
+                if (vo.getOrderType() != null) {
+                    String orderTypeStr = vo.getOrderType().toString();
+                    if ("2".equals(orderTypeStr)) {
+                        vo.setOrderType("直播订单");
+                    } else if ("3".equals(orderTypeStr)) {
+                        vo.setOrderType("点播订单");
+                    } else {
+                        vo.setOrderType("商城订单");
+                    }
+                }
             }
         }
         ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<>(FsStoreOrderItemExportVO.class);

+ 41 - 0
fs-company/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -18,9 +18,18 @@ import com.fs.common.utils.ParseUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.config.cloud.CloudHostProper;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCoursePeriod;
+import com.fs.course.domain.FsUserCourseVideo;
+import com.fs.course.param.FsCourseWatchLogParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.framework.security.LoginUser;
@@ -88,6 +97,14 @@ public class FsStoreOrderScrmController extends BaseController
     private ICompanyUserService companyUserService;
     @Autowired
     private CloudHostProper cloudHostProper;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
 
     /**
      * 查询订单列表
@@ -237,6 +254,30 @@ public class FsStoreOrderScrmController extends BaseController
         FsStoreOrderScrm order=fsStoreOrderService.selectFsStoreOrderById(id);
         order.setUserPhone(ParseUtils.parsePhone(order.getUserPhone()));
         order.setUserAddress(ParseUtils.parseAddress(order.getUserAddress()));
+
+        if (order.getCompanyUserId() != null) {
+            CompanyUser companyUser = companyUserService.selectCompanyUserByUserId(order.getCompanyUserId());
+            Company company = companyService.selectCompanyById(companyUser.getCompanyId());
+            order.setCompanyUserName(companyUser.getUserName());
+            order.setCompanyName(company.getCompanyName());
+        } else if (order.getCompanyId() != null) {
+            Company company = companyService.selectCompanyById(order.getCompanyId());
+            order.setCompanyName(company.getCompanyName());
+        }
+        if (order.getOrderType() != null && order.getOrderType() == 3) {
+            FsCourseWatchLogParam param = new FsCourseWatchLogParam();
+            param.setVideoId(Long.valueOf(order.getVideoId()));
+            FsCourseWatchLog log = fsCourseWatchLogService.selectFsCourseWatchLogWithUCCV(order.getUserId(), order.getCompanyUserId(), order.getCourseId(), order.getVideoId());
+            if (log != null) {
+                FsUserCoursePeriod fsUserCoursePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(log.getPeriodId());
+                order.setPeriodName(fsUserCoursePeriod.getPeriodName());
+            }
+            if (order.getVideoId() != null) {
+                FsUserCourseVideo fsUserCourseVideo = fsUserCourseVideoService.selectFsUserCourseVideoByVideoId(Long.valueOf(order.getVideoId()));
+                order.setVideoName(fsUserCourseVideo.getTitle());
+            }
+        }
+
         FsUser user=userService.selectFsUserById(order.getUserId());
         user.setPhone(ParseUtils.parsePhone(user.getPhone()));
         FsStoreOrderItemScrm itemMap=new FsStoreOrderItemScrm();

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

@@ -47,6 +47,7 @@ public class CourseConfig implements Serializable {
     private Integer isNegative;//是否为负数 0、不允许,1、允许
 
     private Integer isOpen;
+    private Boolean completionCountdown;
 
     /**
      * 侧边栏是否仅展示当天课程

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

@@ -755,4 +755,6 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
 
     List<FsSopMyCourseH5LinkVO> getSopCourseH5StudyListByQwExId(@Param("qwExternalId") Long qwExternalId);
 
+    @Select("select * from fs_course_watch_log where user_id=#{userId} and company_user_id=#{companyUserId} and course_id=#{courseId} and video_id=#{videoId} limit 1")
+    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(@Param("userId") Long userId,@Param("companyUserId") Long companyUserId,@Param("courseId") Integer courseId,@Param("videoId") Integer videoId);
 }

+ 27 - 0
fs-service/src/main/java/com/fs/course/param/newfs/FsUserCourseVideoRemainTimeParam.java

@@ -0,0 +1,27 @@
+package com.fs.course.param.newfs;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class FsUserCourseVideoRemainTimeParam implements Serializable {
+    @NotNull(message = "视频id不能为空")
+    @ApiModelProperty(value = "视频id")
+    private Integer videoId;
+
+    @NotNull(message = "用户id不能为空")
+    @ApiModelProperty(value = "用户id")
+    private Long fsUserId;
+
+    @NotNull(message = "课程id不能为空")
+    @ApiModelProperty(value = "课程id")
+    private Integer courseId;
+
+    @NotNull(message = "销售id不能为空")
+    @ApiModelProperty(value = "销售id")
+    private Long companyUserId;
+
+}

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

@@ -169,4 +169,6 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     R decryptLink(String url);
 
     List<FsCourseWatchLog> selectFsUserWatchLogByExtId(QwExternalContact qwExternalContact);
+
+    FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId);
 }

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

@@ -1724,4 +1724,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectFsUserWatchLogByExtId(qwExternalContact);
     }
 
+    @Override
+    public FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId) {
+        return fsCourseWatchLogMapper.selectFsCourseWatchLogWithUCCV(userId, companyUserId, courseId, videoId);
+    }
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -358,6 +358,15 @@ public class FsStoreOrderScrm extends BaseEntity
     @TableField(exist = false)
     private Boolean isLive = false;
 
+    @TableField(exist = false)
+    private String companyUserName;
+    @TableField(exist = false)
+    private String companyName;
+    @TableField(exist = false)
+    private String periodName;
+    @TableField(exist = false)
+    private String videoName;
+
      // 是否审核,1-是,0-否
     private Integer isAudit;
 

+ 8 - 2
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderItemScrmMapper.java

@@ -140,9 +140,12 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +
@@ -251,9 +254,12 @@ public interface FsStoreOrderItemScrmMapper
             "<if test = 'maps.companyUserNickName != null and  maps.companyUserNickName !=  \"\" '> " +
             "and cu.nick_name like concat('%', #{maps.companyUserNickName}, '%') " +
             "</if>" +
-            "<if test = 'maps.orderType != null    '> " +
+            "<if test = 'maps.orderType != null and maps.orderType != -1    '> " +
             "and o.order_type =#{maps.orderType} " +
             "</if>" +
+            "<if test = 'maps.orderType != null and maps.orderType == -1    '> " +
+            "and o.order_type in (2, 3) " +
+            "</if>" +
             "<if test = 'maps.createTimeList != null    '> " +
             " AND date_format(o.create_time,'%y%m%d') &gt;= date_format(#{maps.createTimeList[0]},'%y%m%d') " +
             " AND date_format(o.create_time,'%y%m%d') &lt;= date_format(#{maps.createTimeList[1]},'%y%m%d') " +

+ 10 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -681,6 +681,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Transactional
     public R addOrEdit(FsStoreProductAddEditParam param) {
         ProductAttrCountDto countDto=computedProductCount(param.getValues());
+        Date nowDate = DateUtils.getNowDate();
         FsStoreProductScrm product=new FsStoreProductScrm();
         BeanUtils.copyProperties(param,product);
         product.setPrice(countDto.getMinPrice());
@@ -744,6 +745,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 //默认已审核
                 product.setIsAudit("1");
             }
+            product.setUpdateTime(nowDate);
             fsStoreProductMapper.updateFsStoreProduct(product);
             // 清除缓存
             clearProductDetailCache(product.getProductId());
@@ -761,6 +763,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
             }
         }
         else{
+            product.setCreateTime(nowDate);
+            product.setUpdateTime(nowDate);
             fsStoreProductMapper.insertFsStoreProduct(product);
         }
         storeAuditLogUtil.addOperLog(product.getProductId());
@@ -1248,6 +1252,7 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         int failureNum = 0;
         StringBuilder successMsg = new StringBuilder();
         StringBuilder failureMsg = new StringBuilder();
+        Date nowDate = DateUtils.getNowDate();
         for (FsStoreProductExportVO productVO : list){
             try
             {
@@ -1255,6 +1260,8 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
                 if (product.getBarCode()==null || product.getBarCode()==""){
                     throw new CustomException("商品编号为空");
                 }
+                product.setCreateTime(nowDate);
+                product.setUpdateTime(nowDate);
                 this.insertFsStoreProduct(product);
                 ProductArrtDTO formatDetailDto = ProductArrtDTO.builder()
                         .value("规格")
@@ -1647,11 +1654,13 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         // 查询原商品的规格属性与属性值
         List<FsStoreProductAttrScrm> attrList = fsStoreProductAttrMapper.selectFsStoreProductAttrByProductId(productId);
         List<FsStoreProductAttrValueScrm> attrValueList = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(productId);
-
+        Date nowDate = DateUtils.getNowDate();
         FsStoreProductScrm copy = new FsStoreProductScrm();
         BeanUtils.copyProperties(fsStoreProductScrm, copy);
         copy.setProductId(null);
         copy.setIsAudit("0");
+        copy.setCreateTime(nowDate);
+        copy.setUpdateTime(nowDate);
         fsStoreProductMapper.insertFsStoreProduct(copy);
 
         // 复制规格属性

+ 35 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreUserEndCategoryScrmServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.hisStore.service.impl;
 
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.domain.FsStoreUserEndCategoryScrm;
@@ -15,11 +16,14 @@ import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /**
@@ -40,6 +44,14 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
     @Autowired
     private FsStoreProductTagRelationScrmMapper productTagRelationMapper;
 
+    @Autowired
+    private RedisCache redisCache;
+
+    /** Redis key 用于存储固定的好评率随机值 */
+    private static final String POSITIVE_RATING_REDIS_KEY = "product:positiveRating:fixed:";
+    private static final double MIN_RATING = 95.0;
+    private static final double MAX_RATING = 99.9;
+
     @Override
     public List<FsStoreUserEndCategoryProductVO> listProductsByCategoryId(Long categoryId, String keyword) {
         if (categoryId == null) return new ArrayList<>();
@@ -121,7 +133,7 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
             vo.setOtPrice(p.getOtPrice());
             vo.setSales(p.getSales());
             vo.setTagList(tagMap.getOrDefault(pid, new ArrayList<>()));
-            vo.setPositiveRating();
+            vo.setPositiveRating(getFixedPositiveRating(p.getProductId()));
             result.add(vo);
         }
         out.put("list", result);
@@ -179,4 +191,26 @@ public class FsStoreUserEndCategoryScrmServiceImpl implements IFsStoreUserEndCat
         productUserEndCategoryMapper.deleteByCategoryIds(ids);
         return mapper.deleteByIds(ids);
     }
+
+    /**
+     * 获取固定的好评率随机值
+     * 先从 Redis 获取,如果不存在则生成随机值并保存到 Redis(永久保存)
+     * @return 固定的好评率值
+     */
+    private BigDecimal getFixedPositiveRating(Long productId) {
+        // 先从 Redis 获取
+        BigDecimal cachedRating = redisCache.getCacheObject(POSITIVE_RATING_REDIS_KEY + productId);
+        if (cachedRating != null) {
+            return cachedRating;
+        }
+        
+        // Redis 不存在,生成随机值
+        double rating = ThreadLocalRandom.current().nextDouble(MIN_RATING, MAX_RATING);
+        BigDecimal fixedRating = new BigDecimal(rating).setScale(1, RoundingMode.HALF_UP);
+        
+        // 保存到 Redis(永久保存,不设置过期时间)
+        redisCache.setCacheObject(POSITIVE_RATING_REDIS_KEY + productId, fixedRating);
+        
+        return fixedRating;
+    }
 }

+ 3 - 1
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportVO.java

@@ -14,6 +14,9 @@ public class FsStoreOrderItemExportVO implements Serializable
 
     private Long itemId;
 
+    @Excel(name = "订单类型")
+    private String orderType; // 订单类型 2.直播订单 3.点播订单
+
     /** 订单号 */
     @Excel(name = "订单号")
     private String orderCode;
@@ -121,7 +124,6 @@ public class FsStoreOrderItemExportVO implements Serializable
     private String isAudit;
 
 
-    private String orderType; // 订单类型 2.直播
     private BigDecimal payPrice;// 应付金额
     private BigDecimal deductionPrice;// 应付金额
     private BigDecimal payDelivery;// 应付邮费

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

@@ -21,6 +21,9 @@ public class FsStoreProductListQueryVO implements Serializable
     /** 商品图片 */
     private String image;
 
+    /** 轮播图 */
+    private String sliderImage;
+
     /** 商品名称 */
     private String productName;
 

+ 7 - 7
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreUserEndCategoryProductVO.java

@@ -4,9 +4,7 @@ import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.List;
-import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * 用户端分类下的商品项:商品ID、名称、售价、原价、销量、产品标签列表
@@ -15,8 +13,6 @@ import java.util.concurrent.ThreadLocalRandom;
 public class FsStoreUserEndCategoryProductVO implements Serializable {
 
     private static final long serialVersionUID = 1L;
-    private static final double min = 95.0;
-    private static final double max = 99.9;
 
     private Long productId;
     private String productName;
@@ -32,8 +28,12 @@ public class FsStoreUserEndCategoryProductVO implements Serializable {
     private Long sales;
     /** 产品标签名称列表 */
     private List<String> tagList;
-    public void setPositiveRating() {
-        double rating = ThreadLocalRandom.current().nextDouble(min, max);
-        this.positiveRating = new BigDecimal(rating).setScale(1, RoundingMode.HALF_UP);
+    
+    /**
+     * 设置好评率(从外部传入固定值)
+     * @param rating 好评率值
+     */
+    public void setPositiveRating(BigDecimal rating) {
+        this.positiveRating = rating;
     }
 }

+ 41 - 20
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -93,7 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectFsStoreOrderVo">
-        select id, order_code,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type from fs_store_order_scrm
+        select id, order_code,service_fee, extend_order_id,pay_order_id,bank_order_id, user_id,order_visit, real_name, user_phone, user_address, cart_id, freight_price, total_num, total_price, total_postage, pay_price, pay_postage,pay_delivery,pay_money, deduction_price, coupon_id, coupon_price, paid, pay_time, pay_type, create_time, update_time, status, refund_status, refund_reason_wap_img, refund_reason_wap_explain, refund_reason_time, refund_reason_wap, refund_reason, refund_price, delivery_sn, delivery_name, delivery_type, delivery_id, gain_integral, use_integral, pay_integral, back_integral, mark, is_del, remark, cost, verify_code, store_id, shipping_type, is_channel, is_remind, is_sys_del,is_prescribe,prescribe_id ,company_id,company_user_id,is_package,package_json,item_json,order_type,package_id,finish_time,delivery_status,delivery_pay_status,delivery_time,delivery_pay_time,delivery_pay_money,tui_money,tui_money_status,delivery_import_time,tui_user_id,tui_user_money_status,order_create_type,store_house_code,dept_id,is_edit_money,customer_id,is_pay_remain,delivery_send_time,certificates,schedule_id,backend_edit_product_type,video_id,course_id from fs_store_order_scrm
     </sql>
 
     <select id="selectFsStoreOrderList" parameterType="FsStoreOrderScrm" resultMap="FsStoreOrderResult">
@@ -1154,10 +1154,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1174,9 +1174,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1330,10 +1333,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1350,9 +1353,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1483,10 +1489,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1503,9 +1509,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1640,10 +1649,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1660,9 +1669,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1793,10 +1805,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -1813,9 +1825,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -1989,10 +2004,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
             <if test="maps.isHealth != null and maps.isHealth !=  ''   ">
                 and (o.company_id is null
-                or o.order_type = 2)
+                or o.order_type = 2 or o.order_type = 3)
             </if>
             <if test="maps.notHealth != null  ">
-                and o.company_id is not null
+                and o.company_id is not null and o.order_type = 0
             </if>
             <if test="maps.companyUserId != null  ">
                 and o.company_user_id =#{maps.companyUserId}
@@ -2009,9 +2024,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="maps.productName != null and  maps.productName !=  '' ">
                 and fsp.product_name like concat('%', #{maps.productName}, '%')
             </if>
-            <if test="maps.orderType != null    ">
+            <if test="maps.orderType != null and maps.orderType != -1">
                 and o.order_type =#{maps.orderType}
             </if>
+            <if test="maps.orderType != null and maps.orderType == -1">
+                and o.order_type in (2, 3)
+            </if>
             <if test="maps.payType != null    ">
                 and o.pay_type =#{maps.payType}
             </if>
@@ -2183,9 +2201,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="maps.companyUserNickName != null and maps.companyUserNickName != ''">
             AND cu.nick_name LIKE CONCAT('%', #{maps.companyUserNickName}, '%')
         </if>
-        <if test="maps.orderType != null">
+        <if test="maps.orderType != null and maps.orderType != -1">
             AND o.order_type = #{maps.orderType}
         </if>
+        <if test="maps.orderType != null and maps.orderType == -1">
+            AND o.order_type in (2, 3)
+        </if>
         <if test="maps.payType != null">
             AND o.pay_type = #{maps.payType}
         </if>

+ 8 - 16
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -514,13 +514,11 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1"'>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and p.is_new=1 and p.is_display=1 order by p.sort desc limit #{count}
+        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
     </select>
     <select id="selectFsStoreProductNewQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -528,16 +526,14 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1"'>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != ""'>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and p.is_new=1 and p.is_display=1 order by p.sort desc
+        and p.is_new=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
     </select>
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -545,13 +541,11 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1" '>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by p.sort desc limit #{count}
+        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc limit #{count}
     </select>
     <select id="selectFsStoreProductHotQueryPage" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
@@ -559,16 +553,14 @@
             inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
-        <if test='config.isAudit == "1" '>
-            and p.is_audit = '1'
-        </if>
+            and p.is_audit = 1
         <if test='appId != null and appId != ""'>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
         </if>
         <if test='keyword != null and keyword != ""'>
             and p.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        and  p.is_hot=1 and p.is_display=1 order by p.sort desc
+        and  p.is_hot=1 and p.is_display=1 order by COALESCE(p.sort, 999999) asc, p.create_time desc
     </select>
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p

+ 4 - 4
fs-service/src/main/resources/mapper/hisStore/FsStoreProductUserEndCategoryMapper.xml

@@ -22,19 +22,19 @@
 
     <select id="selectDistinctProductIdsByCategoryId" resultType="java.lang.Long">
         select distinct a.product_id from fs_store_product_user_end_category a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1
+        where a.user_end_category_id = #{categoryId} and c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
         <if test="keyword != null and keyword != ''">
             and c.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        order by c.sort desc, c.create_time desc, a.product_id
+        order by c.sort asc, c.create_time desc, a.product_id
     </select>
 
     <select id="selectDistinctProductIds" resultType="java.lang.Long">
         select distinct a.product_id from fs_store_product_user_end_category  a left join fs_store_product_scrm c on a.product_id = c.product_id
-        where c.is_del = 0 and c.is_show = 1
+        where c.is_del = 0 and c.is_show = 1 and c.is_display = 1 and c.is_audit = 1
         <if test="keyword != null and keyword != ''">
             and c.product_name like CONCAT('%', #{keyword}, '%')
         </if>
-        order by c.sort desc, c.create_time desc, a.product_id
+        order by c.sort asc, c.create_time desc, a.product_id
     </select>
 </mapper>

+ 6 - 3
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -629,7 +629,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             lufe.company_id AS companyId,
             COALESCE(c.company_name, '总台') AS companyName,
             COALESCE(COUNT(DISTINCT CASE
-                WHEN lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                WHEN lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS totalCompleteCount
         FROM live_watch_user lwu
@@ -684,7 +685,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(COUNT(DISTINCT CASE
                 WHEN lwu.live_flag = 1
                      AND lwu.replay_flag = 0
-                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS liveCompleteCount
         FROM live_watch_user lwu
@@ -739,7 +741,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             COALESCE(COUNT(DISTINCT CASE
                 WHEN lwu.live_flag = 0
                      AND lwu.replay_flag = 1
-                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)
+                     AND lwu.online_seconds >= 1200
+<!--                     AND lwu.online_seconds >= COALESCE(vd.total_duration, 0)-->
                      AND COALESCE(vd.total_duration, 0) > 0
                 THEN lwu.user_id END), 0) AS replayCompleteCount
         FROM live_watch_user lwu

+ 86 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -3,6 +3,7 @@ package com.fs.app.controller.course;
 
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.controller.AppBaseController;
@@ -16,10 +17,17 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.dto.BatchSendCourseDTO;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.*;
 import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
+import com.fs.course.param.newfs.FsUserCourseVideoRemainTimeParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
@@ -28,6 +36,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
+import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
@@ -64,6 +73,14 @@ public class CourseFsUserController extends AppBaseController {
 
     @Autowired
     private RedisTemplate redisTemplate;
+    @Autowired
+    private ISysConfigService sysConfigService;
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    private RedisCache redisCache;
 
 
 
@@ -103,6 +120,75 @@ public class CourseFsUserController extends AppBaseController {
         return R.ok().put("data",course);
     }
 
+    @ApiOperation("h5课程完课倒计时")
+    @PostMapping("/getRemainTime")
+    public R getRemainTime(@RequestBody FsUserCourseVideoRemainTimeParam param)
+    {
+        String json = sysConfigService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        Integer remainTime = 0;
+        if (config.getCompletionCountdown() != null && config.getCompletionCountdown()) {
+            FsCourseWatchLog fsCourseWatchLog = fsCourseWatchLogMapper.selectFsCourseWatchLogWithUCCV(param.getFsUserId(), param.getCompanyUserId(), param.getCourseId(), param.getVideoId());
+            if (fsCourseWatchLog == null) {
+                return R.error("未查询到用户的看课记录!");
+            }
+            
+            // 如果已经完课,剩余时间为0
+            if (fsCourseWatchLog != null && fsCourseWatchLog.getLogType() != null && fsCourseWatchLog.getLogType() == 2) {
+                remainTime = 0;
+            } else {
+                // 获取已观看时长(优先从Redis获取,因为可能还未同步到数据库)
+                Long watchedDuration = 0L;
+                String redisKey = "h5wxuser:watch:duration:" + param.getFsUserId() + ":" + param.getVideoId() + ":" + param.getCompanyUserId();
+                String durationStr = redisCache.getCacheObject(redisKey);
+                if (durationStr != null) {
+                    watchedDuration = Long.valueOf(durationStr);
+                } else if (fsCourseWatchLog != null && fsCourseWatchLog.getDuration() != null) {
+                    watchedDuration = fsCourseWatchLog.getDuration();
+                }
+                
+                // 获取视频总时长(参照scheduleUpdateDurationToDatabase中的getFsUserVideoDuration方法)
+                Long videoDuration = getFsUserVideoDuration(param.getVideoId().longValue());
+                
+                if (videoDuration != null && videoDuration > 0 && config.getAnswerRate() != null) {
+                    // 参照scheduleUpdateDurationToDatabase中的完课逻辑:percentage >= config.getAnswerRate()
+                    // 计算需要观看的时长(秒)
+                    long requiredDuration = videoDuration * config.getAnswerRate() / 100;
+                    
+                    // 计算剩余时间(秒)
+                    long remainTimeSeconds = requiredDuration - watchedDuration;
+                    remainTime = (int) Math.max(0, remainTimeSeconds);
+                }
+            }
+        }
+
+        return R.ok().put("remainTime",remainTime);
+    }
+
+    private Long getFsUserVideoDuration(Long videoId) {
+        //将视频时长也存到redis
+        String videoRedisKey = "h5wxuser:video:duration:" + videoId;
+        Long videoDuration = 0L;
+        try {
+            videoDuration = redisCache.getCacheObject(videoRedisKey);
+        } catch (Exception e) {
+            String string = redisCache.getCacheObject(videoRedisKey);
+            if (string != null) {
+                videoDuration = Long.parseLong(string);
+            }
+            logger.error("key中id为S:{}", videoDuration);
+        }
+
+        if (videoDuration == null || videoDuration == 0) {
+            FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(videoId);
+            if (video != null && video.getDuration() != null) {
+                videoDuration = video.getDuration();
+                redisCache.setCacheObject(videoRedisKey, video.getDuration());
+            }
+        }
+        return videoDuration;
+    }
+
     @Login
     @ApiOperation("H5课程详情")
     @GetMapping("/videoDetails")