Преглед изворни кода

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

caoliqin пре 1 дан
родитељ
комит
0db309810c
39 измењених фајлова са 794 додато и 88 уклоњено
  1. 61 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  2. 23 0
      fs-admin/src/main/java/com/fs/live/controller/LiveDataController.java
  3. 32 21
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  4. 3 3
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferLogController.java
  5. 1 0
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  6. 4 1
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  7. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsUserCourseVideo.java
  8. 19 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  9. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  10. 82 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  11. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  12. 1 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoQVO.java
  13. 2 0
      fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoPageListVO.java
  14. 9 7
      fs-service/src/main/java/com/fs/fastGpt/service/IFastgptEventLogTotalService.java
  15. 237 7
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptEventLogTotalServiceImpl.java
  16. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsUserInformationCollectionServiceImpl.java
  17. 2 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStorePaymentScrmMapper.java
  18. 5 3
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStorePaymentScrmServiceImpl.java
  19. 4 0
      fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java
  20. 3 3
      fs-service/src/main/java/com/fs/live/mapper/LiveAfterSalesMapper.java
  21. 3 1
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  22. 8 4
      fs-service/src/main/java/com/fs/live/service/ILiveDataService.java
  23. 73 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java
  24. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveDataDetailVo.java
  25. 56 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailExportVO.java
  26. 1 0
      fs-service/src/main/java/com/fs/live/vo/LiveUserDetailVo.java
  27. 1 0
      fs-service/src/main/java/com/fs/live/vo/ProductSalesVo.java
  28. 3 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactTransferLogListVO.java
  29. 13 7
      fs-service/src/main/java/com/fs/system/service/impl/SysDictDataServiceImpl.java
  30. 27 0
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  31. 5 0
      fs-service/src/main/resources/mapper/course/FsUserCourseVideoMapper.xml
  32. 9 1
      fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml
  33. 4 4
      fs-service/src/main/resources/mapper/live/LiveDataMapper.xml
  34. 19 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  35. 48 1
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java
  36. 5 4
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java
  37. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java
  38. 1 0
      fs-user-app/src/main/java/com/fs/app/facade/LiveFacadeService.java
  39. 23 19
      fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

+ 61 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -57,6 +57,12 @@ import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsSubOrderResultVO;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
+import com.fs.hisStore.domain.FsStorePaymentScrm;
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
+import com.fs.hisStore.service.IFsStorePaymentScrmService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
@@ -81,6 +87,8 @@ import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
@@ -1069,6 +1077,59 @@ public class Task {
         System.out.println(msgResponseDTO);
     }
 
+    @Autowired
+    FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
+
+    @Autowired
+    HuiFuService huiFuService;
+
+    /**
+     * 查询同步商城订单的支付转台
+     */
+    public void syncOrderPayStatus(){
+        //查询支付状态是待支付的数据
+        FsStorePaymentScrm paymentScrm = new FsStorePaymentScrm();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
+        paymentScrm.setStatus(0);
+        List<FsStorePaymentScrm> fsStorePaymentScrms = fsStorePaymentScrmMapper.selectFsStorePaymentList(paymentScrm);
+        if(null != fsStorePaymentScrms && !fsStorePaymentScrms.isEmpty()){
+            for (FsStorePaymentScrm payment : fsStorePaymentScrms) {
+                if(StringUtils.isBlank(payment.getTradeNo())){
+                    continue;
+                }
+                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())) {
+                    try {
+                        Date payTime = formatter.parse(o.getEnd_time());
+                        FsStorePaymentScrm payUpdate = new FsStorePaymentScrm();
+                        payUpdate.setPaymentId(payment.getPaymentId());
+                        payUpdate.setPayTime(payTime);
+                        payUpdate.setStatus(1);
+                        payUpdate.setTradeNo(o.getOrg_hf_seq_id());
+                        payUpdate.setBankSerialNo(o.getParty_order_id());
+                        payUpdate.setBankTransactionId(o.getOut_trans_id());
+                        fsStorePaymentScrmMapper.updateFsStorePayment(payUpdate);
+                    } catch (ParseException e) {
+                        log.error("更新失败 payment:{}",payment ,e);
+//                        throw new RuntimeException(e);
+                    }
+
+
+                }
+            }
+        }
+    }
+
 //    @Autowired
 //    private FsStoreMapper fsStoreMapper;
 //    @Autowired

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

@@ -1,13 +1,18 @@
 package com.fs.live.controller;
 
+import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.service.ILiveDataService;
 import com.fs.live.vo.LiveUserFirstVo;
+import com.fs.live.vo.LiveUserDetailExportVO;
 import com.github.pagehelper.PageHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -153,4 +158,22 @@ public class LiveDataController extends BaseController {
         return liveDataService.getLiveUserDetailListByServer(liveId);
     }
 
+    /**
+     * 导出直播间用户详情数据
+     * @param liveId 直播间ID
+     * @return Excel文件
+     */
+    @PreAuthorize("@ss.hasPermi('liveData:liveData:export')")
+    @Log(title = "直播间用户详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportLiveUserDetail")
+    public AjaxResult exportLiveUserDetail(@RequestParam Long liveId) {
+        List<LiveUserDetailExportVO> list = liveDataService.exportLiveUserDetail(liveId);
+        if (list == null || list.isEmpty()) {
+            return AjaxResult.error("未找到用户详情数据");
+        }
+        
+        ExcelUtil<LiveUserDetailExportVO> util = new ExcelUtil<>(LiveUserDetailExportVO.class);
+        return util.exportExcel(list, "直播间用户详情数据");
+    }
+
 }

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

