实现 高性能分批导出+动态进度条

main
HUOJIN\92525 2025-05-08 17:01:38 +08:00
parent dec6af6da1
commit fc5f67d70f
9 changed files with 202 additions and 27 deletions

View File

@ -1,5 +1,6 @@
package net.lab1024.sa.admin.module.business.wms.base.address.controller;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.entity.AddressEntity;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.form.AddressAddForm;
@ -11,6 +12,7 @@ import net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO
import net.lab1024.sa.admin.module.business.wms.base.address.service.AddressQueryService;
import net.lab1024.sa.admin.module.business.wms.base.address.service.AddressService;
import net.lab1024.sa.admin.module.business.wms.base.item.domain.vo.ItemsExcelVO;
import net.lab1024.sa.admin.module.business.wms.excel.ExportTaskService;
import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ValidateList;
import net.lab1024.sa.base.common.util.SmartExcelUtil;
@ -47,6 +49,9 @@ public class AddressController {
@Resource
private AddressQueryService addressQueryService;
@Resource
private ExportTaskService exportTaskService;
@Operation(summary = "分页查询 @author hj")
@PostMapping("/address/queryPage")
@SaCheckPermission("address:query")
@ -91,7 +96,7 @@ public class AddressController {
@Operation(summary = "地址下拉查询")
@PostMapping("/address/queryAddress")
public ResponseDTO<List<AddressEntity>> queryAddress(@RequestBody AddressSelect addressSelect) {
public ResponseDTO<PageResult<AddressVO>> queryAddress(@RequestBody @Valid AddressSelect addressSelect) {
return ResponseDTO.ok(addressQueryService.queryAddress(addressSelect));
}
@ -103,15 +108,23 @@ public class AddressController {
return addressService.importAddress(file);
}
@Operation(summary = "导出 霍锦")
@GetMapping("/address/exportAddress")
@SaCheckPermission("address:exportAddress")
public void exportAddress(HttpServletResponse response) throws IOException {
List<AddressExcelVO> addressList = addressQueryService.queryAddressExcel();
Long start = System.currentTimeMillis();
SmartExcelUtil.exportExcel(response, "收货地址信息.xlsx", "收货地址", AddressExcelVO.class, addressList);
Long end = System.currentTimeMillis();
System.out.println("导出耗时:" + (end - start));
@PostMapping("/address/createExportTask")
public ResponseDTO<String> createExportTask() {
String taskId = exportTaskService.createTask();
return ResponseDTO.ok(taskId);
}
@GetMapping("/address/progress/{taskId}")
public ResponseDTO<Long> getExportProgress(@PathVariable String taskId) {
Long progress = exportTaskService.getProgress(taskId);
return ResponseDTO.ok(progress);
}
@Operation(summary = "导出 霍锦")
@GetMapping("/address/exportAddress/{taskId}")
@SaCheckPermission("address:exportAddress")
public void exportAddress(@PathVariable String taskId, HttpServletResponse response) {
System.out.println(exportTaskService.isTaskExists(taskId));
addressQueryService.exportAddress(taskId, response);
}
}

View File

@ -1,8 +1,10 @@
package net.lab1024.sa.admin.module.business.wms.base.address.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.entity.AddressEntity;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.form.AddressQueryForm;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.form.AddressSelect;
import net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -34,10 +36,25 @@ public interface AddressDao extends BaseMapper<AddressEntity> {
*/
List<AddressVO> queryPage(Page page, @Param("queryForm") AddressQueryForm queryForm);
/**
*
*
* @param page
* @param addressSelect
* @return
*/
List<AddressVO> queryAddress(Page page, @Param("addressSelect") AddressSelect addressSelect);
/**
*
* @return
*/
@Select("SELECT name,person,telephone,address FROM t_address")
@Options(fetchSize = 2000) // 每次拉取2000条
@Options(fetchSize = 2000)
Cursor<AddressVO> selectAllByCursor();
//游标分页
List<AddressVO> listByCursor(@Param("lastId") Long lastId, @Param("pageSize") int pageSize);
}

View File

@ -1,9 +1,14 @@
package net.lab1024.sa.admin.module.business.wms.base.address.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.lab1024.sa.base.common.domain.PageParam;
@Data
public class AddressSelect {
@EqualsAndHashCode(callSuper = false)
public class AddressSelect extends PageParam {
@Schema(description = "收货单位")
private String name;
}

View File

@ -1,6 +1,8 @@
package net.lab1024.sa.admin.module.business.wms.base.address.domain.vo;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.*;
import cn.idev.excel.enums.poi.FillPatternTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -13,6 +15,7 @@ import java.time.LocalDateTime;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ColumnWidth(20)
public class AddressExcelVO {
@ExcelProperty("收货单位")
private String name;

View File

@ -27,7 +27,7 @@ public interface AddressQueryService {
* @param addressSelect
* @return List<AddressEntity>
*/
List<AddressEntity> queryAddress(AddressSelect addressSelect);
PageResult<AddressVO> queryAddress(AddressSelect addressSelect);
/**
* id
@ -60,4 +60,6 @@ public interface AddressQueryService {
List<AddressExcelVO> queryAddressExcel3();
void exportAddress(String taskId,HttpServletResponse response);
}

View File

@ -3,6 +3,9 @@ package net.lab1024.sa.admin.module.business.wms.base.address.service.impl;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.metadata.style.WriteFont;
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
@ -18,16 +21,20 @@ import net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO
import net.lab1024.sa.admin.module.business.wms.base.address.manager.AddressManager;
import net.lab1024.sa.admin.module.business.wms.base.address.service.AddressQueryService;
import net.lab1024.sa.admin.module.business.wms.base.item.domain.vo.ItemsExcelVO;
import net.lab1024.sa.admin.module.business.wms.excel.ExportTaskService;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.util.SmartResponseUtil;
import net.lab1024.sa.base.module.support.dict.constant.DictConst;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cursor.Cursor;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
@ -43,6 +50,9 @@ public class AddressQueryServiceImpl implements AddressQueryService {
@Resource
private AddressManager addressManager;
@Resource
private ExportTaskService exportTaskService;
/**
*
*
@ -56,13 +66,15 @@ public class AddressQueryServiceImpl implements AddressQueryService {
}
/**
*
*
*
* @param addressSelect
* @return List<AddressEntity>
*/
public List<AddressEntity> queryAddress(AddressSelect addressSelect) {
return addressManager.list();
public PageResult<AddressVO> queryAddress(AddressSelect addressSelect) {
Page<?> page = SmartPageUtil.convert2PageQuery(addressSelect);
List<AddressVO> list = addressDao.queryAddress(page, addressSelect);
return SmartPageUtil.convert2PageResult(page, list);
}
/**
@ -197,5 +209,66 @@ public class AddressQueryServiceImpl implements AddressQueryService {
return list;
}
public void exportAddress(String taskId, HttpServletResponse response) {
System.out.println("开始读取地址数据...");
long startTime = System.currentTimeMillis();
SmartResponseUtil.setDownloadFileHeader(response, "收货地址信息.xlsx", null);
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
headWriteCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontName("宋体");
headWriteFont.setColor(IndexedColors.WHITE.getIndex());
headWriteCellStyle.setWriteFont(headWriteFont);
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setFillForegroundColor(IndexedColors.BLACK.getIndex());
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
try (ExcelWriter excelWriter = FastExcel.write(response.getOutputStream(), AddressExcelVO.class).registerWriteHandler(horizontalCellStyleStrategy).build()) {
WriteSheet writeSheet = FastExcel.writerSheet("收货地址信息").build();
//总条数
long total = addressManager.count();
// 初始化为最小ID-1
long lastId = 0;
// 根据测试调整
int pageSize = 2000;
//进度条
long processed = 0;
while (true) {
List<AddressVO> batch = addressDao.listByCursor(lastId, pageSize);
if (batch.isEmpty()) {
break;
}
List<AddressExcelVO> excelData = new ArrayList<>();
for (AddressVO address : batch) {
AddressExcelVO excelVO = AddressExcelVO.builder()
.name(address.getName())
.person(address.getPerson())
.telephone(address.getTelephone())
.address(address.getAddress())
.build();
excelData.add(excelVO);
}
excelWriter.write(excelData, writeSheet);
lastId = batch.stream().mapToLong(AddressVO::getAddressId).max().orElse(0);
processed += batch.size();
//计算进度条
long progress = processed * 100 / total;
exportTaskService.updateProgress(taskId, progress);
System.out.println("已处理:" + processed + "条数据,进度:" + progress + "%");
}
System.out.println("导出耗时:" + (System.currentTimeMillis() - startTime) + "ms");
excelWriter.finish();
exportTaskService.updateProgress(taskId, 100);
exportTaskService.cleanupTask(taskId);
} catch (Exception e) {
throw new BusinessException("导出失败");
}
}
}

View File

@ -0,0 +1,38 @@
package net.lab1024.sa.admin.module.business.wms.excel;
import jakarta.servlet.ServletOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Service
@Slf4j
public class ExportTaskService {
private final ConcurrentMap<String, Long> progressMap = new ConcurrentHashMap<>();
public String createTask() {
String taskId = System.currentTimeMillis() + "-" + new Random().nextInt(1000000);
progressMap.put(taskId, 0L);
return taskId;
}
public boolean isTaskExists(String taskId) {
return progressMap.containsKey(taskId);
}
public void updateProgress(String taskId, long progress) {
progressMap.put(taskId, progress);
}
public Long getProgress(String taskId) {
return progressMap.getOrDefault(taskId, -1L);
}
public void cleanupTask(String taskId) {
progressMap.remove(taskId);
}
}

View File

@ -4,7 +4,10 @@
<!-- 查询结果列 -->
<sql id="base_columns">
t_address.address_id,
t_address
.
address_id
,
t_address.name,
t_address.person,
t_address.telephone,
@ -20,23 +23,34 @@
<where>
<!--收货单位-->
<if test="queryForm.addressId != null ">
AND t_address.address_id=#{queryForm.addressId}
AND t_address.address_id=#{queryForm.addressId}
</if>
</where>
order by t_address.address_id desc
</select>
<select id="listByCursor" resultType="net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO">
<select id="queryAddress" resultType="net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO">
SELECT
t_address.address_id,
t_address.name,
t_address.person,
t_address.telephone,
t_address.address
<include refid="base_columns"/>
FROM t_address
<where>
<!--收货单位-->
<if test="addressSelect.name != null ">
AND t_address.name like concat(#{addressSelect.name},'%')
</if>
</where>
</select>
<select id="listByCursor" resultType="net.lab1024.sa.admin.module.business.wms.base.address.domain.vo.AddressVO">
SELECT t_address.address_id,
t_address.name,
t_address.person,
t_address.telephone,
t_address.address
FROM t_address
WHERE t_address.address_id > #{lastId}
ORDER BY t_address.address_id ASC -- 必须升序
LIMIT #{pageSize}
ORDER BY t_address.address_id ASC -- 必须升序
LIMIT #{pageSize}
</select>
</mapper>

View File

@ -4,12 +4,15 @@ import cn.idev.excel.FastExcel;
import cn.idev.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
import cn.idev.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.metadata.style.WriteFont;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
@ -42,6 +45,12 @@ public final class SmartExcelUtil {
public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head,Collection<?> data) throws IOException {
// 设置下载消息头
SmartResponseUtil.setDownloadFileHeader(response, fileName, null);
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
headWriteCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontName("宋体");
headWriteFont.setColor(IndexedColors.WHITE.getIndex());
headWriteCellStyle.setWriteFont(headWriteFont);
// 下载
FastExcel.write(response.getOutputStream(), head)
.autoCloseStream(Boolean.FALSE)
@ -49,6 +58,7 @@ public final class SmartExcelUtil {
.doWrite(data);
}
/**
* sheet
*/