18 Commits 734e582589 ... 0118575127

Author SHA1 Message Date
  zhangxin 0118575127 修复评论bug 4 days ago
  zhangxin 7d350a2d0d 修改导出文件bug 4 days ago
  zhangxin 50b4593871 修复售后单查询bug 4 days ago
  zhangxin a95088b2c0 修改退款导出文件名为英文 4 days ago
  zhangxin 3eb15977b6 Merge branch 'dev' into 202511 4 days ago
  zhangxin df58204a65 修改导出文件名为英文 4 days ago
  zhangxin 60a1962bd1 订单导出问题 5 days ago
  zhangxin b22d67d487 订单导出优化中断操作 5 days ago
  zhangxin 401404205f 订单导出优化 5 days ago
  zhangxin 3ef6dc8fab 导出中断限制 5 days ago
  zhangxin 2a8f4a0efa Merge branch 'dev' into 202511 6 days ago
  zhangxin e415580749 修复查询所属企业bug 6 days ago
  zhangxin d3bb79243c 正常订单导出设置目录 6 days ago
  zhangxin db77b371fa Merge branch 'dev' into 202511 6 days ago
  zhangxin 0b3f2c1e46 正常订单导出 6 days ago
  zhangxin bc8920885b 正常订单统计问题 1 week ago
  zhangxin eff2980a86 Merge branch 'dev' into 202511 1 week ago
  zhangxin 3c3e529c0f 正常订单统计 1 week ago
23 changed files with 1765 additions and 142 deletions
  1. 85 0
      yami-shop-bean/src/main/java/com/yami/shop/bean/model/ExportTask.java
  2. 4 0
      yami-shop-bean/src/main/java/com/yami/shop/bean/model/ProdComm.java
  3. 16 0
      yami-shop-bean/src/main/java/com/yami/shop/bean/vo/ExportContext.java
  4. 66 0
      yami-shop-bean/src/main/java/com/yami/shop/bean/vo/ExportTaskVo.java
  5. 75 0
      yami-shop-bean/src/main/java/com/yami/shop/bean/vo/OrderCountVo.java
  6. 6 1
      yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ChannelController.java
  7. 133 0
      yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ExportTaskController.java
  8. 40 3
      yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderController.java
  9. 21 6
      yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderRefundController.java
  10. 7 6
      yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProdCommController.java
  11. 32 0
      yami-shop-service/src/main/java/com/yami/shop/dao/ExportTaskMapper.java
  12. 3 0
      yami-shop-service/src/main/java/com/yami/shop/dao/OrderMapper.java
  13. 46 0
      yami-shop-service/src/main/java/com/yami/shop/service/ExportTaskService.java
  14. 2 1
      yami-shop-service/src/main/java/com/yami/shop/service/OrderRefundService.java
  15. 7 1
      yami-shop-service/src/main/java/com/yami/shop/service/OrderService.java
  16. 430 0
      yami-shop-service/src/main/java/com/yami/shop/service/impl/ExportTaskServiceImpl.java
  17. 138 30
      yami-shop-service/src/main/java/com/yami/shop/service/impl/OrderRefundServiceImpl.java
  18. 159 37
      yami-shop-service/src/main/java/com/yami/shop/service/impl/OrderServiceImpl.java
  19. 366 48
      yami-shop-service/src/main/java/com/yami/shop/utils/ExportUtils.java
  20. 34 0
      yami-shop-service/src/main/resources/mapper/ExportTaskMapper.xml
  21. 83 0
      yami-shop-service/src/main/resources/mapper/OrderMapper.xml
  22. 6 6
      yami-shop-service/src/main/resources/mapper/OrderRefundMapper.xml
  23. 6 3
      yami-shop-service/src/main/resources/mapper/ProdCommMapper.xml

+ 85 - 0
yami-shop-bean/src/main/java/com/yami/shop/bean/model/ExportTask.java

@@ -0,0 +1,85 @@
+
+
+package com.yami.shop.bean.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 导出任务实体
+ */
+@Data
+@TableName("tz_export_task")
+public class ExportTask implements Serializable {
+
+
+    private static final long serialVersionUID = 6222259729062826852L;
+    /**
+     * ID
+     */
+    @TableId(type = IdType.UUID)
+    @ApiModelProperty(value = "id",required=true)
+    private String id;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 导出类型1-正常订单 2-售后订单
+     */
+    private Integer exportType;
+
+    /**
+     * 导出信息
+     */
+    private String exportMsg;
+    /**
+     * 导出状态 0-进行中,1-已生成文件,2-生成文件失败,3-已下载
+     */
+    private Integer exportStatus;
+    /**
+     * 任务名称
+     */
+    private String taskName;
+    /**
+     * 创建时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    /**
+     * 修改时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+    /**
+     * 文件路径
+     */
+    private String fileUrl;
+
+    /**
+     * 文件报错路径
+     */
+    private String fullPath;
+    /**
+     * 文件名称
+     */
+    private String fileName;
+    /**
+     * 线程编号
+     */
+    private String taskId;
+
+
+}

+ 4 - 0
yami-shop-bean/src/main/java/com/yami/shop/bean/model/ProdComm.java

