thumbnail 출처 : https://unsplash.com/photos/9IDgEybpF6o?utm_source=63921&utm_medium=referral
잘못된 내용이나 더 나은 방식 등에 대한 의견은 댓글로 피드백 부탁드리겠습니다. :)
A representation of an uploaded file received in a multipart request.
The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storages will be cleared at the end of request processing.
Since:
29.09.2003
See Also:
MultipartHttpServletRequest, MultipartResolver
Author:
Juergen Hoeller, Trevor D. Cook
엑셀 파일 업로드 및 업로드 파일에서 데이터 산출
로직 구간에서 정상 엑셀 파일을 검증하는 부분에 대해 정리하였습니다.Apache POI 라이브러리 의존성 설정
<!-- poi excel 라이브러리 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
Multipart request 처리 (commons-io, commons-fileupload)
<!-- MultipartHttpServletRequest -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
화면 내에서 전달받은 request의 Multipart를 대체하기 위해 MockMultipartFile로 stubbing 할 수 있습니다. (request로 multipart를 정상적으로 전달되었다고 가정)
@Test
@TestDesc("MockMultipartFile 동작 테스트")
public void getMockExcelUploadTest() throws IOException {
/*MockMultipartHttpServletRequest multipartHttpServletRequest = new MockMultipartHttpServletRequest();*/ // controller test 시 사용
String fileName = "testCustomerUpload";
String contentType = "xls";
String filePath = "src/test/resources/excel/testCustomerUpload.xls";
MockMultipartFile mockMultipartFile = getMockMultipartFile(fileName, contentType, filePath);
String getFileName = mockMultipartFile.getOriginalFilename().toLowerCase();
assertThat(getFileName, is(fileName.toLowerCase() + "." + contentType));
}
private MockMultipartFile getMockMultipartFile(String fileName, String contentType, String path) throws IOException {
FileInputStream fileInputStream = new FileInputStream(new File(path));
return new MockMultipartFile(fileName, fileName + "." + contentType, contentType, fileInputStream);
}
해당 테스트는 multipart를 통해 excel 파일을 전달 받았을 때 받은 파일을 POI를 통해 excel workbook으로 확인하고 관련 정보를 확인하는 내용입니다.
@Test
@TestDesc("엑셀 정보 가져오기 확인")
public void getExcelSettingInfo() throws IOException {
// arrange
ExcelUploadVO testUploadVO = getTestUploadVO();
String fileName = "testCustomerUpload";
String contentType = "xls";
String filePath = "src/test/resources/excel/testCustomerUpload.xls";
MockMultipartFile mFile = getMockMultipartFile(fileName, contentType, filePath);
// act
Map<String, Object> resultMap = new HashMap<String, Object>();
boolean error = false; // 정상 엑셀파일이 아닌 경우 true
String ret_msg = null; // 확인 사유를 기록하는 변수
// 엑셀파일 설정정보
int startCellNum = testUploadVO.getStartCellNum(); // 시작할 셀
int totalCellNum = testUploadVO.getTotalCellNum(); // 총 셀수
int sheetNum = testUploadVO.getSheetNum(); // 조회할 시트번호
int startRowNum = testUploadVO.getStartRowNum(); // 시작하는 기준 행 번호
String filename = mFile.getOriginalFilename().toLowerCase(); // 파일명
int indexDot = filename.lastIndexOf(".") != -1 ? filename.lastIndexOf(".") : 0;
String fileExtention = filename.substring(indexDot); // 확장자
Workbook workbook = null;
if (fileExtention.equals(".xlsx")) {
workbook = new XSSFWorkbook(mFile.getInputStream());// Excel 2007
} else if (fileExtention.equals(".xls")) {
workbook = new HSSFWorkbook(mFile.getInputStream());// Excel 2003
}
// workbook 생성 여부 체크
if (workbook == null || workbook.getNumberOfSheets() == 0) {
error = true;
ret_msg= "엑셀파일에 시트가 존재하지 않습니다.";
resultMap.put("error", error);
resultMap.put("ret_msg", ret_msg);
}
Sheet sheet = workbook.getSheetAt(sheetNum); // 조회할 시트
int lastRowNum = sheet.getLastRowNum();
if(lastRowNum ==(startRowNum-1)) {
error = true;
ret_msg= "입력된 데이터가 존재하지 않습니다.";
resultMap.put("error", error);
resultMap.put("ret_msg", ret_msg);
// return resultMap;
} else if((lastRowNum - startRowNum+1)>500) {
error = true;
ret_msg= "입력건수가 500건을 초과하였습니다.";
resultMap.put("error", error);
resultMap.put("ret_msg", ret_msg);
// return resultMap;
}
// 엑셀 양식 확인
// 정해진 시작 열과 마지막 열이 아닌 경우 확인
Row columRow = sheet.getRow(startRowNum-1);
String startColumnName = testUploadVO.getStartColumnName();
String lastColumnName = testUploadVO.getLastColumnName();
boolean excelFormCheck = true;
if (!StringUtils.isEmpty(startColumnName) && !StringUtils.isEmpty(lastColumnName)
&& columRow.getCell(startCellNum) != null && columRow.getCell(totalCellNum) != null) {
if(!startColumnName.equals(columRow.getCell(startCellNum).getStringCellValue()) || !lastColumnName.equals(columRow.getCell(totalCellNum).getStringCellValue())) {
excelFormCheck = false;
}
} else {
excelFormCheck = false;
}
if(!excelFormCheck){
error = true;
ret_msg= "잘못된 엑셀양식 입니다.";
resultMap.put("error", error);
resultMap.put("ret_msg", ret_msg);
// return resultMap;
}
// assert
assertThat(resultMap.isEmpty(), is(true));
assertThat(lastRowNum, is(98));
assertThat(lastRowNum == (startRowNum - 1), is(false));
assertThat((lastRowNum - startRowNum + 1), is(98));
assertThat(columRow.getCell(startCellNum), is(notNullValue()));
assertThat(columRow.getCell(totalCellNum), is(notNullValue()));
assertThat(startColumnName, is("계정코드")); // 업로드 엑셀 파일의 첫 셀의 title
assertThat(lastColumnName, is("구분")); // 업로드 엑셀 파일의 마지막 셀의 title
assertThat(startColumnName.equals(columRow.getCell(startCellNum).getStringCellValue()), is(true));
assertThat(lastColumnName.equals(columRow.getCell(totalCellNum).getStringCellValue()), is(true));
assertThat(excelFormCheck, is(true));
}
(POI 3.14 기준) 엑셀 버전에 따라 다르게 처리됩니다.
XSSFWorkbook
HSSFWorkbook
Workbook workbook = null;
if (fileExtention.equals(".xlsx")) {
workbook = new XSSFWorkbook(mFile.getInputStream());// Excel 2007
} else if (fileExtention.equals(".xls")) {
workbook = new HSSFWorkbook(mFile.getInputStream());// Excel 2003
}
해당 로직에서 개선 욕심을 가진다면 다음의 부분을 수정할 것 같습니다.
cell.getStringCellValue()
로 값 처리HSSFDateUtil.isCellDateFormatted(cell)
로 숫자값이 날짜를 출력하는 값인지 확인 후 적절한 날짜 형식으로 변환하여 처리cell.getNumericCellValue()
로 값 처리cell.getBooleanCellValue()
로 값을 확인false
cell.getCachedFormulaResultType()
으로 CELL_TYPE_NUMERIC
, CELL_TYPE_STRING
, CELL_TYPE_BOOLEAN
인지 판별한 후 값을 처리cell.getCellFormula()
로 값 처리cell.getErrorCellValue()
로 값을 확인FormulaError
라는 enum에서 확인할 수 있음.