You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.6 KiB
Java
191 lines
6.6 KiB
Java
package egovframework.util.excel;
|
|
|
|
import org.apache.poi.poifs.crypt.EncryptionInfo;
|
|
import org.apache.poi.poifs.crypt.EncryptionMode;
|
|
import org.apache.poi.poifs.crypt.Encryptor;
|
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
|
import org.apache.poi.ss.usermodel.*;
|
|
import org.apache.poi.ss.util.CellRangeAddress;
|
|
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.lang.reflect.Field;
|
|
import java.security.GeneralSecurityException;
|
|
import java.util.List;
|
|
|
|
import static egovframework.util.excel.SuperClassReflectionUtils.getAllFieldsWithExcelColumn;
|
|
|
|
public abstract class BaseSxssfExcelFile implements ExcelFile {
|
|
protected static final int ROW_ACCESS_WINDOW_SIZE = 10000;
|
|
protected static final int ROW_START_INDEX = 0;
|
|
protected static final int COLUMN_START_INDEX = 0;
|
|
protected SXSSFWorkbook workbook;
|
|
protected Sheet sheet;
|
|
protected int titleRowOffset = 0; // 제목 행이 있으면 1, 없으면 0
|
|
|
|
public BaseSxssfExcelFile() {
|
|
this.workbook = new SXSSFWorkbook(ROW_ACCESS_WINDOW_SIZE);
|
|
}
|
|
|
|
/**
|
|
* 제목 행을 렌더링합니다.
|
|
* 첫 번째 행에 제목을 생성하고 모든 열에 걸쳐 셀을 병합합니다.
|
|
*
|
|
* @param sheetName 시트명
|
|
* @param title 제목 텍스트
|
|
* @param columnCount 열 개수
|
|
*/
|
|
protected void renderTitleRow(String sheetName, String title, int columnCount) {
|
|
// 시트가 없으면 생성
|
|
if (sheet == null) {
|
|
sheet = workbook.createSheet(sheetName);
|
|
}
|
|
|
|
// 제목 행 생성 (row 0)
|
|
Row titleRow = sheet.createRow(ROW_START_INDEX);
|
|
|
|
// 제목 스타일 생성 (굵게, 가운데 정렬)
|
|
CellStyle titleStyle = workbook.createCellStyle();
|
|
titleStyle.setAlignment(HorizontalAlignment.LEFT);
|
|
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
// 제목 폰트 생성 (맑은 고딕, 22 포인트, 굵게)
|
|
Font titleFont = workbook.createFont();
|
|
//titleFont.setFontName("맑은 고딕"); 폰트 개이상함
|
|
titleFont.setFontHeightInPoints((short) 22);
|
|
titleFont.setBold(true);
|
|
titleStyle.setFont(titleFont);
|
|
|
|
// 제목 행 높이 설정
|
|
titleRow.setHeightInPoints(33);
|
|
|
|
// 첫 번째 셀에 제목 입력
|
|
createCell(titleRow, COLUMN_START_INDEX, title, titleStyle);
|
|
|
|
// 모든 열에 걸쳐 셀 병합 (row 0, column 0 ~ columnCount-1)
|
|
if (columnCount > 1) {
|
|
sheet.addMergedRegion(new CellRangeAddress(
|
|
ROW_START_INDEX, // 시작 행
|
|
ROW_START_INDEX, // 종료 행
|
|
COLUMN_START_INDEX, // 시작 열
|
|
columnCount - 1 // 종료 열
|
|
));
|
|
}
|
|
|
|
// 제목 행 오프셋 설정
|
|
titleRowOffset = 1;
|
|
}
|
|
|
|
protected void renderHeaders(ExcelMetadata excelMetadata) {
|
|
// 시트가 없으면 생성 (제목 행이 없는 경우)
|
|
if (sheet == null) {
|
|
sheet = workbook.createSheet(excelMetadata.getSheetName());
|
|
}
|
|
|
|
// 헤더 행 생성 (제목 행이 있으면 row 1, 없으면 row 0)
|
|
Row row = sheet.createRow(ROW_START_INDEX + titleRowOffset);
|
|
int columnIndex = COLUMN_START_INDEX;
|
|
CellStyle style = createCellStyle(workbook, true);
|
|
style.setAlignment(HorizontalAlignment.CENTER);
|
|
for (String fieldName : excelMetadata.getDataFieldNames()) {
|
|
createCell(row, columnIndex++, excelMetadata.getHeaderName(fieldName), style);
|
|
}
|
|
|
|
// SXSSFWorkbook에서 autoSizeColumn을 사용하기 위해 각 열을 추적하도록 설정
|
|
// 헤더 생성 직후 추적을 시작하여 헤더도 너비 계산에 포함되도록 함
|
|
int columnCount = excelMetadata.getDataFieldNames().size();
|
|
for (int i = 0; i < columnCount; i++) {
|
|
((org.apache.poi.xssf.streaming.SXSSFSheet) sheet).trackColumnForAutoSizing(COLUMN_START_INDEX + i);
|
|
}
|
|
}
|
|
|
|
protected void renderDataLines(ExcelSheetData data, ExcelMetadata metadata) {
|
|
CellStyle style = createCellStyle(workbook, false);
|
|
// 데이터 시작 행 (제목 행이 있으면 row 2, 없으면 row 1)
|
|
int rowIndex = ROW_START_INDEX + titleRowOffset + 1;
|
|
List<Field> fields = getAllFieldsWithExcelColumn(data.getType());
|
|
|
|
// 데이터 행 생성
|
|
for (Object record : data.getDataList()) {
|
|
Row row = sheet.createRow(rowIndex++);
|
|
int columnIndex = COLUMN_START_INDEX;
|
|
try {
|
|
for (Field field : fields) {
|
|
field.setAccessible(true);
|
|
createCell(row, columnIndex++, field.get(record), style);
|
|
}
|
|
} catch (IllegalAccessException e) {
|
|
throw new RuntimeException("Error accessing data field rendering data lines.", e);
|
|
}
|
|
}
|
|
|
|
// 각 열의 너비 조정
|
|
// (renderHeaders에서 이미 trackColumnForAutoSizing 호출됨)
|
|
for (int i = 0; i < fields.size(); i++) {
|
|
int columnIndex = COLUMN_START_INDEX + i;
|
|
Field field = fields.get(i);
|
|
String fieldName = field.getName();
|
|
|
|
// headerWidth가 지정되어 있으면 해당 값 사용, 없으면 자동 조정
|
|
int headerWidth = metadata.getHeaderWidth(fieldName);
|
|
if (headerWidth > 0) {
|
|
// headerWidth가 지정된 경우 해당 값으로 설정 (POI 단위: 1/256 문자)
|
|
sheet.setColumnWidth(columnIndex, headerWidth * 256);
|
|
} else {
|
|
// headerWidth가 0이면 자동 조정
|
|
sheet.autoSizeColumn(columnIndex);
|
|
|
|
// 한글 폰트는 autoSizeColumn이 정확하지 않으므로 여유분 추가
|
|
// 현재 너비의 1.3배로 설정 (최소 3000, 최대 15000)
|
|
int currentWidth = sheet.getColumnWidth(columnIndex);
|
|
int newWidth = (int) (currentWidth * 1.3);
|
|
|
|
// 최소 너비 3000 (약 11.7문자), 최대 너비 15000 (약 58.6문자)
|
|
newWidth = Math.max(3000, Math.min(newWidth, 15000));
|
|
sheet.setColumnWidth(columnIndex, newWidth);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void write(OutputStream stream) {
|
|
try {
|
|
workbook.write(stream);
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void writeWithEncryption(OutputStream stream, String password) {
|
|
try {
|
|
if (password == null) {
|
|
write(stream);
|
|
} else {
|
|
POIFSFileSystem fileSystem = new POIFSFileSystem();
|
|
OutputStream encryptorStream = getEncryptorStream(fileSystem, password);
|
|
workbook.write(encryptorStream);
|
|
encryptorStream.close(); // this is necessary before writing out the FileSystem
|
|
fileSystem.writeFilesystem(stream); // write the encrypted file to the response stream
|
|
fileSystem.close();
|
|
}
|
|
workbook.close();
|
|
stream.close();
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private OutputStream getEncryptorStream(POIFSFileSystem fileSystem, String password) {
|
|
try {
|
|
Encryptor encryptor = new EncryptionInfo(EncryptionMode.agile).getEncryptor();
|
|
encryptor.confirmPassword(password);
|
|
return encryptor.getDataStream(fileSystem);
|
|
} catch (IOException | GeneralSecurityException e) {
|
|
throw new RuntimeException("Failed to obtain encrypted data stream from POIFSFileSystem.");
|
|
}
|
|
}
|
|
}
|
|
|