Files
SR-SYSTEM/backend/src/main/java/com/srsystem/util/ExcelParser.java
T
2026-06-05 11:22:13 +09:00

148 lines
5.9 KiB
Java

package com.srsystem.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Function;
public class ExcelParser {
public static <T> ParseResult<T> parse(MultipartFile file, RowMapper<T> mapper, List<Validator<T>> validators) throws IOException {
List<T> data = new ArrayList<>();
List<ParseFailure> failures = new ArrayList<>();
try (InputStream is = file.getInputStream(); Workbook wb = new XSSFWorkbook(is)) {
Sheet sheet = wb.getSheetAt(0);
if (sheet.getPhysicalNumberOfRows() < 2) {
return new ParseResult<>(List.of(), List.of(new ParseFailure(0, "데이터 행이 없습니다")));
}
Row headerRow = sheet.getRow(0);
int colCount = headerRow.getPhysicalNumberOfCells();
String[] headers = new String[colCount];
for (int i = 0; i < colCount; i++) {
headers[i] = getCellValue(headerRow.getCell(i)).trim().toLowerCase();
}
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) continue;
try {
Map<String, String> rowData = new HashMap<>();
for (int c = 0; c < colCount; c++) {
String val = getCellValue(row.getCell(c));
if (!val.isEmpty()) {
rowData.put(headers[c], val);
}
}
if (rowData.isEmpty()) continue;
T item = mapper.map(rowData);
for (Validator<T> v : validators) {
String err = v.validate(item, rowData);
if (err != null) {
throw new ValidationException(err);
}
}
data.add(item);
} catch (Exception e) {
failures.add(new ParseFailure(r + 1, e.getMessage()));
}
}
}
return new ParseResult<>(data, failures);
}
public static byte[] generateTemplate(String[] headers, String sheetName, Map<Integer, String[]> dropdowns) throws IOException {
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
Sheet sheet = wb.createSheet(sheetName);
Row headerRow = sheet.createRow(0);
CellStyle headerStyle = wb.createCellStyle();
Font headerFont = wb.createFont();
headerFont.setBold(true);
headerFont.setColor(IndexedColors.WHITE.getIndex());
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.INDIGO.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
headerStyle.setBorderTop(BorderStyle.THIN);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
sheet.autoSizeColumn(i);
}
if (dropdowns != null) {
for (Map.Entry<Integer, String[]> entry : dropdowns.entrySet()) {
int col = entry.getKey();
String[] values = entry.getValue();
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(values);
CellRangeAddressList range = new CellRangeAddressList(1, 1000, col, col);
DataValidation validation = helper.createValidation(constraint, range);
validation.setShowErrorBox(true);
validation.createErrorBox("유효성 검사", "허용된 값만 입력 가능합니다.");
sheet.addValidationData(validation);
}
}
wb.write(bos);
return bos.toByteArray();
}
}
private static String getCellValue(Cell cell) {
if (cell == null) return "";
return switch (cell.getCellType()) {
case STRING -> cell.getStringCellValue().trim();
case NUMERIC -> {
double val = cell.getNumericCellValue();
if (val == Math.floor(val) && !Double.isInfinite(val)) {
yield String.valueOf((long) val);
}
yield String.valueOf(val);
}
case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
case FORMULA -> {
try { yield String.valueOf((int) cell.getNumericCellValue()); }
catch (Exception e) { yield cell.getStringCellValue(); }
}
default -> "";
};
}
public record ParseResult<T>(List<T> data, List<ParseFailure> failures) {
public boolean hasFailures() { return !failures.isEmpty(); }
}
public record ParseFailure(int row, String reason) {}
@FunctionalInterface
public interface RowMapper<T> {
T map(Map<String, String> rowData) throws Exception;
}
@FunctionalInterface
public interface Validator<T> {
String validate(T item, Map<String, String> rawData);
}
private static class ValidationException extends RuntimeException {
public ValidationException(String message) { super(message); }
}
}