-
기본 클래스 설명 =======
-
ExcelExportHandler(내보내기 유틸리티 클래스)
-
DataQuery(일반 데이터 조회)
-
DataProcessor(함수 매개변수)
-
SpringContextUtil(빈 객체 가져오기)
-
코드 ====
ExcelExportHandler
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.itxl.common.utils.SpringContextUtil;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author kimMinJoon
* @date 2024/3/27
*/
public class ExcelExportHandler<T>{
private Map<String,WriteSheet> 시트맵 = null;
private Map<String,WriteTable> 테이블맵 = null;
private List<WriteHandler> 쓰기핸들러리스트 = null;
private com.alibaba.excel.ExcelWriter 작성자 = null;
private OutputStream 출력스트림 = null;
private String 현재시트이름 = "sheet";
private Integer 시트번호 = 1;
private Integer 현재시트데이터크기 = 0;
private static final Integer 시트최대갯수 = 1000000;
private List<List<String>> 헤더리스트 = null;
/**
* 응답 파일 스트림 타입
*/
private static final String 콘텐츠타입 = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
/**
* 응답 헤더 파일명 인코딩 형식
*/
private static final String 인코딩 = "UTF-8";
private Long 전체데이터수;
private boolean 다중스레드사용 = false;
public static <T> ExcelExportHandler<T> 생성(){
return new ExcelExportHandler<>();
}
public ExcelExportHandler<T> 응답설정(HttpServletResponse 응답,String 파일이름) throws IOException {
응답.setContentType(콘텐츠타입);
응답.setCharacterEncoding(인코딩);
// 여기서 URLEncoder.encode는 중국어 문자 깨짐 방지를 위함 (easyexcel과 무관)
파일이름 = URLEncoder.encode(파일이름, 인코딩).replaceAll("\\+", "%20");
응답.setHeader("fileName",파일이름+".xlsx");
return 출력스트림설정(응답.getOutputStream());
}
public ExcelExportHandler<T> 출력스트림설정(OutputStream 출력스트림){
this.출력스트림 = 출력스트림;
return this;
}
private WriteSheet 시트가져오기(String 시트이름){
if (CollUtil.isEmpty(시트맵)){
시트맵 = new HashMap<>();
}
if (StrUtil.isEmpty(시트이름)){
시트이름 = 현재시트이름;
}
if (시트번호 > 1){
시트이름 = 시트이름 + 시트번호;
}
WriteSheet 시트 = 시트맵.get(시트이름);
if (시트 == null){
시트 = new WriteSheet();
시트.setSheetName(시트이름);
시트맵.put(시트이름,시트);
}
return 시트;
}
public ExcelExportHandler<T> 현재시트설정(String 시트이름){
this.현재시트이름 = 시트이름;
return this;
}
private WriteTable 테이블가져오기(String 테이블이름){
if (CollUtil.isEmpty(테이블맵)){
테이블맵 = new HashMap<>();
}
if (StrUtil.isEmpty(테이블이름)){
테이블이름 = "table";
}
return 테이블맵.computeIfAbsent(테이블이름, k -> new WriteTable());
}
public ExcelExportHandler<T> 쓰기핸들러추가(WriteHandler 쓰기핸들러){
if (CollUtil.isEmpty(쓰기핸들러리스트)){
쓰기핸들러리스트 = new ArrayList<>();
}
쓰기핸들러리스트.add(쓰기핸들러);
return this;
}
public ExcelExportHandler<T> 빌더(){
ExcelWriterBuilder 작성빌더 = EasyExcelFactory.write(출력스트림);
if (CollUtil.isNotEmpty(쓰기핸들러리스트)){
for (WriteHandler 쓰기핸들러 : 쓰기핸들러리스트) {
작성빌더.registerWriteHandler(쓰기핸들러);
}
}
this.작성자 = 작성빌더.build();
return this;
}
public ExcelExportHandler<T> 헤더설정(List<String> 헤더리스트){
return 헤더목록설정(헤더변환(헤더리스트));
}
private List<List<String>> 헤더변환(List<String> 헤더리스트) {
List<List<String>> 결과 = new ArrayList<>(헤더리스트.size());
for (String 헤더 : 헤더리스트) {
결과.add(Collections.singletonList(헤더));
}
return 결과;
}
private ExcelExportHandler<T> 헤더목록설정(List<List<String>> 헤더리스트){
테이블가져오기(null).setHead(헤더리스트);
return this;
}
public ExcelExportHandler<T> 데이터쓰기(List<List<Object>> 데이터리스트){
if (CollUtil.isEmpty(데이터리스트)){
return this;
}
while (현재시트데이터크기 + 데이터리스트.size() > 시트최대갯수){
List<List<Object>> 왼쪽데이터 = ListUtil.sub(데이터리스트, 0, 시트최대갯수 - 현재시트데이터크기);
작성자.write(왼쪽데이터,시트가져오기(null),테이블가져오기(null));
데이터리스트 = ListUtil.sub(데이터리스트, 시트최대갯수 - 현재시트데이터크기, 데이터리스트.size());
현재시트데이터크기 = 0;
시트번호++;
}
현재시트데이터크기 += 데이터리스트.size();
작성자.write(데이터리스트,시트가져오기(null),테이블가져오기(null));
return this;
}
public ExcelExportHandler<T> 데이터쓰기(DataQuery<List<List<Object>>> 데이터조회) throws InterruptedException {
return 데이터쓰기(데이터조회, 데이터조회.getPageSize());
}
public ExcelExportHandler<T> 데이터쓰기(DataQuery<List<List<Object>>> 데이터조회, Integer 페이지크기) throws InterruptedException {
if (전체데이터수 == null){
List<List<Object>> 결과 = 데이터조회.accept();
return 데이터쓰기(결과);
}
데이터조회.setPageSize(페이지크기);
int 전체페이지수 = PageUtil.totalPage(전체데이터수, 데이터조회.getPageSize());
AtomicInteger 현재페이지번호 = new AtomicInteger(1);
if (!다중스레드사용){
데이터쓰기(현재페이지번호, 전체페이지수, 데이터조회);
return this;
}
ThreadPoolTaskExecutor 스레드풀 = SpringContextUtil.getBean(ThreadPoolTaskExecutor.class);
List<Future<?>> 퓨처리스트 = new ArrayList<>();
int 스레드풀크기 = Math.min(전체페이지수, 4);
for (int i = 0; i < 스레드풀크기; i++) {
Future<?> 퓨처 = 스레드풀.submit(() -> {
데이터쓰기(현재페이지번호, 전체페이지수, 데이터조회);
});
퓨처리스트.add(퓨처);
}
for (Future<?> 퓨처 : 퓨처리스트) {
try {
퓨처.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
return this;
}
private void 데이터쓰기(AtomicInteger 현재페이지번호, Integer 전체페이지수, DataQuery<List<List<Object>>> 데이터조회){
while (현재페이지번호.get() <= 전체페이지수) {
데이터조회.setPageNum(현재페이지번호.getAndIncrement());
List<List<Object>> 결과 = 데이터조회.accept();
synchronized (작성자){
데이터쓰기(결과);
}
데이터조회.removePage();
}
}
public static List<List<Object>> 데이터변환(List<Object> 데이터리스트,List<String> 필드리스트){
SimpleDateFormat 날짜포맷 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
List<List<Object>> 결과 = new ArrayList<>(데이터리스트.size());
for (Object 데이터 : 데이터리스트) {
JSONObject json = JSONUtil.parseObj(데이터);
List<Object> 객체리스트 = new ArrayList<>(필드리스트.size());
for (String 필드 : 필드리스트) {
Object 값 = json.get(필드);
if (값 == null){
값 = "";
}else if (값 instanceof Date){
값 = 날짜포맷.format(값);
}else{
값 = 값.toString();
}
객체리스트.add(값);
}
결과.add(객체리스트);
}
return 결과;
}
public void 닫기() {
if (작성자 == null){
return;
}
작성자.close();
}
/**
* 페이지네이션 쿼리 활성화
* @param 전체데이터수
* @return
*/
public ExcelExportHandler<T> 페이지네이션(Long 전체데이터수){
this.전체데이터수 = 전체데이터수;
return this;
}
/**
* 다중 스레딩 활성화
* @param 전체데이터수
* @return
*/
public ExcelExportHandler<T> 다중스레드(Long 전체데이터수){
this.다중스레드사용 = true;
return 페이지네이션(전체데이터수);
}
/**
* 셀 병합
* @param 첫번째행
* @param 마지막행
* @param 첫번째열
* @param 마지막열
* @return
*/
public ExcelExportHandler<T> 셀병합(Integer 첫번째행, Integer 마지막행, Integer 첫번째열, Integer 마지막열){
List<List<Integer>> 병합정보 = new ArrayList<>();
병합정보.add(ListUtils.newArrayList(첫번째행,마지막행,첫번째열,마지막열));
return 셀병합(병합정보);
}
/**
* 셀 병합
* @param 병합정보
* @return
*/
public ExcelExportHandler<T> 셀병합(List<List<Integer>> 병합정보) {
SheetWriteHandler 병합핸들러 = new SheetWriteHandler(){
@Override
public void afterSheetCreate(SheetWriteHandlerContext 컨텍스트) {
for (List<Integer> 병합 : 병합정보) {
CellRangeAddress 주소리스트 = new CellRangeAddress(병합.get(0), 병합.get(1), 병합.get(2), 병합.get(3));
컨텍스트.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(주소리스트);
}
}
};
return 쓰기핸들러추가(병합핸들러);
}
/**
* 드롭다운 목록 설정
* @param 축정보
* @param 데이터
* @return
*/
public ExcelExportHandler<T> 드롭다운목록설정(List<Integer> 축정보,List<String> 데이터){
SheetWriteHandler 드롭다운핸들러 = new SheetWriteHandler() {
@Override
public void afterSheetCreate(SheetWriteHandlerContext 컨텍스트) {
CellRangeAddressList 셀범위주소리스트 = new CellRangeAddressList(축정보.get(0), 축정보.get(1), 축정보.get(2), 축정보.get(3));
DataValidationHelper 헬퍼 = 컨텍스트.getWriteSheetHolder().getSheet().getDataValidationHelper();
DataValidationConstraint 제약조건 = 헬퍼.createExplicitListConstraint(데이터.toArray(new String[0]));
DataValidation 데이터검증 = 헬퍼.createValidation(제약조건, 셀범위주소리스트);
컨텍스트.getWriteSheetHolder().getSheet().addValidationData(데이터검증);
}
};
return 쓰기핸들러추가(드롭다운핸들러);
}
/**
* 드롭다운 목록 설정
* @param 열
* @param 데이터
* @return
*/
public ExcelExportHandler<T> 드롭다운목록설정(Integer 열,List<String> 데이터){
//드롭다운 목록을 열 열 아래 1000행으로 설정
List<Integer> 축정보 = ListUtils.newArrayList(1,1000,열,열);
return 드롭다운목록설정(축정보,데이터);
}
/**
* 열 너비 설정
* @return
*/
public ExcelExportHandler<T> 열너비설정(List<List<Integer>> 열너비정보){
AbstractColumnWidthStyleStrategy 전략 = new AbstractColumnWidthStyleStrategy(){
@Override
protected void setColumnWidth(CellWriteHandlerContext 컨텍스트) {
Sheet 시트 = 컨텍스트.getWriteSheetHolder().getSheet();
for (List<Integer> 열너비 : 열너비정보) {
시트.setColumnWidth(열너비.get(0),열너비.get(1) * 256);
}
}
};
return 쓰기핸들러추가(전략);
}
/**
* 열 너비 설정
* @return
*/
public ExcelExportHandler<T> 열너비설정(Integer 열,Integer 너비){
List<List<Integer>> 열너비정보 = new ArrayList<>();
열너비정보.add(ListUtils.newArrayList(열,너비));
return 열너비설정(열너비정보);
}
/**
* 행 높이 설정
* @param 행
* @param 높이
* @return
*/
public ExcelExportHandler<T> 행높이설정(Integer 행,Integer 높이){
List<List<Integer>> 행높이정보 = new ArrayList<>();
행높이정보.add(ListUtils.newArrayList(행,높이));
return 행높이설정(행높이정보);
}
/**
* 행 높이 설정
* @param 행높이정보
* @return
*/
private ExcelExportHandler<T> 행높이설정(List<List<Integer>> 행높이정보) {
SheetWriteHandler 전략 = new SheetWriteHandler(){
@Override
public void afterSheetCreate(SheetWriteHandlerContext 컨텍스트) {
Sheet 시트 = 컨텍스트.getWriteSheetHolder().getSheet();
for (List<Integer> 행높이 : 행높이정보) {
Row 행 = 시트.getRow(행높이.get(0));
행.setHeightInPoints(행높이.get(1));
}
}
};
return 쓰기핸들러추가(전략);
}
/**
* 셀 스타일 설정
* @param 첫번째행
* @param 마지막행
* @param 첫번째열
* @param 마지막열
* @return
*/
public ExcelExportHandler<T> 셀스타일설정(Integer 첫번째행, Integer 마지막행, Integer 첫번째열, Integer 마지막열,CellStyle 셀스타일){
List<List<Integer>> 축정보 = new ArrayList<>();
축정보.add(ListUtils.newArrayList(첫번째행,마지막행,첫번째열,마지막열));
return 셀스타일설정(축정보,셀스타일);
}
/**
* 셀 스타일 설정
* @param 축정보
* @return
*/
public ExcelExportHandler<T> 셀스타일설정(List<List<Integer>> 축정보,CellStyle 셀스타일){
SheetWriteHandler 핸들러 = new SheetWriteHandler(){
@Override
public void afterSheetCreate(SheetWriteHandlerContext 컨텍스트) {
Sheet 시트 = 컨텍스트.getWriteSheetHolder().getSheet();
for (List<Integer> 축 : 축정보) {
for (int i = 축.get(0); i < 축.get(1); i++) {
Row 행 = 시트.getRow(i);
for (int j = 축.get(2); j < 축.get(3); j++) {
//스타일 설정
Cell 셀 = 행.getCell(j);
셀.setCellStyle(셀스타일);
}
}
}
SheetWriteHandler.super.afterSheetCreate(컨텍스트);
}
};
return 쓰기핸들러추가(핸들러);
}
public static void 오류처리(HttpServletResponse 응답,String 메시지) throws IOException {
응답.reset();
응답.setContentType("application/json");
응답.sendError(HttpServletResponse.SC_NOT_FOUND,메시지);
}
}
DataQuery
import org.itxl.common.utils.DataProcessor;
/**
* @author kimMinJoon
* @date 2024/3/25
*/
public abstract class DataQuery<T> implements DataProcessor<T> {
private final ThreadLocal<Integer> 로컬 = new ThreadLocal<>();
private Integer 페이지크기 = 10000;
public void 페이지번호설정(Integer 페이지번호){
로컬.set(페이지번호);
}
public Integer 페이지번호가져오기(){
return 로컬.get();
}
public void 페이지정보제거(){
로컬.remove();
}
public Integer getPageSize() {
return 페이지크기;
}
public void setPageSize(Integer 페이지크기) {
if (페이지크기 == null){
return;
}
this.페이지크기 = 페이지크기;
}
@Override
public T accept() {
return andThen();
}
}
DataProcessor
/**
* @author kimMinJoon
* 일반 함수 매개변수
* @param <T>
*/
public interface DataProcessor<T>{
/**
* 메소드 구현
* @return T
*/
T andThen();
/**
* 메소드 실행
* @return T
*/
T accept();
}
SpringContextUtil
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author kimMinJoon
* @date 2024/2/2
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext 애플리케이션컨텍스트;
public static ApplicationContext getApplicationContext() {
return 애플리케이션컨텍스트;
}
public static Object getBean(String 빈이름) {
return 애플리케이션컨텍스트.getBean(빈이름);
}
public static <T> T getBean(Class<T> 클래스) {
return 애플리케이션컨텍스트.getBean(클래스);
}
public static <T> T getBean(String 빈이름, Class<T> 클래스) {
return 애플리케이션컨텍스트.getBean(빈이름, 클래스);
}
@Override
public void setApplicationContext(ApplicationContext 애플리케이션컨텍스트) throws BeansException {
if (SpringContextUtil.애플리케이션컨텍스트 == null){
SpringContextUtil.애플리케이션컨텍스트 = 애플리케이션컨텍스트;
}
}
}
- 사용 방법 ====
ExcelExportHandler<Object> 내보내기도구 = ExcelExportHandler.생성();
try {
내보내기도구.응답설정(response,"테스트")
.헤더설정(ListUtils.newArrayList("테스트"))
.빌더()
.다중스레드(436L)
.데이터쓰기(new DataQuery<List<List<Object>>>() {
@Override
public List<List<Object>> andThen() {
List<Object> 데이터=new ArrayList<>();
Date 날짜 = new Date();
데이터.add(날짜);
List<List<Object>> 결과 = new ArrayList<>();
결과.add(데이터);
return 결과;
}
});
} catch (Exception e) {
try {
ExcelExportHandler.오류처리(response,e.getMessage());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}finally {
내보내기도구.닫기();
}