Quellcode durchsuchen

直播数据 权限

yuhongqi vor 1 Tag
Ursprung
Commit
a2bbe39ac3
32 geänderte Dateien mit 1276 neuen und 58 gelöschten Zeilen
  1. 5 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  2. 44 3
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  3. 6 6
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  4. 1 1
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  5. 43 0
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  6. 6 6
      fs-company/src/main/java/com/fs/company/controller/live/LiveAfterSalesController.java
  7. 8 1
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  8. 8 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java
  9. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java
  10. 2 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsUserScrmService.java
  11. 285 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  12. 5 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserScrmServiceImpl.java
  13. 16 0
      fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  14. 8 5
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  15. 11 2
      fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  16. 3 0
      fs-service/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java
  17. 1 0
      fs-service/src/main/java/com/fs/live/param/LiveDataParam.java
  18. 28 0
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  19. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java
  20. 1 1
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  21. 2 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  22. 417 3
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  23. 26 10
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  24. 13 8
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  25. 1 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  26. 5 0
      fs-service/src/main/java/com/fs/live/vo/FsMyLiveOrderListQueryVO.java
  27. 98 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java
  28. 40 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  29. 28 0
      fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java
  30. 136 0
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  31. 16 0
      fs-service/src/main/resources/mapper/live/LiveMapper.xml
  32. 8 7
      fs-service/src/main/resources/mapper/live/LiveOrderMapper.xml

