221102~1103_광고 관리 플랫폼 대행사 센터 제작 47_보고서 파일 다운로드 기능 구현

창고·2022년 11월 3일
0

해당 게시글은 개인 프로젝트인 "광고 관리 플랫폼 대행사 센터 제작"
#129 "보고서 파일 다운로드 기능 구현" 이슈를 다루고 있습니다.

1. 진행 사항

(1) 요약

  • CSV, 엑셀 파일 출력 관련 레퍼런스 조사
  • StatisticsQueryRepository 수정
  • 캠페인 실적 보고서 출력 기능 구현
    • ReportService 구현
    • 페이지 내 보고서 다운로드 버튼 구현
    • API Endpoints 수정

(2) 상세 내용

  • apache.poi를 사용하여 엑셀 다운로드 기능을 구현하였음
	// Apachi poi
	implementation 'org.apache.poi:poi:5.2.2' // .xls
	implementation 'org.apache.poi:poi-ooxml:5.2.2' // .xlsx
  • 출력해야 할 보고서의 대상이 광고주 / 캠페인 / 소재 / 소재 실적으로 4가지 종류여서 메소드를 종류마다 다 만드는 것은 효율성이 떨어진다고 판단, 동적 처리를 위해 다음과 같이 진행하였음
    • 보고서 타입 Enum 클래스를 생성 (ReportType)
  public enum ReportType {
    PERFORMANCE("실적 보고서"),
    CREATIVE("소재 보고서"),
    CAMPAIGN("캠페인 보고서"),
    CLIENT("광고주 보고서");

    @Getter
    private final String description;

    ReportType(String description) {
        this.description = description;
    }
  }
  • 광고주 / 캠페인 / 소재 / 소재 실적 컨트롤러에서 보고서 타입을 지정해서 Report 서비스에 전달
    @GetMapping("/report")
    public ResponseEntity campaignReport(
            @PathVariable("clientId") String clientId,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate lastDate,
            HttpServletResponse response
    ) {
        return ResponseEntity.ok(reportService.getPerformanceStatistics(response, clientId, startDate, lastDate, ReportType.CAMPAIGN));
    }  
  • Report 서비스는 보고서 타입에 따라 동적으로 통계 조회 및 보고서 생성 메소드 실행
      // 실적 보고서, 소재 실적 보고서
    @Transactional(readOnly = true)
    public Object getPerformanceStatistics(HttpServletResponse response, Long id, LocalDate startDate, LocalDate lastDate, ReportType reportType) {

        LocalDate defaultLastDate = LocalDate.parse(LocalDate.now().minusDays(1)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        LocalDate startDateBeforeSevenDays = defaultLastDate.minusDays(6);
        LocalDate startDateBeforeThirtyDays = defaultLastDate.minusDays(30);


        if (lastDate == null) {
            lastDate = defaultLastDate;
        }

        if (startDate == null) {
            startDate = startDateBeforeThirtyDays;
        }

        if (reportType == ReportType.PERFORMANCE) {
            List<PerformanceStatisticsDto> performanceList = statisticsQueryRepository.findByCreative_IdAndStatisticsDefault(id, startDate, lastDate);
            createPerformanceExcelReportResponse(response, performanceList, reportType);
            return null;
        }

        if (reportType == ReportType.CREATIVE) {
            List<PerformanceStatisticsDto> performanceList = statisticsQueryRepository.findByCampaign_IdAndStatisticsDefault(id, startDate, lastDate);
            createPerformanceExcelReportResponse(response, performanceList, reportType);
            return null;
        }

        return null;
    }
  • 보고서를 제작하는 Report 서비스 생성
    • 보고서는 실적 보고서와 소진액 보고서 2가지 종류가 있음
    • 실적 보고서는 공통으로 제공하며, 소진액 보고서는 현재 광고주 리스트에서만 지원
  • Cell 내부 데이터 포맷 적용 (자릿수 구분, % 등)
            CellStyle numberCellStyle = workbook.createCellStyle();
            numberCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("#,##0"));
            CellStyle percentCellStyle = workbook.createCellStyle();
            percentCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
            CellStyle dateCellStyle = workbook.createCellStyle();
            dateCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy"));
  • 보고서 제작 메소드에서 보고서 타입에 따른 동적 처리 진행
    // 실적 보고서 파일 생성
    private void createPerformanceExcelReportResponse(HttpServletResponse response, List<PerformanceStatisticsDto> performanceList, ReportType reportType) {

        try {
            Workbook workbook = new SXSSFWorkbook();
            Sheet sheet = workbook.createSheet(setFileName(reportType) + "_실적_보고서"); // 동적

            // 숫자 포맷 적용
            CellStyle numberCellStyle = workbook.createCellStyle();
            numberCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("#,##0"));
            CellStyle percentCellStyle = workbook.createCellStyle();
            percentCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00%"));
            CellStyle dateCellStyle = workbook.createCellStyle();
            dateCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy"));

            LocalDate reportDate = LocalDate.parse(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            final String fileName = setFileName(reportType) + "_실적_보고서_" + reportDate; // 동적
            
            
            ....
            
            
            for (int i = 0; i < performanceList.size(); i++) {
                row = sheet.createRow(i + 1);

                PerformanceStatisticsDto performance = performanceList.get(i);

                Cell cell = null;

                cell = row.createCell(0); // 기간 동적
                setDate(cell, performance);

                cell = row.createCell(1); // 동적 (광고주 / 캠페인 / 소재 ID)
                setIdValue(cell, performance, reportType);

                cell = row.createCell(2); // 동적 (광고주명 / 캠페인명 / 소재 키워드)
                setNameValue(cell, performance, reportType);

                cell = row.createCell(3); // 동적 (광고주 업종 / 예산 / 입찰가)
                cell.setCellStyle(numberCellStyle);
                setSubValue(cell, performance, reportType);
                
                ....
                

    private String setFileName(ReportType reportType) {

        return switch (reportType) {
            case PERFORMANCE -> "상세";
            case CREATIVE -> "소재";
            case CAMPAIGN -> "캠페인";
            case CLIENT -> "광고주";
        };
    }

    private void setIdValue(Cell cell, PerformanceStatisticsDto psd, ReportType reportType) {

        switch (reportType) {
            case PERFORMANCE -> cell.setCellValue(psd.getCreativeId());
            case CREATIVE -> cell.setCellValue(psd.getCreativeId());
            case CAMPAIGN -> cell.setCellValue(psd.getCampaignId());
            case CLIENT -> cell.setCellValue(psd.getClientId());
        }
        ;
    }

    private void setNameValue(Cell cell, PerformanceStatisticsDto psd, ReportType reportType) {

        switch (reportType) {
            case PERFORMANCE -> cell.setCellValue(psd.getKeyword());
            case CREATIVE -> cell.setCellValue(psd.getKeyword());
            case CAMPAIGN -> cell.setCellValue(psd.getName());
            case CLIENT -> cell.setCellValue(psd.getUsername());
        }
        ;
    }

    private void setSubValue(Cell cell, PerformanceStatisticsDto psd, ReportType reportType) {

        switch (reportType) {
            case PERFORMANCE -> cell.setCellValue(psd.getBidingPrice());
            case CREATIVE -> cell.setCellValue(psd.getBidingPrice());
            case CAMPAIGN -> cell.setCellValue(psd.getBudget());
            case CLIENT -> cell.setCellValue(psd.getCategory());
        }
        ;
    }

2. 결과

  • 광고 관리 페이지 : 광고주 리스트
  • 광고주 실적 보고서
  • 광고주 소진액 보고서
  • 광고 관리 페이지 : 캠페인 리스트
  • 캠페인 실적 보고서
  • 광고 관리 페이지 : 소재 리스트
  • 소재 실적 보고서
  • 광고 관리 페이지 : 소재 실적 리스트
  • 소재 실적 상세 보고서 (일자별)

3. 미흡한 점 및 개선해야 할 부분

  • 초기에 Column의 width를 지정하는 부분이 있는데 이 영역도 동적 처리가 필요할 것으로 보인다... (소재 실적 상세 보고서의 날짜 부분이 휑한 경우처럼)
  • 현재 기간을 지정하고 조회하는 것은 정상 작동하나, '최근 7일간 통계', '최근 30일간 통계' 버튼을 누르고 조회하는 것은 적용이 아직 안 되고 있음. (두 기능은 param 값을 받지 않고 작동하기 때문) form에 있는 input 에 일자 데이터를 집어넣어야 하는데 이는 javascript로 처리해야 하는 것으로 확인.
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글