Spring/Spring Batch

[Spring Batch] Spring Batch 로 정산 시스템 만들어보기 (4)

신민석 2024. 9. 2. 16:43

🎯 정산 정보 CSV 파일 다운로드 API


요구사항을 다시 한번 살펴보면 다음과 같습니다.

 정산 결과 파일 생성 및 다운로드

  • 정산 결과 파일 생성: 정산 결과를 CSV 또는 Excel 파일로 생성하는 기능을 구현합니다. 파일에는 가게별 매출, 수수료, 최종 정산 금액 등이 포함되어야 합니다.
  • 파일 저장소 관리: 생성된 파일을 AWS S3 또는 로컬 파일 시스템에 저장하는 기능을 구현합니다.
  • 파일 다운로드 API: 저장된 정산 결과 파일을 다운로드할 수 있는 REST API를 제공합니다. 가게별로 파일을 조회하고 다운로드할 수 있어야 합니다.

 

요구사항에 맞춰 만들어야 하는 REST API 는 두개입니다.

 

- 가게별 일매출 정산 정보를 CSV 로 다운받는 API

- 가게별 월매출 정산 정보를 CSV 로 다운받는 API

 

현재 일매출, 월매출 정산 정보는 각각 다른 테이블에 저장되어 있으므로 DB 에서 조회해 CSV 로 다운받을 수 있는 형식으로만 보내주면 될거 같네요. (크게 어려운 부분은 없으니 가볍게 보셔도 괜찮을거 같습니다.)

 

즉, 배치를 실행해 DB 에 저장된 정산 정보를 REST API 를 통해 다운로드 할 수 있어야 합니다. 현재 프로젝트는 멀티 모듈로 구성되어 있고 REST API 와 관련된 로직들은 settlement-system-api 모듈에 만들어보겠습니다.

 

  SettlementRepository

package com.repository;

import com.entity.Settlement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.Optional;

@Repository
public interface SettlementRepository extends JpaRepository<Settlement, Long> {

    @Query("SELECT s FROM Settlement s WHERE s.shopId = :shopId AND FUNCTION('DATE', s.settlementDateTime) = :date")
    Optional<Settlement> findByShopIdAndSettlementDate(@Param("shopId") Long shopId, @Param("date") LocalDate date);

}

 

Settlement 테이블은 가게별 일매출 정산 정보가 포함되어 있습니다. 따라서  파라미터로 shopId 와 날짜를 받아와 해당하는 Settlement 를 반환하는 메서드를 만들었습니다.

 

 

  MonthlySettlementRepository

package com.repository;

import com.entity.MonthlySettlement;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.Optional;

@Repository
public interface MonthlySettlementRepository extends JpaRepository<MonthlySettlement, Long> {

    @Query("SELECT m FROM MonthlySettlement m WHERE m.shopId = :shopId AND FUNCTION('MONTH', m.settlementDateTime) = FUNCTION('MONTH', :date) AND FUNCTION('YEAR', m.settlementDateTime) = FUNCTION('YEAR', :date)")
    Optional<MonthlySettlement> findByShopIdAndSettlementMonthly(@Param("shopId") Long shopId, @Param("date") LocalDate date);


}

 

MonthlySettlement 테이블은 가게별 월매출 정산 정보가 포함되어 있습니다. 따라서  파라미터로 shopId 와 날짜를 받아와 해당하는 MonthlySettlement 를 반환하는 메서드를 만들었습니다.

 

  SettlementService

package com.service;

import com.entity.MonthlySettlement;
import com.entity.Settlement;
import com.repository.MonthlySettlementRepository;
import com.repository.SettlementRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class SettlementService {

    private final SettlementRepository settlementRepository;
    private final MonthlySettlementRepository monthlySettlementRepository;

    public Settlement findByIdAndDate(Long shopId, LocalDate localDate){
        Optional<Settlement> byShopIdAndSettlementDate = settlementRepository.findByShopIdAndSettlementDate(shopId, localDate);
        return byShopIdAndSettlementDate.get();
    }

    public MonthlySettlement findByIdAndMonth(Long shopId, LocalDate localDate) {
        Optional<MonthlySettlement> byShopIdAndSettlementMonthly = monthlySettlementRepository.findByShopIdAndSettlementMonthly(shopId, localDate);
        return byShopIdAndSettlementMonthly.get();
    }

}

 

