[Java] 엑셀 파일을 읽고 해당 내용이 몇개 있는지 세는 프로그램

Denia·2022년 9월 7일
0

이번에도 지인 쇼핑몰 운영을 도와주다가 불편함을 발견했습니다.

그래서 이번에도 코딩을 업으로 삼으려고 하고 있으니 "프로그램을 사용하여 조금 편하게 일을 해보자" 싶어서 프로그램을 간단하게 만들어봤습니다. (저번에 사용한 프로그램이랑 코드가 거의 비슷합니다 ㅎㅎ..)

불편한 부분

자주 있는 일은 아니지만 매번 대량으로 주문을 받다보면 이번 주문에 해당 상품이 옵션별로 몇개씩 팔렸는지를 알아야 하는 때가 있습니다. (이걸 알아야 우리도 도매처한테 주문을 할 수 있으니까 ?)

그래서 보통은 엑셀 파일로 되어있는걸 타이틀 부분에 필터를 걸어서 옵션별로 하나 하나씩 확인하고, 수량 부분을 드래그 해서 SUM 의 값을 확인했습니다.

자주 하는 일은 아니라서 그냥 하려면 할 수도 있지만 옷의 종류가 많고 옵션이 많으면 클릭 & 드래그를 생각보다 많이 해야합니다. (시간도 생각보다 약 7~8분 정도 걸리는 ?)

그래서 조금이라도 귀찮은 일을 줄이고 싶어서 프로그램을 만들게 됐습니다.

프로그램 동작시에 필요한 파일

  • 이번에 주문이 들어온 주문의 상세 리스트가 필요합니다. (상품명 , 해당 상품의 옵션 , 상품 별 수량 이 모두 들어가 있는 엑셀 파일 / 사용하고 있는 쇼핑몰에서 다운로드 받을 수 있었습니다.)

프로그램 동작 구성

  1. Java의 HashMap 을 사용
  2. 상품명 + 옵션 을 합쳐서 한개의 String 을 만들어 Key로 사용
  3. 해당 Key에 맞는 수량을 읽어서 Value로 저장
  4. 엑셀 파일을 쭉 읽어 가면서 Key 의 Value 값을 꾸준히 업데이트
  5. 모두 다 읽었으면 CSV 파일로 저장 , 저장할때 전체 수량도 같이 기입

프로그램 사용법

  1. IDE에서 해당 코드를 JAR 파일로 만듬
  2. Launch4J 프로그램을 사용하여 JAR 파일을 exe 파일로 변환
  3. 지인 컴퓨터에서 exe 파일을 다운로드 하여 사용
    (※Launch4J를 사용할때 설정을 좀 만져주면 exe 파일로 실행 시켰을때 JRE가 없으면 자동으로 JRE를 다운로드 받을 수 있게 사이트가 열린다.)

사용한 코드 (주석을 달아놓음)

특징
1. 프로그램 종료 전에 스캐너의 nextLine 메서드를 추가해서 에러 발생시에 바로 꺼지지 않고 메세지를 확인 후 꺼지게 만듬
2. Gradle을 사용해서 Build 후 jar 파일로 만듬 -> 다른 컴퓨터에서 사용하기 위해서
3. OpenCSV , apache.poi 라이브러리를 사용함

더 자세한 내용은 아래의 Github 을 참고해주세요.

Github
https://github.com/Denia-park/CountClothes

Main

package org.example;