+ 5 - 3
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -53,6 +53,7 @@ import com.fs.hisStore.service.*;
 import com.fs.hisStore.vo.*;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -173,7 +174,7 @@ public class FsStoreOrderScrmController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:list')")
     @PostMapping("/list")
     public TableDataInfo list(@RequestBody FsStoreOrderParam param) {
-        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -249,7 +250,7 @@ public class FsStoreOrderScrmController extends BaseController {
     @PreAuthorize("@ss.hasPermi('store:storeOrder:payRemainList')")
     @GetMapping("/payRemainList")
     public TableDataInfo payRemainList(FsStoreOrderParam param) {
-        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         if(!StringUtils.isEmpty(param.getCreateTimeRange())){
             param.setCreateTimeList(param.getCreateTimeRange().split("--"));
         }
@@ -793,7 +794,8 @@ public class FsStoreOrderScrmController extends BaseController {
 
     @GetMapping("/getCustomerOrderList")
     public TableDataInfo getCustomerOrderList(FsStoreOrderParam param) {
-        startPage();
+//        startPage();
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
         List<FsStoreOrderVO> list = fsStoreOrderService.selectFsCustomerStoreOrderListVO(param);
         if (list != null) {
             for (FsStoreOrderVO vo : list) {

+ 44 - 3
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -2,6 +2,7 @@ package com.fs.hisStore.task;
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
@@ -21,7 +22,11 @@ import com.fs.hisStore.dto.DateComparisonConfigDTO;
 import com.fs.hisStore.enums.ShipperCodeEnum;
 import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
 import com.fs.hisStore.param.FsStoreOrderAddTuiMoneyParam;
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.domain.LiveOrderItem;
@@ -49,6 +54,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 
 import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -89,9 +95,6 @@ public class LiveTask {
     @Autowired
     private ILiveOrderPaymentService liveOrderPaymentService;
 
-    @Autowired
-    private IFsUserService userService;
-
     @Autowired
     private ICompanyService companyService;
 
@@ -149,6 +152,44 @@ public class LiveTask {
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
+    @Autowired
+    private HuiFuService huiFuService;
+
+    @Autowired
+    private IFsStoreOrderScrmService orderService;
+
+
+    // 订单银行回调数据丢失补偿
+    public void recoveryBankOrder() {
+        // 查询出来最近三十分钟的订单 待支付 未退款
+        List<LiveOrder> list = liveOrderService.selectBankOrder();
+        if(list == null || list.isEmpty()) return;
+        for (LiveOrder liveOrder : list) {
+            List<LiveOrderPayment> liveOrderPayments = liveOrderPaymentMapper.selectLiveOrderPaymentByOrderId(liveOrder.getOrderId());
+            if(liveOrderPayments == null || liveOrderPayments.isEmpty()) continue;
+            for (LiveOrderPayment payment : liveOrderPayments) {
+                V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
+                request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+                request.setOrgHfSeqId(payment.getTradeNo());
+                HuiFuQueryOrderResult o = null;
+                try {
+                    o = huiFuService.queryOrder(request);
+                } catch (Exception e) {
+                    log.error("查询失败:"+e.getMessage());
+                    continue;
+                }
+                log.info("汇付返回"+o);
+                if ("00000000".equals(o.getResp_code()) && "S".equals(o.getTrans_stat())) {
+                    String[] order=o.getOrg_req_seq_id().split("-");
+                    if ("live".equals(order[0])) {
+                        liveOrderService.payConfirm(1, null, order[1], o.getOrg_hf_seq_id(), o.getOut_trans_id(), o.getParty_order_id());
+                    }
+                }
+            }
+        }
+    }
+
+
     public void PushErp() throws ParseException {
         List<Long> ids = liveOrderMapper.selectOrderIdByNoErp();
         if(ids == null) return;

+ 6 - 6
fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java

@@ -64,7 +64,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 获取售后记录详细信息
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:query')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:query')")
     @GetMapping(value = "/{id}")
     public R getInfo(@PathVariable("id") Long id)
     {
@@ -82,7 +82,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 查询售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:list')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:list')")
     @GetMapping("/list")
     public TableDataInfo list(LiveAfterSalesVo liveAfterSales)
     {
@@ -97,7 +97,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 导出售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:export')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:export')")
     @Log(title = "售后记录", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(LiveAfterSalesVo liveAfterSales)
@@ -117,7 +117,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 新增售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:add')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:add')")
     @Log(title = "售后记录", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody LiveAfterSales liveAfterSales)
@@ -128,7 +128,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 修改售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:edit')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:edit')")
     @Log(title = "售后记录", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody LiveAfterSales liveAfterSales)
@@ -149,7 +149,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 删除售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:remove')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:remove')")
     @Log(title = "售后记录", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

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

@@ -93,7 +93,7 @@ public class LiveController extends BaseController {
     @Log(title = "直播", businessType = BusinessType.DELETE)
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
-        return toAjax(liveService.deleteLiveByLiveIds(liveIds));
+        return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:query')")

+ 43 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java

@@ -109,5 +109,48 @@ public class LiveDataController extends BaseController {
         return R.ok(liveViewData);
     }
 
+    /**
+     * 查询直播间详情数据(SQL方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailBySql")
+    public R getLiveDataDetailBySql(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailBySql(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(SQL方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListBySql")
+    public R getLiveUserDetailListBySql(@RequestParam Long liveId) {
+        return liveDataService.getLiveUserDetailListBySql(liveId);
+    }
+
+    /**
+     * 查询直播间详情数据(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveDataDetailByServer")
+    public R getLiveDataDetailByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveDataDetailByServer(liveId);
+    }
+
+    /**
+     * 查询直播间用户详情列表(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:query')")
+    @GetMapping("/getLiveUserDetailListByServer")
+    public R getLiveUserDetailListByServer(@RequestParam Long liveId) {
+        return liveDataService.getLiveUserDetailListByServer(liveId);
+    }
 
 }

+ 6 - 6
fs-company/src/main/java/com/fs/company/controller/live/LiveAfterSalesController.java

@@ -52,7 +52,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 查询售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:list')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:list')")
     @GetMapping("/list")
     public TableDataInfo list(LiveAfterSalesVo liveAfterSales)
     {
@@ -69,7 +69,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 导出售后记录列表
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:export')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:export')")
     @Log(title = "售后记录", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(LiveAfterSalesVo liveAfterSales)
@@ -89,7 +89,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 获取售后记录详细信息
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:query')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:query')")
     @GetMapping(value = "/{id}")
     public R getInfo(@PathVariable("id") Long id)
     {
@@ -107,7 +107,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 新增售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:add')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:add')")
     @Log(title = "售后记录", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody LiveAfterSales liveAfterSales)
@@ -118,7 +118,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 修改售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:edit')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:edit')")
     @Log(title = "售后记录", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody LiveAfterSales liveAfterSales)
@@ -129,7 +129,7 @@ public class LiveAfterSalesController extends BaseController
     /**
      * 删除售后记录
      */
-    @PreAuthorize("@ss.hasPermi('live:liveAfteraSales:remove')")
+    @PreAuthorize("@ss.hasPermi('live:liveAfterSales:remove')")
     @Log(title = "售后记录", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{ids}")
     public AjaxResult remove(@PathVariable Long[] ids)

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

@@ -181,6 +181,9 @@ public class LiveController extends BaseController
     @PutMapping
     public AjaxResult edit(@RequestBody Live live)
     {
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        live.setCompanyUserId(user.getUserId());
+        live.setCompanyId(user.getCompanyId());
         return toAjax(liveService.updateLive(live));
     }
 
@@ -192,7 +195,11 @@ public class LiveController extends BaseController
 	@DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds)
     {
-        return toAjax(liveService.deleteLiveByLiveIds(liveIds));
+        Live live = new Live();
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        live.setCompanyUserId(user.getUserId());
+        live.setCompanyId(user.getCompanyId());
+        return toAjax(liveService.deleteLiveByLiveIds(liveIds, live));
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:query')")

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserScrmMapper.java

@@ -20,6 +20,7 @@ import com.fs.hisStore.domain.FsUserWatchStatisticsScrm;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
+import org.springframework.security.core.parameters.P;
 
 import java.math.BigDecimal;
 import java.util.List;
@@ -335,4 +336,11 @@ public interface FsUserScrmMapper
      */
     @Update("update fs_user set order_count = order_count + 1, total_amount = IFNULL(total_amount, 0) + #{amount} where user_id = #{userId}")
     void updateUserOrderCountAndAmount(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
+
+    /**
+     * 累计成交总额
+     * @param payMoney 成交金额
+     */
+    @Update("update fs_user set total_amount = IFNULL(total_amount, 0) + #{payMoney} where user_id = #{userId}")
+    void incPayMoney(@Param("payMoney") BigDecimal payMoney, @Param("userId") Long userId);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderParam.java

@@ -116,4 +116,7 @@ public class FsStoreOrderParam extends BaseEntity implements Serializable
     //银行交易流水号
     private String bankTransactionId;
 
+    private Integer pageNum;
+    private Integer pageSize;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsUserScrmService.java

@@ -28,6 +28,7 @@ import com.fs.hisStore.vo.FsUserTuiVO;
 import com.fs.hisStore.vo.h5.*;
 import com.github.pagehelper.PageInfo;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
@@ -292,4 +293,5 @@ public interface IFsUserScrmService
 
     void handleFsUserWx(FsUserScrm user, LoginMaWxParam param, WxMaJscode2SessionResult session);
 
+    void incPayMoney(BigDecimal payMoney, Long userId);
 }

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

@@ -31,7 +31,18 @@ import com.fs.hisStore.domain.*;
 import com.fs.hisStore.enums.CompanyEnum;
 import com.fs.hisStore.mapper.*;
 import com.fs.hisStore.utils.StoreAuditLogUtil;
+import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveGoods;
+import com.fs.live.domain.LiveAutoTask;
+import com.fs.live.domain.LiveLotteryConf;
+import com.fs.live.domain.LiveLotteryProductConf;
+import com.fs.live.mapper.LiveLotteryProductConfMapper;
+import com.fs.live.mapper.LiveMapper;
+import com.fs.live.service.ILiveService;
+import com.fs.live.service.ILiveGoodsService;
+import com.fs.live.service.ILiveAutoTaskService;
+import com.fs.live.service.ILiveLotteryConfService;
+import com.fs.live.vo.LiveGoodsVo;
 import com.fs.statis.dto.ModifyMoreDTO;
 import com.fs.hisStore.dto.ProductArrtDTO;
 import com.fs.hisStore.dto.ProductAttrCountDto;
@@ -40,10 +51,12 @@ import com.fs.hisStore.param.FsStoreProductQueryParam;
 import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
 import com.fs.hisStore.vo.*;
 import com.fs.statis.dto.ProductAuditDTO;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
 import org.springframework.transaction.annotation.Propagation;
@@ -57,8 +70,10 @@ import org.springframework.transaction.annotation.Transactional;
  */
 @Service
 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
+@Slf4j
 public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
 {
+
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductMapper;
     @Autowired
@@ -102,6 +117,24 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Autowired
     private FsStoreProductCategoryScrmMapper fsStoreProductCategoryScrmMapper;
 
+    @Autowired
+    private ILiveService liveService;
+
+    @Autowired
+    private LiveMapper liveMapper;
+
+    @Autowired
+    private ILiveGoodsService liveGoodsService;
+
+    @Autowired
+    private ILiveAutoTaskService liveAutoTaskService;
+
+    @Autowired
+    private ILiveLotteryConfService liveLotteryConfService;
+
+    @Autowired
+    private LiveLotteryProductConfMapper liveLotteryProductConfMapper;
+
     /**
      * 查询商品
      *
@@ -169,7 +202,258 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     public int deleteFsStoreProductByIds(Long[] productIds)
     {
         storeAuditLogUtil.addBatchAuditArray(productIds, "", "");
-        return fsStoreProductMapper.deleteFsStoreProductByIds(productIds);
+
+        int result = fsStoreProductMapper.deleteFsStoreProductByIds(productIds);
+
+        // 异步处理商品删除联动逻辑
+        if (result > 0) {
+            try {
+                log.info("批量删除商品:{}", productIds);
+                handleProductDeleteAsync(productIds);
+            } catch (Exception e) {
+                log.error("商品删除异步处理失败:{}", e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 异步处理商品删除联动逻辑
+     * 删除所有未直播、直播中和直播回放的直播间中关联的商品、定时任务、抽奖等
+     *
+     * @param productIds 被删除的商品ID数组
+     */
+    @Async
+    public void handleProductDeleteAsync(Long[] productIds) {
+        try {
+            log.info("开始异步处理商品删除联动,商品IDs: {}", Arrays.toString(productIds));
+
+            // 查询所有未直播(1)、直播中(2)和直播回放(4)的直播间
+            // 使用 LiveMapper 查询状态为1,2,4的直播间(包括所有类型)
+            List<Live> allLiveList = liveMapper.liveListAll();
+            // 同时查询其他类型的直播间(如果liveListAll只查询类型2,3,我们需要补充查询类型1的)
+            // 为了确保完整性,我们使用 selectLiveList 方法查询所有状态为1,2,4的直播间
+            Live queryLive = new Live();
+            List<Live> allLiveList2 = liveService.selectLiveList(queryLive);
+            // 合并并去重,过滤出状态为1,2,4的直播间
+            Set<Long> liveIdSet = new HashSet<>();
+            List<Live> targetLiveList = new ArrayList<>();
+            for (Live live : allLiveList) {
+                if (live.getLiveId() != null && !liveIdSet.contains(live.getLiveId())) {
+                    liveIdSet.add(live.getLiveId());
+                    targetLiveList.add(live);
+                }
+            }
+            for (Live live : allLiveList2) {
+                if (live.getStatus() != null && (live.getStatus() == 1 || live.getStatus() == 2 || live.getStatus() == 4)) {
+                    if (live.getLiveId() != null && !liveIdSet.contains(live.getLiveId())) {
+                        liveIdSet.add(live.getLiveId());
+                        targetLiveList.add(live);
+                    }
+                }
+            }
+
+            if (targetLiveList.isEmpty()) {
+                log.info("没有找到需要处理的直播间");
+                return;
+            }
+
+            log.info("找到 {} 个需要处理的直播间", targetLiveList.size());
+
+            // 遍历每个被删除的商品
+            for (Long productId : productIds) {
+                processProductDeleteForLives(productId, targetLiveList);
+            }
+
+            log.info("商品删除联动处理完成");
+        } catch (Exception e) {
+            log.error("异步处理商品删除联动失败", e);
+        }
+    }
+
+    /**
+     * 处理单个商品在指定直播间列表中的删除联动
+     *
+     * @param productId 商品ID
+     * @param liveList 直播间列表
+     */
+    private void processProductDeleteForLives(Long productId, List<Live> liveList) {
+        for (Live live : liveList) {
+            try {
+                Long liveId = live.getLiveId();
+                if (liveId == null) {
+                    continue;
+                }
+
+                // 1. 删除直播商品
+                deleteLiveGoodsByProductId(productId, liveId);
+
+                // 2. 删除直播定时任务(直播上下架、直播卡片)
+                deleteLiveAutoTasksByProductId(productId, liveId);
+
+                // 3. 删除直播抽奖(产品关联被删除的商品)
+                deleteLiveLotteryProductConfByProductId(productId, liveId);
+
+                // 4. 删除直播定时任务(抽奖,里面关联了这个商品的)
+                deleteLiveAutoTasksByLotteryProductId(productId, liveId);
+
+            } catch (Exception e) {
+                log.error("处理直播间 {} 的商品 {} 删除联动失败", live.getLiveId(), productId, e);
+            }
+        }
+    }
+
+    /**
+     * 删除直播商品
+     *
+     * @param productId 商品ID
+     * @param liveId 直播间ID
+     */
+    private void deleteLiveGoodsByProductId(Long productId, Long liveId) {
+        try {
+            LiveGoods queryGoods = new LiveGoods();
+            queryGoods.setProductId(productId);
+            queryGoods.setLiveId(liveId);
+            List<LiveGoods> goodsList = liveGoodsService.selectLiveGoodsList(queryGoods);
+
+            if (!goodsList.isEmpty()) {
+                Long[] goodsIds = goodsList.stream()
+                        .map(LiveGoods::getGoodsId)
+                        .toArray(Long[]::new);
+                liveGoodsService.deleteLiveGoodsByGoodsIds(goodsIds);
+                log.info("删除直播间 {} 的商品 {} 相关的直播商品 {} 个", liveId, productId, goodsIds.length);
+            }
+        } catch (Exception e) {
+            log.error("删除直播商品失败,productId: {}, liveId: {}", productId, liveId, e);
+        }
+    }
+
+    /**
+     * 删除直播定时任务(直播上下架、直播卡片)
+     *
+     * @param productId 商品ID
+     * @param liveId 直播间ID
+     */
+    private void deleteLiveAutoTasksByProductId(Long productId, Long liveId) {
+        try {
+            LiveAutoTask queryTask = new LiveAutoTask();
+            queryTask.setLiveId(liveId);
+            List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
+
+            List<Long> taskIdsToDelete = new ArrayList<>();
+
+            for (LiveAutoTask task : taskList) {
+                // 任务类型:1-定时推送卡片商品 6-自动上下架
+                if (task.getTaskType() != null && (task.getTaskType() == 1L || task.getTaskType() == 6L)) {
+                    try {
+                        if (task.getTaskType() == 1L) {
+                            // 商品推荐任务
+                            LiveGoodsVo liveGoodsVo = JSON.parseObject(task.getContent(), LiveGoodsVo.class);
+                            if (liveGoodsVo != null && productId.equals(liveGoodsVo.getProductId())) {
+                                taskIdsToDelete.add(task.getId());
+                            }
+                        } else if (task.getTaskType() == 6L) {
+                            // 商品上下架任务
+                            JSONObject jsonObject = JSON.parseObject(task.getContent());
+                            Long taskProductId = jsonObject.getLong("productId");
+                            if (taskProductId != null && productId.equals(taskProductId)) {
+                                taskIdsToDelete.add(task.getId());
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析自动化任务content失败,taskId: {}, error: {}", task.getId(), e.getMessage());
+                    }
+                }
+            }
+
+            if (!taskIdsToDelete.isEmpty()) {
+                Long[] ids = taskIdsToDelete.toArray(new Long[0]);
+                liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
+                log.info("删除直播间 {} 的商品 {} 相关的定时任务 {} 个", liveId, productId, taskIdsToDelete.size());
+            }
+        } catch (Exception e) {
+            log.error("删除直播定时任务失败,productId: {}, liveId: {}", productId, liveId, e);
+        }
+    }
+
+    /**
+     * 删除直播抽奖(产品关联被删除的商品)
+     *
+     * @param productId 商品ID
+     * @param liveId 直播间ID
+     */
+    private void deleteLiveLotteryProductConfByProductId(Long productId, Long liveId) {
+        try {
+            LiveLotteryProductConf queryConf = new LiveLotteryProductConf();
+            queryConf.setProductId(productId);
+            queryConf.setLiveId(liveId);
+            List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
+
+            if (!confList.isEmpty()) {
+                Long[] ids = confList.stream()
+                        .map(LiveLotteryProductConf::getId)
+                        .toArray(Long[]::new);
+                liveLotteryProductConfMapper.deleteLiveLotteryProductConfByIds(ids);
+                log.info("删除直播间 {} 的商品 {} 相关的抽奖商品配置 {} 个", liveId, productId, ids.length);
+            }
+        } catch (Exception e) {
+            log.error("删除直播抽奖商品配置失败,productId: {}, liveId: {}", productId, liveId, e);
+        }
+    }
+
+    /**
+     * 删除直播定时任务(抽奖,里面关联了这个商品的)
+     *
+     * @param productId 商品ID
+     * @param liveId 直播间ID
+     */
+    private void deleteLiveAutoTasksByLotteryProductId(Long productId, Long liveId) {
+        try {
+            // 查询该直播间下所有包含该商品的抽奖配置
+            LiveLotteryProductConf queryConf = new LiveLotteryProductConf();
+            queryConf.setProductId(productId);
+            queryConf.setLiveId(liveId);
+            List<LiveLotteryProductConf> confList = liveLotteryProductConfMapper.selectLiveLotteryProductConfList(queryConf);
+
+            if (confList.isEmpty()) {
+                return;
+            }
+
+            // 获取所有相关的抽奖ID
+            Set<Long> lotteryIds = confList.stream()
+                    .map(LiveLotteryProductConf::getLotteryId)
+                    .collect(Collectors.toSet());
+
+            // 查询该直播间的所有定时任务
+            LiveAutoTask queryTask = new LiveAutoTask();
+            queryTask.setLiveId(liveId);
+            List<LiveAutoTask> taskList = liveAutoTaskService.selectLiveAutoTaskList(queryTask);
+
+            List<Long> taskIdsToDelete = new ArrayList<>();
+
+            for (LiveAutoTask task : taskList) {
+                // 任务类型:4-抽奖
+                if (task.getTaskType() != null && task.getTaskType() == 4L) {
+                    try {
+                        LiveLotteryConf liveLotteryConf = JSON.parseObject(task.getContent(), LiveLotteryConf.class);
+                        if (liveLotteryConf != null && lotteryIds.contains(liveLotteryConf.getLotteryId())) {
+                            taskIdsToDelete.add(task.getId());
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析自动化任务content失败,taskId: {}, error: {}", task.getId(), e.getMessage());
+                    }
+                }
+            }
+
+            if (!taskIdsToDelete.isEmpty()) {
+                Long[] ids = taskIdsToDelete.toArray(new Long[0]);
+                liveAutoTaskService.deleteLiveAutoTaskByIds(ids);
+                log.info("删除直播间 {} 的商品 {} 相关的抽奖定时任务 {} 个", liveId, productId, taskIdsToDelete.size());
+            }
+        } catch (Exception e) {
+            log.error("删除抽奖定时任务失败,productId: {}, liveId: {}", productId, liveId, e);
+        }
     }
 
     /**

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserScrmServiceImpl.java

@@ -1157,4 +1157,9 @@ public class FsUserScrmServiceImpl implements IFsUserScrmService
             throw new RuntimeException(e);
         }
     }
+
+    @Override
+    public void incPayMoney(BigDecimal payMoney, Long userId) {
+        fsUserMapper.incPayMoney(payMoney, userId);
+    }
 }

+ 16 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveDataMapper.java

@@ -3,8 +3,10 @@ package com.fs.live.mapper;
 
 import com.fs.live.domain.LiveData;
 import com.fs.live.vo.LiveDashBoardDataVo;
+import com.fs.live.vo.LiveDataDetailVo;
 import com.fs.live.vo.LiveDataListVo;
 import com.fs.live.vo.LiveDataStatisticsVo;
+import com.fs.live.vo.LiveUserDetailVo;
 import com.fs.live.vo.RecentLiveDataVo;
 import com.fs.live.vo.TrendDataVO;
 import org.apache.ibatis.annotations.Param;
@@ -153,4 +155,18 @@ public interface LiveDataMapper {
      * @return 列表数据
      */
     List<LiveDataListVo> selectLiveDataListByLiveIds(@Param("liveIds") List<Long> liveIds);
+
+    /**
+     * 查询直播间详情数据(SQL方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    LiveDataDetailVo selectLiveDataDetailBySql(@Param("liveId") Long liveId);
+
+    /**
+     * 查询直播间用户详情列表(SQL方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId);
 }

+ 8 - 5
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -2,6 +2,7 @@ package com.fs.live.mapper;
 
 
 import com.fs.live.domain.Live;
+import com.fs.live.param.LiveDataParam;
 import com.fs.live.vo.LiveListVo;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -75,7 +76,7 @@ public interface LiveMapper
      * @param liveIds 需要删除的数据主键集合
      * @return 结果
      */
-    public int deleteLiveByLiveIds(Long[] liveIds);
+    public int deleteLiveByLiveIds(@Param("liveIds") Long[] liveIds,@Param("live") Live live);
 
     List<Live> liveList();
 
@@ -135,15 +136,17 @@ public interface LiveMapper
 
     @Select({"<script>" +
             "select * from live where 1=1 " +
-            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1 " +
+            " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if>" +
             " </script>"})
-    List<Live> listLiveData(@Param("companyId")Long companyId);
+    List<Live> listLiveData(@Param("param") LiveDataParam param);
 
     @Select({"<script>" +
             "select count(1) from live where 1=1 " +
-            " <if test='companyId!=null' > and company_id = #{companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1" +
+            " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if>" +
             " </script>"})
-    int listLiveDataCount(@Param("companyId") Long companyId);
+    int listLiveDataCount(@Param("param") LiveDataParam param);
 
 
     List<Live> liveShowList(@Param("companyIds") List<Long> companyIds);

+ 11 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -104,7 +104,7 @@ public interface LiveOrderMapper {
     @Select("select * from live_order where `status` = 3 AND TIMESTAMPDIFF(HOUR, start_time, NOW()) >= 48  ")
     List<LiveOrder> selectLiveOrderByFinish();
 
-    @Select("select * from live_order where `status` = 1 and extend_order_id is not null and delivery_sn is null ")
+    @Select("select * from live_order where `status` = 1 and extend_order_id is not null and delivery_sn is null and refund_status = 0")
     List<LiveOrder> selectUpdateExpress();
 
     @Select("select order_id from live_order where `status` = 2 and delivery_code is not null and delivery_sn is not null and is_pay = 1")
@@ -369,7 +369,7 @@ public interface LiveOrderMapper {
     int batchUpdateErpByOrderIds(@Param("maps")ArrayList<Map<String, String>> maps);
 
     @Select({"<script> " +
-            "select o.order_id,o.total_num ,o.order_code,o.item_json,o.pay_price,o.status,o.delivery_sn as delivery_id,o.finish_time  from live_order o  " +
+            "select o.order_id,o.total_num,o.create_time, o.discount_money ,o.live_id,o.order_code,o.item_json,o.pay_price,o.status,o.delivery_sn as delivery_id,o.finish_time  from live_order o  " +
             "where o.is_del=0 " +
             "<if test = 'maps.status != null and maps.status != \"\"     '> " +
             "and o.status =#{maps.status} " +
@@ -447,4 +447,13 @@ public interface LiveOrderMapper {
 
     @Select("SELECT * FROM live_order WHERE user_id=#{userId} LIMIT 1")
     LiveOrder selectOrderByUserIdLimit1(@Param("userId") Long userId);
+
+    @Select("SELECT * FROM live_order WHERE live_id= #{liveId}")
+    List<LiveOrder> selectOrderByLiveId(@Param("liveId") Long liveId);
+
+    /*
+    * 查询订单创建时间为最近30分钟的订单
+    * */
+    @Select("SELECT * FROM live_order WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 MINUTE) and status = 0 and refund_status = 0")
+    List<LiveOrder> selectBankOrder();
 }

+ 3 - 0
fs-service/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java

@@ -140,4 +140,7 @@ public interface LiveWatchUserMapper {
 
     @Update("update live_watch_user set single_visible = #{status} where live_id = #{liveId} and user_id=#{userId}")
     void updateSingleVisible(@Param("liveId") long liveId,@Param("status") int status,@Param("userId") long userId);
+
+    @Select("select * from live_watch_user where live_id = #{liveId}")
+    List<LiveWatchUser> selectLiveWatchUserListByLiveId(@Param("liveId") Long liveId);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/live/param/LiveDataParam.java

@@ -93,6 +93,7 @@ public class LiveDataParam {
 
     private Integer pageNum;
     private Integer pageSize;
+    private String watchType;
 
 
 }

+ 28 - 0
fs-service/src/main/java/com/fs/live/service/ILiveDataService.java

@@ -135,4 +135,32 @@ public interface ILiveDataService {
     List<LiveUserFirstVo> inviteList(Long liveId);
 
     R listLiveData(LiveDataParam param);
+
+    /**
+     * 查询直播间详情数据(SQL方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    R getLiveDataDetailBySql(Long liveId);
+
+    /**
+     * 查询直播间用户详情列表(SQL方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    R getLiveUserDetailListBySql(Long liveId);
+
+    /**
+     * 查询直播间详情数据(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 详情数据
+     */
+    R getLiveDataDetailByServer(Long liveId);
+
+    /**
+     * 查询直播间用户详情列表(查询数据服务器处理方式)
+     * @param liveId 直播间ID
+     * @return 用户详情列表
+     */
+    R getLiveUserDetailListByServer(Long liveId);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveOrderService.java

@@ -257,4 +257,6 @@ public interface ILiveOrderService {
     List<LiveOrderDeliveryNoteExportVO> getDeliveryNote(LiveOrderParam param);
 
     LiveOrder selectOrderByUserIdLimit1(Long userId);
+
+    List<LiveOrder> selectBankOrder();
 }

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -76,7 +76,7 @@ public interface ILiveService
      * @param liveIds 需要删除的直播主键集合
      * @return 结果
      */
-    public int deleteLiveByLiveIds(Long[] liveIds);
+    public int deleteLiveByLiveIds(Long[] liveIds,Live live);
 
     /**
      * 删除直播信息

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -379,6 +379,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_1.getValue()));
         order.setRefundExplain(param.getReasons());
         order.setCancelReason(param.getExplains());
+        order.setIsAfterSales(1);
         order.setRefundTime(new Date());
         liveOrderService.updateLiveOrder(order);
         //生成售后订单
@@ -869,6 +870,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         LiveOrder order = liveOrderService.selectLiveOrderByOrderId(String.valueOf(storeAfterSales.getOrderId()));
         order.setStatus(storeAfterSales.getOrderStatus());
         order.setRefundStatus(OrderInfoEnum.REFUND_STATUS_0.getValue().toString());
+        order.setIsAfterSales(0);
         liveOrderService.updateLiveOrder(order);
         //操作记录
         LiveAfterSalesLogs logs = new LiveAfterSalesLogs();

+ 417 - 3
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -5,6 +5,8 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.spring.SpringUtils;
+import com.fs.hisStore.domain.FsUserScrm;
+import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.live.domain.*;
 import com.fs.live.mapper.*;
 import com.fs.live.param.LiveDataParam;
@@ -13,6 +15,16 @@ import com.fs.live.service.ILiveUserFavoriteService;
 import com.fs.live.service.ILiveUserFollowService;
 import com.fs.live.service.ILiveUserLikeService;
 import com.fs.live.vo.*;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.course.domain.FsUserCompanyUser;
+import com.fs.course.mapper.FsUserCompanyUserMapper;
+import com.fs.his.domain.FsUser;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import java.util.stream.Collectors;
 import io.swagger.models.auth.In;
 import org.slf4j.Logger;
@@ -67,7 +79,16 @@ public class LiveDataServiceImpl implements ILiveDataService {
     private LiveMsgMapper liveMsgMapper;
     @Autowired
     private LiveMapper liveMapper;
-
+    @Autowired
+    private FsUserScrmMapper fsUserScrmMapper;
+    @Autowired
+    private FsUserCompanyUserMapper  fsUserCompanyUserMapper;
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
 
     /* 直播大屏展示 数据接口 */
     @Override
@@ -139,8 +160,8 @@ public class LiveDataServiceImpl implements ILiveDataService {
     public R listLiveData(LiveDataParam param) {
         // 第一步:查询当前公司的直播间数据
         // 直播类型 只展示已结束和直播回放的数据 录播展示直播中和已结束的直播数据
-        List<Live> lives = liveMapper.listLiveData(param.getCompanyId());
-        int total = liveMapper.listLiveDataCount(param.getCompanyId());
+        List<Live> lives = liveMapper.listLiveData(param);
+        int total = liveMapper.listLiveDataCount(param);
 
         if (lives == null || lives.isEmpty()) {
             LiveDataStatisticsVo statistics = new LiveDataStatisticsVo();
@@ -661,4 +682,397 @@ public class LiveDataServiceImpl implements ILiveDataService {
         return column;
     }
 
+    @Override
+    public R getLiveDataDetailBySql(Long liveId) {
+        LiveDataDetailVo detailVo = liveDataMapper.selectLiveDataDetailBySql(liveId);
+        if (detailVo == null) {
+            detailVo = new LiveDataDetailVo();
+        }
+        // 查询单品销量统计
+        List<ProductSalesVo> productSalesList = getProductSalesList(liveId);
+        detailVo.setProductSalesList(productSalesList);
+        return R.ok().put("data", detailVo);
+    }
+
+    @Override
+    public R getLiveUserDetailListBySql(Long liveId) {
+        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
+        return R.ok().put("data", userDetailList);
+    }
+
+    @Override
+    public R getLiveDataDetailByServer(Long liveId) {
+        // 查询数据服务器处理方式:通过查询各个表的数据,在内存中计算统计
+        LiveDataDetailVo detailVo = calculateLiveDataDetailByServer(liveId);
+        // 查询单品销量统计
+        List<ProductSalesVo> productSalesList = getProductSalesList(liveId);
+        detailVo.setProductSalesList(productSalesList);
+        return R.ok().put("data", detailVo);
+    }
+
+    @Override
+    public R getLiveUserDetailListByServer(Long liveId) {
+        // 查询数据服务器处理方式:通过查询各个表的数据,在内存中计算
+        List<LiveUserDetailVo> userDetailList = calculateLiveUserDetailListByServer(liveId);
+        return R.ok().put("data", userDetailList);
+    }
+
+    /**
+     * 通过查询数据服务器处理方式计算直播间详情数据
+     */
+    private LiveDataDetailVo calculateLiveDataDetailByServer(Long liveId) {
+        LiveDataDetailVo detailVo = new LiveDataDetailVo();
+
+        // 查询视频时长
+        LiveVideoMapper liveVideoMapper = SpringUtils.getBean(LiveVideoMapper.class);
+        List<LiveVideo> videos = liveVideoMapper.selectByLiveId(liveId);
+        Long videoDuration = videos.stream()
+                .filter(v -> v.getVideoType() != null && (v.getVideoType() == 1 || v.getVideoType() == 2))
+                .mapToLong(v -> v.getDuration() != null ? v.getDuration() : 0L)
+                .sum();
+        detailVo.setVideoDuration(videoDuration);
+
+        // 查询观看用户数据
+        LiveWatchUser queryParam = new LiveWatchUser();
+        queryParam.setLiveId(liveId);
+        List<LiveWatchUser> watchUsers = liveWatchUserMapper.selectLiveWatchUserList(queryParam);
+
+        // 累计观看人数
+        long totalViewers = watchUsers.stream().map(LiveWatchUser::getUserId).distinct().count();
+        detailVo.setTotalViewers(totalViewers);
+
+        // 直播观看人数
+        long liveViewers = watchUsers.stream()
+                .filter(w -> w.getLiveFlag() != null && w.getLiveFlag() == 1 && (w.getReplayFlag() == null || w.getReplayFlag() == 0))
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setLiveViewers(liveViewers);
+
+        // 回放观看人数
+        long playbackViewers = watchUsers.stream()
+                .filter(w -> w.getReplayFlag() != null && w.getReplayFlag() == 1 && (w.getLiveFlag() == null || w.getLiveFlag() == 0))
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setPlaybackViewers(playbackViewers);
+
+        // 累计完课人数(观看时长 >= 视频时长)
+        long totalCompletedCourses = watchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null && videoDuration > 0 && w.getOnlineSeconds() >= videoDuration)
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setTotalCompletedCourses(totalCompletedCourses);
+
+        // 计算到课完课率
+        if (totalViewers > 0) {
+            detailVo.setTotalCompletionRate(BigDecimal.valueOf(totalCompletedCourses * 100.0 / totalViewers).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        // 直播相关统计
+        List<LiveWatchUser> liveWatchUsers = watchUsers.stream()
+                .filter(w -> w.getLiveFlag() != null && w.getLiveFlag() == 1 && (w.getReplayFlag() == null || w.getReplayFlag() == 0))
+                .collect(Collectors.toList());
+
+        long liveOver20Minutes = liveWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1200)
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setLiveOver20Minutes(liveOver20Minutes);
+
+        long liveOver30Minutes = liveWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1800)
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setLiveOver30Minutes(liveOver30Minutes);
+
+        if (liveViewers > 0) {
+            detailVo.setLiveCompletionRate20(BigDecimal.valueOf(liveOver20Minutes * 100.0 / liveViewers).setScale(2, RoundingMode.HALF_UP));
+            detailVo.setLiveCompletionRate30(BigDecimal.valueOf(liveOver30Minutes * 100.0 / liveViewers).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        // 直播平均时长
+        double liveAvgDuration = liveWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null)
+                .mapToLong(LiveWatchUser::getOnlineSeconds)
+                .average()
+                .orElse(0.0);
+        detailVo.setLiveAvgDuration((long) liveAvgDuration);
+
+        // 回放相关统计
+        List<LiveWatchUser> playbackWatchUsers = watchUsers.stream()
+                .filter(w -> w.getReplayFlag() != null && w.getReplayFlag() == 1 && (w.getLiveFlag() == null || w.getLiveFlag() == 0))
+                .collect(Collectors.toList());
+
+        long playbackOver20Minutes = playbackWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1200)
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setPlaybackOver20Minutes(playbackOver20Minutes);
+
+        long playbackOver30Minutes = playbackWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null && w.getOnlineSeconds() >= 1800)
+                .map(LiveWatchUser::getUserId)
+                .distinct()
+                .count();
+        detailVo.setPlaybackOver30Minutes(playbackOver30Minutes);
+
+        if (playbackViewers > 0) {
+            detailVo.setPlaybackCompletionRate20(BigDecimal.valueOf(playbackOver20Minutes * 100.0 / playbackViewers).setScale(2, RoundingMode.HALF_UP));
+            detailVo.setPlaybackCompletionRate30(BigDecimal.valueOf(playbackOver30Minutes * 100.0 / playbackViewers).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        // 回放平均时长
+        double playbackAvgDuration = playbackWatchUsers.stream()
+                .filter(w -> w.getOnlineSeconds() != null)
+                .mapToLong(LiveWatchUser::getOnlineSeconds)
+                .average()
+                .orElse(0.0);
+        detailVo.setPlaybackAvgDuration((long) playbackAvgDuration);
+
+        // 回放完播率
+        if (videoDuration > 0) {
+            detailVo.setPlaybackFinishRate(BigDecimal.valueOf(playbackAvgDuration * 100.0 / videoDuration).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        // 查询直播峰值
+        LiveData liveData = liveDataMapper.selectLiveDataByLiveId(liveId);
+        if (liveData != null && liveData.getPeakConcurrentViewers() != null) {
+            detailVo.setLivePeak(liveData.getPeakConcurrentViewers());
+        }
+
+        // 查询订单数据
+        LiveOrderMapper liveOrderMapper = SpringUtils.getBean(LiveOrderMapper.class);
+        LiveOrder orderQuery = new LiveOrder();
+        orderQuery.setLiveId(liveId);
+        List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
+
+        BigDecimal gmv = orders.stream()
+                .filter(o -> "1".equals(o.getIsPay()))
+                .map(LiveOrder::getPayPrice)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        detailVo.setGmv(gmv);
+
+        long paidUsers = orders.stream()
+                .filter(o -> "1".equals(o.getIsPay()))
+                .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
+                .map(o -> {
+                    try {
+                        return Long.parseLong(o.getUserId());
+                    } catch (NumberFormatException e) {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .distinct()
+                .count();
+        detailVo.setPaidUsers(paidUsers);
+
+        long paidOrders = orders.stream()
+                .filter(o -> "1".equals(o.getIsPay()))
+                .count();
+        detailVo.setPaidOrders(paidOrders);
+
+        // 计算转化率
+        if (detailVo.getLivePeak() > 0) {
+            detailVo.setPeakConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / detailVo.getLivePeak()).setScale(2, RoundingMode.HALF_UP));
+            detailVo.setPeakRValue(gmv.divide(BigDecimal.valueOf(detailVo.getLivePeak()), 2, RoundingMode.HALF_UP));
+        }
+
+        if (totalViewers > 0) {
+            detailVo.setTotalViewerConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / totalViewers).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        if (liveOver30Minutes > 0) {
+            detailVo.setCompletion30MinConversionRate(BigDecimal.valueOf(paidUsers * 100.0 / liveOver30Minutes).setScale(2, RoundingMode.HALF_UP));
+        }
+
+        if (totalCompletedCourses > 0) {
+            detailVo.setCompletionRValue(gmv.divide(BigDecimal.valueOf(totalCompletedCourses), 2, RoundingMode.HALF_UP));
+        }
+
+        return detailVo;
+    }
+
+    /**
+     * 通过查询数据服务器处理方式计算用户详情列表
+     */
+    private List<LiveUserDetailVo> calculateLiveUserDetailListByServer(Long liveId) {
+        // 查询观看用户
+        List<LiveWatchUser> watchUsers = liveWatchUserMapper.selectLiveWatchUserListByLiveId(liveId);
+
+        LiveOrder orderQuery = new LiveOrder();
+        orderQuery.setLiveId(liveId);
+        List<LiveOrder> orders = liveOrderMapper.selectLiveOrderList(orderQuery);
+
+
+        // 按用户ID分组统计
+        Map<Long, LiveUserDetailVo> userDetailMap = new HashMap<>();
+
+        for (LiveWatchUser watchUser : watchUsers) {
+            Long userId = watchUser.getUserId();
+            if (userId == null) continue;
+
+            LiveUserDetailVo userDetail = userDetailMap.computeIfAbsent(userId, k -> {
+                LiveUserDetailVo vo = new LiveUserDetailVo();
+                vo.setUserId(userId);
+                // 查询用户信息
+                FsUserScrm user = fsUserScrmMapper.selectFsUserByUserId(userId);
+                if (user != null) {
+                    vo.setUserName(user.getNickName() != null ? user.getNickName() : (user.getNickName() != null ? user.getNickName() : "未知用户"));
+                } else {
+                    vo.setUserName("未知用户");
+                }
+                // 查询用户和销售关系
+                FsUserCompanyUser queryUserCompanyUser = new FsUserCompanyUser();
+                queryUserCompanyUser.setUserId(userId);
+                List<FsUserCompanyUser> userCompanyUserList = fsUserCompanyUserMapper.selectFsUserCompanyUserList(queryUserCompanyUser);
+                FsUserCompanyUser userCompanyUser = userCompanyUserList != null && !userCompanyUserList.isEmpty() ? userCompanyUserList.get(0) : null;
+                if (userCompanyUser != null) {
+                    if (userCompanyUser.getCompanyId() != null) {
+                        Company company = companyMapper.selectCompanyById(userCompanyUser.getCompanyId());
+                        if (company != null) {
+                            vo.setCompanyName(company.getCompanyName());
+                        }
+                    }
+                    if (userCompanyUser.getCompanyUserId() != null) {
+                        CompanyUser companyUser = companyUserMapper.selectCompanyUserByUserId(userCompanyUser.getCompanyUserId());
+                        if (companyUser != null) {
+                            vo.setSalesName(companyUser.getUserName());
+                        }
+                    }
+                }
+                return vo;
+            });
+
+            // 累加观看时长
+            if (watchUser.getOnlineSeconds() != null) {
+                if (watchUser.getLiveFlag() != null && watchUser.getLiveFlag() == 1 && (watchUser.getReplayFlag() == null || watchUser.getReplayFlag() == 0)) {
+                    userDetail.setLiveWatchDuration(userDetail.getLiveWatchDuration() + watchUser.getOnlineSeconds());
+                } else if (watchUser.getReplayFlag() != null && watchUser.getReplayFlag() == 1 && (watchUser.getLiveFlag() == null || watchUser.getLiveFlag() == 0)) {
+                    userDetail.setPlaybackWatchDuration(userDetail.getPlaybackWatchDuration() + watchUser.getOnlineSeconds());
+                }
+            }
+        }
+
+        // 统计订单数据
+        Map<Long, List<LiveOrder>> userOrdersMap = orders.stream()
+                .filter(o -> o.getUserId() != null && !o.getUserId().isEmpty())
+                .filter(o -> {
+                    try {
+                        Long.parseLong(o.getUserId());
+                        return true;
+                    } catch (NumberFormatException e) {
+                        return false;
+                    }
+                })
+                .collect(Collectors.groupingBy(o -> {
+                    try {
+                        return Long.parseLong(o.getUserId());
+                    } catch (NumberFormatException e) {
+                        return 0L;
+                    }
+                }));
+
+        for (Map.Entry<Long, List<LiveOrder>> entry : userOrdersMap.entrySet()) {
+            Long userId = entry.getKey();
+            List<LiveOrder> userOrders = entry.getValue();
+
+            LiveUserDetailVo userDetail = userDetailMap.computeIfAbsent(userId, k -> {
+                LiveUserDetailVo vo = new LiveUserDetailVo();
+                vo.setUserId(userId);
+                FsUserScrm user = fsUserScrmMapper.selectFsUserByUserId(userId);
+                if (user != null) {
+                    vo.setUserName(user.getNickName() != null ? user.getNickName() : (user.getNickName() != null ? user.getNickName() : "未知用户"));
+                } else {
+                    vo.setUserName("未知用户");
+                }
+                // 查询用户和销售关系
+                FsUserCompanyUser queryUserCompanyUser = new FsUserCompanyUser();
+                queryUserCompanyUser.setUserId(userId);
+                List<FsUserCompanyUser> userCompanyUserList = fsUserCompanyUserMapper.selectFsUserCompanyUserList(queryUserCompanyUser);
+                if (userCompanyUserList != null && !userCompanyUserList.isEmpty()) {
+                    FsUserCompanyUser userCompanyUser = userCompanyUserList.get(0);
+                    if (userCompanyUser.getCompanyId() != null) {
+                        Company company = companyMapper.selectCompanyById(userCompanyUser.getCompanyId());
+                        if (company != null) {
+                            vo.setCompanyName(company.getCompanyName());
+                        }
+                    }
+                    if (userCompanyUser.getCompanyUserId() != null) {
+                        CompanyUser companyUser = companyUserMapper.selectCompanyUserByUserId(userCompanyUser.getCompanyUserId());
+                        if (companyUser != null) {
+                            vo.setSalesName(companyUser.getUserName());
+                        }
+                    }
+                }
+                return vo;
+            });
+
+            userDetail.setOrderCount((long) userOrders.size());
+            BigDecimal orderAmount = userOrders.stream()
+                    .filter(o -> "1".equals(o.getIsPay()))
+                    .map(LiveOrder::getPayPrice)
+                    .filter(Objects::nonNull)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            userDetail.setOrderAmount(orderAmount);
+        }
+
+        // 转换为列表并排序
+        List<LiveUserDetailVo> result = new ArrayList<>(userDetailMap.values());
+        result.sort((a, b) -> {
+            int compare = b.getOrderAmount().compareTo(a.getOrderAmount());
+            if (compare != 0) return compare;
+            return Long.compare(b.getLiveWatchDuration(), a.getLiveWatchDuration());
+        });
+
+        return result;
+    }
+
+    /**
+     * 查询单品销量统计
+     */
+    private List<ProductSalesVo> getProductSalesList(Long liveId) {
+
+        List<LiveOrder> orders = liveOrderMapper.selectOrderByLiveId(liveId);
+
+        // 按商品ID分组统计
+        Map<Long, ProductSalesVo> productSalesMap = new HashMap<>();
+
+        for (LiveOrder order : orders) {
+            if (!"1".equals(order.getIsPay()) || order.getProductId() == null) {
+                continue;
+            }
+
+            ProductSalesVo productSales = productSalesMap.computeIfAbsent(order.getProductId(), k -> {
+                ProductSalesVo vo = new ProductSalesVo();
+                vo.setProductId(order.getProductId());
+                // 查询商品名称
+                FsStoreProductScrmMapper productMapper = SpringUtils.getBean(FsStoreProductScrmMapper.class);
+                FsStoreProductScrm product = productMapper.selectFsStoreProductById(order.getProductId());
+                if (product != null) {
+                    vo.setProductName(product.getProductName());
+                } else {
+                    vo.setProductName("未知商品");
+                }
+                return vo;
+            });
+
+            productSales.setSalesCount(productSales.getSalesCount() + 1);
+            if (order.getPayPrice() != null) {
+                productSales.setSalesAmount(productSales.getSalesAmount().add(order.getPayPrice()));
+            }
+        }
+
+        List<ProductSalesVo> result = new ArrayList<>(productSalesMap.values());
+        result.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
+
+        return result;
+    }
+
 }

+ 26 - 10
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -732,6 +732,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
             //增加用户购买次数
             userService.incPayCount(Long.valueOf(order.getUserId()));
+            userService.incPayMoney(order.getPayMoney(), Long.valueOf(order.getUserId()));
 
             order.setStatus(OrderInfoEnum.STATUS_1.getValue());
             order.setPayTime(LocalDateTime.now());
@@ -1382,6 +1383,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         order.setRefundStatus(String.valueOf(OrderInfoEnum.REFUND_STATUS_2.getValue()));
         liveUserLotteryRecordMapper.updateOrderStatusByOrderId(order.getOrderId(), -2);
         baseMapper.updateLiveOrder(order);
+        //删除用户购买总额
+        userService.incPayMoney(order.getPayMoney().negate(), Long.valueOf(order.getUserId()));
 
         //退库存
         //获取订单下的商品
@@ -1785,15 +1788,15 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             }
             expressService.subscribeEspress(order.getOrderCode(), order.getDeliveryCode(), order.getDeliverySn(), lastFourNumber);
 
-            TemplateBean templateBean = TemplateBean.builder()
-                    .orderId(order.getOrderId().toString())
-                    .orderCode(order.getOrderCode().toString())
-                    .deliveryId(order.getDeliverySn())
-                    .deliveryName(order.getDeliveryName())
-                    .userId(Long.valueOf(order.getUserId()))
-                    .templateType(TemplateListenEnum.TYPE_2.getValue())
-                    .build();
-            publisher.publishEvent(new TemplateEvent(this, templateBean));
+//            TemplateBean templateBean = TemplateBean.builder()
+//                    .orderId(order.getOrderId().toString())
+//                    .orderCode(order.getOrderCode().toString())
+//                    .deliveryId(order.getDeliverySn())
+//                    .deliveryName(order.getDeliveryName())
+//                    .userId(Long.valueOf(order.getUserId()))
+//                    .templateType(TemplateListenEnum.TYPE_2.getValue())
+//                    .build();
+//            publisher.publishEvent(new TemplateEvent(this, templateBean));
 
             LiveOrderPayment fsStorePayment  = liveOrderPaymentMapper.selectLiveOrderLatestPayByOrderId(order.getOrderId());
             FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
@@ -3312,6 +3315,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         return baseMapper.selectOrderByUserIdLimit1(userId);
     }
 
+    @Override
+    public List<LiveOrder> selectBankOrder() {
+        return baseMapper.selectBankOrder();
+    }
+
 
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
@@ -3563,7 +3571,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Override
     @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
     public R cancelOrder(LiveOrder order) {
-        if(order.getStatus() == 1){
+        if(order.getStatus() == OrderInfoEnum.STATUS_0.getValue()){
             LiveOrder liveOrder = baseMapper.selectLiveOrderByOrderId(String.valueOf(order.getOrderId()));
             if(liveOrder == null) return R.error("订单不存在");
             // 更新用户抽奖记录
@@ -3584,6 +3592,14 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             liveGoodsMapper.updateLiveGoods(goods);
             // 退券
             this.refundCoupon(order);
+            TemplateBean templateBean = TemplateBean.builder()
+                    .orderId(order.getOrderId().toString())
+                    .orderCode(order.getOrderCode().toString())
+                    .remark("您的订单已取消")
+                    .userId(Long.valueOf(order.getUserId()))
+                    .templateType(TemplateListenEnum.TYPE_1.getValue())
+                    .build();
+            publisher.publishEvent(new TemplateEvent(this, templateBean));
 
             return R.ok("操作成功");
         }else {

+ 13 - 8
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -185,7 +185,6 @@ public class LiveServiceImpl implements ILiveService
     public List<Live> asyncToCache() {
         // 同步直播间数据到缓存
         List<Live> list = liveList();
-        log.info("开始同步直播间数据到缓存,共{}条数据", list.size());
         ThreadUtil.execute(()->{
             // 清空原有的 ZSet 数据
             redisCache.deleteObject(LiveKeysConstant.LIVE_HOME_PAGE_LIST);
@@ -193,7 +192,6 @@ public class LiveServiceImpl implements ILiveService
                 redisCache.zSetAdd(LiveKeysConstant.LIVE_HOME_PAGE_LIST, JSON.toJSONString(live), live.getCreateTime().getTime());
                 redisCache.expire(LiveKeysConstant.LIVE_HOME_PAGE_LIST, LiveKeysConstant.LIVE_HOME_PAGE_LIST_EXPIRE, TimeUnit.SECONDS);
             }
-            log.info("直播间数据同步到缓存完成");
         });
         return list;
     }
@@ -221,10 +219,8 @@ public class LiveServiceImpl implements ILiveService
 			liveVo.setNowDuration(seconds);
 		}
         ThreadUtil.execute(()->{
-            log.info("同步直播间详情数据到缓存{}", id);
             redisCache.deleteObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, live.getLiveId()));
             redisCache.setCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, live.getLiveId()), liveVo,LiveKeysConstant.LIVE_HOME_PAGE_DETAIL_EXPIRE, TimeUnit.SECONDS);
-            log.info("直播间数据同步到缓存完成");
         });
 
         return liveVo;
@@ -234,10 +230,8 @@ public class LiveServiceImpl implements ILiveService
     public LiveConfigVo asyncToCacheLiveConfig(Long liveId) {
         LiveConfigVo liveConfigVo = currentActivities(liveId);
         ThreadUtil.execute(()->{
-            log.info("同步配置信息到缓存{}", liveConfigVo);
             redisCache.deleteObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG, liveId,liveId));
             redisCache.setCacheObject(String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, liveId), liveConfigVo,LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_EXPIRE, TimeUnit.SECONDS);
-            log.info("直播间数据同步到缓存完成");
         });
         return liveConfigVo;
     }
@@ -500,6 +494,11 @@ public class LiveServiceImpl implements ILiveService
      */
     @Override
     public int updateLive(Live live){
+        Live exist = baseMapper.selectLiveByLiveId(live.getLiveId());
+        if (live.getCompanyId() != null && exist.getCompanyId() != null && !Objects.equals(exist.getCompanyId(), live.getCompanyId())) {
+            log.error("updateLive:"+ live.getLiveId());
+            return -1;
+        }
         live.setUpdateTime(DateUtils.getNowDate());
         List<LiveVideo> videos = liveVideoService.listByLiveId(live.getLiveId(), 1);
         if(!videos.isEmpty()){
@@ -542,8 +541,8 @@ public class LiveServiceImpl implements ILiveService
      * @return 结果
      */
     @Override
-    public int deleteLiveByLiveIds(Long[] liveIds){
-        return baseMapper.deleteLiveByLiveIds(liveIds);
+    public int deleteLiveByLiveIds(Long[] liveIds,Live live){
+        return baseMapper.deleteLiveByLiveIds(liveIds, live);
     }
 
     /**
@@ -765,6 +764,9 @@ public class LiveServiceImpl implements ILiveService
         if (exist.getStatus() == 3) {
             return R.error("直播已结束");
         }
+        if (live.getCompanyId() != null && exist.getCompanyId() != null && !Objects.equals(exist.getCompanyId(), live.getCompanyId())) {
+            return R.error("您没有权限操作此直播间");
+        }
         log.error("finishLive:" + live.getLiveId());
         exist.setStatus(3);
         exist.setFinishTime(LocalDateTime.now());
@@ -790,6 +792,9 @@ public class LiveServiceImpl implements ILiveService
         if (exist.getIsShow() != 1) {
             return R.error("直播已下架");
         }
+        if (live.getCompanyId() != null && exist.getCompanyId() != null && !Objects.equals(exist.getCompanyId(), live.getCompanyId())) {
+            return R.error("您没有权限操作此直播间");
+        }
         String rtmpPushUrl = generateRtmpPushUrl("rtmp://200149.push.tlivecloud.com", "live", exist.getLiveId().toString());
         String hlvPlayUrl = generateHlvPlayUrl("https://live.test.ifeiyu100.com", "live", exist.getLiveId().toString());
         Date now = new Date();

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -314,7 +314,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         try {
             Long onlineSeconds = liveWatchUser.getOnlineSeconds();
             if(onlineSeconds == null) onlineSeconds = 0L;
-            liveWatchUser.setOnlineSeconds(onlineSeconds + (System.currentTimeMillis() - liveWatchUser.getUpdateTime().getTime()));
+            liveWatchUser.setOnlineSeconds(onlineSeconds + (System.currentTimeMillis() - liveWatchUser.getUpdateTime().getTime()) / 1000);
         } catch (Exception e) {
             log.error("设置在线时长异常:{}", e.getMessage());
         }

+ 5 - 0
fs-service/src/main/java/com/fs/live/vo/FsMyLiveOrderListQueryVO.java

@@ -25,6 +25,7 @@ public class FsMyLiveOrderListQueryVO implements Serializable
     /** 订单ID */
     private Long id;
     private Long orderId;
+    private Long liveId;
 
     /** 订单号 */
     private String orderCode;
@@ -45,10 +46,14 @@ public class FsMyLiveOrderListQueryVO implements Serializable
     private String deliveryId;
 
     private Integer isAfterSales;
+    private Integer discountMoney;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date finishTime;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
     private List<LiveOrderItem> items;
 
 

+ 98 - 0
fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java

@@ -0,0 +1,98 @@
+package com.fs.live.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 直播数据详情VO
+ *
+ * @author fs
+ * @date 2025-01-18
+ */
+@Data
+public class LiveDataDetailVo {
+    /** 视频时长(秒) */
+    private Long videoDuration = 0L;
+
+    /** 累计观看人数 */
+    private Long totalViewers = 0L;
+
+    /** 累计完课人数 */
+    private Long totalCompletedCourses = 0L;
+
+    /** 到课完课率(累计完课人数/累计观看人数) */
+    private BigDecimal totalCompletionRate = BigDecimal.ZERO;
+
+    /** 直播观看人数 */
+    private Long liveViewers = 0L;
+
+    /** >20分钟人数(直播) */
+    private Long liveOver20Minutes = 0L;
+
+    /** >30分钟人数(直播) */
+    private Long liveOver30Minutes = 0L;
+
+    /** 到课完课率直播(>20分钟人数(直播)/直播观看人数) */
+    private BigDecimal liveCompletionRate20 = BigDecimal.ZERO;
+
+    /** 到课完课率直播(>30分钟人数(直播)/直播观看人数) */
+    private BigDecimal liveCompletionRate30 = BigDecimal.ZERO;
+
+    /** 回放观看人数 */
+    private Long playbackViewers = 0L;
+
+    /** >20分钟人数(回放) */
+    private Long playbackOver20Minutes = 0L;
+
+    /** >30分钟人数(回放) */
+    private Long playbackOver30Minutes = 0L;
+
+    /** 到课完课率回放(>20分钟人数(回放)/回放观看人数) */
+    private BigDecimal playbackCompletionRate20 = BigDecimal.ZERO;
+
+    /** 到课完课率回放(>30分钟人数(回放)/回放观看人数) */
+    private BigDecimal playbackCompletionRate30 = BigDecimal.ZERO;
+
+    /** 直播峰值 */
+    private Long livePeak = 0L;
+
+    /** 直播平均时长(秒) */
+    private Long liveAvgDuration = 0L;
+
+    /** 回放平均时长(秒) */
+    private Long playbackAvgDuration = 0L;
+
+    /** 回放完播率(回放平均时长/视频时长) */
+    private BigDecimal playbackFinishRate = BigDecimal.ZERO;
+
+    /** GMV */
+    private BigDecimal gmv = BigDecimal.ZERO;
+
+    /** 付费人数 */
+    private Long paidUsers = 0L;
+
+    /** 付费单数 */
+    private Long paidOrders = 0L;
+
+    /** 峰值转化率 */
+    private BigDecimal peakConversionRate = BigDecimal.ZERO;
+
+    /** 总到课转化率 */
+    private BigDecimal totalViewerConversionRate = BigDecimal.ZERO;
+
+    /** 30min完课转化率 */
+    private BigDecimal completion30MinConversionRate = BigDecimal.ZERO;
+
+    /** 峰值R值 */
+    private BigDecimal peakRValue = BigDecimal.ZERO;
+
+    /** 完课R值 */
+    private BigDecimal completionRValue = BigDecimal.ZERO;
+
+    /** 单品销量统计 */
+    private List<ProductSalesVo> productSalesList;
+}
+
+

+ 40 - 0
fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java

@@ -0,0 +1,40 @@
+package com.fs.live.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 直播间用户详情VO
+ *
+ * @author fs
+ * @date 2025-01-18
+ */
+@Data
+public class LiveUserDetailVo {
+    /** 用户ID */
+    private Long userId;
+
+    /** 用户名称 */
+    private String userName;
+
+    /** 今天看了直播多长时间(秒) */
+    private Long liveWatchDuration = 0L;
+
+    /** 看了回放多长时间(秒) */
+    private Long playbackWatchDuration = 0L;
+
+    /** 下了几笔单 */
+    private Long orderCount = 0L;
+
+    /** 金额是多少 */
+    private BigDecimal orderAmount = BigDecimal.ZERO;
+
+    /** 对应的分公司 */
+    private String companyName;
+
+    /** 分公司的销售是谁 */
+    private String salesName;
+}
+
+

+ 28 - 0
fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java

@@ -0,0 +1,28 @@
+package com.fs.live.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 单品销量统计VO
+ *
+ * @author fs
+ * @date 2025-01-18
+ */
+@Data
+public class ProductSalesVo {
+    /** 商品ID */
+    private Long productId;
+
+    /** 商品名称 */
+    private String productName;
+
+    /** 销量 */
+    private Long salesCount = 0L;
+
+    /** 销售额 */
+    private BigDecimal salesAmount = BigDecimal.ZERO;
+}
+
+

+ 136 - 0
fs-service/src/main/resources/mapper/live/LiveDataMapper.xml

@@ -407,4 +407,140 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                  order_stats.gmv, order_stats.paidUsers, order_stats.paidOrders, order_stats.salesCount
         ORDER BY l.start_time DESC
     </select>
+
+    <!-- 查询直播间详情数据(SQL方式) -->
+    <select id="selectLiveDataDetailBySql" resultType="com.fs.live.vo.LiveDataDetailVo">
+        SELECT
+            COALESCE(video_duration.total_duration, 0) AS videoDuration,
+            COUNT(DISTINCT lwu.user_id) AS totalViewers,
+            COUNT(DISTINCT CASE
+                WHEN lwu.online_seconds >= COALESCE(video_duration.total_duration, 0) AND video_duration.total_duration > 0
+                THEN lwu.user_id
+            END) AS totalCompletedCourses,
+            CASE
+                WHEN COUNT(DISTINCT lwu.user_id) > 0 THEN
+                    ROUND(COUNT(DISTINCT CASE
+                        WHEN lwu.online_seconds >= COALESCE(video_duration.total_duration, 0) AND video_duration.total_duration > 0
+                        THEN lwu.user_id
+                    END) * 100.0 / COUNT(DISTINCT lwu.user_id), 2)
+                ELSE 0
+            END AS totalCompletionRate,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END) AS liveViewers,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1200 THEN lwu.user_id END) AS liveOver20Minutes,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1800 THEN lwu.user_id END) AS liveOver30Minutes,
+            CASE
+                WHEN COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END) > 0 THEN
+                    ROUND(COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1200 THEN lwu.user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END), 2)
+                ELSE 0
+            END AS liveCompletionRate20,
+            CASE
+                WHEN COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END) > 0 THEN
+                    ROUND(COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1800 THEN lwu.user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END), 2)
+                ELSE 0
+            END AS liveCompletionRate30,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END) AS playbackViewers,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 AND lwu.online_seconds >= 1200 THEN lwu.user_id END) AS playbackOver20Minutes,
+            COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 AND lwu.online_seconds >= 1800 THEN lwu.user_id END) AS playbackOver30Minutes,
+            CASE
+                WHEN COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END) > 0 THEN
+                    ROUND(COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 AND lwu.online_seconds >= 1200 THEN lwu.user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END), 2)
+                ELSE 0
+            END AS playbackCompletionRate20,
+            CASE
+                WHEN COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END) > 0 THEN
+                    ROUND(COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 AND lwu.online_seconds >= 1800 THEN lwu.user_id END) * 100.0 / COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END), 2)
+                ELSE 0
+            END AS playbackCompletionRate30,
+            COALESCE(ld.peak_concurrent_viewers, 0) AS livePeak,
+            COALESCE(AVG(CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.online_seconds END), 0) AS liveAvgDuration,
+            COALESCE(AVG(CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.online_seconds END), 0) AS playbackAvgDuration,
+            CASE
+                WHEN COALESCE(video_duration.total_duration, 0) > 0 THEN
+                    ROUND(COALESCE(AVG(CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.online_seconds END), 0) * 100.0 / video_duration.total_duration, 2)
+                ELSE 0
+            END AS playbackFinishRate,
+            COALESCE(order_stats.gmv, 0) AS gmv,
+            COALESCE(order_stats.paidUsers, 0) AS paidUsers,
+            COALESCE(order_stats.paidOrders, 0) AS paidOrders,
+            CASE
+                WHEN COALESCE(ld.peak_concurrent_viewers, 0) > 0 THEN
+                    ROUND(order_stats.paidUsers * 100.0 / ld.peak_concurrent_viewers, 2)
+                ELSE 0
+            END AS peakConversionRate,
+            CASE
+                WHEN COUNT(DISTINCT lwu.user_id) > 0 THEN
+                    ROUND(order_stats.paidUsers * 100.0 / COUNT(DISTINCT lwu.user_id), 2)
+                ELSE 0
+            END AS totalViewerConversionRate,
+            CASE
+                WHEN COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1800 THEN lwu.user_id END) > 0 THEN
+                    ROUND(order_stats.paidUsers * 100.0 / COUNT(DISTINCT CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 AND lwu.online_seconds >= 1800 THEN lwu.user_id END), 2)
+                ELSE 0
+            END AS completion30MinConversionRate,
+            CASE
+                WHEN COALESCE(ld.peak_concurrent_viewers, 0) > 0 THEN
+                    ROUND(order_stats.gmv / ld.peak_concurrent_viewers, 2)
+                ELSE 0
+            END AS peakRValue,
+            CASE
+                WHEN COUNT(DISTINCT CASE
+                    WHEN lwu.online_seconds >= COALESCE(video_duration.total_duration, 0) AND video_duration.total_duration > 0
+                    THEN lwu.user_id
+                END) > 0 THEN
+                    ROUND(order_stats.gmv / COUNT(DISTINCT CASE
+                        WHEN lwu.online_seconds >= COALESCE(video_duration.total_duration, 0) AND video_duration.total_duration > 0
+                        THEN lwu.user_id
+                    END), 2)
+                ELSE 0
+            END AS completionRValue
+        FROM live l
+        LEFT JOIN live_data ld ON l.live_id = ld.live_id
+        LEFT JOIN live_watch_user lwu ON l.live_id = lwu.live_id
+        LEFT JOIN (
+            SELECT live_id, SUM(COALESCE(duration, 0)) AS total_duration
+            FROM live_video
+            WHERE video_type IN (1, 2)
+            GROUP BY live_id
+        ) video_duration ON l.live_id = video_duration.live_id
+        LEFT JOIN (
+            SELECT
+                live_id,
+                SUM(CASE WHEN is_pay = '1' THEN pay_price ELSE 0 END) AS gmv,
+                COUNT(DISTINCT CASE WHEN is_pay = '1' THEN user_id END) AS paidUsers,
+                SUM(CASE WHEN is_pay = '1' THEN 1 ELSE 0 END) AS paidOrders
+            FROM live_order
+            GROUP BY live_id
+        ) order_stats ON l.live_id = order_stats.live_id
+        WHERE l.live_id = #{liveId}
+    </select>
+
+    <!-- 查询直播间用户详情列表(SQL方式) -->
+    <select id="selectLiveUserDetailListBySql" resultType="com.fs.live.vo.LiveUserDetailVo">
+        SELECT
+            u.user_id AS userId,
+            COALESCE(u.nickname,u.nick_name, '未知用户') AS userName,
+            COALESCE(SUM(CASE WHEN lwu.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.online_seconds ELSE 0 END), 0) AS liveWatchDuration,
+            COALESCE(SUM(CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.online_seconds ELSE 0 END), 0) AS playbackWatchDuration,
+            COALESCE(order_info.orderCount, 0) AS orderCount,
+            COALESCE(order_info.orderAmount, 0) AS orderAmount,
+            COALESCE(c.company_name, '') AS companyName,
+            COALESCE(cu.user_name, '') AS salesName
+        FROM live_watch_user lwu
+        LEFT JOIN fs_user u ON lwu.user_id = u.user_id
+        LEFT JOIN (
+            SELECT
+                CAST(user_id AS UNSIGNED) AS user_id,
+                COUNT(DISTINCT order_id) AS orderCount,
+                SUM(CASE WHEN is_pay = '1' THEN pay_price ELSE 0 END) AS orderAmount
+            FROM live_order
+            WHERE live_id = #{liveId} AND user_id IS NOT NULL AND user_id != ''
+            GROUP BY user_id
+        ) order_info ON lwu.user_id = order_info.user_id
+        LEFT JOIN fs_user_company_user fucu ON lwu.user_id = fucu.user_id
+        LEFT JOIN company c ON fucu.company_id = c.company_id
+        LEFT JOIN company_user cu ON fucu.company_user_id = cu.user_id
+        WHERE lwu.live_id = #{liveId}
+        GROUP BY u.user_id, u.nick_name, u.nickname, order_info.orderCount, order_info.orderAmount, c.company_name, cu.user_name
+        ORDER BY order_info.orderAmount DESC, liveWatchDuration DESC
+    </select>
 </mapper>

+ 16 - 0
fs-service/src/main/resources/mapper/live/LiveMapper.xml

@@ -235,6 +235,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach item="liveId" collection="array" open="(" separator="," close=")">
             #{liveId}
         </foreach>
+        <if test="live != null">
+            <if test="live.companyId != null"> and company_id = #{live.companyId}</if>
+        </if>
     </delete>
 
     <update id="updateBatchLiveList" parameterType="com.fs.live.vo.LiveListVo">
@@ -242,6 +245,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach item="liveId" collection="liveVo.liveIds" open="(" separator="," close=")">
             #{liveId}
         </foreach>
+
+        <if test="liveVo.companyId != null">
+            <if test="liveVo.companyId != null"> and company_id = #{liveVo.companyId}</if>
+        </if>
     </update>
 
     <update id="deleteBatchLiveList" parameterType="com.fs.live.vo.LiveListVo">
@@ -250,6 +257,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach item="liveId" collection="liveVo.liveIds" open="(" separator="," close=")">
             #{liveId}
         </foreach>
+        <if test="liveVo.companyId != null">
+            <if test="liveVo.companyId != null"> and company_id = #{liveVo.companyId}</if>
+        </if>
     </update>
 
     <update id="handleShelfOrUnAdmin" parameterType="com.fs.live.vo.LiveListVo">
@@ -257,6 +267,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach item="liveId" collection="liveVo.liveIds" open="(" separator="," close=")">
             #{liveId}
         </foreach>
+        <if test="liveVo.companyId != null">
+            <if test="liveVo.companyId != null"> and company_id = #{liveVo.companyId}</if>
+        </if>
     </update>
 
     <update id="handleDeleteSelectedAdmin" parameterType="com.fs.live.vo.LiveListVo">
@@ -265,6 +278,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <foreach item="liveId" collection="liveVo.liveIds" open="(" separator="," close=")">
             #{liveId}
         </foreach>
+        <if test="liveVo.companyId != null">
+            <if test="liveVo.companyId != null"> and company_id = #{liveVo.companyId}</if>
+        </if>
     </update>
 
     <update id="updateLiveList">

+ 8 - 7
fs-service/src/main/resources/mapper/live/LiveOrderMapper.xml

@@ -976,12 +976,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             o.coupon_price,
             o.cancel_reason,
 
-            <!-- 销售信息 -->
+
             cu.nick_name AS companyUserNickName,
             cu.phonenumber AS companyUserPhone,
             cu.create_time AS companyUserCreateTime,
 
-            <!-- 客户信息 -->
+
             u.user_code AS userCode,
             u.level AS userLevel,
             u.nick_name AS nickName,
@@ -991,7 +991,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             u.status AS userStatus,
             u.update_time AS latestBindTime,
 
-            <!-- 商品信息 -->
+
             p.product_name AS productName,
             p.cost AS costPrice,
             p.price AS price,
@@ -999,14 +999,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             p.prescribe_spec AS productSpec,
             p.prescribe_factory AS supplierName,
 
-            <!-- 门店信息 -->
+
             s.store_name AS storeName,
             s.store_id AS storeId,
 
-            <!-- 门店信息 -->
-            spavs.bar_code,
+
+            GROUP_CONCAT(DISTINCT spavs.bar_code SEPARATOR ',') AS bar_codes,
             spcs.cate_name,
-            <!-- 支付信息 -->
+
             lop.bank_transaction_id AS bankTransactionId
 
         FROM
@@ -1095,6 +1095,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND o.user_phone = #{userPhone}
             </if>
         </where>
+        GROUP BY o.order_id
         ORDER BY o.create_time DESC
     </select>