Repository 에서 구현한 각각의 메서드를 이용해 조회된 정산 정보를 반환합니다.

 

  CsvHelper

package com.utils;

import com.entity.MonthlySettlement;
import com.entity.Settlement;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;

import java.io.IOException;
import java.io.Writer;

public class CsvHelper {

    public static void writeSettlementsToCsv(Writer writer, Settlement settlement) {
        try (CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(
                "ShopName", "SettlementDateTime", "TotalSales", "TotalRefunds", "NetSales"))) {

            csvPrinter.printRecord(
                    settlement.getShopName(),
                    settlement.getSettlementDateTime(),
                    settlement.getTotalSales(),
                    settlement.getTotalRefunds(),
                    settlement.getNetSales()
            );

        } catch (IOException e) {
            e.printStackTrace();
            // 로그 처리 또는 예외 처리 로직 추가 가능
        }
    }

    public static void writeMonthlySettlementsToCsv(Writer writer, MonthlySettlement settlement) {
        try (CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(
                "ShopName", "SettlementDateTime", "TotalSales", "TotalRefunds", "NetSales"))) {

            csvPrinter.printRecord(
                    settlement.getShopName(),
                    settlement.getSettlementDateTime(),
                    settlement.getTotalSales(),
                    settlement.getTotalRefunds(),
                    settlement.getNetSales()
            );

        } catch (IOException e) {
            e.printStackTrace();
            // 로그 처리 또는 예외 처리 로직 추가 가능
        }
    }
}

 

객체를 CSV 형식으로 변환을 도와주는 클래스 입니다. 각각 Settlement, MonthlySettlement 정보를 원하는 CSV 형식으로 만들면 됩니다.

 

(CSV 변환 라이브러리를 사용하기 위해 build.gradle 을 아래와 같이 추가해야 합니다.)

implementation 'org.apache.commons:commons-csv:1.10.0'

 

  SettlementController

package com.controller;

import com.entity.MonthlySettlement;
import com.entity.Settlement;
import com.service.SettlementService;
import com.utils.CsvHelper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.Writer;
import java.time.LocalDate;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/settlements")
public class SettlementController {

    private final SettlementService settlementService;

    @GetMapping("/daily/download/csv")
    public void downloadDailySettlement(HttpServletResponse response, @RequestParam("shopId") Long shopId, @RequestParam("localDate") LocalDate localDate) throws IOException {
        Settlement settlement = settlementService.findByIdAndDate(shopId, localDate);

        response.setContentType("text/csv");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"settlements.csv\"");

        try (Writer writer = response.getWriter()){
            CsvHelper.writeSettlementsToCsv(writer, settlement);
        }
    }

    @GetMapping("/monthly/download/csv")
    public void downloadMonthlySettlement(HttpServletResponse response, @RequestParam("shopId") Long shopId, @RequestParam("localDate") LocalDate localDate) throws IOException {

        response.setContentType("test/csv");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"settlements.csv\"");

        MonthlySettlement monthlySettlement = settlementService.findByIdAndMonth(shopId, localDate);

        try (Writer writer = response.getWriter()){
            CsvHelper.writeMonthlySettlementsToCsv(writer, monthlySettlement);
        }

    }
}

 

downloadDailySettlementdownloadMonthlySettlement 는 각각 일별, 월별 정산 정보를 CSV 로 다운받기 위한 RESTAPI 입니다.

 

자! 이제 서버를 구동하고 CSV 파일이 잘 다운되는지 확인해보겠습니다.

 

http://localhost:8080/api/settlements/daily/download/csv?shopId=1&localDate=2024-09-01

 

다음과 같이 요청을 보내면 CSV 파일이 지정한 형식으로 잘 저장되는것을 확인할 수 있습니다.

 

 

지금까지 가게에 저장된 거래 정보를 이용해 정산 배치를 만들어 봤고, 이를 CSV 파일로 다운로드 할 수 있는 API 까지 만들어 봤습니다.

Spring Batch 를 이용해 간단한 정산 시스템 프로젝트를 만들데, 아직 효율적인 배치 프로그램을 만들기 위해선 공부해야할 부분이 많아 보이네요 ㅜ 

 

 

(전체 코드는 아래 깃허브 주소를 참고해주세요.)

https://github.com/sinminseok/settlement-system

 

GitHub - sinminseok/settlement-system

Contribute to sinminseok/settlement-system development by creating an account on GitHub.

github.com