opt:新增excel 导出

This commit is contained in:
2025-11-20 16:20:14 +08:00
parent c32665d1be
commit f79a077703
15 changed files with 597 additions and 85 deletions

View File

@@ -15,11 +15,11 @@
*/
package org.nl.common.utils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.nl.common.exception.BadRequestException;
import org.nl.config.language.LangProcess;
@@ -31,12 +31,11 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* File工具类扩展 hutool 工具包
@@ -204,27 +203,120 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
/**
* 导出excel
*/
public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
File file = new File(tempPath);
BigExcelWriter writer = ExcelUtil.getBigWriter(file);
// 一次性写出内容,使用默认样式,强制输出标题
// 2. 【核心优化】预先计算每一列的最大"等效宽度"
Map<Integer, Integer> maxColumnWidthMap = new HashMap<>();
if (list != null && !list.isEmpty()) {
List<String> headers = new ArrayList<>(list.get(0).keySet());
for (Map<String, Object> row : list) {
for (int i = 0; i < headers.size(); i++) {
String headerKey = headers.get(i);
Object valueObj = row.get(headerKey);
String cellValue = valueObj != null ? valueObj.toString() : "";
// ### 使用新的宽度计算方法 ###
int valueWidth = calculateExcelColumnWidth(cellValue);
maxColumnWidthMap.put(i, Math.max(maxColumnWidthMap.getOrDefault(i, 0), valueWidth));
}
}
}
// 3. 一次性写出内容
writer.write(list, true);
SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
//上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法
sheet.trackAllColumnsForAutoSizing();
//列宽自适应
writer.autoSizeColumnAll();
//response为HttpServletResponse对象
// 4. 根据预先计算的最大宽度,设置列宽
SXSSFSheet sheet = (SXSSFSheet) writer.getSheet();
// 这个系数可以根据需要微调现在因为我们的计算更精确1.1或1.2就足够了
float widthCoefficient = 1.1f;
int minWidth = 256 * 12; // 稍微增大最小宽度
int maxWidth = 256 * 60; // 适当增大最大宽度
for (Map.Entry<Integer, Integer> entry : maxColumnWidthMap.entrySet()) {
int columnIndex = entry.getKey();
int maxContentWidth = entry.getValue();
// 使用计算出的"等效宽度"来设置列宽
int excelWidth = (int) (maxContentWidth * 256 * widthCoefficient);
excelWidth = Math.max(excelWidth, minWidth);
excelWidth = Math.min(excelWidth, maxWidth);
sheet.setColumnWidth(columnIndex, excelWidth);
}
// 如果没有数据,给表头设置一个合适的默认宽度
if (maxColumnWidthMap.isEmpty()) {
SXSSFRow headerRow = sheet.getRow(0);
if (headerRow != null) {
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
// 默认宽度也可以基于表头文字计算
String headerValue = headerRow.getCell(i).getStringCellValue();
int headerWidth = calculateExcelColumnWidth(headerValue);
sheet.setColumnWidth(i, (int) (headerWidth * 256 * 1.2));
}
}
}
// 5. 设置响应头信息
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
//test.xls是弹出下载对话框的文件名不能为中文中文请自行编码
response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
ServletOutputStream out = response.getOutputStream();
// 终止后删除临时文件
file.deleteOnExit();
writer.flush(out, true);
//此处记得关闭输出Servlet流
IoUtil.close(out);
String fileName = URLEncoder.encode("数据导出.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
// 6. 写出到响应流
try (ServletOutputStream out = response.getOutputStream()) {
file.deleteOnExit();
writer.flush(out, true);
} finally {
writer.close(); // 确保资源被释放
}
}
/**
* 【新增辅助方法】计算字符串在Excel中的等效显示宽度
* 为中文字符和全角符号赋予更高的权重。
* @param str 要计算宽度的字符串
* @return 等效宽度值
*/
private static int calculateExcelColumnWidth(String str) {
if (str == null || str.isEmpty()) {
return 0;
}
int width = 0;
for (char c : str.toCharArray()) {
// 判断是否为中文字符或全角符号
if (isChineseChar(c) || isFullWidthSymbol(c)) {
// 中文字符宽度加权例如算作1.8个字符
width += 2;
} else {
// 英文字符、数字、半角符号算作1个字符
width += 1;
}
}
return width;
}
/**
* 判断字符是否为中文字符
*/
private static boolean isChineseChar(char c) {
// 根据Unicode编码范围判断
return (c >= 0x4E00 && c <= 0x9FA5);
}
/**
* 判断字符是否为全角符号
* 全角符号的Unicode编码通常在 0xFF00-0xFFEF 之间
*/
private static boolean isFullWidthSymbol(char c) {
return (c >= 0xFF00 && c <= 0xFFEF);
}
public static String getFileType(String type) {

View File

@@ -1,23 +1,29 @@
package org.nl.wms.basedata_manage.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.nl.common.base.TableDataInfo;
import org.nl.common.domain.query.PageQuery;
import org.nl.common.logging.annotation.Log;
import org.nl.wms.basedata_manage.service.IStructattrService;
import org.nl.wms.basedata_manage.service.dao.Structattr;
import org.springframework.data.domain.Pageable;
import org.nl.wms.system_manage.service.columnInfo.ColumnInfoService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
*
@@ -36,6 +42,9 @@ public class StructattrController {
@Resource
private IStructattrService structattrService;
@Resource
private ColumnInfoService columnInfoService;
@GetMapping
@Log("查询仓位")
public ResponseEntity<Object> query(@RequestParam Map whereJson, PageQuery page) {
@@ -70,4 +79,31 @@ public class StructattrController {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@PostMapping("/exportFile")
public void exportFile(@RequestBody Map whereJson, HttpServletResponse response) {
PageQuery page = new PageQuery();
page.setPage(0);
page.setSize(99999);
IPage<Structattr> structIvt = structattrService.queryAll(whereJson, page);
List<Structattr> structIvtList = structIvt.getRecords();
List<Map> maps = structIvtList.stream().map(structattr -> {
try {
// BeanUtils.describe() 会把 JavaBean 转成 Mapkey 是属性名value 是属性值)
return BeanUtils.describe(structattr);
} catch (Exception e) {
e.printStackTrace();
return new HashMap();
}
}).collect(Collectors.toList());
try {
columnInfoService.exportFile("st_ivt_structattr", maps, response,
null,
null);
} catch (IOException e) {
log.info("EXCEL 导出异常,异常原因=【{}】",e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -116,5 +116,4 @@ public interface IStructattrService extends IService<Structattr> {
void changeStruct(StructattrChangeDto changeDto);
BigDecimal calculateSectOccupancyRate(String sect_code);
}

View File

@@ -0,0 +1,20 @@
package org.nl.wms.system_manage.service.columnInfo;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* <p>
* 数据库功能
* </p>
*
* @author zhengxuming
* @since 2025年11月20日14:17:16
*/
public interface ColumnInfoService {
<T> void exportFile(String tableName, List<Map> data, HttpServletResponse response, List passParam, Map<String, String> customizMap) throws IOException;
}

View File

@@ -0,0 +1,18 @@
package org.nl.wms.system_manage.service.columnInfo.dao.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface ColumnInfoMapper {
/**
* 获取表的列信息
* @param tableName 表名
* @return 列信息列表
*/
List<Map<String, String>> getTableColumns(@Param("tableName") String tableName);
}

View File

@@ -0,0 +1,12 @@
<?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="org.nl.wms.system_manage.service.columnInfo.dao.mapper.ColumnInfoMapper">
<select id="getTableColumns" resultType="java.util.Map">
SELECT column_name AS columnName, column_comment AS columnComment
FROM information_schema.columns
WHERE table_schema = (SELECT DATABASE())
AND table_name = #{tableName}
ORDER BY ordinal_position
</select>
</mapper>

View File

@@ -0,0 +1,89 @@
package org.nl.wms.system_manage.service.columnInfo.impl;
import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
import org.nl.common.exception.BadRequestException;
import org.nl.common.utils.FileUtil;
import org.nl.wms.system_manage.service.columnInfo.ColumnInfoService;
import org.nl.wms.system_manage.service.columnInfo.dao.mapper.ColumnInfoMapper;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* 数据库功能
* </p>
*
* @author zhengxuming
* @since 2025年11月20日14:17:16
*/
@Service
public class ColumnInfoServiceImpl implements ColumnInfoService {
@Resource
private ColumnInfoMapper columnInfoMapper;
/**
* 获取表的列信息,返回列名和列注释的映射
* @param tableName 表名
* @return 列名和列注释的映射
*/
private Map<String, String> TableColumn(String tableName) {
Map<String, String> columnMap = new LinkedHashMap<>();
List<Map<String, String>> columns = columnInfoMapper.getTableColumns(tableName);
if (!CollectionUtils.isEmpty(columns)) {
for (Map<String, String> column : columns) {
String columnName = column.get("columnName");
String columnComment = column.get("columnComment");
// 如果列注释为空,则使用列名作为注释
if (StringUtils.isEmpty(columnComment)) {
columnComment = columnName;
}
columnMap.put(columnName, columnComment);
}
}
return columnMap;
}
@Override
@SneakyThrows
public <T> void exportFile(String tableName, List<Map> data, HttpServletResponse response, List passParam, Map<String, String> customizMap) {
if (CollectionUtils.isEmpty(data)) {
throw new BadRequestException("导出失败,没有导出数据" + data.size());
}
List<Map<String, Object>> excel_lst = new ArrayList<>();
Map<String, String> map = new LinkedHashMap<>();
if (!StringUtils.isEmpty(tableName)) {
map = this.TableColumn(tableName);
if (!CollectionUtils.isEmpty(passParam)) {
for (Object s : passParam) {
map.remove(s);
}
}
}
if(!CollectionUtils.isEmpty(customizMap)) {
map.putAll(customizMap);
}
if (CollectionUtils.isEmpty(map)) {
throw new BadRequestException("导出失败,表结构信息失败" + tableName);
}
for (Map item : data) {
Map<String, Object> excelMap = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
excelMap.put(entry.getValue(), item.get(entry.getKey()));
}
excel_lst.add(excelMap);
}
if (CollectionUtils.isEmpty(excel_lst)) {
throw new BadRequestException("导出失败,没有获取到导出参数" + data.size());
}
FileUtil.downloadExcel(excel_lst, response);
}
}

View File

@@ -1,10 +1,11 @@
<?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="org.nl.wms.warehouse_manage.service.dao.mapper.IOStorInvMapper">
<select id="queryAllByPage" resultType="org.nl.wms.warehouse_manage.service.dao.IOStorInv">
SELECT DISTINCT ios.* FROM st_ivt_iostorinv ios
<select id="queryAllByPage" resultType="org.nl.wms.warehouse_manage.service.dto.IOStorInvDto">
SELECT DISTINCT ios.*,m.material_code,m.material_name FROM st_ivt_iostorinv ios
LEFT JOIN st_ivt_iostorinvdtl dtl ON ios.iostorinv_id = dtl.iostorinv_id
LEFT JOIN st_ivt_iostorinvdis dis ON dtl.iostorinvdtl_id = dis.iostorinvdtl_id
LEFT JOIN md_me_materialbase m on dtl.material_id = m.material_id
<where>
ios.is_delete = '0' AND ios.io_type = '0'
<if test="params.bill_code != null">
@@ -161,10 +162,11 @@
</select>
<select id="queryOutBillPage" resultType="org.nl.wms.warehouse_manage.service.dao.IOStorInv">
SELECT DISTINCT ios.* FROM st_ivt_iostorinv ios
<select id="queryOutBillPage" resultType="org.nl.wms.warehouse_manage.service.dto.IOStorInvDto">
SELECT DISTINCT ios.* ,m.material_code,m.material_name FROM st_ivt_iostorinv ios
LEFT JOIN st_ivt_iostorinvdtl dtl ON ios.iostorinv_id = dtl.iostorinv_id
LEFT JOIN st_ivt_iostorinvdis dis ON dtl.iostorinvdtl_id = dis.iostorinvdtl_id
LEFT JOIN md_me_materialbase m on dtl.material_id = m.material_id
<where>
ios.is_delete = '0' AND ios.io_type = '1'
<if test="params.bill_code != null">

View File

@@ -0,0 +1,200 @@
package org.nl.wms.warehouse_manage.service.dto;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* @author dsh
* 2025/5/19
*/
@Data
public class IOStorInvDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 出入库单标识
*/
private String iostorinv_id;
/**
* 单据编号
*/
private String bill_code;
/**
* 出入类型
*/
private String io_type;
/**
* 单据类型
*/
private String bill_type;
/**
* 业务日期
*/
private String biz_date;
/**
* 仓库标识
*/
private String stor_id;
/**
* 仓库编码
*/
private String stor_code;
/**
* 仓库名称
*/
private String stor_name;
/**
* 来源方标识
*/
private String source_id;
/**
* 来源方名称
*/
private String source_name;
/**
* 来源方类型
*/
private String source_type;
/**
* 总数量
*/
private BigDecimal total_qty;
/**
* 总重量
*/
private BigDecimal total_weight;
/**
* 明细数
*/
private Integer detail_count;
/**
* 单据状态 10生成 20分配中 30分配完 99完成
*/
private String bill_status;
/**
* 备注
*/
private String remark;
/**
* 生成方式
*/
private String create_mode;
/**
* 制单人
*/
private String input_optid;
/**
* 制单人姓名
*/
private String input_optname;
/**
* 制单时间
*/
private String input_time;
/**
* 修改人
*/
private String update_optid;
/**
* 修改人姓名
*/
private String update_optname;
/**
* 修改时间
*/
private String update_time;
/**
* 分配人
*/
private String dis_optid;
/**
* 分配人姓名
*/
private String dis_optname;
/**
* 分配时间
*/
private String dis_time;
/**
* 确认人
*/
private String confirm_optid;
/**
* 确认人姓名
*/
private String confirm_optname;
/**
* 确认时间
*/
private String confirm_time;
/**
* 部门ID
*/
private String sysdeptid;
/**
* 公司ID
*/
private String syscompanyid;
/**
* 是否删除
*/
private String is_delete;
/**
* 是否已上传
*/
private String is_upload;
/**
* 回传人
*/
private String upload_optid;
/**
* 回传时间
*/
private String upload_time;
private String barcode;
private String vehicle_code;
private String struct_code;
private String material_code;
private String material_name;
}