Browse Source

合并订单导出

yuhongqi 15 hours ago
parent
commit
27e39b7a62

+ 197 - 0
fs-admin/src/main/java/com/fs/live/controller/OrderController.java

@@ -10,6 +10,9 @@ import com.fs.his.utils.PhoneUtil;
 import com.fs.hisStore.service.IMergedOrderService;
 import com.fs.live.param.MergedOrderQueryParam;
 import com.fs.live.vo.MergedOrderVO;
+import com.fs.live.vo.MergedOrderExportVO;
+import com.fs.common.utils.poi.ExcelUtil;
+import org.springframework.beans.BeanUtils;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -19,7 +22,10 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 合并订单Controller
@@ -34,6 +40,8 @@ public class OrderController extends BaseController
 {
     @Autowired
     private IMergedOrderService mergedOrderService;
+    // 设置最大导出数量限制为20000条
+    private static final int maxExportCount = 20000;
 
     /**
      * 查询合并订单列表
@@ -49,7 +57,196 @@ public class OrderController extends BaseController
             vo.setPhone(ParseUtils.parsePhone(vo.getPhone()));
             vo.setSalesPhone(ParseUtils.parsePhone(vo.getSalesPhone()));
             vo.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            vo.setCost(BigDecimal.ZERO);
         }
         return getDataTable(list);
     }
+
+    /**
+     * 导出合并订单列表
+     */
+    @ApiOperation("导出合并订单列表")
+    @Log(title = "合并订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        
+        // 转换为导出VO
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, false);
+        
+        // 如果数据量在限制范围内,正常导出
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单数据");
+    }
+
+    /**
+     * 导出合并订单明细
+     */
+    @ApiOperation("导出合并订单明细")
+    @Log(title = "合并订单明细", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItems")
+    public AjaxResult exportItems(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细");
+    }
+
+    /**
+     * 导出合并订单(明文)
+     */
+    @ApiOperation("导出合并订单(明文)")
+    @Log(title = "合并订单(明文)", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportDetails")
+    public AjaxResult exportDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        // 转换为导出VO(明文模式,不脱敏)
+        List<MergedOrderExportVO> exportList = convertToExportVO(list, true);
+
+        ExcelUtil<MergedOrderExportVO> util = new ExcelUtil<>(MergedOrderExportVO.class);
+        return util.exportExcel(exportList, "合并订单(明文)");
+    }
+
+    /**
+     * 导出合并订单明细(明文)
+     */
+    @ApiOperation("导出合并订单明细(明文)")
+    @Log(title = "合并订单明细(明文)", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItemsDetails")
+    public AjaxResult exportItemsDetails(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单明细(明文)");
+    }
+
+    /**
+     * 导出合并订单发货单
+     */
+    @ApiOperation("导出合并订单发货单")
+    @Log(title = "合并订单发货单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportShipping")
+    public AjaxResult exportShipping(MergedOrderQueryParam param)
+    {
+        // 先查询数据,限制查询20001条,用于判断是否超过限制
+        PageHelper.startPage(1, maxExportCount + 1);
+        List<MergedOrderVO> list = mergedOrderService.selectMergedOrderList(param);
+        // 如果查询结果超过20000条,返回错误提示
+        if (list != null && list.size() > maxExportCount) {
+            return AjaxResult.error("导出数据量超过限制,最多只能导出" + maxExportCount + "条数据,请缩小查询范围后重试");
+        }
+        ExcelUtil<MergedOrderVO> util = new ExcelUtil<>(MergedOrderVO.class);
+        return util.exportExcel(list, "合并订单发货单");
+    }
+
+    /**
+     * 将 MergedOrderVO 转换为 MergedOrderExportVO
+     * @param list 原始数据列表
+     * @param isPlainText 是否为明文模式(true:不脱敏,false:脱敏)
+     * @return 导出VO列表
+     */
+    private List<MergedOrderExportVO> convertToExportVO(List<MergedOrderVO> list, boolean isPlainText)
+    {
+        if (list == null || list.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        return list.stream().map(vo -> {
+            MergedOrderExportVO exportVO = new MergedOrderExportVO();
+            
+            // 订单基本信息(参考 FsStoreOrderItemExportVO 的顺序)
+            exportVO.setOrderCode(vo.getOrderCode());
+            exportVO.setStatus(vo.getStatus() != null ? String.valueOf(vo.getStatus()) : null);
+            exportVO.setUserId(vo.getUserId());
+            
+            // 产品信息
+            exportVO.setProductName(vo.getProductName());
+            exportVO.setBarCode(vo.getBarCode());
+            exportVO.setProductSpec(vo.getProductSpec());
+            exportVO.setTotalNum(vo.getTotalNum());
+            exportVO.setPrice(vo.getTotalPrice()); // 产品价格使用订单总价
+            exportVO.setCost(vo.getCost());
+            exportVO.setFPrice(null); // 结算价,合并订单暂无此字段
+            exportVO.setCateName(vo.getCateName());
+            
+            // 收货信息
+            exportVO.setRealName(vo.getRealName());
+            if (isPlainText) {
+                exportVO.setUserPhone(vo.getUserPhone());
+                exportVO.setUserAddress(vo.getUserAddress());
+            } else {
+                exportVO.setUserPhone(ParseUtils.parsePhone(vo.getUserPhone()));
+                exportVO.setUserAddress(ParseUtils.parseAddress(vo.getUserAddress()));
+            }
+            
+            // 时间信息
+            exportVO.setCreateTime(vo.getCreateTime());
+            exportVO.setPayTime(vo.getPayTime());
+            
+            // 物流信息
+            exportVO.setDeliverySn(vo.getDeliveryCode()); // 快递公司编号,合并订单暂无此字段
+            exportVO.setDeliveryName(vo.getDeliveryName()); // 快递公司,合并订单暂无此字段
+            exportVO.setDeliveryId(vo.getDeliveryId());
+            
+            // 公司和销售信息
+            exportVO.setCompanyName(vo.getCompanyName());
+            exportVO.setCompanyUserNickName(vo.getCompanyUserNickName());
+            
+            // 套餐信息
+            exportVO.setPackageName(null); // 套餐名称,合并订单暂无此字段
+            exportVO.setGroupBarCode(null); // 组合码,合并订单暂无此字段
+            
+            // 凭证信息
+            exportVO.setIsUpload(null); // 是否上传凭证,合并订单暂无此字段
+            exportVO.setUploadTime(null); // 上传时间,合并订单暂无此字段
+            
+            // 档期信息
+            exportVO.setScheduleName(null); // 归属档期,合并订单暂无此字段
+            
+            // 银行交易流水号
+            exportVO.setBankTransactionId(vo.getBankTransactionId());
+            
+            // 金额信息
+            exportVO.setTotalPrice(vo.getTotalPrice());
+            exportVO.setPayPrice(vo.getPayPrice());
+            exportVO.setPayMoney(vo.getPayMoney());
+            exportVO.setPayPostage(vo.getPayDelivery()); // 额外运费,合并订单暂无此字段
+            exportVO.setPayDelivery(vo.getPayDelivery());
+            
+            return exportVO;
+        }).collect(Collectors.toList());
+    }
 }

+ 4 - 2
fs-live-app/src/main/java/com/fs/live/task/Task.java

@@ -665,12 +665,14 @@ public class Task {
                     if (currentTimeMillis >= endTimeMillis) {
                         log.info("直播间视频播放完成,开始打标签: liveId={}, startTime={}, videoDuration={}, endTime={}, currentTime={}", 
                                 liveId, startTimeMillis, videoDuration, endTimeMillis, currentTimeMillis);
+
+                        // 标记为已处理,稍后删除缓存
+                        processedLiveIds.add(liveId);
                         
                         // 调用打标签方法
                         liveWatchUserService.qwTagMarkByLiveWatchLog(liveId);
                         
-                        // 标记为已处理,稍后删除缓存
-                        processedLiveIds.add(liveId);
+
                     }
                 } catch (Exception e) {
                     log.error("处理直播间打标签缓存异常: key={}, error={}", key, e.getMessage(), e);

+ 91 - 3
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.LiveKeysConstant;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.date.DateUtil;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.hisStore.domain.FsUserScrm;
@@ -26,6 +27,7 @@ import com.fs.live.vo.LiveGoodsVo;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.time.DateUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
@@ -336,6 +338,9 @@ public class WebSocketServer {
                                 redisCache.redisTemplate.opsForHash().put(hashKey, userIdField, currentDuration.toString());
                                 // 设置过期时间(2小时)
                                 redisCache.redisTemplate.expire(hashKey, 2, TimeUnit.HOURS);
+                                
+                                // 实时更新用户看课状态(仅在直播期间)
+                                updateWatchLogTypeInRealTime(liveId, watchUserId, currentDuration);
                             }
                         } catch (Exception e) {
                             log.error("心跳更新观看时长失败, liveId={}, userId={}", liveId, watchUserId, e);
@@ -1154,6 +1159,87 @@ public class WebSocketServer {
         }
     }
     
+    /**
+     * 实时更新用户看课状态(在心跳时调用)
+     * 在直播期间实时更新用户的看课状态,而不是等到关闭 WebSocket 或清理无效会话时才更新
+     * @param liveId 直播间ID
+     * @param userId 用户ID
+     * @param watchDuration 观看时长(秒)
+     */
+    @Async
+    public void updateWatchLogTypeInRealTime(Long liveId, Long userId, Long watchDuration) {
+        try {
+            // 获取当前直播/回放状态
+            Map<String, Integer> flagMap = liveWatchUserService.getLiveFlagWithCache(liveId);
+            Integer currentLiveFlag = flagMap.get("liveFlag");
+            
+            // 只在直播状态(liveFlag = 1)时更新
+            if (currentLiveFlag == null || currentLiveFlag != 1) {
+                return;
+            }
+            
+            // 获取用户的 companyId 和 companyUserId(使用带缓存的查询方法)
+            LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService.selectEntityByLiveIdUserIdWithCache(liveId, userId);
+            if (liveUserFirstEntry == null) {
+                return;
+            }
+            
+            Long companyId = liveUserFirstEntry.getCompanyId();
+            Long companyUserId = liveUserFirstEntry.getCompanyUserId();
+            
+            // 如果 companyId 和 companyUserId 有效,则更新看课状态
+            if (companyId != null && companyId > 0 && companyUserId != null && companyUserId > 0) {
+                // 检查是否达到关键观看时长节点,在这些节点实时更新
+                // 关键节点:3分钟(180秒)、20分钟(1200秒)、30分钟(1800秒)
+                boolean isKeyDuration = (watchDuration == 180 || watchDuration == 1200 || watchDuration == 1800) ||
+                                       (watchDuration > 180 && watchDuration % 60 == 0); // 每分钟更新一次
+                
+                // 使用 Redis 缓存控制更新频率,避免频繁更新数据库
+                // 策略:在关键节点立即更新,其他时候每60秒更新一次
+                String updateLockKey = "live:watch:log:update:lock:" + liveId + ":" + userId;
+                String lastUpdateKey = "live:watch:log:last:duration:" + liveId + ":" + userId;
+                
+                // 获取上次更新的时长
+                Long lastUpdateDuration = redisCache.getCacheObject(lastUpdateKey);
+                
+                // 如果达到关键节点,或者距离上次更新已超过60秒,则更新
+                boolean shouldUpdate = false;
+                if (isKeyDuration) {
+                    // 关键节点立即更新
+                    shouldUpdate = true;
+                } else if (lastUpdateDuration == null || (watchDuration - lastUpdateDuration) >= 60) {
+                    // 每60秒更新一次
+                    shouldUpdate = true;
+                }
+                
+                if (shouldUpdate) {
+                    // 使用分布式锁,避免并发更新(锁超时时间10秒)
+                    Boolean canUpdate = redisCache.setIfAbsent(updateLockKey, "1", 10, TimeUnit.SECONDS);
+                    
+                    if (Boolean.TRUE.equals(canUpdate)) {
+                        // 异步更新,避免阻塞心跳处理
+                        CompletableFuture.runAsync(() -> {
+                            try {
+                                updateLiveWatchLogTypeByDuration(liveId, userId, companyId, companyUserId, watchDuration);
+                                // 更新上次更新的时长
+                                redisCache.setCacheObject(lastUpdateKey, watchDuration, 2, TimeUnit.HOURS);
+                            } catch (Exception e) {
+                                log.error("实时更新看课状态异常:liveId={}, userId={}, error={}", 
+                                        liveId, userId, e.getMessage(), e);
+                            } finally {
+                                // 释放锁
+                                redisCache.deleteObject(updateLockKey);
+                            }
+                        });
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("实时更新看课状态异常:liveId={}, userId={}, error={}", 
+                    liveId, userId, e.getMessage(), e);
+        }
+    }
+    
     /**
      * 根据在线时长更新 LiveWatchLog 的 logType
      * @param liveId 直播间ID
@@ -1165,8 +1251,8 @@ public class WebSocketServer {
     private void updateLiveWatchLogTypeByDuration(Long liveId, Long userId, Long companyId, 
                                                    Long companyUserId, Long onlineSeconds) {
         try {
-            // 获取直播视频总时长(videoType = 1 的视频)
-            List<LiveVideo> videos = liveVideoService.listByLiveId(liveId, 1);
+            // 获取直播视频总时长(videoType = 1 的视频,使用带缓存的查询方法
+            List<LiveVideo> videos = liveVideoService.listByLiveIdWithCache(liveId, 1);
             long totalVideoDuration = 0L;
             if (videos != null && !videos.isEmpty()) {
                 totalVideoDuration = videos.stream()
@@ -1186,7 +1272,7 @@ public class WebSocketServer {
             if (logs == null || logs.isEmpty()) {
                 return;
             }
-            
+            Date now = DateUtil.getDate();
             for (LiveWatchLog log : logs) {
                 boolean needUpdate = false;
                 Integer newLogType = log.getLogType();
@@ -1199,11 +1285,13 @@ public class WebSocketServer {
                 // ③ 如果直播视频 >= 40分钟,在线时长 >= 30分钟,logType 设置为 2(完课)
                 else if (totalVideoDuration >= 2400 && onlineSeconds >= 1800) { // 40分钟 = 2400秒,30分钟 = 1800秒
                     newLogType = 2;
+                    log.setFinishTime(now);
                     needUpdate = true;
                 }
                 // 如果直播视频 >= 20分钟且 < 40分钟,在线时长 >= 20分钟,logType 设置为 2(完课)
                 else if (totalVideoDuration >= 1200 && totalVideoDuration < 2400 && onlineSeconds >= 1200) { // 20分钟 = 1200秒
                     newLogType = 2;
+                    log.setFinishTime(now);
                     needUpdate = true;
                 }
                 

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

@@ -4227,7 +4227,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 this.updateFsStoreOrder(order);
             }
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             if((order.getPayType().equals("1")||order.getPayType().equals("2")||order.getPayType().equals("3")) && order.getPayMoney().compareTo(new BigDecimal(0))>0){
                 String json = configService.selectConfigByKey(STORE_PAY_CONF);
                 FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
@@ -4366,7 +4366,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             if(!order.getIsPayRemain().equals(0)){
                 return R.error("此订单已支付");
             }
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();
@@ -4485,7 +4485,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             //易宝支付
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();
             storePayment.setCompanyId(order.getCompanyId());
@@ -4584,7 +4584,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
         FsUserScrm user=userService.selectFsUserById(order.getUserId());
         if(user!=null){
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey(STORE_PAY_CONF);
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             FsStorePaymentScrm storePayment=new FsStorePaymentScrm();

+ 34 - 0
fs-service/src/main/java/com/fs/live/param/MergedOrderQueryParam.java

@@ -4,6 +4,7 @@ import com.fs.common.param.BaseQueryParam;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 /**
  * 合并订单查询参数
@@ -81,5 +82,38 @@ public class MergedOrderQueryParam extends BaseQueryParam implements Serializabl
 
     /** 员工姓名(company_user表的nick_name) */
     private String companyUserNickName;
+
+    /** 商品规格 */
+    private String productSpec;
+
+    /** 商品数量 */
+    private Integer totalNum;
+
+    /** 销售价格 */
+    private BigDecimal price;
+
+    /** 收货地址 */
+    private String userAddress;
+
+    /** 成本价格 */
+    private BigDecimal cost;
+
+    /** 供应商名称 */
+    private String supplierName;
+
+    /** 订单类型 */
+    private String orderType;
+
+    /** 上传凭证:0-未上传,1-已上传 */
+    private String isUpload;
+
+    /** 档期归属ID */
+    private Long scheduleId;
+
+    /** ERP账户 */
+    private String erpAccount;
+
+    /** ERP电话 */
+    private String erpPhoneNumber;
 }
 

+ 8 - 0
fs-service/src/main/java/com/fs/live/service/ILiveUserFirstEntryService.java

@@ -66,4 +66,12 @@ public interface ILiveUserFirstEntryService {
     List<LiveUserFirstProfit> selectLiveProfitList();
 
     LiveUserFirstEntry selectEntityByLiveIdUserId(long liveId, long userId);
+
+    /**
+     * 查询用户首次进入直播间记录(带Redis缓存)
+     * @param liveId 直播间ID
+     * @param userId 用户ID
+     * @return 用户首次进入直播间记录
+     */
+    LiveUserFirstEntry selectEntityByLiveIdUserIdWithCache(long liveId, long userId);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/live/service/ILiveVideoService.java

@@ -86,4 +86,12 @@ public interface ILiveVideoService
     LiveVideo selectLiveVideoByLiveIdAndType(Long id, int i);
 
     void updateFinishStatus(String string);
+
+    /**
+     * 根据直播间ID和类型查询视频列表(带Redis缓存)
+     * @param liveId 直播间ID
+     * @param type 视频类型
+     * @return 视频列表
+     */
+    List<LiveVideo> listByLiveIdWithCache(Long liveId, Integer type);
 }

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

@@ -2012,6 +2012,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCreateTime(new Date());
         liveOrder.setUpdateTime(new Date());
         liveOrder.setPayDelivery(deliveryMoney);
+        liveOrder.setPayPostage(deliveryMoney);
         liveOrder.setProductId(fsStoreProduct.getProductId());
         liveOrder.setStatus(OrderInfoEnum.STATUS_0.getValue());
         liveOrder.setPayType("1");
@@ -2971,6 +2972,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 return R.error("订单生成失败,请重试");
             }
             LiveOrderPayment storePayment = liveOrderPaymentMapper.selectByBuissnessId(liveOrder.getOrderId());
+            storePayment.setAppId(liveOrder.getAppId());
             if (storePayment != null) {
                 storePayment.setStatus(1);
                 liveOrderPaymentMapper.updateLiveOrderPayment(storePayment);
@@ -3136,7 +3138,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 storePayment.setOpenId(openId);
                 storePayment.setUserId(user.getUserId());
                 storePayment.setBusinessId(String.valueOf(order.getOrderId()));
-                storePayment.setAppId(fsPayConfig.getAppId() == null ? "" : fsPayConfig.getAppId());
+                storePayment.setAppId(param.getAppId());
                 liveOrderPaymentMapper.insertLiveOrderPayment(storePayment);
 
                 if (fsPayConfig.getType().equals("hf")){
@@ -3645,6 +3647,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         liveOrder.setCreateTime(new Date());
         liveOrder.setUpdateTime(new Date());
         liveOrder.setPayDelivery(deliveryMoney);
+        // todo 过两个月,把这个字段切过来 2025 1216
+        liveOrder.setPayPostage(deliveryMoney);
         liveOrder.setProductId(fsStoreProduct.getProductId());
         liveOrder.setStatus(OrderInfoEnum.STATUS_0.getValue());
         liveOrder.setPayType("1");
@@ -3793,6 +3797,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
 
         liveOrder.setPayDelivery(storePostage);
+        liveOrder.setPayPostage(storePostage);
 
         return storePostage;
     }

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

@@ -3,6 +3,9 @@ package com.fs.live.service.impl;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 
 import com.fs.live.vo.LiveUserFirstProfit;
@@ -22,6 +25,9 @@ import com.fs.live.service.ILiveUserFirstEntryService;
 public class LiveUserFirstEntryServiceImpl implements ILiveUserFirstEntryService {
     @Autowired
     private LiveUserFirstEntryMapper baseMapper;
+    
+    @Autowired
+    private RedisCache redisCache;
 
     /**
      * 查询用户每日首次进入直播间记录
@@ -112,4 +118,26 @@ public class LiveUserFirstEntryServiceImpl implements ILiveUserFirstEntryService
     public LiveUserFirstEntry selectEntityByLiveIdUserId(long liveId,long userId) {
         return baseMapper.selectEntityByLiveIdUserId(liveId, userId);
     }
+
+    @Override
+    public LiveUserFirstEntry selectEntityByLiveIdUserIdWithCache(long liveId, long userId) {
+        // Redis缓存键
+        String cacheKey = "live:user:first:entry:" + liveId + ":" + userId;
+        
+        // 先从缓存中获取
+        LiveUserFirstEntry cachedEntry = redisCache.getCacheObject(cacheKey);
+        if (cachedEntry != null) {
+            return cachedEntry;
+        }
+        
+        // 缓存未命中,从数据库查询
+        LiveUserFirstEntry entry = baseMapper.selectEntityByLiveIdUserId(liveId, userId);
+        
+        // 将查询结果存入缓存,缓存时间1小时
+        if (entry != null) {
+            redisCache.setCacheObject(cacheKey, entry, 1, TimeUnit.HOURS);
+        }
+        
+        return entry;
+    }
 }

+ 27 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveVideoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.live.service.impl;
 
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.live.domain.LiveVideo;
@@ -17,6 +18,7 @@ import java.net.URI;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 直播视频Service业务层处理
@@ -29,6 +31,9 @@ public class LiveVideoServiceImpl implements ILiveVideoService
 {
     @Autowired
     private LiveVideoMapper liveVideoMapper;
+    
+    @Autowired
+    private RedisCache redisCache;
 
     /**
      * 查询直播视频
@@ -180,4 +185,26 @@ public class LiveVideoServiceImpl implements ILiveVideoService
         liveVideoMapper.updateFinishStatus(string);
     }
 
+    @Override
+    public List<LiveVideo> listByLiveIdWithCache(Long liveId, Integer type) {
+        // Redis缓存键
+        String cacheKey = "live:video:list:" + liveId + ":" + type;
+        
+        // 先从缓存中获取
+        List<LiveVideo> cachedVideos = redisCache.getCacheObject(cacheKey);
+        if (cachedVideos != null && !cachedVideos.isEmpty()) {
+            return cachedVideos;
+        }
+        
+        // 缓存未命中,从数据库查询
+        List<LiveVideo> videos = liveVideoMapper.selectByIdAndType(liveId, type);
+        
+        // 将查询结果存入缓存,缓存时间1小时
+        if (videos != null) {
+            redisCache.setCacheObject(cacheKey, videos, 1, TimeUnit.HOURS);
+        }
+        
+        return videos;
+    }
+
 }

+ 153 - 0
fs-service/src/main/java/com/fs/live/vo/MergedOrderExportVO.java

@@ -0,0 +1,153 @@
+package com.fs.live.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 合并订单导出VO(参考 FsStoreOrderItemExportVO 的格式)
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Data
+public class MergedOrderExportVO implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    /** 订单状态 */
+    @Excel(name = "订单状态", dictType = "store_order_status")
+    private String status;
+
+    /** 会员ID */
+    @Excel(name = "会员ID")
+    private Long userId;
+
+    /** 产品名称 */
+    @Excel(name = "产品名称")
+    private String productName;
+
+    /** 产品编码 */
+    @Excel(name = "产品编码")
+    private String barCode;
+
+    /** 规格 */
+    @Excel(name = "规格")
+    private String productSpec;
+
+    /** 产品数量 */
+    @Excel(name = "产品数量")
+    private Integer totalNum;
+
+    /** 产品价格 */
+    @Excel(name = "产品价格")
+    private BigDecimal price;
+
+    /** 成本价 */
+    @Excel(name = "成本价")
+    private BigDecimal cost;
+
+    /** 结算价 */
+    @Excel(name = "结算价")
+    private BigDecimal FPrice;
+
+    /** 商品分类 */
+    @Excel(name = "商品分类")
+    private String cateName;
+
+    /** 用户姓名 */
+    @Excel(name = "收货人姓名")
+    private String realName;
+
+    /** 用户电话 */
+    @Excel(name = "收货人电话")
+    private String userPhone;
+
+    /** 详细地址 */
+    @Excel(name = "详细地址")
+    private String userAddress;
+
+    /** 下单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "下单时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 支付时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date payTime;
+
+    /** 快递公司编号 */
+    @Excel(name = "快递公司编号")
+    private String deliverySn;
+
+    /** 快递名称/送货人姓名 */
+    @Excel(name = "快递公司")
+    private String deliveryName;
+
+    /** 快递单号/手机号 */
+    @Excel(name = "快递单号")
+    private String deliveryId;
+
+    /** 所属公司 */
+    @Excel(name = "所属公司")
+    private String companyName;
+
+    /** 所属销售 */
+    @Excel(name = "所属销售")
+    private String companyUserNickName;
+
+    /** 套餐名称 */
+//    @Excel(name = "套餐名称")
+    private String packageName;
+
+    /** 组合码 */
+//    @Excel(name = "组合码")
+    private String groupBarCode;
+
+    /** 是否上传凭证 0:未上传 1:已上传 */
+//    @Excel(name = "是否上传凭证 0:未上传 1:已上传")
+    private Integer isUpload;
+
+    /** 上传时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @Excel(name = "上传时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date uploadTime;
+
+    /** 归属档期 */
+    @Excel(name = "归属档期")
+    private String scheduleName;
+
+    /** 银行交易流水号 */
+    @Excel(name = "银行交易流水号")
+    private String bankTransactionId;
+
+    /** 商品金额 */
+    @Excel(name = "商品金额")
+    private BigDecimal totalPrice;
+
+    /** 应付金额 */
+    @Excel(name = "应付金额")
+    private BigDecimal payPrice;
+
+    /** 实付金额 */
+    @Excel(name = "实付金额")
+    private BigDecimal payMoney;
+
+    /** 额外运费 */
+    @Excel(name = "额外运费")
+    private BigDecimal payPostage;
+
+    /** 物流代收金额 */
+    @Excel(name = "物流代收金额")
+    private BigDecimal payDelivery;
+}
+

+ 6 - 0
fs-service/src/main/java/com/fs/live/vo/MergedOrderVO.java

@@ -72,6 +72,12 @@ public class MergedOrderVO implements Serializable
     /** 物流单号 */
     private String deliveryId;
 
+    /** 物流公司编码 */
+    private String deliveryCode;
+
+    /** 物流公司名称 */
+    private String deliveryName;
+
     /** 是否可以申请售后 */
     private Integer isAfterSales;
 

+ 17 - 6
fs-service/src/main/resources/mapper/hisStore/MergedOrderMapper.xml

@@ -20,6 +20,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       o.package_json,
       o.item_json,
       o.delivery_id,
+      o.delivery_sn as deliveryCode,
+      o.delivery_name as deliveryName,
+
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -86,9 +89,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           <if test="maps.deliveryId != null and maps.deliveryId != ''">
             AND o.delivery_id LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
-          <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
-            AND o.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
-          </if>
+        <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+        </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
           </if>
@@ -96,7 +99,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND o.real_name LIKE CONCAT('%', #{maps.realName}, '%')
           </if>
           <if test="maps.productName != null and maps.productName != ''">
-            AND fspc.productName LIKE CONCAT('%', #{maps.productName}, '%')
+            AND fspc.product_name LIKE CONCAT('%', #{maps.productName}, '%')
           </if>
           <if test="maps.deliveryStatus != null">
             AND o.delivery_status = #{maps.deliveryStatus}
@@ -151,6 +154,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       o.package_json,
       o.item_json,
       o.delivery_id,
+    o.delivery_sn as deliveryCode,
+    o.delivery_name as deliveryName,
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -218,7 +223,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             AND o.delivery_id LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
           <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
-            AND o.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
           </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
@@ -282,6 +287,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       NULL AS package_json,
       o.item_json,
       o.delivery_sn AS delivery_id,
+
+        o.delivery_code as deliveryCode,
+        o.delivery_name as deliveryName,
       o.finish_time,
       o.create_time,
       o.pay_time,
@@ -300,7 +308,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         fspc.product_name as productName,
         fspc.prescribe_spec as productSpec,
         fspc.cost as cost,
-        o.pay_delivery as payDelivery,
+        o.pay_postage as payDelivery,
         o.discount_money as discountMoney,
         fss.store_id as storeId,
         fss.store_name as storeName,
@@ -351,6 +359,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           <if test="maps.deliveryId != null and maps.deliveryId != ''">
             AND o.delivery_sn LIKE CONCAT('%', #{maps.deliveryId}, '%')
           </if>
+        <if test="maps.bankTransactionId != null and maps.bankTransactionId != ''">
+            AND sp_latest.bank_transaction_id LIKE CONCAT('%', #{maps.bankTransactionId}, '%')
+        </if>
           <if test="maps.userPhone != null and maps.userPhone != ''">
             AND o.user_phone LIKE CONCAT('%', #{maps.userPhone}, '%')
           </if>

+ 3 - 2
fs-user-app/src/main/java/com/fs/app/controller/live/LiveOrderController.java

@@ -24,6 +24,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.core.config.WxMaConfiguration;
+import com.fs.core.utils.OrderCodeUtils;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.enums.ShipperCodeEnum;
@@ -971,7 +972,7 @@ public class LiveOrderController extends AppBaseController
 
             String json = configService.selectConfigByKey("his.pay");
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             //易宝支付
             LiveOrderPayment storePayment=new LiveOrderPayment();
             storePayment.setCompanyId(order.getCompanyId());
@@ -1085,7 +1086,7 @@ public class LiveOrderController extends AppBaseController
                 return R.error("只有顺丰物流支持尾款支付");
             }
 
-            String payCode = IdUtil.getSnowflake(0, 0).nextIdStr();
+            String payCode =  OrderCodeUtils.getOrderSn();
             String json = configService.selectConfigByKey("his.pay");
             FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
             LiveOrderPayment storePayment=new LiveOrderPayment();