@@ -138,6 +138,10 @@ public class ProdComm implements Serializable{
 
     @TableField(exist = false)
     private String nickName;
+    @TableField(exist = false)
+    private String userMobile;
+    @TableField(exist = false)
+    private String channelName;
 
     @TableField(exist = false)
     private String avatar;

+ 16 - 0
yami-shop-bean/src/main/java/com/yami/shop/bean/vo/ExportContext.java

@@ -0,0 +1,16 @@
+package com.yami.shop.bean.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.concurrent.CompletableFuture;
+
+
+@Data
+@AllArgsConstructor
+public class ExportContext {
+    private CompletableFuture<Void> future;
+    private Thread executionThread;
+    private volatile boolean isCancelled = false;
+    private volatile int processedRows = 0;
+}

+ 66 - 0
yami-shop-bean/src/main/java/com/yami/shop/bean/vo/ExportTaskVo.java

@@ -0,0 +1,66 @@
+
+
+package com.yami.shop.bean.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 导出任务实体
+ */
+@Data
+public class ExportTaskVo{
+
+
+    /**
+     * ID
+     */
+    private String id;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 导出类型1-正常订单 2-售后订单
+     */
+    private Integer exportType;
+
+    /**
+     * 导出信息
+     */
+    private String exportMsg;
+    /**
+     * 导出状态 0-进行中,1-已生成文件,2-生成文件失败,3-已下载
+     */
+    private Integer exportStatus;
+    /**
+     * 任务名称
+     */
+    private String taskName;
+    /**
+     * 操作人
+     */
+    private String operator;
+    /**
+     * 创建时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    /**
+     * 修改时间
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+
+}

+ 75 - 0
yami-shop-bean/src/main/java/com/yami/shop/bean/vo/OrderCountVo.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
+ *
+ * https://www.gz-yami.com/
+ *
+ */
+
+package com.yami.shop.bean.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.yami.shop.bean.model.OrderRefundRecord;
+import com.yami.shop.bean.model.RefundAppointment;
+import com.yami.shop.bean.model.RefundDelivery;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Digits;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/***
+ * 订单统计信息
+ */
+@Data
+public class OrderCountVo implements Serializable{
+
+    @ApiModelProperty(value = "订单总数")
+    private long orderTotal =0L;
+
+    @ApiModelProperty(value = "订单总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal orderMoneyTotal= BigDecimal.ZERO;
+
+    @ApiModelProperty(value = "商品总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal shopMoneyTotal= BigDecimal.ZERO;
+
+    /**
+     * 运费总金额
+     */
+    @ApiModelProperty(value = "运费总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal carriageMoneyTotal= BigDecimal.ZERO;
+    /**
+     * 支付总金额
+     */
+    @ApiModelProperty(value = "支付总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal payMoneyTotal= BigDecimal.ZERO;
+
+    /**
+     * 积分总金额
+     */
+    @ApiModelProperty(value = "积分总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal pointsMoneyTotal= BigDecimal.ZERO;
+
+    /**
+     * 现金总金额
+     */
+    @ApiModelProperty(value = "现金总金额")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal moneyTotal= BigDecimal.ZERO;
+
+    /**
+     * 客单价
+     */
+    @ApiModelProperty(value = "客单价")
+    @Digits(integer = 10, fraction = 2, message = "运费总金额最多保留2位小数")
+    private BigDecimal averageOrderValueMoney = BigDecimal.ZERO;
+
+
+}

+ 6 - 1
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ChannelController.java

@@ -108,7 +108,12 @@ public class ChannelController {
     @GetMapping("/list")
     @ApiOperation(value = "查询全部")
     public R<List<Channel>> listByUserId() {
-        Long userId = SecurityUtils.getSysUser().getUserId();
+        Long userId = null;
+        try {
+            userId = SecurityUtils.getSysUser().getUserId();
+        } catch (Exception e) {
+            throw new GlobalException("获取当前登录用户失败");
+        }
         List<Channel> channels = null;
         if(userId == Constant.SUPER_ADMIN_ID){
             channels = channelService.list();

+ 133 - 0
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ExportTaskController.java

@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
+ *
+ * https://www.gz-yami.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+
+package com.yami.shop.platform.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yami.shop.bean.enums.DvyType;
+import com.yami.shop.bean.enums.OrderStatus;
+import com.yami.shop.bean.model.*;
+import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.ExportTaskVo;
+import com.yami.shop.bean.vo.OrderCountVo;
+import com.yami.shop.common.config.Constant;
+import com.yami.shop.common.exception.GlobalException;
+import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
+import com.yami.shop.security.comment.dao.AppConnectMapper;
+import com.yami.shop.security.comment.model.AppConnect;
+import com.yami.shop.security.platform.util.SecurityUtils;
+import com.yami.shop.service.*;
+import com.yami.shop.sys.service.SysUserService;
+import com.yami.shop.utils.CullenUtils;
+import com.yami.shop.wx.service.impl.WxProviderServiceImpl;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotBlank;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+/**
+ * 导出任务controller
+ */
+
+@RestController
+@RequestMapping("/platform/exportTask")
+@AllArgsConstructor
+public class ExportTaskController {
+
+    @Autowired
+    private ExportTaskService exportTaskService;
+
+    /**
+     * 直接下载文件
+     */
+    @GetMapping("/download")
+    @ApiOperation("下载文件")
+    public void downloadFile(
+            @RequestParam("fileId")  String fileId,
+            HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+        if (StringUtils.isEmpty(fileId)){
+            throw new GlobalException("文件id不能为空");
+        }
+        Long userId = getUserId();
+        // 记录开始时间
+        try {
+            exportTaskService.downloadFile(request, response, fileId,userId);
+        } catch (Exception e) {
+            throw new GlobalException(e.getMessage());
+        }
+    }
+
+    /**
+     * 中断导出
+     */
+    @GetMapping("/cancel/{fileId}")
+    public R<String> cancelExport(@PathVariable String fileId) {
+        if (StringUtils.isEmpty(fileId)){
+            throw new GlobalException("文件id不能为空");
+        }
+        Long userId = getUserId();
+        return exportTaskService.cancelExport(fileId,userId);
+    }
+
+    /**
+     * 查询导出任务列表
+     */
+    @GetMapping("/page")
+    public R<IPage<ExportTaskVo>> page(PageParam<ExportTaskVo> page,@RequestParam Integer exportType) {
+        if (ObjectUtil.isEmpty(exportType)){
+            throw new GlobalException("导出文件类型不能为空");
+        }
+        Long userId = getUserId();
+        IPage<ExportTaskVo> exportTaskIPage = exportTaskService.findPage(page,userId,exportType);
+        return R.SUCCESS(exportTaskIPage);
+    }
+
+    /**
+     * 获取当前用户信息
+     * @return
+     */
+    private Long getUserId(){
+        Long userId = null;
+        try {
+            userId = SecurityUtils.getSysUser().getUserId();
+        } catch (Exception e) {
+            throw new GlobalException("导出获取当前登录用户失败");
+        }
+        if (ObjectUtil.isEmpty(userId)){
+            throw new GlobalException("导出获取当前登录用户信息失败,请登录");
+        }
+        if (userId == Constant.SUPER_ADMIN_ID){
+            userId =null;
+        }
+        return userId;
+    }
+
+
+
+}

+ 40 - 3
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderController.java

@@ -18,10 +18,12 @@ import com.yami.shop.bean.enums.DvyType;
 import com.yami.shop.bean.enums.OrderStatus;
 import com.yami.shop.bean.model.*;
 import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.OrderCountVo;
 import com.yami.shop.common.config.Constant;
 import com.yami.shop.common.exception.GlobalException;
 import com.yami.shop.common.util.PageParam;
 import com.yami.shop.common.util.R;
+import com.yami.shop.dao.ExportTaskMapper;
 import com.yami.shop.security.comment.dao.AppConnectMapper;
 import com.yami.shop.security.comment.model.AppConnect;
 import com.yami.shop.security.platform.util.SecurityUtils;
@@ -31,13 +33,17 @@ import com.yami.shop.utils.CullenUtils;
 import com.yami.shop.wx.service.impl.WxProviderServiceImpl;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
+import lombok.extern.log4j.Log4j;
+import lombok.extern.log4j.Log4j2;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
+import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -52,6 +58,7 @@ import java.util.stream.Collectors;
 @RestController
 @RequestMapping("/platform/order")
 @AllArgsConstructor
+@Log4j2
 public class OrderController {
 
     private final OrderService orderService;
@@ -66,6 +73,9 @@ public class OrderController {
 
     @Autowired
     private SysUserService sysUserService;
+
+    @Autowired
+    private ExportTaskService exportTaskService;
     /**
      * 分页获取
      */
@@ -233,15 +243,42 @@ public class OrderController {
     /**
      * 导出
      * @param orderParam
-     * @param response
      */
     @GetMapping("/export")
     @ApiOperation("后管端-快递订单列表导出")
-    public void export(BackendOrderParam orderParam, HttpServletResponse response) {
+    public R<String> export(BackendOrderParam orderParam) {
+        getBackendOrderParam(orderParam);
+        Long userId = null;
+        try {
+            userId = SecurityUtils.getSysUser().getUserId();
+        } catch (Exception e) {
+            throw new GlobalException("导出获取当前登录用户失败");
+        }
+        if (ObjectUtil.isEmpty(userId)){
+            throw new GlobalException("导出获取当前登录用户信息失败,请登录");
+        }
+        ExportTask exportTaskOne =  exportTaskService.findByUserIdAndStatusAndType(userId,0,1);//查询导出任务为正常订单并且在进行中的数据
+        // 异步执行
+        if (exportTaskOne!=null){
+            return R.FAIL("正常订单有导出任务正在执行中,请中断或者等待完成");
+        }
+
+         return orderService.export(orderParam,userId);
+    }
+
+
+    /**
+     * 订单订单统计数
+     * @param orderParam
+     */
+    @GetMapping("/orderCount")
+    @ApiOperation("后管端-订单列表统计")
+    public R<OrderCountVo> orderCount(BackendOrderParam orderParam) {
         getBackendOrderParam(orderParam);
-         orderService.export(orderParam,response);
+        return R.SUCCESS(orderService.orderCount(orderParam));
     }
 
+
     private void getBackendOrderParam(BackendOrderParam orderParam){
         if (orderParam.getChannelIdList()==null||orderParam.getChannelIdList().isEmpty()){
             throw new GlobalException("请求参数-所属企业不允许为空");

+ 21 - 6
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/OrderRefundController.java

@@ -5,10 +5,7 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.yami.shop.bean.dto.OrderRefundDto;
-import com.yami.shop.bean.model.OrderRefund;
-import com.yami.shop.bean.model.OrderRefundRecord;
-import com.yami.shop.bean.model.RefundAppointment;
-import com.yami.shop.bean.model.RefundDelivery;
+import com.yami.shop.bean.model.*;
 import com.yami.shop.bean.param.BackendOrderParam;
 import com.yami.shop.bean.param.OrderRefundCountParam;
 import com.yami.shop.bean.param.OrderRefundStaisticsParam;
@@ -20,6 +17,7 @@ import com.yami.shop.common.util.PageParam;
 import com.yami.shop.common.util.R;
 import com.yami.shop.dao.*;
 import com.yami.shop.security.platform.util.SecurityUtils;
+import com.yami.shop.service.ExportTaskService;
 import com.yami.shop.service.OrderRefundService;
 import com.yami.shop.sys.service.SysUserService;
 import io.swagger.annotations.Api;
@@ -55,6 +53,9 @@ public class OrderRefundController {
     private final RefundDeliveryMapper refundDeliveryMapper;
     @Autowired
     private SysUserService sysUserService;
+
+    @Autowired
+    private ExportTaskService exportTaskService;
     /**
      * 分页查询
      *
@@ -145,9 +146,23 @@ public class OrderRefundController {
      */
     @GetMapping("/export")
     @ApiOperation("后管端-退款订单列表导出")
-    public void export(OrderRefundStaisticsParam orderRefund, HttpServletResponse response) {
+    public R<String> export(OrderRefundStaisticsParam orderRefund, HttpServletResponse response) {
         getOrderRefundStaisticsParam(orderRefund);
-        orderRefundService.export(orderRefund,response);
+        Long userId = null;
+        try {
+            userId = SecurityUtils.getSysUser().getUserId();
+        } catch (Exception e) {
+            throw new GlobalException("导出获取当前登录用户失败");
+        }
+        if (ObjectUtil.isEmpty(userId)){
+            throw new GlobalException("导出获取当前登录用户信息失败,请登录");
+        }
+        ExportTask exportTaskOne =  exportTaskService.findByUserIdAndStatusAndType(userId,0,2);//查询导出任务为退款订单并且在进行中的数据
+        // 异步执行
+        if (exportTaskOne!=null){
+            return R.FAIL("售后订单有导出任务正在执行中,请中断或者等待完成");
+        }
+        return orderRefundService.export(orderRefund,userId);
     }
 
     private void getOrderRefundStaisticsParam(OrderRefundStaisticsParam orderRefund){

+ 7 - 6
yami-shop-platform/src/main/java/com/yami/shop/platform/controller/ProdCommController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.yami.shop.bean.model.ProdComm;
 import com.yami.shop.bean.po.CommPo;
 import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
 import com.yami.shop.service.ProdCommService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -24,22 +25,22 @@ public class ProdCommController {
 
     @GetMapping("/backendCommList")
     @ApiOperation(value = "后台管理-用户评价")
-    public ResponseEntity<IPage<ProdComm>> backendCommList(PageParam<ProdComm> page, CommPo po) {
-        return ResponseEntity.ok(prodCommService.backendCommList(page,po));
+    public R<IPage<ProdComm>> backendCommList(PageParam<ProdComm> page, CommPo po) {
+        return R.SUCCESS(prodCommService.backendCommList(page,po));
     }
 
     @GetMapping("/backendCommAudit")
     @ApiOperation(value = "后台管理-用户评价-(审核,1:通过,0:待审核, -1:不通过审核)")
-    public ResponseEntity<Void> backendCommAudit(Long prodCommId, Integer status) {
+    public R<Void> backendCommAudit(Long prodCommId, Integer status) {
         prodCommService.backendCommAudit(prodCommId,status);
-        return ResponseEntity.ok().build();
+        return R.SUCCESS();
     }
 
     @GetMapping("/reply")
     @ApiOperation(value = "后台-商家回复")
-    public ResponseEntity<Void> reply(Long prodCommId, String replyContent) {
+    public R<Void> reply(Long prodCommId, String replyContent) {
         prodCommService.reply(prodCommId,replyContent);
-        return ResponseEntity.ok().build();
+        return R.SUCCESS();
     }
 
 

+ 32 - 0
yami-shop-service/src/main/java/com/yami/shop/dao/ExportTaskMapper.java

@@ -0,0 +1,32 @@
+
+
+package com.yami.shop.dao;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.yami.shop.bean.app.dto.MyOrderDto;
+import com.yami.shop.bean.app.dto.MyOrderItemDto;
+import com.yami.shop.bean.app.dto.OrderCountData;
+import com.yami.shop.bean.distribution.UserShoppingDataDto;
+import com.yami.shop.bean.model.ExportTask;
+import com.yami.shop.bean.model.Order;
+import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.ExportTaskVo;
+import com.yami.shop.bean.vo.OrderCountVo;
+import com.yami.shop.common.util.PageAdapter;
+import com.yami.shop.common.util.PageParam;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface ExportTaskMapper extends BaseMapper<ExportTask> {
+
+
+    ExportTask findByUserIdAndStatusAndType(@Param("userId") Long userId,@Param("exportStatus") int exportStatus,@Param("exportType") int exportType);
+
+    ExportTask findByUserIdAndId(@Param("id") String fileId,@Param("userId")  Long userId);
+
+    IPage<ExportTaskVo> findPage(@Param("page") PageParam<ExportTaskVo> page,@Param("userId") Long userId,@Param("exportType") Integer exportType);
+}

+ 3 - 0
yami-shop-service/src/main/java/com/yami/shop/dao/OrderMapper.java

@@ -19,6 +19,7 @@ import com.yami.shop.bean.app.dto.OrderCountData;
 import com.yami.shop.bean.distribution.UserShoppingDataDto;
 import com.yami.shop.bean.model.Order;
 import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.OrderCountVo;
 import com.yami.shop.common.util.PageAdapter;
 import com.yami.shop.common.util.PageParam;
 import org.apache.ibatis.annotations.Param;
@@ -141,4 +142,6 @@ public interface OrderMapper extends BaseMapper<Order> {
     IPage<Order> deliveryOrder(@Param("page") PageParam<Order> page, @Param("orderParam") OrderStatisticsParam orderParam);
 
     List<Order> findList( @Param("orderParam")BackendOrderParam orderParam);
+
+    OrderCountVo orderCount(@Param("orderParam") BackendOrderParam orderParam);
 }

+ 46 - 0
yami-shop-service/src/main/java/com/yami/shop/service/ExportTaskService.java

@@ -0,0 +1,46 @@
+
+
+package com.yami.shop.service;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yami.shop.bean.app.dto.OrderCountData;
+import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
+import com.yami.shop.bean.model.ExportTask;
+import com.yami.shop.bean.model.Order;
+import com.yami.shop.bean.model.OrderItem;
+import com.yami.shop.bean.model.OrderRefund;
+import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.ExportContext;
+import com.yami.shop.bean.vo.ExportTaskVo;
+import com.yami.shop.bean.vo.OrderCountVo;
+import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author lgh on 2018/09/15.
+ */
+public interface ExportTaskService extends IService<ExportTask> {
+
+
+    ExportTask findByUserIdAndStatusAndType(Long userId, int exportStatus, int exportType);
+
+    void downloadFile(HttpServletRequest request, HttpServletResponse response, String fileId, Long userId)  throws IOException;
+
+    void putTaskId(String taskId, ExportContext context);
+
+    void removeTaskId(String taskId);
+
+    R<String> cancelExport(String fileId, Long userId);
+
+    IPage<ExportTaskVo> findPage(PageParam<ExportTaskVo> page,Long userId,Integer exportType);
+}

+ 2 - 1
yami-shop-service/src/main/java/com/yami/shop/service/OrderRefundService.java

@@ -15,6 +15,7 @@ import com.yami.shop.bean.param.OrderRefundStaisticsParam;
 import com.yami.shop.bean.param.StatisticsRefundParam;
 import com.yami.shop.bean.vo.OrderRefundVo;
 import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
 import com.yami.shop.common.util.hb.HBR;
 import org.apache.ibatis.annotations.Param;
 
@@ -220,7 +221,7 @@ public interface OrderRefundService extends IService<OrderRefund> {
      */
     OrderRefundVo selectInfoById(Long refundId);
 
-    void export(OrderRefundStaisticsParam orderRefund, HttpServletResponse response);
+    R<String> export(OrderRefundStaisticsParam orderRefund, Long userId);
 
     Integer queryRemainGoodsNumber(String orderNumber);
 }

+ 7 - 1
yami-shop-service/src/main/java/com/yami/shop/service/OrderService.java

@@ -16,11 +16,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.yami.shop.bean.app.dto.OrderCountData;
 import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
+import com.yami.shop.bean.model.ExportTask;
 import com.yami.shop.bean.model.Order;
 import com.yami.shop.bean.model.OrderItem;
 import com.yami.shop.bean.model.OrderRefund;
 import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.OrderCountVo;
 import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
 
 import javax.servlet.http.HttpServletResponse;
 import java.util.Date;
@@ -177,5 +180,8 @@ public interface OrderService extends IService<Order> {
      */
     IPage<Order> deliveryOrder(PageParam<Order> page, OrderStatisticsParam orderParam);
 
-    void export(BackendOrderParam orderParam, HttpServletResponse response);
+    R<String> export(BackendOrderParam orderParam, Long userId);
+
+
+    OrderCountVo orderCount(BackendOrderParam orderParam);
 }

+ 430 - 0
yami-shop-service/src/main/java/com/yami/shop/service/impl/ExportTaskServiceImpl.java

@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2018-2999 广州亚米信息科技有限公司 All rights reserved.
+ *
+ * https://www.gz-yami.com/
+ *
+ * 未经允许,不可做商业用途!
+ *
+ * 版权所有,侵权必究!
+ */
+
+package com.yami.shop.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.poi.excel.ExcelUtil;
+import cn.hutool.poi.excel.ExcelWriter;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.LinkedTreeMap;
+import com.yami.shop.bean.app.dto.OrderCountData;
+import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
+import com.yami.shop.bean.dto.hb.HBBaseReq;
+import com.yami.shop.bean.enums.*;
+import com.yami.shop.bean.event.CancelOrderEvent;
+import com.yami.shop.bean.event.ReceiptOrderEvent;
+import com.yami.shop.bean.event.SubmitOrderEvent;
+import com.yami.shop.bean.event.SubmitScoreOrderEvent;
+import com.yami.shop.bean.model.*;
+import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.ExportContext;
+import com.yami.shop.bean.vo.ExportTaskVo;
+import com.yami.shop.bean.vo.OrderCountVo;
+import com.yami.shop.common.config.Constant;
+import com.yami.shop.common.exception.GlobalException;
+import com.yami.shop.common.util.Arith;
+import com.yami.shop.common.util.PageAdapter;
+import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
+import com.yami.shop.dao.*;
+import com.yami.shop.service.ExportTaskService;
+import com.yami.shop.service.OrderItemService;
+import com.yami.shop.service.OrderService;
+import com.yami.shop.service.OrderSettlementService;
+import com.yami.shop.utils.CullenUtils;
+import com.yami.shop.utils.ExportUtils;
+import com.yami.shop.utils.HBSignUtil;
+import com.yami.shop.wx.po.RefundInfoPo;
+import com.yami.shop.wx.service.WxProviderService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import static com.yami.shop.common.util.HttpUtil.post;
+
+/**
+ * @author lgh on 2018/09/15.
+ */
+@Slf4j
+@Service
+@AllArgsConstructor
+public class ExportTaskServiceImpl extends ServiceImpl<ExportTaskMapper, ExportTask> implements ExportTaskService {
+
+    @Autowired
+    ExportTaskMapper exportTaskMapper;
+    @Autowired
+    private TransactionTemplate transactionTemplate;
+    // 下载信号量,控制并发
+    private static final Semaphore downloadSemaphore = new Semaphore(3);
+
+
+    // 存储正在执行的任务
+    private final Map<String, ExportContext> exportTasks = new ConcurrentHashMap<>();
+    @Override
+    public ExportTask findByUserIdAndStatusAndType(Long userId, int exportStatus, int exportType) {
+        return baseMapper.findByUserIdAndStatusAndType(userId,exportStatus,exportType);
+    }
+    @Override
+    public void downloadFile(HttpServletRequest request, HttpServletResponse response, String fileId, Long userId)  throws IOException  {
+
+        ExportTask exportTask = baseMapper.findByUserIdAndId(fileId,userId);
+
+        if (exportTask==null){
+            throw new GlobalException("导出任务不存在或不属于该用户,文件id:"+fileId);
+        }
+        if (!(exportTask.getExportStatus()==1||exportTask.getExportStatus()==3)){
+            throw new GlobalException("导出任务未生成不允许下载");
+        }
+        String clientIp = getClientIp(request);
+        String filePath = null;
+        try {
+            // 2. 参数验证
+            filePath = exportTask.getFileUrl();
+            // 3. 路径安全验证
+            String safeFilePath = ExportUtils.sanitize(exportTask.getFileUrl());
+            Path safePath = Paths.get(safeFilePath).normalize();
+            // 4. 文件存在性检查
+            File file = safePath.toFile();
+            if (!file.exists()) {
+                throw new FileNotFoundException("文件不存在: " + filePath);
+            }
+            if (!file.isFile()) {
+                throw new IllegalArgumentException("不是有效的文件: " + filePath);
+            }
+            // 7. 设置响应头
+            setResponseHeaders(response, file, request, exportTask);
+            // 8. 执行文件下载
+            transferFile(file, response);
+            exportTask.setExportMsg("已下载");
+            exportTask.setExportStatus(3);
+            exportTaskMapper.updateById(exportTask);
+            log.info("文件下载成功-文件: {},IP: {}, 时间: {}ms",
+                    file.getName(),
+                    clientIp,
+                    System.currentTimeMillis());
+        }   catch (FileNotFoundException e) {
+            handleException(e, e.getMessage(), response,
+                    HttpStatus.NOT_FOUND.value());
+        } catch (IllegalArgumentException e) {
+            handleException(e, e.getMessage(), response,
+                    HttpStatus.BAD_REQUEST.value());
+        } catch (Exception e) {
+            log.error("文件下载异常: {}", filePath, e);
+            handleException(e, "服务器内部错误", response,
+                    HttpStatus.INTERNAL_SERVER_ERROR.value());
+        } finally {
+            downloadSemaphore.release();
+        }
+    }
+
+    @Override
+    public void putTaskId(String taskId, ExportContext context) {
+        exportTasks.put(taskId, context);
+    }
+
+    @Override
+    public void removeTaskId(String taskId) {
+        exportTasks.remove(taskId);
+    }
+
+    @Override
+    public R<String> cancelExport(String fileId, Long userId) {
+        log.info("");
+        // 1. 使用事务确保数据一致性
+        return transactionTemplate.execute(status -> {
+            try {
+                ExportTask exportTask = exportTaskMapper.findByUserIdAndId(fileId, userId);  // 加锁
+                if (exportTask == null) {
+                    return R.FAIL("中断失败-未查询到任务");
+                }
+
+                // 检查任务是否已结束
+                if (exportTask.getExportStatus()!=0) {
+                    return R.FAIL("任务不在进行中,无法取消");
+                }
+
+                // 3. 安全取消执行
+                ExportContext context = exportTasks.get(exportTask.getTaskId());
+                if (context != null) {
+                    return safeCancelExportTask(context, exportTask);
+                }
+
+                // 上下文已不存在,直接返回成功
+                return R.SUCCESS("任务已不存在,标记为取消");
+
+            } catch (Exception e) {
+                status.setRollbackOnly();
+                log.error("取消导出任务失败, fileId: {}", fileId, e);
+                return R.FAIL("系统错误,取消失败");
+            }
+        });
+    }
+
+    @Override
+    public IPage<ExportTaskVo> findPage(PageParam<ExportTaskVo> page, Long userId,Integer exportType) {
+        return exportTaskMapper.findPage(page,userId,exportType);
+    }
+
+    /**
+     * 传输文件到响应流
+     */
+    private long transferFile(File file, HttpServletResponse response) throws IOException {
+
+        long bytesTransferred = 0;
+        long fileSize = file.length();
+
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r");
+             FileChannel fileChannel = raf.getChannel();
+             OutputStream outputStream = response.getOutputStream()) {
+
+            WritableByteChannel outputChannel = Channels.newChannel(outputStream);
+
+            // 使用直接缓冲区提高性能
+            ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024); // 64KB
+            while (bytesTransferred < fileSize) {
+                buffer.clear();
+                int bytesRead = fileChannel.read(buffer);
+
+                if (bytesRead == -1) {
+                    break;
+                }
+
+                buffer.flip();
+                while (buffer.hasRemaining()) {
+                    outputChannel.write(buffer);
+                }
+                bytesTransferred += bytesRead;
+            }
+
+            outputStream.flush();
+
+        } catch (ClosedChannelException e) {
+            log.info("客户端中断了下载: {}", file.getName());
+            throw e;
+        }
+        return bytesTransferred;
+    }
+    /**
+     * 设置响应头
+     */
+    private void setResponseHeaders(HttpServletResponse response,
+                                    File file,
+                                    HttpServletRequest request,
+                                    ExportTask exportTask) {
+
+        // 1. 文件名处理
+        String fileName = exportTask.getFileName() != null
+                ? exportTask.getFileName()
+                : file.getName();
+
+        String safeFileName = sanitizeFileName(fileName);
+        String encodedFileName = encodeFileNameForBrowser(safeFileName, request);
+
+        // 2. 内容类型
+        String contentType = ExportUtils.getContentType(file);
+        response.setContentType(contentType);
+
+        // 3. 下载头
+        response.setHeader("Content-Disposition",
+                "attachment; filename=\"" + encodedFileName + "\"");
+
+        // 4. 文件信息
+        response.setContentLengthLong(file.length());
+        response.setHeader("Accept-Ranges", "none"); // 明确不支持断点续传
+        response.setHeader("X-File-Size", String.valueOf(file.length()));
+        response.setHeader("X-File-Name", safeFileName);
+        response.setHeader("X-File-Modified",
+                Instant.ofEpochMilli(file.lastModified()).toString());
+
+        // 5. 安全头
+        response.setHeader("X-Content-Type-Options", "nosniff");
+        response.setHeader("X-Frame-Options", "DENY");
+        response.setHeader("X-XSS-Protection", "1; mode=block");
+        response.setHeader("Content-Security-Policy", "default-src 'none'");
+        response.setHeader("Referrer-Policy", "no-referrer");
+
+        // 6. 缓存控制
+        response.setHeader("Cache-Control",
+                "no-store, no-cache, must-revalidate, max-age=0");
+        response.setHeader("Pragma", "no-cache");
+        response.setHeader("Expires", "0");
+    }
+
+    /**
+     * 文件名编码
+     */
+    private String encodeFileNameForBrowser(String fileName, HttpServletRequest request) {
+        String userAgent = request.getHeader("User-Agent");
+
+        try {
+            if (userAgent == null) {
+                return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
+                        .replace("+", "%20");
+            }
+
+            if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
+                // IE浏览器
+                return URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+            } else if (userAgent.contains("Firefox")) {
+                // Firefox
+                return "=?UTF-8?B?" + Base64.getEncoder()
+                        .encodeToString(fileName.getBytes(StandardCharsets.UTF_8)) + "?=";
+            } else {
+                // Chrome, Safari, Edge等
+                return new String(fileName.getBytes(StandardCharsets.UTF_8),
+                        StandardCharsets.ISO_8859_1);
+            }
+        } catch (Exception e) {
+            log.warn("文件名编码异常,使用原始文件名: {}", fileName, e);
+            return fileName;
+        }
+    }
+    /**
+     * 处理异常
+     */
+    private void handleException(Exception e, String message,
+                                 HttpServletResponse response, int status) {
+        response.setStatus(status);
+        response.setContentType("application/json");
+
+        try {
+            JsonObject error = new JsonObject();
+            error.addProperty("code", status);
+            error.addProperty("message", message);
+            error.addProperty("timestamp", Instant.now().toString());
+
+            if (status >= 500) {
+                error.addProperty("errorId", UUID.randomUUID().toString());
+            }
+
+            response.getWriter().write(error.toString());
+        } catch (IOException ex) {
+            log.error("写入错误响应失败", ex);
+        }
+    }
+    /**
+     * 清理文件名
+     */
+    private String sanitizeFileName(String fileName) {
+        if (StringUtils.isBlank(fileName)) {
+            return "download_" + System.currentTimeMillis();
+        }
+
+        // 移除路径分隔符和危险字符
+        String sanitized = fileName
+                .replaceAll("[\\\\/:*?\"<>|]", "_")
+                .replaceAll("\\s+", " ")
+                .trim();
+
+        // 限制文件名长度
+        int maxLength = 255;
+        if (sanitized.length() > maxLength) {
+            int lastDot = sanitized.lastIndexOf('.');
+            if (lastDot > 0 && lastDot < maxLength - 10) {
+                // 保留扩展名
+                String ext = sanitized.substring(lastDot);
+                String name = sanitized.substring(0, maxLength - ext.length() - 1);
+                sanitized = name + "_" + ext;
+            } else {
+                sanitized = sanitized.substring(0, maxLength);
+            }
+        }
+
+        return sanitized;
+    }
+    /**
+     * 获取客户端IP
+     */
+    private String getClientIp(HttpServletRequest request) {
+        String[] headers = {
+                "X-Forwarded-For",
+                "Proxy-Client-IP",
+                "WL-Proxy-Client-IP",
+                "HTTP_CLIENT_IP",
+                "HTTP_X_FORWARDED_FOR"
+        };
+
+        for (String header : headers) {
+            String ip = request.getHeader(header);
+            if (StringUtils.isNotBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
+                return ip.split(",")[0].trim();
+            }
+        }
+
+        return request.getRemoteAddr();
+    }
+
+
+    /**
+     * 安全取消导出任务
+     */
+    private R<String> safeCancelExportTask(ExportContext context, ExportTask exportTask) {
+        // 方案2: 设置取消标志,等待任务自己结束
+        context.setCancelled(true);
+        return R.SUCCESS("任务正在结束,请稍后查看");
+    }
+}

+ 138 - 30
yami-shop-service/src/main/java/com/yami/shop/service/impl/OrderRefundServiceImpl.java

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.binarywang.wxpay.exception.WxPayException;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.yami.shop.bean.dto.OrderRefundDto;
 import com.yami.shop.bean.dto.hb.HBBaseReq;
 import com.yami.shop.bean.dto.hb.OrderPaymentRequest;
@@ -23,6 +24,7 @@ import com.yami.shop.bean.event.ReceiptOrderEvent;
 import com.yami.shop.bean.model.*;
 import com.yami.shop.bean.param.*;
 import com.yami.shop.bean.pay.RefundInfoDto;
+import com.yami.shop.bean.vo.ExportContext;
 import com.yami.shop.bean.vo.OrderRefundSkuVo;
 import com.yami.shop.bean.vo.OrderRefundVo;
 import com.yami.shop.common.config.Constant;
@@ -31,6 +33,7 @@ import com.yami.shop.common.exception.GlobalException;
 import com.yami.shop.common.exception.YamiShopBindException;
 import com.yami.shop.common.util.Arith;
 import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
 import com.yami.shop.common.util.hb.HBR;
 import com.yami.shop.dao.*;
 import com.yami.shop.service.*;
@@ -44,15 +47,26 @@ import org.apache.poi.ss.usermodel.CellStyle;
 import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.math.BigDecimal;
+import java.rmi.server.ExportException;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import static com.yami.shop.common.util.HttpUtil.post;
 
@@ -85,7 +99,18 @@ public class OrderRefundServiceImpl extends ServiceImpl<OrderRefundMapper, Order
     private final HBSignUtil hbSignUtil;
     private final OrderRefundSkuMapper orderRefundSkuMapper;
 
-
+    @Autowired
+    private ExportTaskService exportTaskService;
+
+    //导出
+    // 线程池配置
+    private final ExecutorService exportExecutor = Executors.newFixedThreadPool(
+            5,
+            new ThreadFactoryBuilder()
+                    .setNameFormat("refund-order-export-thread-%d")
+                    .setDaemon(true)
+                    .build()
+    );
     /**
      * 获取分页对象
      */
@@ -1304,12 +1329,12 @@ public class OrderRefundServiceImpl extends ServiceImpl<OrderRefundMapper, Order
         return orderRefundMapper.selectInfoById(refundId);
     }
     /**
-     * 导出正常订单
+     * 导出售后订单
      * @param orderRefund
-     * @param response
+     * @param userId
      */
     @Override
-    public void export(OrderRefundStaisticsParam orderRefund, HttpServletResponse response) {
+    public R<String> export(OrderRefundStaisticsParam orderRefund,Long userId) {
 
         List<OrderRefundVo> orderRefundList= orderRefundMapper.findOrderRefund(orderRefund);
         for (OrderRefundVo record : orderRefundList) {
@@ -1318,21 +1343,63 @@ public class OrderRefundServiceImpl extends ServiceImpl<OrderRefundMapper, Order
         if (orderRefundList.isEmpty()){
             throw new GlobalException("该次导出未查询到数据,不允许导出");
         }
-        exportNormalOrders(orderRefundList,response,"退款订单");
+        String taskId = UUID.randomUUID().toString();
+        ExportTask exportTask = new ExportTask();
+        exportTask.setUserId(userId);
+        exportTask.setExportStatus(0);//导出状态进行中
+        exportTask.setTaskId(taskId);
+        exportTask.setExportType(2);//导出类型
+        exportTask.setTaskName("售后订单导出");
+        exportTask.setExportMsg("导出中");
+        exportTask.setCreateTime(new Date());
+        exportTask.setUpdateTime(new Date());
+        exportTaskService.save(exportTask);
+        // 异步执行导出
+        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+            Thread currentThread = Thread.currentThread();
+            ExportContext context = new ExportContext(null, currentThread, false, 0);
+            exportTaskService.putTaskId(taskId,context);
+            try {
+                // 记录任务开始
+                log.info("开始执行导出任务,用户ID: {}", userId);
+                exportNormalOrders(orderRefundList,exportTask,"refundOrder",context);
+                // 记录任务完成
+                log.info("导出任务完成,用户ID: {}", userId);
+            } catch (Exception e) {
+                log.error("导出任务异常,用户ID: {}", userId, e);
+                exportTask.setExportStatus(2);
+                exportTask.setExportMsg("导出失败,"+e.getMessage());
+                exportTaskService.updateById(exportTask);
+            }finally {
+                exportTaskService.removeTaskId(taskId);
+            }
+        }, exportExecutor);
+        // 存储任务上下文
+        exportTaskService.putTaskId(taskId, new ExportContext(future, null, false, 0));
+        return R.SUCCESS("成功");
     }
 
     /**
      * 导出正常订单数据到Excel(直接输出到HttpServletResponse)
      */
     public void exportNormalOrders(List<OrderRefundVo> orderList,
-                                   HttpServletResponse response,
-                                   String fileName) {
-        // 设置响应头
-        ExportUtils.setupResponse(response, fileName);
-
-        try (Workbook workbook = new XSSFWorkbook()) {
+                                   ExportTask exportTask,
+                                   String fileName, ExportContext context) {
+        // 2. 生成安全的文件名
+        String safeFileName = ExportUtils.generateSafeFileName(fileName);
+        // 3. 准备文件路径
+        try {
+            ExportUtils.prepareExportFile(safeFileName, exportTask);
+        } catch (ExportException e) {
+            exportTask.setExportStatus(2);//处理失败
+            exportTask.setExportMsg(e.getMessage());
+            exportTaskService.updateById(exportTask);
+            return;
+        }
+        File file = new File(exportTask.getFileUrl());
+        try (Workbook workbook = new SXSSFWorkbook(100)) {
             // 创建Sheet
-            Sheet sheet = workbook.createSheet("退款订单");
+            Sheet sheet = workbook.createSheet("售后订单");
 
             // 设置列宽
             setupColumnWidth(sheet);
@@ -1353,23 +1420,42 @@ public class OrderRefundServiceImpl extends ServiceImpl<OrderRefundMapper, Order
                     "所属企业", "买家姓名", "买家电话"
             };
             // 创建合并单元格的表头
-            ExportUtils.createMergedHeader(sheet, headerStyle,"退款订单",19);
+            ExportUtils.createMergedHeader(sheet, headerStyle,"售后订单",19);
 
             // 创建列标题行
             ExportUtils.createColumnHeaders(sheet, headerStyle,headers);
 
             // 填充数据
-            fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle);
-
-            // 写入响应流
-            workbook.write(response.getOutputStream());
-            response.getOutputStream().flush();
-
+            fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle,context);
+            // 写入文件
+            try (FileOutputStream fos = new FileOutputStream(file);
+                 BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+                workbook.write(bos);
+            }
+            // 检查是否被中断
+            if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
+                log.info("导出任务 {} 被中断,已处理 {} 行", exportTask.getTaskId(), context.getProcessedRows());
+                exportTask.setExportStatus(1); // 3-已取消
+                exportTask.setExportMsg("导出被用户取消,已处理" + context.getProcessedRows() + "行");
+                exportTaskService.updateById(exportTask);
+                return;
+            }
             log.info("订单导出成功,文件:{},记录数:{}", fileName, orderList.size());
-
+            exportTask.setExportStatus(1);
+            exportTask.setExportMsg("导出成功");
+            exportTask.setUpdateTime(new Date());
+            exportTaskService.updateById(exportTask);
+        } catch (InterruptedException e) {
+            log.error("导出任务 {} 被中断", exportTask.getTaskId());
+            Thread.currentThread().interrupt(); // 重新设置中断状态
+            exportTask.setExportStatus(4);
+            exportTask.setExportMsg("导出被用户取消");
+            exportTaskService.updateById(exportTask);
         } catch (Exception e) {
             log.error("订单导出失败", e);
-            throw new RuntimeException("导出失败:" + e.getMessage());
+            exportTask.setExportStatus(2);
+            exportTask.setExportMsg("导出失败:" + e.getMessage());
+            exportTaskService.updateById(exportTask);
         }
     }
 
@@ -1404,17 +1490,39 @@ public class OrderRefundServiceImpl extends ServiceImpl<OrderRefundMapper, Order
      * 填充订单数据
      */
     private void fillOrderData(Sheet sheet, List<OrderRefundVo> orderList,
-                               CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle) {
+                               CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle,ExportContext context) throws InterruptedException{
+        int batchSize = 100; // 每批处理1000条
+        int totalRows = orderList.size();
         int rowNum = 2; // 从第3行开始(0-based索引)
         int indexNum= 1; //序号
-        for (OrderRefundVo orderRefundVo : orderList) {
-            Row row = sheet.createRow(rowNum++);
-            row.setHeightInPoints(18);
-            // 序号
-            ExportUtils.createCell(sheet,row, 0,orderRefundVo.getOrderRefundSkuList().size(),rowNum, indexNum, dataStyle);
-            fillOrderRowData(sheet,rowNum,row, orderRefundVo, dataStyle, numberStyle, dateStyle);
-            rowNum+=orderRefundVo.getOrderRefundSkuList().size()-1;
-            indexNum++;
+
+        for (int i = 0; i < totalRows; i += batchSize) {
+            int end = Math.min(i + batchSize, totalRows);
+            List<OrderRefundVo> batch = orderList.subList(i, end);
+
+            for (OrderRefundVo orderRefundVo : orderList) {
+                // 定期检查中断标志
+                if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
+                    context.setProcessedRows(indexNum);
+                    break;
+                }
+                Row row = sheet.createRow(rowNum++);
+                row.setHeightInPoints(18);
+                // 序号
+                ExportUtils.createCell(sheet,row, 0,orderRefundVo.getOrderRefundSkuList().size(),rowNum, indexNum, dataStyle);
+                fillOrderRowData(sheet,rowNum,row, orderRefundVo, dataStyle, numberStyle, dateStyle);
+                rowNum+=orderRefundVo.getOrderRefundSkuList().size()-1;
+                indexNum++;
+            }
+
+            // 定期刷新到磁盘
+            if (rowNum % 100 == 0) {
+                try {
+                    ((SXSSFSheet) sheet).flushRows(100);
+                } catch (IOException e) {
+                    log.warn("刷新行数据失败", e);
+                }
+            }
         }
 
     }

+ 159 - 37
yami-shop-service/src/main/java/com/yami/shop/service/impl/OrderServiceImpl.java

@@ -18,12 +18,9 @@ import cn.hutool.core.lang.Snowflake;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONUtil;
 import cn.hutool.poi.excel.ExcelUtil;
 import cn.hutool.poi.excel.ExcelWriter;
 import com.alibaba.fastjson.JSON;
-import com.alibaba.excel.EasyExcel;
-import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -31,6 +28,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gson.internal.LinkedTreeMap;
 import com.yami.shop.bean.app.dto.OrderCountData;
 import com.yami.shop.bean.app.dto.ShopCartOrderMergerDto;
@@ -42,13 +40,16 @@ import com.yami.shop.bean.event.SubmitOrderEvent;
 import com.yami.shop.bean.event.SubmitScoreOrderEvent;
 import com.yami.shop.bean.model.*;
 import com.yami.shop.bean.param.*;
+import com.yami.shop.bean.vo.ExportContext;
+import com.yami.shop.bean.vo.OrderCountVo;
 import com.yami.shop.common.config.Constant;
 import com.yami.shop.common.exception.GlobalException;
 import com.yami.shop.common.util.Arith;
-import com.yami.shop.common.util.Json;
 import com.yami.shop.common.util.PageAdapter;
 import com.yami.shop.common.util.PageParam;
+import com.yami.shop.common.util.R;
 import com.yami.shop.dao.*;
+import com.yami.shop.service.ExportTaskService;
 import com.yami.shop.service.OrderItemService;
 import com.yami.shop.service.OrderService;
 import com.yami.shop.service.OrderSettlementService;
@@ -58,30 +59,40 @@ import com.yami.shop.utils.HBSignUtil;
 import com.yami.shop.wx.po.RefundInfoPo;
 import com.yami.shop.wx.service.WxProviderService;
 import lombok.AllArgsConstructor;
+import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.xssf.streaming.SXSSFSheet;
 import org.apache.poi.xssf.streaming.SXSSFWorkbook;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.cache.annotation.Cacheable;
 import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.rmi.server.ExportException;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
 import static com.yami.shop.common.util.HttpUtil.post;
@@ -109,6 +120,18 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     private Snowflake snowflake;
     private final OrderRefundSkuMapper orderRefundSkuMapper;
     private final HBSignUtil hbSignUtil;
+    @Autowired
+    private ExportTaskService exportTaskService;
+
+    //导出
+    // 线程池配置
+    private final ExecutorService exportExecutor = Executors.newFixedThreadPool(
+            5,
+            new ThreadFactoryBuilder()
+                    .setNameFormat("order-export-thread-%d")
+                    .setDaemon(true)
+                    .build()
+    );
 
 
     @Override
@@ -1370,10 +1393,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
          * 导出正常订单
          *
          * @param orderParam
-         * @param response
+         * @param userId
          */
         @Override
-        public void export (BackendOrderParam orderParam, HttpServletResponse response) {
+        public R<String> export (BackendOrderParam orderParam, Long userId) {
             List<Order> orderList = orderMapper.findList(orderParam);
             if (!orderList.isEmpty()) {
                 orderList.forEach(c -> {
@@ -1383,22 +1406,80 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                 });
             }
             if (orderList.isEmpty()) {
-                throw new GlobalException("该次导出未查询到数据,不允许导出");
+                return R.FAIL("未查询到数据不允许导出");
+            }
+            String taskId = UUID.randomUUID().toString();
+            ExportTask exportTask = new ExportTask();
+            exportTask.setUserId(userId);
+            exportTask.setExportStatus(0);//导出状态进行中
+            exportTask.setTaskId(taskId);
+            exportTask.setExportType(1);//导出类型
+            exportTask.setTaskName("正常订单导出");
+            exportTask.setExportMsg("导出中");
+            exportTask.setCreateTime(new Date());
+            exportTask.setUpdateTime(new Date());
+            exportTaskService.save(exportTask);
+            // 异步执行导出
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                Thread currentThread = Thread.currentThread();
+                ExportContext context = new ExportContext(null, currentThread, false, 0);
+                exportTaskService.putTaskId(taskId,context);
+                try {
+                    // 记录任务开始
+                    log.info("开始执行导出任务,用户ID: {}", userId);
+                    exportNormalOrders(orderList,exportTask, "order",context);
+                    // 记录任务完成
+                    log.info("导出任务完成,用户ID: {}", userId);
+                } catch (Exception e) {
+                    log.error("导出任务异常,用户ID: {}", userId, e);
+                    exportTask.setExportStatus(2);
+                    exportTask.setExportMsg("导出失败,"+e.getMessage());
+                    exportTaskService.updateById(exportTask);
+                }finally {
+                    exportTaskService.removeTaskId(taskId);
+                }
+            }, exportExecutor);
+            // 存储任务上下文
+            exportTaskService.putTaskId(taskId, new ExportContext(future, null, false, 0));
+           return R.SUCCESS("成功");
+        }
+    @Override
+    public OrderCountVo orderCount(BackendOrderParam orderParam) {
+        OrderCountVo orderCountVo = orderMapper.orderCount(orderParam);
+        if (orderCountVo!=null){
+            if (orderCountVo.getOrderMoneyTotal()!=null&&orderCountVo.getCarriageMoneyTotal()!=null){
+                BigDecimal add = orderCountVo.getOrderMoneyTotal().add(orderCountVo.getCarriageMoneyTotal());
+                orderCountVo.setOrderMoneyTotal(add);
+                orderCountVo.setPayMoneyTotal(add);
+            }
+            if (orderCountVo.getOrderTotal()!=0L
+                    &&orderCountVo.getOrderMoneyTotal()!=null
+                    &&orderCountVo.getOrderMoneyTotal().compareTo(BigDecimal.ZERO) != 0){
+                BigDecimal unitPrice = orderCountVo.getOrderMoneyTotal().divide(new BigDecimal(orderCountVo.getOrderTotal()), 2, RoundingMode.HALF_UP);
+                orderCountVo.setAverageOrderValueMoney(unitPrice);
             }
-            exportNormalOrders(orderList, response, "正常订单");
-
         }
+        return orderCountVo;
+    }
 
-        /**
+    /**
          * 导出正常订单数据到Excel(直接输出到HttpServletResponse)
          */
-        public void exportNormalOrders (List < Order > orderList,
-                HttpServletResponse response,
-                String fileName){
-            // 设置响应头
-            ExportUtils.setupResponse(response, fileName);
-
-            try (Workbook workbook = new XSSFWorkbook()) {
+        public void exportNormalOrders (List < Order > orderList, ExportTask exportTask,
+                String fileName,ExportContext context){
+            // 2. 生成安全的文件名
+            String safeFileName = ExportUtils.generateSafeFileName(fileName);
+            // 3. 准备文件路径
+            try {
+                ExportUtils.prepareExportFile(safeFileName, exportTask);
+            } catch (ExportException e) {
+                exportTask.setExportStatus(2);//处理失败
+                exportTask.setExportMsg(e.getMessage());
+                exportTaskService.updateById(exportTask);
+                return;
+            }
+            File file = new File(exportTask.getFileUrl());
+            try (Workbook workbook = new SXSSFWorkbook(100)) {
                 // 创建Sheet
                 Sheet sheet = workbook.createSheet("正常订单");
 
@@ -1427,17 +1508,36 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                 ExportUtils.createColumnHeaders(sheet, headerStyle, headers);
 
                 // 填充数据
-                fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle);
-
-                // 写入响应流
-                workbook.write(response.getOutputStream());
-                response.getOutputStream().flush();
-
+                fillOrderData(sheet, orderList, dataStyle, numberStyle, dateStyle, context);
+                // 写入文件
+                try (FileOutputStream fos = new FileOutputStream(file);
+                     BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+                    workbook.write(bos);
+                }
+                // 检查是否被中断
+                if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
+                    log.info("导出任务 {} 被中断,已处理 {} 行", exportTask.getTaskId(), context.getProcessedRows());
+                    exportTask.setExportStatus(1); // 3-已取消
+                    exportTask.setExportMsg("导出被用户取消,已处理" + context.getProcessedRows() + "行");
+                    exportTaskService.updateById(exportTask);
+                    return;
+                }
                 log.info("订单导出成功,文件:{},记录数:{}", fileName, orderList.size());
-
+                exportTask.setExportStatus(1);
+                exportTask.setExportMsg("导出成功");
+                exportTask.setUpdateTime(new Date());
+                exportTaskService.updateById(exportTask);
+            } catch (InterruptedException e) {
+                log.error("导出任务 {} 被中断", exportTask.getTaskId());
+                Thread.currentThread().interrupt(); // 重新设置中断状态
+                exportTask.setExportStatus(4);
+                exportTask.setExportMsg("导出被用户取消");
+                exportTaskService.updateById(exportTask);
             } catch (Exception e) {
                 log.error("订单导出失败", e);
-                throw new RuntimeException("导出失败:" + e.getMessage());
+                exportTask.setExportStatus(2);
+                exportTask.setExportMsg("导出失败:" + e.getMessage());
+                exportTaskService.updateById(exportTask);
             }
         }
 
@@ -1471,19 +1571,41 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
          * 填充订单数据
          */
         private void fillOrderData (Sheet sheet, List < Order > orderList,
-                CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle){
+                CellStyle dataStyle, CellStyle numberStyle, CellStyle dateStyle,ExportContext context)  throws InterruptedException{
+            int batchSize = 100; // 每批处理1000条
+            int totalRows = orderList.size();
             int rowNum = 2; // 从第3行开始(0-based索引)
             int indexNum = 1; //序号
-            for (Order order : orderList) {
-                Row row = sheet.createRow(rowNum++);
-                row.setHeightInPoints(18);
-                // 订单编号
-                ExportUtils.createCell(sheet, row, 0, order.getOrderItems().size(), rowNum, indexNum, dataStyle);
-                fillOrderRowData(sheet, rowNum, row, order, dataStyle, numberStyle, dateStyle);
-                rowNum += order.getOrderItems().size() - 1;
-                indexNum++;
+            for (int i = 0; i < totalRows; i += batchSize) {
+                int end = Math.min(i + batchSize, totalRows);
+                List<Order> batch = orderList.subList(i, end);
+                for (Order order : batch) {
+                    // 定期检查中断标志
+                    if (context.isCancelled() || Thread.currentThread().isInterrupted()) {
+                        context.setProcessedRows(indexNum);
+                        break;
+//                    throw new InterruptedException("导出被中断");
+                    }
+                    Row row = sheet.createRow(rowNum++);
+                    row.setHeightInPoints(18);
+                    //序号
+                    ExportUtils.createCell(sheet, row, 0, order.getOrderItems().size(), rowNum, indexNum, dataStyle);
+                    fillOrderRowData(sheet, rowNum, row, order, dataStyle, numberStyle, dateStyle);
+                    rowNum += order.getOrderItems().size() - 1;
+                    indexNum++;
+                }
+
+                // 定期刷新到磁盘
+                if (rowNum % 100 == 0) {
+                    try {
+                        ((SXSSFSheet) sheet).flushRows(100);
+                    } catch (IOException e) {
+                        log.warn("刷新行数据失败", e);
+                    }
+                }
             }
 
+
         }
 
         /**
@@ -1613,4 +1735,4 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
             }
         }
 
-    }
+}

+ 366 - 48
yami-shop-service/src/main/java/com/yami/shop/utils/ExportUtils.java

@@ -1,23 +1,63 @@
 package com.yami.shop.utils;
 
-import com.google.common.collect.Maps;
-import com.yami.shop.common.exception.GlobalException;
+import com.yami.shop.bean.model.ExportTask;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.util.DigestUtils;
 
 import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.net.URLEncoder;
-import java.util.Date;
-import java.util.Map;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.rmi.server.ExportException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.regex.Pattern;
 
 /**
  * 导出工具类
  * @author zx
  * @date 2015-12-12 00:00:00
  */
+@Log4j2
 public class ExportUtils {
+    private static final Pattern UNSAFE_PATTERN =
+            Pattern.compile("(\\.\\.|[/\\\\][.]+[/\\\\]|//|\\\\\\\\)");
 
+    private static final Set<String> BLACKLISTED_PATHS = Set.of(
+            "/etc/", "/bin/", "/sbin/", "/usr/", "/var/", "/proc/",
+            "C:\\Windows\\", "C:\\Program Files\\", "C:\\ProgramData\\"
+    );
+
+    /**
+     * 导出文件保存路径
+     */
+    public static String savePath = "/opt/dev_projects";
+//    /**
+//     * 本地导出文件保存路径
+//     */
+//    public static String savePath = "D:\\test\\exports\\";
+
+    /**
+     * 单个文件大小限制(MB)
+     */
+    private static int maxFileSize = 100;
+    /**
+     * 允许的导出文件类型
+     */
+    private static  List<String> allowedExtensions = Arrays.asList(".xlsx", ".xls", ".csv");
     /**
      * 创建合并单元格的表头(模拟模板中的"正常订单"标题)
      */
@@ -61,14 +101,6 @@ public class ExportUtils {
     public static  void createColumnHeaders(Sheet sheet, CellStyle headerStyle,String[] headers) {
         Row headerRow = sheet.createRow(1);
         headerRow.setHeightInPoints(20);
-
-//        String[] headers = {
-//                "订单编号", "下单时间", "付款时间", "订单状态", "订单类型",
-//                "商品名称", "规格", "单价(元)", "数量", "小计(元)",
-//                "商品总数量", "商品总额(元)", "运费(元)", "积分抵扣(元)",
-//                "实付金额", "所属企业", "买家姓名", "买家电话"
-//        };
-
         for (int i = 0; i < headers.length; i++) {
             Cell cell = headerRow.createCell(i);
             cell.setCellValue(headers[i]);
@@ -252,7 +284,7 @@ public class ExportUtils {
         style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
     }
     /**
-     * 为合并区域设置边框样式
+     * 为合并区域设置边框样式(重用样式,减少创建次数)
      */
     public static void setMergedRegionBorder(Sheet sheet, CellRangeAddress mergedRegion) {
         int firstRow = mergedRegion.getFirstRow();
@@ -260,54 +292,140 @@ public class ExportUtils {
         int firstCol = mergedRegion.getFirstColumn();
         int lastCol = mergedRegion.getLastColumn();
 
-        // 为合并区域的每个单元格设置特定边框
+        Workbook workbook = sheet.getWorkbook();
+
+        // 为不同的边框组合创建样式
+        Map<String, CellStyle> styleMap = new HashMap<>();
+
         for (int row = firstRow; row <= lastRow; row++) {
+            Row sheetRow = sheet.getRow(row);
+            if (sheetRow == null) {
+                sheetRow = sheet.createRow(row);
+            }
+
             for (int col = firstCol; col <= lastCol; col++) {
-                Row sheetRow = sheet.getRow(row);
-                if (sheetRow == null) {
-                    sheetRow = sheet.createRow(row);
+                // 生成样式key
+                String styleKey = generateStyleKey(row, col, firstRow, lastRow, firstCol, lastCol);
+
+                CellStyle borderStyle = styleMap.get(styleKey);
+                if (borderStyle == null) {
+                    borderStyle = workbook.createCellStyle();
+
+                    // 设置左边框(只有最左侧单元格)
+                    if (col == firstCol) {
+                        borderStyle.setBorderLeft(BorderStyle.THIN);
+                        borderStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+                    }
+
+                    // 设置右边框(只有最右侧单元格)
+                    if (col == lastCol) {
+                        borderStyle.setBorderRight(BorderStyle.THIN);
+                        borderStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+                    }
+
+                    // 设置上边框(只有最上方单元格)
+                    if (row == firstRow) {
+                        borderStyle.setBorderTop(BorderStyle.THIN);
+                        borderStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+                    }
+
+                    // 设置下边框(只有最下方单元格)
+                    if (row == lastRow) {
+                        borderStyle.setBorderBottom(BorderStyle.THIN);
+                        borderStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+                    }
+
+                    // 如果是左上角单元格,设置居中
+                    if (row == firstRow && col == firstCol) {
+                        setCenterAlignment(borderStyle);
+                    }
+
+                    styleMap.put(styleKey, borderStyle);
                 }
+
                 Cell cell = sheetRow.getCell(col);
                 if (cell == null) {
                     cell = sheetRow.createCell(col);
                 }
 
-                CellStyle borderStyle = sheet.getWorkbook().createCellStyle();
-
-                // 设置左边框(只有最左侧单元格)
-                if (col == firstCol) {
-                    borderStyle.setBorderLeft(BorderStyle.THIN);
-                    borderStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-                }
-
-                // 设置右边框(只有最右侧单元格)
-                if (col == lastCol) {
-                    borderStyle.setBorderRight(BorderStyle.THIN);
-                    borderStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-                }
+                cell.setCellStyle(borderStyle);
+            }
+        }
+    }
 
-                // 设置上边框(只有最上方单元格)
-                if (row == firstRow) {
-                    borderStyle.setBorderTop(BorderStyle.THIN);
-                    borderStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-                }
+    private static String generateStyleKey(int row, int col, int firstRow, int lastRow, int firstCol, int lastCol) {
+        StringBuilder key = new StringBuilder();
 
-                // 设置下边框(只有最下方单元格)
-                if (row == lastRow) {
-                    borderStyle.setBorderBottom(BorderStyle.THIN);
-                    borderStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
-                }
-                // 如果是左上角单元格,设置居中
-                if (row == firstRow && col == firstCol) {
-                    setCenterAlignment(borderStyle);
-                    // 可选:设置自动换行
-//                    borderStyle.setWrapText(true);
-                }
+        // 标识位置类型
+        if (row == firstRow) key.append("T");
+        if (row == lastRow) key.append("B");
+        if (col == firstCol) key.append("L");
+        if (col == lastCol) key.append("R");
 
-                cell.setCellStyle(borderStyle);
-            }
+        // 特殊标记左上角单元格
+        if (row == firstRow && col == firstCol) {
+            key.append("_CENTER");
         }
+
+        return key.toString();
     }
+//    /**
+//     * 为合并区域设置边框样式
+//     */
+//    public static void setMergedRegionBorder(Sheet sheet, CellRangeAddress mergedRegion) {
+//        int firstRow = mergedRegion.getFirstRow();
+//        int lastRow = mergedRegion.getLastRow();
+//        int firstCol = mergedRegion.getFirstColumn();
+//        int lastCol = mergedRegion.getLastColumn();
+//
+//        // 为合并区域的每个单元格设置特定边框
+//        for (int row = firstRow; row <= lastRow; row++) {
+//            for (int col = firstCol; col <= lastCol; col++) {
+//                Row sheetRow = sheet.getRow(row);
+//                if (sheetRow == null) {
+//                    sheetRow = sheet.createRow(row);
+//                }
+//                Cell cell = sheetRow.getCell(col);
+//                if (cell == null) {
+//                    cell = sheetRow.createCell(col);
+//                }
+//
+//                CellStyle borderStyle = sheet.getWorkbook().createCellStyle();
+//
+//                // 设置左边框(只有最左侧单元格)
+//                if (col == firstCol) {
+//                    borderStyle.setBorderLeft(BorderStyle.THIN);
+//                    borderStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+//                }
+//
+//                // 设置右边框(只有最右侧单元格)
+//                if (col == lastCol) {
+//                    borderStyle.setBorderRight(BorderStyle.THIN);
+//                    borderStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+//                }
+//
+//                // 设置上边框(只有最上方单元格)
+//                if (row == firstRow) {
+//                    borderStyle.setBorderTop(BorderStyle.THIN);
+//                    borderStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+//                }
+//
+//                // 设置下边框(只有最下方单元格)
+//                if (row == lastRow) {
+//                    borderStyle.setBorderBottom(BorderStyle.THIN);
+//                    borderStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
+//                }
+//                // 如果是左上角单元格,设置居中
+//                if (row == firstRow && col == firstCol) {
+//                    setCenterAlignment(borderStyle);
+//                    // 可选:设置自动换行
+////                    borderStyle.setWrapText(true);
+//                }
+//
+//                cell.setCellStyle(borderStyle);
+//            }
+//        }
+//    }
     /**
      * 设置居中对齐
      */
@@ -344,4 +462,204 @@ public class ExportUtils {
         return false;
     }
 
+
+    public static String sanitize(String filePath) {
+        if (StringUtils.isEmpty(filePath)) {
+            throw new IllegalArgumentException("文件路径不能为空");
+        }
+
+        // 移除空字符
+        filePath = filePath.replaceAll("\\x00", "");
+
+        // 检测路径遍历攻击
+        if (UNSAFE_PATTERN.matcher(filePath).find()) {
+            throw new SecurityException("检测到非法路径字符");
+        }
+
+        // 规范化路径
+        Path normalizedPath = Paths.get(filePath).normalize();
+
+        // 检查黑名单路径
+        String normalizedStr = normalizedPath.toString().toLowerCase();
+        for (String blacklisted : BLACKLISTED_PATHS) {
+            if (normalizedStr.startsWith(blacklisted.toLowerCase())) {
+                throw new SecurityException("禁止访问系统目录");
+            }
+        }
+
+        return normalizedPath.toString();
+    }
+
+    /**
+     * 生成安全的文件名
+     */
+    public static String generateSafeFileName(String originalName) {
+        // 移除特殊字符,防止路径遍历攻击
+        String safeName = originalName.replaceAll("[\\\\/:*?\"<>|]", "");
+
+        // 添加时间戳和随机数防止冲突
+        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
+        String random = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 9999));
+
+        if (safeName.endsWith(".xlsx")) {
+            String nameWithoutExt = safeName.substring(0, safeName.length() - 5);
+            return String.format("%s_%s_%s.xlsx", nameWithoutExt, timestamp, random);
+        } else {
+            return String.format("%s_%s_%s.xlsx", safeName, timestamp, random);
+        }
+    }
+    /**
+     * 准备导出文件
+     */
+    public static void prepareExportFile(String fileName, ExportTask exportTask)
+            throws ExportException {
+
+        try {
+            // 创建按日期和操作人分组的目录结构
+            String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
+            String userDir = DigestUtils.md5DigestAsHex(exportTask.getUserId().toString().getBytes()).substring(0, 8);
+
+            // 完整保存路径
+            String fullSavePath = savePath +
+                    "orders/" + dateStr + "/" + userDir + "/";
+
+            // 创建目录
+            File saveDir = new File(fullSavePath);
+            if (!saveDir.exists()) {
+                boolean created = saveDir.mkdirs();
+                if (!created) {
+                    throw new ExportException("创建保存目录失败: " + fullSavePath);
+                }
+
+                // 设置目录权限(Linux系统)
+                if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+                    Runtime.getRuntime().exec("chmod 755 " + saveDir.getAbsolutePath());
+                }
+            }
+            // 检查磁盘空间
+            checkDiskSpace(saveDir);
+            File file = new File(fullSavePath, fileName);
+            // 记录文件信息
+            exportTask.setFileUrl(file.getAbsolutePath());
+            exportTask.setFileName(fileName);
+            exportTask.setFullPath(fullSavePath);
+
+        } catch (IOException e) {
+            throw new ExportException("准备导出文件失败", e);
+        }
+    }
+    /**
+     * 检查磁盘空间
+     */
+    private static void checkDiskSpace(File directory) throws ExportException {
+        try {
+            long freeSpace = directory.getFreeSpace();
+            long minRequiredSpace = maxFileSize * 1024L * 1024L;
+
+            if (freeSpace < minRequiredSpace) {
+                throw new ExportException(String.format(
+                        "磁盘空间不足,剩余: %.2fMB, 需要: %dMB",
+                        freeSpace / 1024.0 / 1024.0,
+                        maxFileSize
+                ));
+            }
+        } catch (Exception e) {
+            throw new ExportException("检查磁盘空间失败", e);
+        }
+    }
+    public static String getContentType(File file) {
+        String fileName = file.getName();
+        String extension = "";
+
+        int lastDotIndex = fileName.lastIndexOf('.');
+        if (lastDotIndex > 0) {
+            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
+        }
+
+        // 根据文件扩展名返回对应的 MIME 类型
+        switch (extension) {
+            case "pdf":
+                return "application/pdf";
+            case "txt":
+                return "text/plain";
+            case "html":
+            case "htm":
+                return "text/html";
+            case "css":
+                return "text/css";
+            case "js":
+                return "application/javascript";
+            case "json":
+                return "application/json";
+            case "xml":
+                return "application/xml";
+            case "jpg":
+            case "jpeg":
+                return "image/jpeg";
+            case "png":
+                return "image/png";
+            case "gif":
+                return "image/gif";
+            case "bmp":
+                return "image/bmp";
+            case "mp4":
+                return "video/mp4";
+            case "mp3":
+                return "audio/mpeg";
+            case "zip":
+                return "application/zip";
+            case "doc":
+            case "docx":
+                return "application/msword";
+            case "xls":
+                return "application/vnd.ms-excel";
+            case "xlsx":
+                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+            case "ppt":
+            case "pptx":
+                return "application/vnd.ms-powerpoint";
+            case "csv":
+                return "text/csv";
+            default:
+                return "application/octet-stream";  // 默认二进制流
+        }
+    }
+
+    /**
+     * 异步清理临时文件待修改
+     */
+//    @Async
+//    public static void cleanTempFilesAsync(String filePath) {
+//        try {
+//            File directory = new File(filePath);
+//            if (directory.exists() && directory.isDirectory()) {
+//                File[] files = directory.listFiles();
+//                if (files != null) {
+//                    LocalDateTime expireTime = LocalDateTime.now()
+//                            .minusDays(retentionDays);
+//
+//                    for (File file : files) {
+//                        if (file.isFile() && file.getName().endsWith(".xlsx")) {
+//                            // 删除过期文件
+//                            BasicFileAttributes attrs = Files.readAttributes(
+//                                    file.toPath(), BasicFileAttributes.class);
+//                            LocalDateTime fileTime = LocalDateTime.ofInstant(
+//                                    attrs.creationTime().toInstant(), ZoneId.systemDefault());
+//
+//                            if (fileTime.isBefore(expireTime)) {
+//                                boolean deleted = file.delete();
+//                                if (deleted) {
+//                                    log.info("删除过期导出文件: {}", file.getAbsolutePath());
+//                                }
+//                            }
+//                        }
+//                    }
+//                }
+//            }
+//        } catch (Exception e) {
+//            log.warn("清理临时文件失败", e);
+//        }
+//    }
+
+
 }

+ 34 - 0
yami-shop-service/src/main/resources/mapper/ExportTaskMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.yami.shop.dao.ExportTaskMapper">
+
+    <select id="findByUserIdAndStatusAndType" resultType="com.yami.shop.bean.model.ExportTask">
+        select * from tz_export_task where  user_id = #{userId} and export_type =#{exportType} and export_status = #{exportStatus} order by  create_time limit 1
+    </select>
+
+    <select id="findByUserIdAndId" resultType="com.yami.shop.bean.model.ExportTask">
+        select * from tz_export_task
+            <where>
+                <if test="userId != null ">
+                    AND  user_id = #{userId}
+                </if>
+                <if test="id != null  and id != ''">
+                    AND  id = #{id}
+                </if>
+            </where>
+    </select>
+
+    <select id="findPage" resultType="com.yami.shop.bean.vo.ExportTaskVo">
+        select a.id,a.user_id,a.export_type,a.export_msg,a.export_status,a.task_name,a.create_time,a.update_time,b.username as operator  from tz_export_task a
+        left join  tz_sys_user b on a.user_id =b.user_id
+        <where>
+            <if test="userId != null ">
+                AND  a.user_id = #{userId}
+            </if>
+            <if test="exportType != null ">
+                AND  a.export_type = #{exportType}
+            </if>
+        </where>
+        order by  a.create_time desc
+    </select>
+</mapper>

+ 83 - 0
yami-shop-service/src/main/resources/mapper/OrderMapper.xml

@@ -1242,4 +1242,87 @@
         </where>
         order by a.create_time desc,a.order_id desc
     </select>
+
+    <select id="orderCount" resultType="com.yami.shop.bean.vo.OrderCountVo">
+        select count(1) as orderTotal,
+        sum(orderMoney) as orderMoneyTotal,
+        sum(shopMoney) as shopMoneyTotal,
+        sum(carriageMoney) as carriageMoneyTotal,
+        sum(orderMoney) as payMoneyTotal,
+        ROUND(sum(pointsMoney) / 100,2) as pointsMoneyTotal,
+        sum(money) as moneyTotal
+        from (
+        select
+        a.order_number,
+        a.total as orderMoney,
+        sum(c.product_total_amount) as shopMoney,
+        IFNUll(a.freight_amount,0) as carriageMoney,
+        IFNUll(a.offset_points,0) as pointsMoney,
+        IFNUll(a.actual_total,0) as money
+        FROM tz_order a
+        LEFT JOIN tz_user_addr_order b on a.addr_order_id=b.addr_order_id
+        LEFT JOIN tz_order_item c on a.order_number =c.order_number
+        left join tz_user e on a.user_id = e.user_id
+        <where>
+            and a.delete_status =0
+            <if test="orderParam.orderNumber != null and orderParam.orderNumber != ''">
+                and a.order_number = #{orderParam.orderNumber}
+            </if>
+            <if test="orderParam.channelIdList != null and !orderParam.channelIdList.isEmpty()">
+                and a.channel_id in
+                <foreach collection="orderParam.channelIdList" item="channelId" open="(" close=")" separator=",">
+                    #{channelId}
+                </foreach>
+            </if>
+            <if test="orderParam.userAttrType != null and orderParam.userAttrType != '' and orderParam.userAttrType != 0">
+                AND e.user_attr_type = #{orderParam.userAttrType}
+            </if>
+            <if test="orderParam.dvyType != null and orderParam.dvyType != ''">
+                and a.dvy_type = #{orderParam.dvyType}
+            </if>
+            <if test="orderParam.shopId != null">
+                and a.shop_id = #{orderParam.shopId}
+            </if>
+            <if test="orderParam.startTime != null">
+                and a.create_time &gt; #{orderParam.startTime}
+            </if>
+            <if test="orderParam.endTime != null">
+                and a.create_time &lt; #{orderParam.endTime}
+            </if>
+            <if test="orderParam.refundStatus != null and orderParam.refundStatus != 0">
+                and a.refund_status = #{orderParam.refundStatus}
+            </if>
+            <if test="orderParam.receiver != null">
+                and b.receiver LIKE concat("%",#{orderParam.receiver},"%")
+            </if>
+            <if test="orderParam.userMobile != null">
+                and b.mobile LIKE concat("%",#{orderParam.userMobile},"%")
+            </if>
+
+            <if test="orderParam.orderStatus != null and orderParam.orderStatus != ''">
+                <if test="orderParam.orderStatus == 'all'">
+                    AND a.hb_order_status in (0, 1,20,30,40,50,60,70,80)
+                </if>
+                <if test="orderParam.orderStatus == 'paddingPay'">
+                    AND a.hb_order_status in (0)
+                </if>
+
+                <if test="orderParam.orderStatus == 'paddingShipped'">
+                    AND a.hb_order_status in (1)
+                </if>
+
+                <if test="orderParam.orderStatus == 'paddingReceived'">
+                    AND a.hb_order_status in (20,30,40,70)
+                </if>
+                <if test="orderParam.orderStatus == 'completed'">
+                    AND a.hb_order_status in (80)
+                </if>
+                <if test="orderParam.orderStatus == 'cancel'">
+                    AND a.hb_order_status in (50,60)
+                </if>
+            </if>
+        </where>
+        group by a.order_number
+        ) m
+    </select>
 </mapper>

+ 6 - 6
yami-shop-service/src/main/resources/mapper/OrderRefundMapper.xml

@@ -478,10 +478,10 @@
                 and re.return_money_sts = #{orderRefund.returnMoneySts}
             </if>
             <if test="orderRefund.startTime != null">
-                AND re.create_time &gt;= #{orderRefund.startTime}
+                AND o.create_time &gt;= #{orderRefund.startTime}
             </if>
             <if test="orderRefund.endTime != null">
-                AND re.create_time &lt;= #{orderRefund.endTime}
+                AND o.create_time &lt;= #{orderRefund.endTime}
             </if>
         </where>
 
@@ -522,10 +522,10 @@
                 and refund.return_money_sts = #{orderRefund.returnMoneySts}
             </if>
             <if test="orderRefund.startTime != null">
-                AND refund.create_time &gt;= #{orderRefund.startTime}
+                AND o.create_time &gt;= #{orderRefund.startTime}
             </if>
             <if test="orderRefund.endTime != null">
-                AND refund.create_time &lt;= #{orderRefund.endTime}
+                AND o.create_time &lt;= #{orderRefund.endTime}
             </if>
         </where>
         order by refund.apply_time desc
@@ -587,10 +587,10 @@
                 and refund.return_money_sts = #{orderRefund.returnMoneySts}
             </if>
             <if test="orderRefund.startTime != null">
-                AND refund.create_time &gt;= #{orderRefund.startTime}
+                AND o.create_time &gt;= #{orderRefund.startTime}
             </if>
             <if test="orderRefund.endTime != null">
-                AND refund.create_time &lt;= #{orderRefund.endTime}
+                AND o.create_time &lt;= #{orderRefund.endTime}
             </if>
         </where>
         order by refund.apply_time desc

+ 6 - 3
yami-shop-service/src/main/resources/mapper/ProdCommMapper.xml

@@ -18,7 +18,9 @@
         <result property="isAnonymous" column="is_anonymous"/>
         <result property="status" column="status"/>
         <result property="evaluate" column="evaluate"/>
-
+        <result property="userMobile" column="user_mobile"/>
+        <result property="channelName" column="channel_name"/>
+        <result property="shopName" column="shop_name"/>
         <result property="nickName" column="nick_name"/>
         <result property="avatar" column="avatar"/>
     </resultMap>
@@ -229,15 +231,16 @@
     </select>
 
     <select id="backendCommList" resultMap="BaseResultMap">
-        SELECT b.nick_name,b.pic avatar,a.*
+        SELECT b.nick_name,b.user_mobile,b.pic avatar,a.*,d.channel_name
         FROM tz_prod_comm a
         LEFT JOIN tz_user b on a.user_id=b.user_id
         LEFT JOIN tz_order c on a.order_number =c.order_number
+        LEFT JOIN tz_channel d on d.id =c.channel_id
         <where>
             <if test="po.score != null">
                 and a.score=#{po.score}
             </if>
-            <if test="po.score != null">
+            <if test="po.status != null">
                 and a.status=#{po.status}
             </if>
             <if test="po.replySts != null">