IT/WEB

[JAVA] 엑셀 파일 유효성 검사(Excel Validation)

오달달씨 2021. 10. 20. 16:10
728x90
반응형

 

# ExcelReadUtil.java

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.XMLHelper;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

@Component
@Slf4j
public class ExcelReadUtil {

    private List<HashMap<Object, Object>> distinctArray(List<HashMap<Object, Object>> target) {
        if (target != null) {
            target = target.stream().filter(distinctByKey(o -> o.get(0))).collect(Collectors.toList());
        }
        return target;
    }

    private <T> Predicate<T> distinctByKey(Function<? super T,Object> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
    
    // XSSF and SAX (Event API) 방식으로 읽음
    public List<HashMap<Object, Object>> readExcelFileV2(InputStream isr, String from) throws Exception {
    	List<HashMap<Object, Object>> excelResult = new ArrayList<>();
    	log.info("XSSF and SAX Excel Reader Start");
    	
    	long startTime = System.currentTimeMillis();
    	
        SheetHandler sheetHandler = new SheetHandler();
    	OPCPackage pkg = OPCPackage.open(isr);
    	
        //메모리를 적게 사용하며 sax 형식을 사용할 수 있게 함
    	XSSFReader xssfReader = new XSSFReader(pkg);
    	
        //파일의 데이터를 Table형식으로 읽을 수 있도록 지원
        ReadOnlySharedStringsTable data = new ReadOnlySharedStringsTable(pkg);

        //읽어온 Table에 적용되어 있는 Style
        StylesTable styles = xssfReader.getStylesTable();

        //엑셀의 첫번째 sheet정보만 읽어오기 위해 사용 만약 다중 sheet 처리를 위해서는 반복문 필요
        InputStream sheetStream = xssfReader.getSheetsData().next();
        InputSource sheetSource = new InputSource(sheetStream);
        
        //XMLHandler 생성
        ContentHandler handler = new XSSFSheetXMLHandler(styles, data, sheetHandler, false);

        //SAX 형식의 XMLReader 생성
        XMLReader sheetParser = XMLHelper.newXMLReader();

        //XMLReader에 재정의하여 구현한 인터페이스 설정
        sheetParser.setContentHandler(handler);

        //파싱하여 처리
        sheetParser.parse(sheetSource);
        sheetStream.close();
        pkg.close();
        
        int listSize = sheetHandler.getRowsMapList().size();          
        excelResult = distinctArray(sheetHandler.getRowsMapList());
        
        if (StringUtils.equals(from, "valid")) {
	        HashMap<Object, Object> oldNumberCountMap = new HashMap<>();
	        
	        oldNumberCountMap.put("oldNumberCount", "" + sheetHandler.getOldNumberCount());
	        oldNumberCountMap.put("distinctCount", listSize - excelResult.size());
	        //log.info("oldNumberCountMap : " + oldNumberCountMap);
	        excelResult.add(oldNumberCountMap);
        }
  
        log.info("XSSF and SAX Excel Reader End : " + ((System.currentTimeMillis() - startTime)  ) + " millis Second");
        
    	return excelResult;
    }
}

 

 

# MsgSendServiceImpl.java

@Override
	public HashMap<String, Object> validExcelFile(MultipartFile file) throws Exception {
		JSONObject returnJSON = new JSONObject();
		JSONParser parser = new JSONParser();

  	    List<HashMap<Object, Object>> excelMapList = excelReadUtil.readExcelFileV2(file.getInputStream(), "valid");
  	    
		String oldPhoneNumberCount = "";
		int validCount = 0; // 검증 후 발송 시도 건수

		log.info("excelMapList.size(): [" + excelMapList.size() + "]");
		if (excelMapList.size() > 0) {
			oldPhoneNumberCount = (String) (excelMapList.get(excelMapList.size() - 1)).get("oldNumberCount");

			excelMapList.remove(excelMapList.size() - 1);
		}

		String phoneNumberRegulaExpress = "^010(\\d{7,8})";
		String phoneNumberRegulaExpress2 = "^050(\\d{8,9})";

		String isValid = "true";

		String invalidRowNumber = "";
		String invalidColumnName = "";
		int invalidColumnIdx = 0;

		// 엑셀 파일 업로드 10만건 제한
		int calcListSize = excelMapList.size() - 1; // 엑셀 파일 읽어올때 첫번째 항목도 읽어 오므로 -1 처리

		if (calcListSize > sendLimitCnt) {
			returnJSON.put("status", "EXCEL_ROW_COUNT_ERROR");

			return returnJSON;
		}
		
		// 휴대폰 번호 Validation 체크
		for (int i = 1; i < excelMapList.size(); i++) {
			HashMap<Object, Object> row = excelMapList.get(i);
			/*
			if (row.get(0) == null || StringUtils.isEmpty(((String) row.get(0)))) {
				continue;
			}
			*/
			String phoneNumber = (String) row.get(0);

			if (!(phoneNumber.matches(phoneNumberRegulaExpress) || phoneNumber.matches(phoneNumberRegulaExpress2))) {
				log.info("INVALID PHONE NUMBER : " + phoneNumber);

				isValid = "phoneNumFalse";

				invalidRowNumber = phoneNumber + ",\n";
			} else {
				validCount += 1;
			}
		}
		
		// 변수 Null 체크
		for(int i = 1; i < 10; i++) {
			HashMap<Object, Object> col = excelMapList.get(0);
			
			if(col.get(i) == null) {
				break;
			} else {
				
				for (int j = 1; j < excelMapList.size(); j++) {
					String col_variable = (String) excelMapList.get(j).get(i);

					if(col_variable.equals("") || col_variable.equals(null)) {
						log.info("INVALID COL VARIABLE : " + col.get(i));

						isValid = "colVariableFalse";
						invalidColumnName = (String) col.get(i);
						invalidColumnIdx = j + 1;
						break;
					}
				}
			}
		}
		

		if (isValid.equals("true")) {
			returnJSON.put("status", "SUCCESS");
			returnJSON.put("oldNumberCount", oldPhoneNumberCount);
			returnJSON.put("validCount", validCount);
		} else if(isValid.equals("phoneNumFalse")) {
			invalidRowNumber = invalidRowNumber.substring(0, invalidRowNumber.length() - 2);

			returnJSON.put("status", "FAIL_INVALID_PHONE_NUMBER");
			returnJSON.put("invalidRow", invalidRowNumber);
		} else if(isValid.equals("colVariableFalse")) {

			returnJSON.put("status", "FAIL_INVALID_COL_VARIABLE");
			returnJSON.put("invalidColumnName", invalidColumnName);
			returnJSON.put("invalidColumnIdx", invalidColumnIdx);
		} 

		return returnJSON;
	}

 

# MsgSendController.java

@RequestMapping("/validExcelFile.do")
public HashMap<String, Object> msgSendValidExcelFile(@RequestParam("file") MultipartFile file) throws Exception {
      return msgSendService.validExcelFile(file);
}

 

function validExcelFile() {
            $('#loading').show();

            var formData = new FormData();

            formData.append("file", $("input[name='file_excel']")[0].files[0]);
            
            $.ajax({
                url : '/msgSend/validExcelFile.do',
                method: 'post',
                enctype: 'multipart/form-data',
                data : formData,
                dataType : 'json',
                processData: false,
                contentType: false,
                success : function(data) {
                    //console.log(data)
                    $('#loading').hide();

                    if (data.status == "SUCCESS") {
                        
                        if( 0 < data.validCount ) {
                            var obj = JSON.parse(Base64.decode($("input[name=obj]").val()));

                            //console.log("msg_send_step1 > validExcelFile > sendPossibleCnt: [" + obj.sendPossibleCnt + "]");

                            // 발송 가능 건수 와 실제 발송 하려는 건수 비교
                            if( isNull( obj.sendPossibleCnt ) || obj.sendPossibleCnt >= data.validCount ) {
                                alert('수신번호 검증이 완료되었습니다.\n\n01X 번호 ' + data.oldNumberCount + '건이 제거되었습니다.');

                                return;

                            } else {
                                alert('발송건수가 초과 되었습니다.  확인 후 다시 첨부 바랍니다.\n발송 시도 건수: [' + addCommas( data.validCount ) + ']\n발송 가능 건수: [' + addCommas( obj.sendPossibleCnt )+ ']');

                                page_reload();
                                return;
                            }

                        }
                    }else if (data.status == "FAIL_INVALID_PHONE_NUMBER") {
                        alert('엑셀파일에서 \n' + data.invalidRow + '\n전화번호 오류가 있습니다.');
                        page_reload();
                        return;
                    }else if (data.status == "FAIL_INVALID_COL_VARIABLE") {
                        alert('엑셀파일 ' + data.invalidColumnName + '변수 [' + data.invalidColumnIdx + ']열에 변수 값이 없어서 등록이 실패되었습니다.');
                        page_reload();
                        return;
                    }else if(data.status == "EXCEL_ROW_COUNT_ERROR") {
						alert("엑셀파일 등록은 30만건을 초과할 수 없습니다.");
						page_reload();
						return;
                    }else if (data.status == "FREE_TEMPLATE_VAR_ERROR") {
						alert("템플릿 변수가 일치하지 않습니다.\n엑셀파일 변수와 프리 템플릿 변수를 확인해 주세요.")
						page_reload();
						return;
                    }else{
                    	alert('수신번호 검증이 실패하였습니다.\n관리자에게 문의하십시오.');
                    	return;
                    }
                },
                error: function(xhr, status, error) {
                    $('#loading').hide();

                    alert('수신번호 검증이 실패하였습니다.\n관리자에게 문의하십시오.');
                }
            });
        }

 

728x90
반응형