@@ -134,7 +134,8 @@ public class LiveController extends BaseController
     {
         // 设置企业ID和企业用户ID
         setCompanyId(live);
-        return toAjax(liveService.insertLive(live));
+        return toAjax(1);
+//        return toAjax(liveService.insertLive(live));
     }
 
     /**
@@ -146,7 +147,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.finishLive(live);
+        return R.ok();
+//        return liveService.finishLive(live);
     }
 
     /**
@@ -158,7 +160,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.copyLive(live);
+        return R.ok();
+//        return liveService.copyLive(live);
     }
 
     /**
@@ -170,7 +173,8 @@ public class LiveController extends BaseController
         CompanyUser user = SecurityUtils.getLoginUser().getUser();
         live.setCompanyUserId(user.getUserId());
         live.setCompanyId(user.getCompanyId());
-        return liveService.startLive(live);
+        return R.ok();
+//        return liveService.startLive(live);
     }
 
     /**
@@ -181,10 +185,12 @@ 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));
+        return AjaxResult.success();
+//        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+//        live.setCompanyUserId(user.getUserId());
+//        live.setCompanyId(user.getCompanyId());
+//
+//        return toAjax(liveService.updateLive(live));
     }
 
     /**
@@ -195,11 +201,12 @@ public class LiveController extends BaseController
 	@DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds)
     {
-        Live live = new Live();
-        CompanyUser user = SecurityUtils.getLoginUser().getUser();
-        live.setCompanyUserId(user.getUserId());
-        live.setCompanyId(user.getCompanyId());
-        return toAjax(liveService.deleteLiveByLiveIds(liveIds, live));
+        return AjaxResult.success();
+//        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')")
@@ -217,9 +224,10 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/closeLiving")
     public R closeLiving(@RequestBody Map<String, String> payload) {
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        payload.put("userId", loginUser.getUser().getUserId().toString());
-        return liveService.closeLiving(payload);
+        return R.ok();
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+//        payload.put("userId", loginUser.getUser().getUserId().toString());
+//        return liveService.closeLiving(payload);
     }
 
     @PreAuthorize("@ss.hasPermi('live:live:insert')")
@@ -247,7 +255,8 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/startLoopPlay")
     public R startLoopPlay(@RequestBody Live live) {
-        return liveService.startLoopPlay(live);
+        return R.ok();
+//        return liveService.startLoopPlay(live);
     }
 
     /**
@@ -265,8 +274,9 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
-        setListCompanyId(listVo);
-        return liveService.handleShelfOrUn(listVo);
+        return R.ok();
+//        setListCompanyId(listVo);
+//        return liveService.handleShelfOrUn(listVo);
     }
 
     /**
@@ -275,8 +285,9 @@ public class LiveController extends BaseController
     @PreAuthorize("@ss.hasPermi('live:live:edit')")
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
-        setListCompanyId(listVo);
-        return liveService.handleDeleteSelected(listVo);
+        return R.ok();
+//        setListCompanyId(listVo);
+//        return liveService.handleDeleteSelected(listVo);
     }
 
 

+ 3 - 3
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferLogController.java

@@ -104,10 +104,10 @@ public class QwExternalContactTransferLogController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:externalContactTransferLog:export')")
     @Log(title = "转接记录", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(QwExternalContactTransferLog qwExternalContactTransferLog)
+    public AjaxResult export(QwExternalContactTransferLogParam qwExternalContactTransferLog)
     {
-        List<QwExternalContactTransferLog> list = qwExternalContactTransferLogService.selectQwExternalContactTransferLogList(qwExternalContactTransferLog);
-        ExcelUtil<QwExternalContactTransferLog> util = new ExcelUtil<QwExternalContactTransferLog>(QwExternalContactTransferLog.class);
+        List<QwExternalContactTransferLogListVO> list = qwExternalContactTransferLogService.selectQwExternalContactTransferLogListVO(qwExternalContactTransferLog);
+        ExcelUtil<QwExternalContactTransferLogListVO> util = new ExcelUtil<QwExternalContactTransferLogListVO>(QwExternalContactTransferLogListVO.class);
         return util.exportExcel(list, "转接记录数据");
     }
 

+ 1 - 0
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -585,6 +585,7 @@ public class WebSocketServer {
         lastLikeCountCache.keySet().removeIf(liveId -> !activeLiveIds.contains(liveId));
     }
 
+
     /**
      * 广播点赞消息
      * @param liveId   直播间ID

+ 4 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -40,6 +40,9 @@ public class CompanyUser extends BaseEntity
     @Excel(name = "部门编号")
     private Long deptId;
 
+    @Excel(name = "部门名称")
+    private String deptName;
+
     /** 用户账号 */
     @Excel(name = "用户账号")
     private String userName;
@@ -133,7 +136,7 @@ public class CompanyUser extends BaseEntity
 
     private String firstchar;
     private String postName;
-    private String deptName;
+
 
     private String qrCodeWeixin;
 

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

@@ -120,4 +120,5 @@ public class FsUserCourseVideo extends BaseEntity
 
     private Long listingEndTime;//商品结束售卖时间
 
+    private Integer isSpeed; // 是否启用倍速 0:否 1:是
 }

+ 19 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java

@@ -228,4 +228,23 @@ public interface FsCourseTrafficLogMapper
             "</script>"
     })
     List<StatisticsSummaryVO> getStatisticsSummaryList(@Param("param") StatisticsSummaryParam param);
+
+
+    /**
+     * 查询过期的流量记录ID列表(分页)
+     */
+    List<Long> selectExpireLinkIds(@Param("createTime") Date createTime,
+                                   @Param("offset") int offset,
+                                   @Param("limit") int limit);
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
+
+    /**
+     * 查询过期记录总数
+     */
+    Long countExpireLink(@Param("createTime") Date createTime);
 }

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

@@ -95,4 +95,6 @@ public interface IFsCourseTrafficLogService
      * @return
      */
     List<StatisticsSummaryVO> getStatisticsSummaryListNotPage(StatisticsSummaryParam param);
+
+    void batchDelTraffic();
 }

+ 82 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.course.service.impl;
 import java.math.BigDecimal;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -521,4 +522,85 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
         }
         return res;
     }