import com.opencsv.CSVWriter;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class Main {
    static final String PROGRAM_VERSION = "Version : 1.0 , UpdateDate : 22년 9월 7일";
    static String fileNameCSV = getStringOfNowLocalDateTime();
    static final int PRODUCT_NAME_CELL_INDEX = 1; //B [0부터 시작임.]
    static final int PRODUCT_OPTION_CELL_INDEX = 2; //C [0부터 시작임.]
    static final int PRODUCT_QUANTITY_CELL_INDEX = 3; //D [0부터 시작임.]

    public static void main(String[] args) {
        System.out.println("옷의 수량을 세는 프로그램을 시작합니다. [ " + PROGRAM_VERSION + " ]");

        Map<String, Integer> map = new TreeMap<>();

        Scanner sc = new Scanner(System.in); // 사용자로부터 데이터를 받기 위한 Scanner

        String path = System.getProperty("user.dir") + "\\"; //현재 작업 경로
        String fileName = "countClothes.xlsx"; //파일명 설정

        XSSFSheet sheetDataFromExcel = readExcel(path, fileName); //엑셀 파일 Read
        if (sheetDataFromExcel == null) { //파일을 못 읽어오면 종료.
            System.out.println("파일을 찾지 못했으므로 프로그램을 종료 합니다.");

            System.out.println("Enter 를 치면 정상 종료됩니다.");
            sc.nextLine(); //프로그램 종료 전 Holding
            return; //프로그램 종료
        }

        //행 갯수 가져오기
        int rows = sheetDataFromExcel.getPhysicalNumberOfRows();

        XSSFRow row = sheetDataFromExcel.getRow(0); //Title Row 가져오기
        int cells = row.getPhysicalNumberOfCells(); //Title Cell 수 가져오기
        String[][] dataBufferArr = new String[2][cells]; //행을 읽어서 저장해둘 배열을 생성
        int currentSaveOrder = 0; //dataBufferArr 에서 몇번째 배열인지 알려줄 인자
        NumberFormat f = NumberFormat.getInstance(); //엑셀에서 NumberFormat이 나왔을때 저장할 수 있게 생성함
        f.setGroupingUsed(false);	//지수로 안나오게 설정

        //반드시 "행(row)"을 읽고 "열(cell)"을 읽어야함 ..
        //rowIndex = 0 => Title
        for(int rowIndex = 1 ; rowIndex < rows ; rowIndex++) {
            row = sheetDataFromExcel.getRow(rowIndex);

            for (int i = 0; i < cells; i++) {
                XSSFCell cell = row.getCell(i);
                dataBufferArr[currentSaveOrder][i] = readCell(cell,f);
            }

            //상품명 + 옵션 을 합친 String 을 Key로 사용
            String mapKey = dataBufferArr[currentSaveOrder][PRODUCT_NAME_CELL_INDEX]
                    + " / "
                    + dataBufferArr[currentSaveOrder][PRODUCT_OPTION_CELL_INDEX];

            //수량이 String 으로 되어있으므로 Integer 파싱 후 사용
            int clothesQuantity = Integer.parseInt(dataBufferArr[currentSaveOrder][PRODUCT_QUANTITY_CELL_INDEX]);

            //map 에 내용들을 저장
            map.put(mapKey, map.getOrDefault(mapKey, 0) + clothesQuantity);

            currentSaveOrder ^= 1; //dataBufferArr 에 저장할 순서 변경 0 -> 1 , 1 -> 0 : XOR을 사용했다.
        }

        // map에 저장한 내용들을 CSV 파일에 저장하기
        writeDataToCSV(path, map);

        System.out.println("작업이 완료되었습니다.");

        System.out.println("Enter 를 치면 정상 종료됩니다.");
        sc.nextLine(); //프로그램 종료 전 Holding
    }

    private static void writeDataToCSV(String path, Map<String, Integer> map) {
        File file = new File(path, fileNameCSV);
        int clothesTotalQuantity = 0;

        try (
                FileOutputStream fos = new FileOutputStream(file,true);
                OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
                CSVWriter writer = new CSVWriter(osw)
        ) {
            //제목 저장
            String[] title = {
                    "상품명 [옵션 포함]",
                    "수량",
            };
            writer.writeNext(title,false);

            //entrySet 을 돌면서 map의 내용을 읽어 들인 후 csv 에 출력
            for (Map.Entry<String, Integer> entrySet : map.entrySet()) {
                int mapValue = entrySet.getValue();
                clothesTotalQuantity += mapValue;

                String[] data = {entrySet.getKey(), String.valueOf(mapValue)};
                writer.writeNext(data, false);
            }

            //전체 수량도 출력
            String[] totalSum = {
                    "※전체 수량※ : ",
                    String.valueOf(clothesTotalQuantity),
            };
            writer.writeNext(totalSum,false);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static XSSFSheet readExcel(String path, String fileName){
        try {
            FileInputStream file = new FileInputStream(path + fileName);
            XSSFWorkbook workbook = new XSSFWorkbook(file);

            return workbook.getSheetAt(0); // 첫번째 시트만 사용
        } catch(IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String readCell(XSSFCell cell, NumberFormat f) {
        String tempValue = "";
        if(cell != null){
            //타입 체크
            switch(cell.getCellType()) {
                case STRING:
                    tempValue = cell.getStringCellValue();
                    break;
                case NUMERIC:
                    tempValue = f.format(cell.getNumericCellValue())+"";
                    break;
                case BLANK:
                    tempValue = "";
                    break;
                case ERROR:
                    tempValue = cell.getErrorCellValue()+"";
                    break;
            }
            return tempValue;
        }
        else
            throw new RuntimeException("Cell Read 중 NPE 발생함");
    }
    private static String getStringOfNowLocalDateTime() {
        // 현재 날짜/시간
        LocalDateTime now = LocalDateTime.now(); // 2021-06-17T06:43:21.419878100

        // 포맷팅
        String formatedNow = now.format(DateTimeFormatter.ofPattern("yyMMdd_HH_mm_ss")); // 220628_02_38_02

        return "Count Clothes CSV_" + formatedNow + ".csv"; //Ex) CSV_220628_02_38_02

    }
}
profile
HW -> FW -> Web

0개의 댓글