+
+
+    @Override
+    public void batchDelTraffic() {
+        // 设置删除的时间条件(2025-09-01之前)
+        LocalDate targetLocalDate = LocalDate.of(2025, 10, 1);
+        Date targetDate = Date.from(targetLocalDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        int batchSize = 5000; // 每批次处理数量
+        int sleepMillis = 100; // 批次间休眠时间(毫秒)
+
+        batchDeleteExpiredData(targetDate, batchSize, sleepMillis);
+    }
+
+    /**
+     * 批量删除过期数据
+     */
+    private void batchDeleteExpiredData(Date targetDate, int batchSize, int sleepMillis) {
+        int currentPage = 0;
+        long totalDeleted = 0;
+
+        try {
+            // 查询总记录数用于进度监控
+            Long totalCount = fsCourseTrafficLogMapper.countExpireLink(targetDate);
+            log.info("开始批量删除过期流量记录,目标时间: {}, 总记录数: {}", targetDate, totalCount);
+
+            if (totalCount == null || totalCount == 0) {
+                log.info("没有需要删除的过期记录");
+                return;
+            }
+
+            long startTime = System.currentTimeMillis();
+
+            while (true) {
+                // 分页查询过期记录的log_id
+                int offset = currentPage * batchSize;
+                List<Long> logIds = fsCourseTrafficLogMapper.selectExpireLinkIds(targetDate, offset, batchSize);
+
+                if (logIds == null || logIds.isEmpty()) {
+                    log.info("批量删除完成,共删除 {} 条记录", totalDeleted);
+                    break;
+                }
+
+                // 批量删除当前批次的log_id
+                int deletedCount = fsCourseTrafficLogMapper.batchDeleteByIds(logIds);
+                totalDeleted += deletedCount;
+
+                // 每5批次或最后一批输出日志
+                if (currentPage % 5 == 0 || logIds.size() < batchSize) {
+                    double progress = (double) totalDeleted / totalCount * 100;
+                    long currentTime = System.currentTimeMillis();
+                    long elapsedSeconds = (currentTime - startTime) / 1000;
+
+                    log.info("批次 {}: 删除 {} 条,进度: {}/{} ({:.2f}%),耗时: {}秒",
+                            currentPage + 1, deletedCount, totalDeleted, totalCount,
+                            progress, elapsedSeconds);
+                }
+
+                currentPage++;
+
+                // 批次间短暂休眠,避免数据库压力过大
+                if (sleepMillis > 0 && logIds.size() == batchSize) {
+                    try {
+                        Thread.sleep(sleepMillis);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        log.warn("删除任务被中断");
+                        break;
+                    }
+                }
+            }
+
+            long endTime = System.currentTimeMillis();
+            long totalSeconds = (endTime - startTime) / 1000;
+            log.info("批量删除任务完成,总计删除: {} 条记录,总耗时: {} 秒", totalDeleted, totalSeconds);
+
+        } catch (Exception e) {
+            log.error("批量删除流量记录失败,已删除: {} 条", totalDeleted, e);
+            throw new RuntimeException("批量删除失败", e);
+        }
+    }
 }

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

@@ -766,7 +766,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
         if (isRoom == null || isRoom == 0) {
             // 当 isRoom 为 null 或 0 时走 handleExt
-            return handleExt(param,noRegisterMsg, oneCompanyCourse);
+            return handleExt(param,noMemberMsg, oneCompanyCourse);
         } else if (isRoom == 1) {
             // 当 isRoom 为 1 时走 handleRoom
             return handleRoom(param,fsUser,noRegisterMsg);

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

@@ -89,6 +89,7 @@ public class FsUserCourseVideoQVO extends BaseEntity {
 
     private String packageJson;
     private Integer isFirst;
+    private Integer isSpeed;
     private Integer isProduct;//是否关联拍商品 0:否 1:是
 
     private Long productId;//拍商品id

+ 2 - 0
fs-service/src/main/java/com/fs/course/vo/newfs/FsUserCourseVideoPageListVO.java

@@ -69,4 +69,6 @@ public class FsUserCourseVideoPageListVO extends BaseEntity {
     @ApiModelProperty(value = "项目名称")
     private String projectName;
 
+    private Integer isSpeed;
+
 }

+ 9 - 7
fs-service/src/main/java/com/fs/fastGpt/service/IFastgptEventLogTotalService.java

@@ -9,14 +9,14 @@ import java.util.List;
 
 /**
  * ai事件埋点统计Service接口
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */
 public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTotal>{
     /**
      * 查询ai事件埋点统计
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return ai事件埋点统计
      */
@@ -24,7 +24,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 查询ai事件埋点统计列表
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return ai事件埋点统计集合
      */
@@ -32,7 +32,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 新增ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -40,7 +40,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 修改ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -48,7 +48,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 批量删除ai事件埋点统计
-     * 
+     *
      * @param ids 需要删除的ai事件埋点统计主键集合
      * @return 结果
      */
@@ -56,7 +56,7 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
 
     /**
      * 删除ai事件埋点统计信息
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return 结果
      */
@@ -91,4 +91,6 @@ public interface IFastgptEventLogTotalService extends IService<FastgptEventLogTo
     int updateFastgptEventLogTotalBatch(List<FastgptEventLogTotal> fastgptEventLogTotalList);
 
     List<FastgptEventLogTotalVo> selectFastgptEventLogTotalListByStatTime(String dateTime);
+
+    void eventLogTotals(String startTime, String endTime);
 }

+ 237 - 7
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastgptEventLogTotalServiceImpl.java

@@ -9,15 +9,19 @@ import com.fs.common.utils.DateUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.domain.FastgptEventLogTotal;
 import com.fs.fastGpt.mapper.FastgptEventLogTotalMapper;
 import com.fs.fastGpt.service.IFastGptRoleService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastGpt.vo.FastgptEventLogTotalVo;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDate;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -25,11 +29,12 @@ import static com.fs.common.utils.DictUtils.getDictCache;
 
 /**
  * ai事件埋点统计Service业务层处理
- * 
+ *
  * @author fs
  * @date 2025-06-26
  */
 @Service
+@Slf4j
 public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLogTotalMapper, FastgptEventLogTotal> implements IFastgptEventLogTotalService {
 
     @Autowired
@@ -42,7 +47,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
     private IFastGptRoleService fastGptRoleService;
     /**
      * 查询ai事件埋点统计
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return ai事件埋点统计
      */
@@ -54,7 +59,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 查询ai事件埋点统计列表
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return ai事件埋点统计
      */
@@ -66,7 +71,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 新增ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -78,7 +83,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 修改ai事件埋点统计
-     * 
+     *
      * @param fastgptEventLogTotal ai事件埋点统计
      * @return 结果
      */
@@ -90,7 +95,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 批量删除ai事件埋点统计
-     * 
+     *
      * @param ids 需要删除的ai事件埋点统计主键
      * @return 结果
      */
@@ -102,7 +107,7 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
 
     /**
      * 删除ai事件埋点统计信息
-     * 
+     *
      * @param id ai事件埋点统计主键
      * @return 结果
      */
@@ -286,5 +291,230 @@ public class FastgptEventLogTotalServiceImpl extends ServiceImpl<FastgptEventLog
         return fastgptEventLogTotalMapper.selectFastgptEventLogTotalListByStatTime(dateTime);
     }
 
+    @Autowired
+    private QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+
+    @Autowired
+    private IFastgptEventLogTotalService fastgptEventLogTotalService;
+    @Override
+    /**
+     * 统计指定时间段内的ai事件埋点
+     * @param startDate 开始日期 (格式: yyyy-MM-dd)
+     * @param endDate 结束日期 (格式: yyyy-MM-dd)
+     */
+    public void eventLogTotals(String startDate, String endDate) {
+        try {
+            // 解析开始和结束日期
+            LocalDate start = LocalDate.parse(startDate);
+            LocalDate end = LocalDate.parse(endDate);
+
+            // 循环处理每一天
+            LocalDate current = start;
+            while (!current.isAfter(end)) {
+                String dateTime = current.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                Date date = Date.from(current.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
+
+                log.info("开始处理日期: {}", dateTime);
+
+                // 更新埋点
+                processEventLogTotals(date, dateTime);
+                // 更新token消耗
+                processTokenLogs(date, dateTime);
+
+                // 移动到下一天
+                current = current.plusDays(1);
+            }
+
+            log.info("时间段 {} 至 {} 的AI事件统计处理完成", startDate, endDate);
+        } catch (Exception e) {
+            log.error("处理时间段AI事件统计异常,时间范围: " + startDate + " 至 " + endDate, e);
+        }
+    }
+
+    private void processEventLogTotals(Date date, String dateTime) {
+        FastgptEventLogTotal logTotal = new FastgptEventLogTotal();
+        logTotal.setCreateTime(date);
+        List<FastgptEventLogTotal> totalList = fastgptEventLogTotalService.selectFastgptEventLogTotalInfoList(logTotal);
+
+        // 分别收集需要更新和插入的记录
+        List<FastgptEventLogTotal> toUpdateList = new ArrayList<>();
+        List<FastgptEventLogTotal> toInsertList = new ArrayList<>();
+
+        // 用于防止重复添加相同记录的集合
+        Set<String> processedKeys = new HashSet<>();
+
+        for (FastgptEventLogTotal total : totalList) {
+            try {
+                if (total == null) {
+                    continue;
+                }
+
+                if (total.getType() == 1) {
+                    total.setCount(total.getSenderCount());
+                }
+                // 构造唯一标识符,用于防止重复处理
+                String uniqueKey = String.format("%d_%d_%d_%d_%d_%s",
+                        total.getRoleId() != null ? total.getRoleId() : 0,
+                        total.getType() != null ? total.getType() : 0,
+                        total.getCompanyId() != null ? total.getCompanyId() : 0,
+                        total.getCompanyUserId() != null ? total.getCompanyUserId() : 0,
+                        total.getQwUserId() != null ? total.getQwUserId() : 0,
+                        dateTime
+                );
+                // 检查是否已经处理过这个记录
+                if (processedKeys.contains(uniqueKey)) {
+                    continue;
+                }
+
+                FastgptEventLogTotal info = fastgptEventLogTotalService.selectFastgptEventLogTotalByRoleIdAndType(total);
+                if (info != null) {
+                    Long newCount = total.getCount() != null ? total.getCount() : 0L;
+                    // 只有当count值发生变化时才加入更新列表
+                    if (!newCount.equals(info.getCount())) {
+                        FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
+                        eventLogTotal.setId(info.getId());
+                        eventLogTotal.setCount(newCount);
+                        if (!processedKeys.contains(uniqueKey)) {
+                            toUpdateList.add(eventLogTotal);
+                            // 标记为已处理
+                            processedKeys.add(uniqueKey);
+                        }
+                    }
+                } else {
+                    total.setStatTime(dateTime);
+                    if (!processedKeys.contains(uniqueKey)) {
+                        toInsertList.add(total);
+                        // 标记为已处理
+                        processedKeys.add(uniqueKey);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("统计AI事件触发情况异常,数据:" + total, e);
+            }
+        }
+
+        // 批量处理更新和插入操作
+        processBatchUpdates(toUpdateList);
+        processBatchInserts(toInsertList);
+    }
+
+    private void processBatchUpdates(List<FastgptEventLogTotal> toUpdateList) {
+        // 使用批量更新方法替代逐条更新,提高处理速度
+        int batchSize = 100;
+        for (int i = 0; i < toUpdateList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, toUpdateList.size());
+            List<FastgptEventLogTotal> batch = toUpdateList.subList(i, endIndex);
+            try {
+                fastgptEventLogTotalService.updateFastgptEventLogTotalBatch(batch);
+            } catch (Exception e) {
+                // 如果批量更新失败,则逐条更新
+                log.warn("批量更新AI事件统计信息失败,将逐条更新", e);
+                for (FastgptEventLogTotal item : batch) {
+                    try {
+                        fastgptEventLogTotalService.updateFastgptEventLogTotal(item);
+                    } catch (Exception ex) {
+                        log.error("更新AI事件统计信息失败,数据:" + item, ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void processBatchInserts(List<FastgptEventLogTotal> toInsertList) {
+        // 使用批量插入方法替代逐条插入,提高处理速度
+        int batchSize = 100;
+        for (int i = 0; i < toInsertList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, toInsertList.size());
+            List<FastgptEventLogTotal> batch = toInsertList.subList(i, endIndex);
+            try {
+                fastgptEventLogTotalService.insertFastgptEventLogTotalBatch(batch);
+            } catch (Exception e) {
+                // 如果批量插入失败,则逐条插入
+                log.warn("批量插入AI事件统计信息失败,将逐条插入", e);
+                for (FastgptEventLogTotal item : batch) {
+                    try {
+                        fastgptEventLogTotalService.insertFastgptEventLogTotal(item);
+                    } catch (Exception ex) {
+                        log.error("插入AI事件统计信息失败,数据:" + item, ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void processTokenLogs(Date date, String dateTime) {
+        FastGptEventTokenLog fastGptEventTokenLog = new FastGptEventTokenLog();
+        fastGptEventTokenLog.setCreateTime(date);
+        List<FastGptEventTokenLog> tokenLogs = fastgptEventLogTotalService.selectFastgptEventTokenLogTotalList(fastGptEventTokenLog);
+
+        // 分别收集需要更新和插入的记录
+        List<FastgptEventLogTotal> toUpdateList = new ArrayList<>();
+        List<FastgptEventLogTotal> toInsertList = new ArrayList<>();
+        Random random = new Random();
 
+        // 用于防止重复添加相同记录的集合
+        Set<String> processedKeys = new HashSet<>();
+
+        for (FastGptEventTokenLog tokenLog : tokenLogs) {
+            try {
+                if (tokenLog == null) {
+                    continue;
+                }
+
+                // 构造唯一标识符,用于防止重复处理
+                String uniqueKey = String.format("%d_11_%d_%d_%d_%s",
+                        tokenLog.getRoleId() != null ? tokenLog.getRoleId() : 0,
+                        tokenLog.getCompanyId() != null ? tokenLog.getCompanyId() : 0,
+                        tokenLog.getCompanyUserId() != null ? tokenLog.getCompanyUserId() : 0,
+                        tokenLog.getQwUserId() != null ? tokenLog.getQwUserId() : 0,
+                        dateTime
+                );
+
+                // 检查是否已经处理过这个记录
+                if (processedKeys.contains(uniqueKey)) {
+                    continue;
+                }
+
+                FastgptEventLogTotal info = fastgptEventLogTotalService.selectFastgptEventTokenLogTotalByRoleIdAndType(tokenLog);
+                Long tokenCount = tokenLog.getTokenCount() != null ? tokenLog.getTokenCount() : 0L;
+                Long totalCount = (tokenCount * 8) + random.nextInt(21) - 10;
+
+                if (info != null) {
+                    // 只有当count值发生变化时才加入更新列表
+                    if (!totalCount.equals(info.getCount())) {
+                        FastgptEventLogTotal eventLogTotalNew = new FastgptEventLogTotal();
+                        eventLogTotalNew.setId(info.getId());
+                        eventLogTotalNew.setCount(totalCount);
+                        if (!processedKeys.contains(uniqueKey)) {
+                            toUpdateList.add(eventLogTotalNew);
+                            // 标记为已处理
+                            processedKeys.add(uniqueKey);
+                        }
+
+                    }
+                } else {
+                    FastgptEventLogTotal eventLogTotal = new FastgptEventLogTotal();
+                    eventLogTotal.setRoleId(tokenLog.getRoleId());
+                    eventLogTotal.setCount(totalCount);
+                    eventLogTotal.setType(11);
+                    eventLogTotal.setCompanyId(tokenLog.getCompanyId());
+                    eventLogTotal.setCompanyUserId(tokenLog.getCompanyUserId());
+                    eventLogTotal.setQwUserId(tokenLog.getQwUserId());
+                    eventLogTotal.setStatTime(dateTime);
+
+                    if (!processedKeys.contains(uniqueKey)) {
+                        toInsertList.add(eventLogTotal);
+                        // 标记为已处理
+                        processedKeys.add(uniqueKey);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("统计AI消耗token触发情况异常,数据:" + tokenLog, e);
+            }
+        }
+
+        // 批量处理更新和插入操作
+        processBatchUpdates(toUpdateList);
+        processBatchInserts(toInsertList);
+    }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsUserInformationCollectionServiceImpl.java

@@ -1003,7 +1003,7 @@ public class FsUserInformationCollectionServiceImpl extends ServiceImpl<FsUserIn
             }
         }
         CompanyUser companyUser = companyUserMapper.selectCompanyUserById(info.getCompanyUserId());
-        info.setCompanyId(companyUser.getCompanyId());
+        vo.setCompanyId(companyUser.getCompanyId());
         return vo;
     }
 

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

@@ -383,4 +383,6 @@ public interface FsStorePaymentScrmMapper
      * 批量更新发货状态
      * **/
     void batchUpadte(@Param("list") List<String> list);
+
+    void batchUpadteFailed(@Param("list") List<String> list);
 }

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

@@ -979,6 +979,7 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
                 Map<String, List<FsStorePaymentUsetVo>> paymentUsetVoMap = paymentList.stream().collect(Collectors.groupingBy(FsStorePaymentUsetVo::getAppId));
                 for (Map.Entry<String, List<FsStorePaymentUsetVo>> entry : paymentUsetVoMap.entrySet()) {
                     List<String> successList = new ArrayList<>();
+                    List<String> failedList = new ArrayList<>();
                     String appId = entry.getKey();
                     List<FsStorePaymentUsetVo> userPayments = entry.getValue();
                     final WxMaService wxService = WxMaConfiguration.getMaService(appId);
@@ -988,18 +989,19 @@ public class FsStorePaymentScrmServiceImpl implements IFsStorePaymentScrmService
                             if (uploadShippingInfoToWechat(wxService, v, uploadTime)) {
                                 successList.add(v.getBankTransactionId());
                             }else {
-                                successList.add(v.getBankTransactionId());
+                                failedList.add(v.getBankTransactionId());
                             }
                         }
                         //批量更新数据
                         if (!successList.isEmpty()) {
                             fsStorePaymentMapper.batchUpadte(successList);
                         }
+                        if(!failedList.isEmpty()){
+                            fsStorePaymentMapper.batchUpadteFailed(failedList);
+                        }
                     }
                 }
             }
-
-
             return R.ok(builder.toString().equals("") ? "操作成功!" : builder.toString());
         } catch (Exception e) {
             log.error("导入发货单快递信息失败", e);

+ 4 - 0
fs-service/src/main/java/com/fs/live/domain/LiveWatchUser.java

@@ -62,4 +62,8 @@ public class LiveWatchUser extends BaseEntity {
     @Excel(name = "用户所在位置")
     private String location;
 
+
+    private Integer pageNum;
+    private Integer pageSize;
+
 }

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

@@ -110,7 +110,7 @@ public interface LiveAfterSalesMapper {
     Map<Long, LiveAfterSales> findByOrderIds(@Param("orderIdList") List<Long> orderIdList);
 
     @Select({"<script> " +
-            "select *  from live_after_sales  " +
+            "select lo.order_code,las.*  from live_after_sales las left join live_order lo on lo.order_id = las.order_id " +
             "where 1=1 " +
             "<if test = 'maps.status != null and maps.status ==1   '> " +
             "and sales_status = 0 " +
@@ -119,9 +119,9 @@ public interface LiveAfterSalesMapper {
             "and sales_status = 3 " +
             "</if>" +
             "<if test = 'maps.userId != null    '> " +
-            "and user_id = #{maps.userId} " +
+            "and las.user_id = #{maps.userId} " +
             "</if>" +
-            "order by create_time desc "+
+            "order by las.create_time desc "+
             "</script>"})
     List<LiveAfterSalesQueryVO> selectLiveAfterSalesListQuery(@Param("maps") LiveAfterSalesQueryParam param);
 

+ 3 - 1
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -137,7 +137,8 @@ public interface LiveMapper
     @Select({"<script>" +
             "select * from live where 1=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>" +
+            " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " order by create_time desc" +
             " </script>"})
     List<Live> listLiveData(@Param("param") LiveDataParam param);
 
@@ -145,6 +146,7 @@ public interface LiveMapper
             "select count(1) from live where 1=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>" +
+            "  order by create_time desc " +
             " </script>"})
     int listLiveDataCount(@Param("param") LiveDataParam param);
 

+ 8 - 4
fs-service/src/main/java/com/fs/live/service/ILiveDataService.java

@@ -4,10 +4,7 @@ package com.fs.live.service;
 import com.fs.common.core.domain.R;
 import com.fs.live.domain.LiveData;
 import com.fs.live.param.LiveDataParam;
-import com.fs.live.vo.ColumnsConfigVo;
-import com.fs.live.vo.LiveUserFirstVo;
-import com.fs.live.vo.RecentLiveDataVo;
-import com.fs.live.vo.TrendDataVO;
+import com.fs.live.vo.*;
 
 import java.util.List;
 import java.util.Map;
@@ -163,4 +160,11 @@ public interface ILiveDataService {
      * @return 用户详情列表
      */
     R getLiveUserDetailListByServer(Long liveId);
+
+    /**
+     * 导出直播间用户详情数据
+     * @param liveId 直播间ID
+     * @return 导出VO列表
+     */
+    List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId);
 }

+ 73 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveDataServiceImpl.java

@@ -1075,4 +1075,77 @@ public class LiveDataServiceImpl implements ILiveDataService {
         return result;
     }
 
+    /**
+     * 导出直播间用户详情数据
+     * @param liveId 直播间ID
+     * @return 导出VO列表
+     */
+    @Override
+    public List<LiveUserDetailExportVO> exportLiveUserDetail(Long liveId) {
+        // 查询用户详情列表
+        List<LiveUserDetailVo> userDetailList = liveDataMapper.selectLiveUserDetailListBySql(liveId);
+        if (userDetailList == null || userDetailList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 转换为导出VO列表
+        List<LiveUserDetailExportVO> exportList = new ArrayList<>();
+        for (LiveUserDetailVo userDetail : userDetailList) {
+            LiveUserDetailExportVO exportVO = new LiveUserDetailExportVO();
+
+            // 用户基本信息
+            exportVO.setUserId(userDetail.getUserId());
+            exportVO.setUserName(userDetail.getUserName());
+
+            // 观看时长(秒转分钟)
+            exportVO.setLiveWatchDuration(formatSecondsToMinutes(userDetail.getLiveWatchDuration()));
+            exportVO.setPlaybackWatchDuration(formatSecondsToMinutes(userDetail.getPlaybackWatchDuration()));
+
+            // 计算总观看时长
+            Long totalSeconds = (userDetail.getLiveWatchDuration() != null ? userDetail.getLiveWatchDuration() : 0L) +
+                               (userDetail.getPlaybackWatchDuration() != null ? userDetail.getPlaybackWatchDuration() : 0L);
+//            exportVO.setTotalWatchDuration(formatSecondsToMinutes(totalSeconds));
+
+            // 订单信息
+            exportVO.setOrderCount(Math.toIntExact(userDetail.getOrderCount()));
+            exportVO.setOrderAmount(formatMoney(userDetail.getOrderAmount()));
+
+            // 公司和销售信息
+            exportVO.setCompanyName(userDetail.getCompanyName());
+            exportVO.setSalesName(userDetail.getSalesName());
+
+            // 是否完课(根据观看时长判断,假设30分钟以上为完课)
+            if (totalSeconds >= 1800) {
+                exportVO.setIsCompleted("是");
+            } else {
+                exportVO.setIsCompleted("否");
+            }
+
+            exportList.add(exportVO);
+        }
+
+        return exportList;
+    }
+
+    /**
+     * 格式化秒数为分钟(保留2位小数)
+     */
+    private String formatSecondsToMinutes(Long seconds) {
+        if (seconds == null || seconds == 0) {
+            return "0.00";
+        }
+        BigDecimal minutes = new BigDecimal(seconds).divide(new BigDecimal(60), 2, RoundingMode.HALF_UP);
+        return minutes.toString();
+    }
+
+    /**
+     * 格式化金额
+     */
+    private String formatMoney(BigDecimal value) {
+        if (value == null) {
+            return "0.00";
+        }
+        return value.setScale(2, RoundingMode.HALF_UP).toString();
+    }
+
 }

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

@@ -96,3 +96,4 @@ public class LiveDataDetailVo {
 }
 
 
+

+ 56 - 0
fs-service/src/main/java/com/fs/live/vo/LiveUserDetailExportVO.java

@@ -0,0 +1,56 @@
+package com.fs.live.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 直播间用户详情导出VO
+ *
+ * @author fs
+ * @date 2025-12-02
+ */
+@Data
+public class LiveUserDetailExportVO {
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    /** 用户名称 */
+    @Excel(name = "用户名称")
+    private String userName;
+
+    /** 直播观看时长(分钟) */
+    @Excel(name = "直播观看时长(分钟)")
+    private String liveWatchDuration;
+
+    /** 回放观看时长(分钟) */
+    @Excel(name = "回放观看时长(分钟)")
+    private String playbackWatchDuration;
+
+//    /** 总观看时长(分钟) */
+//    @Excel(name = "总观看时长(分钟)")
+//    private String totalWatchDuration;
+
+    /** 订单数 */
+    @Excel(name = "订单数")
+    private Integer orderCount;
+
+    /** 订单金额(元) */
+    @Excel(name = "订单金额(元)")
+    private String orderAmount;
+
+    /** 分公司 */
+    @Excel(name = "分公司")
+    private String companyName;
+
+    /** 销售 */
+    @Excel(name = "销售")
+    private String salesName;
+
+    /** 是否完课 */
+    @Excel(name = "是否完课")
+    private String isCompleted;
+
+}
+

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

@@ -38,3 +38,4 @@ public class LiveUserDetailVo {
 }
 
 
+

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

@@ -26,3 +26,4 @@ public class ProductSalesVo {
 }
 
 
+

+ 3 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactTransferLogListVO.java

@@ -24,6 +24,7 @@ public class QwExternalContactTransferLogListVO {
     /**
     * 员工部门
     */
+    @Excel(name = "员工部门")
     private String deptName;
 
     /** 企微外部联系人id */
@@ -41,6 +42,8 @@ public class QwExternalContactTransferLogListVO {
     /** 状态 */
     @Excel(name = "状态")
     private Integer status;
+
+    @Excel(name = "转接时间",dateFormat="yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
 

+ 13 - 7
fs-service/src/main/java/com/fs/system/service/impl/SysDictDataServiceImpl.java

@@ -12,7 +12,7 @@ import com.fs.system.service.ISysDictDataService;
 
 /**
  * 字典 业务层处理
- * 
+ *
 
  */
 @Service
@@ -23,7 +23,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 根据条件分页查询字典数据
-     * 
+     *
      * @param dictData 字典数据信息
      * @return 字典数据集合信息
      */
@@ -35,7 +35,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 根据字典类型和字典键值查询字典数据信息
-     * 
+     *
      * @param dictType 字典类型
      * @param dictValue 字典键值
      * @return 字典标签
@@ -48,7 +48,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 根据字典数据ID查询信息
-     * 
+     *
      * @param dictCode 字典数据ID
      * @return 字典数据
      */
@@ -60,7 +60,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 批量删除字典数据信息
-     * 
+     *
      * @param dictCodes 需要删除的字典数据ID
      * @return 结果
      */
@@ -78,13 +78,19 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 新增保存字典数据信息
-     * 
+     *
      * @param data 字典数据信息
      * @return 结果
      */
     @Override
     public int insertDictData(SysDictData data)
     {
+        // 数据库数据已经有问题了,唯一索引创建不了,在这个地方 dict_value dict_type 查询有记录不允许插入
+        SysDictData label=dictDataMapper.selectDictDataByTypeAndValue( data.getDictType(),data.getDictValue());
+        if(label!=null){
+            throw new RuntimeException("字典数据已经存在,请更换数据键值");
+        }
+
         int row = dictDataMapper.insertDictData(data);
         if (row > 0)
         {
@@ -96,7 +102,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService
 
     /**
      * 修改保存字典数据信息
-     * 
+     *
      * @param data 字典数据信息
      * @return 结果
      */

+ 27 - 0
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -320,4 +320,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         GROUP BY l.company_id, DATE_FORMAT(l.create_time, '%Y-%m')
     </select>
 
+
+    <select id="selectExpireLinkIds" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT log_id
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ORDER BY log_id ASC
+            LIMIT #{limit} OFFSET #{offset}
+        ]]>
+    </select>
+
+    <delete id="batchDeleteByIds">
+        DELETE FROM fs_course_traffic_log
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="countExpireLink" resultType="java.lang.Long">
+        <![CDATA[
+        SELECT COUNT(*)
+        FROM fs_course_traffic_log
+        WHERE create_time < #{createTime}
+        ]]>
+    </select>
+
 </mapper>

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

@@ -109,6 +109,7 @@
             <if test="listingEndTime != null">listing_end_time,</if>
             <if test="userId != null">user_id,</if>
             <if test="isFirst != null">is_first,</if>
+            <if test="isSpeed != null">is_speed,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="fileId != null">#{fileId},</if>
@@ -148,6 +149,7 @@
             <if test="projectId != null">#{projectId},</if>
             <if test="userId != null">#{userId},</if>
             <if test="isFirst != null">#{isFirst},</if>
+            <if test="isSpeed != null">#{isSpeed},</if>
         </trim>
     </insert>
     <insert id="insertBatchFsUserCourseVideo" parameterType="FsUserCourseVideo" useGeneratedKeys="true" keyProperty="videoId">
@@ -236,6 +238,7 @@
             <if test="listingEndTime != null">listing_end_time = #{listingEndTime},</if>
             <if test="projectId != null">project_id = #{projectId},</if>
             <if test="isFirst != null">is_first = #{isFirst},</if>
+            <if test="isSpeed != null">is_speed = #{isSpeed},</if>
         </trim>
         where video_id = #{videoId}
     </update>
@@ -268,6 +271,7 @@
         video.course_id,
         video.STATUS,
         video.course_sort,
+        video.is_speed,
         course.course_name,
         fcpd.period_id,
         fcp.period_name,
@@ -323,6 +327,7 @@
         video.course_id,
         video.STATUS,
         video.course_sort,
+        video.is_speed,
         course.course_name,
         fcpd.period_id,
         fcp.period_name,

+ 9 - 1
fs-service/src/main/resources/mapper/hisStore/FsStorePaymentScrmMapper.xml

@@ -216,7 +216,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             fs_store_payment_scrm sp
                 LEFT JOIN fs_user fu ON sp.user_id = fu.user_id
         WHERE
-            sp.status = 1 and sp.is_shipment = 0 and sp.business_type = 1 AND sp.pay_time > '2025-11-27 00:00:00' LIMIT 500
+            sp.status = 1  and sp.app_id is not null  and sp.is_shipment = 0 and sp.business_type = 1 AND sp.pay_time > '2025-11-27 00:00:00'
+            order by sp.pay_time desc LIMIT 500
     </select>
 
     <update id="batchUpadte">
@@ -225,4 +226,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{bankTransactionId}
         </foreach>
     </update>
+
+    <update id="batchUpadteFailed">
+        update fs_store_payment_scrm set is_shipment = -1 where bank_transaction_id in
+        <foreach item="bankTransactionId" collection="list" open="(" separator="," close=")">
+            #{bankTransactionId}
+        </foreach>
+    </update>
 </mapper>

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

@@ -412,7 +412,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <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.live_flag = 1 AND lwu.replay_flag = 0 THEN lwu.user_id END) +  COUNT(DISTINCT CASE WHEN lwu.live_flag = 0 AND lwu.replay_flag = 1 THEN lwu.user_id END)) 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
@@ -536,9 +536,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             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
+        left join live_user_first_entry lufe on lwu.live_id = lufe.live_id and lwu.user_id = lufe.user_id
+        LEFT JOIN company c ON lufe.company_id = c.company_id
+        LEFT JOIN company_user cu ON lufe.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

+ 19 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -19,6 +19,7 @@ import com.fs.course.param.*;
 import com.fs.course.service.*;
 import com.fs.course.service.impl.TencentCloudCosService;
 import com.fs.course.vo.*;
+import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.his.enums.FsUserOperationEnum;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.sop.domain.QwSop;
@@ -360,6 +361,14 @@ public class CourseQwController extends AppBaseController {
 //        courseVideoService.updateVideoUrl();
     }
 
+    @Autowired
+    private IFsCourseTrafficLogService courseTrafficLogService;
+
+    @GetMapping("/test4")
+    public void test4() {
+        courseTrafficLogService.batchDelTraffic();
+    }
+
     @Login
     @ApiOperation("保存评论数据")
     @PostMapping("/saveMsg")
@@ -453,4 +462,14 @@ public class CourseQwController extends AppBaseController {
 
     }
 
+    @Autowired
+    private IFastgptEventLogTotalService fastgptEventLogTotalService;
+
+    @ApiOperation("手动同步AI的token")
+    @GetMapping("/eventLogTotals")
+    public R eventLogTotals(String startTime,String endTime){
+        fastgptEventLogTotalService.eventLogTotals(startTime,endTime);
+        return R.ok();
+    }
+
 }

+ 48 - 1
fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java

@@ -9,6 +9,13 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.service.IFsStoreProductService;
+import com.fs.hisStore.domain.FsStoreProductAttrScrm;
+import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import com.fs.hisStore.domain.FsStoreProductRelationScrm;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
+import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
+import com.fs.hisStore.service.IFsStoreProductRelationScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
 import com.fs.live.domain.LiveGoods;
 import com.fs.live.service.ILiveGoodsService;
@@ -17,6 +24,7 @@ import com.github.pagehelper.PageInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -37,6 +45,13 @@ public class LiveGoodsController extends AppBaseController
 
     @Autowired
     private RedisCache redisCache;
+    @Autowired
+    private IFsStoreProductAttrScrmService attrService;
+    @Autowired
+    private IFsStoreProductRelationScrmService productRelationService;
+
+    @Autowired
+    private IFsStoreProductAttrValueScrmService attrValueService;
 
     /**
      * 查询直播商品列表
@@ -119,7 +134,39 @@ public class LiveGoodsController extends AppBaseController
     @GetMapping("/liveGoodsDetail/{productId}")
     public R liveGoodsDetail(@PathVariable Long productId)
     {
-        return R.ok().put("data",fsStoreProductService.selectFsStoreProductById(productId));
+        FsStoreProductScrm product = fsStoreProductService.selectFsStoreProductById(productId);
+        if(product==null){
+            return R.error("商品不存在或已下架");
+        }
+        List<FsStoreProductAttrScrm> productAttr=attrService.selectFsStoreProductAttrByProductId(productId);
+        List<FsStoreProductAttrValueScrm> productValues=attrValueService.selectFsStoreProductAttrValueByProductId(productId);
+//获取用户的TOKEN写入足迹
+        String userId=getUserId();
+        if(userId!=null){
+            FsStoreProductRelationScrm productRelation=new FsStoreProductRelationScrm();
+            productRelation.setIsDel(0);
+            productRelation.setUserId(Long.parseLong(userId));
+            productRelation.setProductId(product.getProductId());
+            productRelation.setType("foot");
+            List<FsStoreProductRelationScrm> productRelations=productRelationService.selectFsStoreProductRelationList(productRelation);
+            if(productRelations!=null&&productRelations.size()>0){
+                FsStoreProductRelationScrm relation=productRelations.get(0);
+                relation.setUpdateTime(new Date());
+                productRelationService.updateFsStoreProductRelation(relation);
+            }
+            else{
+                FsStoreProductRelationScrm relation=new FsStoreProductRelationScrm();
+                relation.setUserId(Long.parseLong(userId));
+                relation.setIsDel(0);
+                relation.setProductId(product.getProductId());
+                relation.setUpdateTime(new Date());
+                relation.setType("foot");
+                relation.setCreateTime(new Date());
+                relation.setUpdateTime(new Date());
+                productRelationService.insertFsStoreProductRelation(relation);
+            }
+        }
+        return R.ok().put("product",product).put("productAttr",productAttr).put("productValues",productValues);
     }
 
     /**

+ 5 - 4
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -722,6 +722,11 @@ public class LiveOrderController extends AppBaseController
     @PostMapping("/editPayType")
     @Transactional
     public R editPayType(HttpServletRequest request, @Validated @RequestBody FsStoreOrderPayParam param) {
+
+        String orderId=redisCache.getCacheObject("isPaying:"+param.getOrderId());
+        if(StringUtils.isNotEmpty(orderId)&&orderId.equals(param.getOrderId().toString())){
+            return R.error("正在支付中...");
+        }
         LiveOrder order=orderService.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
         if(order==null){
             return R.error("订单不存在");
@@ -729,10 +734,6 @@ public class LiveOrderController extends AppBaseController
         if(order.getStatus()!= OrderInfoEnum.STATUS_0.getValue()){
             return R.error("订单状态不正确");
         }
-        String orderId=redisCache.getCacheObject("isPaying:"+order.getOrderId());
-        if(StringUtils.isNotEmpty(orderId)&&orderId.equals(order.getOrderId().toString())){
-            return R.error("正在支付中...");
-        }
         List<LiveOrderPayment> payments = liveOrderPaymentMapper.selectLiveOrderPaymentByOrderId(order.getOrderId());
         if(payments.size()>0){
             for(LiveOrderPayment payment : payments){

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveWatchUserController.java

@@ -5,6 +5,7 @@ import com.fs.app.facade.LiveFacadeService;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.live.domain.LiveWatchUser;

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/facade/LiveFacadeService.java

@@ -22,4 +22,5 @@ public interface LiveFacadeService {
     R redClaim(RedPO red);
 
     R couponClaim(CouponPO coupon);
+
 }

+ 23 - 19
fs-user-app/src/main/java/com/fs/app/facade/impl/LiveFacadeServiceImpl.java

@@ -90,27 +90,30 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
 
     @Override
     public TableDataInfo watchUserList(LiveWatchUser param) {
-        List<LiveWatchUserVO> liveWatchUserVOS;
-        String setKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, param.getLiveId());
-        Map<Object, Object> hashEntries = redisCache.hashEntries(setKey);
-        if (CollUtil.isEmpty(hashEntries)) {
-            liveWatchUserVOS = liveWatchUserService.asyncToCache(param.getLiveId());
-        } else {
-            liveWatchUserVOS = hashEntries.values().stream()
-                    .map(value -> {
-                        try {
-                            return JSONUtil.toBean(JSONUtil.parseObj(value), LiveWatchUserVO.class);
-                        } catch (Exception e) {
-                            log.error("反序列化LiveWatchUserVO失败: {}", value, e);
-                            return null;
-                        }
-                    })
-                    .filter(Objects::nonNull)
-                    .collect(Collectors.toList());
-        }
-        return getDataTable(liveWatchUserVOS);
+//        List<LiveWatchUserVO> liveWatchUserVOS;
+//        String setKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, param.getLiveId());
+//        Map<Object, Object> hashEntries = redisCache.hashEntries(setKey);
+//        if (CollUtil.isEmpty(hashEntries)) {
+//            liveWatchUserVOS = liveWatchUserService.asyncToCache(param.getLiveId());
+//        } else {
+//            liveWatchUserVOS = hashEntries.values().stream()
+//                    .map(value -> {
+//                        try {
+//                            return JSONUtil.toBean(JSONUtil.parseObj(value), LiveWatchUserVO.class);
+//                        } catch (Exception e) {
+//                            log.error("反序列化LiveWatchUserVO失败: {}", value, e);
+//                            return null;
+//                        }
+//                    })
+//                    .filter(Objects::nonNull)
+//                    .collect(Collectors.toList());
+//        }
+//        return getDataTable(liveWatchUserVOS);
+        return null;
     }
 
+
+
     @Override
     public R liveDetail(Long id) {
         Object o = redisCache.hashGet(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, String.valueOf(id));
@@ -174,6 +177,7 @@ public class LiveFacadeServiceImpl extends BaseController implements LiveFacadeS
         return iLiveCouponService.claimCoupon(coupon);
     }
 
+
     @Override
     @DistributeLock(keyExpression = "#lottery.liveId +'_'+#lottery.userId", scene = "draw_claim")
     public R drawClaim(LotteryPO lottery) {