diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java index e51888c8..b364824b 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java @@ -4,32 +4,95 @@ import com.palnet.biz.api.bas.dos.model.AllStatDataRS; import com.palnet.biz.api.bas.dos.model.CptStatRQ; import com.palnet.biz.api.bas.dos.model.CptStatRS; import com.palnet.biz.api.bas.dos.service.StatisticsDosService; +import com.palnet.biz.api.comn.response.ErrorResponse; +import com.palnet.biz.api.comn.response.SuccessResponse; +import com.palnet.comn.code.ErrorCode; +import com.palnet.comn.exception.CustomException; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.HashMap; +import java.util.Map; + @RestController @Slf4j @RequestMapping("/api/statistics/dos") @RequiredArgsConstructor +@Tag(name = "드론 원스톱 통계 컨트롤러", description = "드론원스톱 통계 관련 API") public class StatisticsDosController { private final StatisticsDosService statisticsDosService; - @GetMapping("/all-data") + @GetMapping("/top-data") + @Operation(summary = "통계 페이지 상단 데이터 조회", description = "가장 많은 비행승인 데이터가 들어온 관할기관 데이터를 조회합니다.") public ResponseEntity allData(){ - log.info("allData"); - AllStatDataRS result = statisticsDosService.allData(); + AllStatDataRS result = null; + + try { + result = statisticsDosService.allData(); + } catch(CustomException e){ + ErrorCode errorCode = ErrorCode.fromCode(e.getSourceErrorCode()); + String paramMessage = (String) e.getParamArray()[0]; + + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", errorCode.code()); + resultMap.put("errorMessage", errorCode.message()); + resultMap.put("errorDesc", paramMessage); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGONE : {}", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse("Server Error", "-1")); + } + return ResponseEntity.ok().body(result); } @GetMapping("/table-data") + @Operation(summary = "통계 페이지 테이블 데이터 조회", description = "관할 기관 별 날짜별로 데이터 건수를 조회합니다.") public ResponseEntity tableData(CptStatRQ rq){ - CptStatRS result = statisticsDosService.cptStatData(rq); + CptStatRS result = null; + + try { + result = statisticsDosService.cptStatData(rq); + } catch(CustomException e){ + ErrorCode errorCode = ErrorCode.fromCode(e.getSourceErrorCode()); + String paramMessage = (String) e.getParamArray()[0]; + + Map resultMap = new HashMap<>(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", errorCode.code()); + resultMap.put("errorMessage", errorCode.message()); + resultMap.put("errorDesc", paramMessage); + return ResponseEntity.ok().body(new SuccessResponse<>(resultMap)); + } catch (Exception e) { + /** + * try{ + ... + } + * try 영역 안 코드들중 문제가 생기면 오는 곳. + * log.error 로그로 원인 파악과 함께 API를 호출한 곳에 서버에러 내려줌 + */ + log.error("IGONE : {}", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse("Server Error", "-1")); + } return ResponseEntity.ok().body(result); } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java index 979bd9ac..f72a346b 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java @@ -2,19 +2,22 @@ package com.palnet.biz.api.bas.dos.model; import lombok.Data; +import java.util.ArrayList; +import java.util.List; + @Data public class AllStatDataRS { - private GroupModel fullApproval; + private List fullApproval; - private GroupModel controlApproval; + private List controlApproval; - private GroupModel nonControlApproval; + private List nonControlApproval; @Data public static class GroupModel{ - private String groupName; + private List groupName; private Long all; @@ -24,13 +27,11 @@ public class AllStatDataRS { private Long day; - } + public GroupModel(){ + this.groupName = new ArrayList<>(); + } - @Data - public static class CptCountModel{ - private String cptName; - private Long count; } } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java index 2587be04..1e9dcd83 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java @@ -1,5 +1,6 @@ package com.palnet.biz.api.bas.dos.model; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.time.LocalDate; @@ -7,9 +8,12 @@ import java.time.LocalDate; @Data public class CptStatRQ { + @Schema(description = "[ year: 연도별, month: 월별, day: 일일 ] 카테고리 선택 컬럼" , example = "month") private String category; + @Schema(description = "검색 시작일자" , example = "2024-03-01") private LocalDate startDt; + @Schema(description = "검색 종료일자" , example = "2024-12-31") private LocalDate endDt; } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java index 055607db..10cce123 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java @@ -18,20 +18,18 @@ import java.util.List; @RequiredArgsConstructor public class StatisticsDosService { - private final DosFltPlanBasRepository dosFltPlanBasRepository; - - private final DosFltPlanAreaRepository dosFltPlanAreaRepository; - private final DosFltPlanAreaQueryRepository dosFltPlanAreaQueryRepository; public AllStatDataRS allData() { - - - AllStatDataRS.GroupModel fullApproval = dosFltPlanAreaQueryRepository.allApplyData(); + List fullApproval = dosFltPlanAreaQueryRepository.allApplyTopData(); + List controlApproval = dosFltPlanAreaQueryRepository.controlApplyTopData(true); + List nonControlApproval = dosFltPlanAreaQueryRepository.controlApplyTopData(false); AllStatDataRS result = new AllStatDataRS(); result.setFullApproval(fullApproval); + result.setControlApproval(controlApproval); + result.setNonControlApproval(nonControlApproval); return result; } @@ -68,5 +66,4 @@ public class StatisticsDosService { } - } diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java index 554e4d6f..c3df98a8 100644 --- a/pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java @@ -10,19 +10,15 @@ import com.palnet.comn.code.ErrorCode; import com.palnet.comn.exception.CustomException; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberTemplate; -import com.querydsl.core.types.dsl.StringTemplate; +import com.querydsl.core.types.dsl.*; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Repository @@ -33,58 +29,73 @@ public class DosFltPlanAreaQueryRepository { private final JPAQueryFactory query; - public AllStatDataRS.GroupModel allApplyData() { + /** + * 총 데이터 조회 + * @return + */ + public List allApplyTopData() { QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea; QDosFltPlanBas qDosFltPlanBas = QDosFltPlanBas.dosFltPlanBas; + Integer year = LocalDate.now().getYear(); + Integer month = LocalDate.now().getMonth().getValue(); + NumberTemplate yearTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN YEAR({0}) = {1} THEN 1 END)", qDosFltPlanBas.applyDt, year); + NumberTemplate monthTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN YEAR({0}) = {1} AND MONTH({2}) = {3} THEN 1 END)", qDosFltPlanBas.applyDt, year, qDosFltPlanBas.applyDt, month); + NumberTemplate todayTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN DATE({0}) = CURDATE() THEN 1 END)", qDosFltPlanBas.applyDt); - // TODO :: CPT_CD의 데이터가 없음으로 임시 F0002처리 - StringTemplate ifTemplate = Expressions.stringTemplate("CASE WHEN {0} IS NULL THEN 'F0002' ELSE {0} END", qDosFltPlanArea.cptCd); + BooleanBuilder builder = new BooleanBuilder(); + builder.and(Expressions.booleanTemplate("{0} IS NOT NULL", qDosFltPlanArea.cptCd)); - Map cptCountModels = query + Map groupModel = query .select( - Projections.bean( - AllStatDataRS.CptCountModel.class, - ifTemplate.as("cptName"), - qDosFltPlanArea.count().as("count") - ) + Projections.bean( + AllStatDataRS.GroupModel.class, + qDosFltPlanArea.cptCd.as("groupName"), + qDosFltPlanArea.count().as("all"), + yearTemplate.as("year"), + monthTemplate.as("month"), + todayTemplate.as("day") + ) ) .from(qDosFltPlanArea) + .leftJoin(qDosFltPlanBas) + .on(qDosFltPlanArea.planSno.eq(qDosFltPlanBas.planSno)) + .where(builder) .groupBy(qDosFltPlanArea.cptCd) .fetch() .stream() .collect(Collectors.toMap( - AllStatDataRS.CptCountModel::getCptName, - AllStatDataRS.CptCountModel::getCount - )); - - for(Map.Entry entry : cptCountModels.entrySet()){ + key -> { - String[] cptCdArray = entry.getKey().split(","); + if(key.getGroupName() == null) return ""; - if(cptCdArray.length > 1) continue; + StringBuilder result = new StringBuilder(); - for(String cptCd : cptCdArray){ - Long newCount = cptCountModels.get(cptCd) + entry.getValue(); - cptCountModels.put(cptCd, newCount); - } - } + key.getGroupName().forEach(node -> { + result.append(node); + result.append(","); + }); - // 정렬 - Long max = -1000L; - String cptCd = ""; + result.deleteCharAt(result.length() - 1); - for(Map.Entry entry : cptCountModels.entrySet()){ - Long value = entry.getValue(); + return result.toString(); + }, + value -> value + )); - if(value > max){ - max = value; - cptCd = entry.getKey(); - } - } + return this.topDataParsing(groupModel); + } + /** + * 관제, 비관제 데이터 조회 + * @param controlFlag TRUE : 관제권 조회, FALSE : 비 관제권 조회 + * @return + */ + public List controlApplyTopData(Boolean controlFlag) { + QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea; + QDosFltPlanBas qDosFltPlanBas = QDosFltPlanBas.dosFltPlanBas; Integer year = LocalDate.now().getYear(); Integer month = LocalDate.now().getMonth().getValue(); @@ -93,13 +104,23 @@ public class DosFltPlanAreaQueryRepository { NumberTemplate monthTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN YEAR({0}) = {1} AND MONTH({2}) = {3} THEN 1 END)", qDosFltPlanBas.applyDt, year, qDosFltPlanBas.applyDt, month); NumberTemplate todayTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN DATE({0}) = CURDATE() THEN 1 END)", qDosFltPlanBas.applyDt); + ListPath groupingColumn = null; BooleanBuilder builder = new BooleanBuilder(); - builder.and(qDosFltPlanArea.cptCd.contains(cptCd)); + builder.and(Expressions.booleanTemplate("{0} IS NOT NULL", qDosFltPlanArea.cptCd)); + + if(controlFlag){ + builder.and(Expressions.booleanTemplate("{0} IS NOT NULL", qDosFltPlanArea.innerCptCd)); + groupingColumn = qDosFltPlanArea.innerCptCd; + } else { + builder.and(Expressions.booleanTemplate("{0} IS NULL", qDosFltPlanArea.innerCptCd)); + groupingColumn = qDosFltPlanArea.cptCd; + } - AllStatDataRS.GroupModel groupModel = query + Map groupModel = query .select( Projections.bean( AllStatDataRS.GroupModel.class, + groupingColumn.as("groupName"), qDosFltPlanArea.count().as("all"), yearTemplate.as("year"), monthTemplate.as("month"), @@ -109,14 +130,37 @@ public class DosFltPlanAreaQueryRepository { .from(qDosFltPlanArea) .leftJoin(qDosFltPlanBas) .on(qDosFltPlanArea.planSno.eq(qDosFltPlanBas.planSno)) - .fetchOne(); + .where(builder) + .groupBy(groupingColumn) + .fetch() + .stream() + .collect(Collectors.toMap( + key -> { + if(key.getGroupName() == null) return ""; + + StringBuilder result = new StringBuilder(); + + key.getGroupName().forEach(node -> { + result.append(node); + result.append(","); + }); + + result.deleteCharAt(result.length() - 1); - groupModel.setGroupName(cptCd); + return result.toString(); + }, + value -> value + )); - return groupModel; + return this.topDataParsing(groupModel); } + /** + * 공항별 데이터 통계 + * @param rq + * @return + */ public List cptStatData(CptStatRQ rq) { QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea; @@ -131,7 +175,10 @@ public class DosFltPlanAreaQueryRepository { String format = this.getFormat(rq.getCategory()); StringTemplate formattedDate = Expressions.stringTemplate("DATE_FORMAT({0},{1})", qDosFltPlanBas.applyDt , format); + String cptCd = competnetAgency.name(); + BooleanBuilder builder = new BooleanBuilder(); + builder.and(Expressions.booleanTemplate("{0} LIKE CONCAT('%', {1}, '%')", qDosFltPlanArea.cptCd, cptCd)); if(!rq.getCategory().equals("year")){ builder.and(qDosFltPlanBas.applyDt.goe(rq.getStartDt())); @@ -171,12 +218,8 @@ public class DosFltPlanAreaQueryRepository { CptStatRS.CptStat cptStatModel = new CptStatRS.CptStat(); cptStatModel.setCptName(competnetAgency.getDesc()); cptStatModel.setCptCd(competnetAgency.name()); - - // TODO :: CPT_CD 나오기전 임시 코드 - if(competnetAgency.name().equals("F0002")){ - cptStatModel.setCountModel(countModel); - cptStatModel.setCoordinateModels(coordinateModels); - } + cptStatModel.setCountModel(countModel); + cptStatModel.setCoordinateModels(coordinateModels); cptStatList.add(cptStatModel); } @@ -184,6 +227,72 @@ public class DosFltPlanAreaQueryRepository { return cptStatList; } + + private List topDataParsing(Map groupModel){ + Map currentMap = new ConcurrentHashMap<>(groupModel); + + // CptCd가 한 개가 아닌 값들에 대한 로직 + for(Map.Entry entry : currentMap.entrySet()){ + + String[] cptCdArray = entry.getKey().split(","); + + // CptCd가 1개일 경우 continue + if(cptCdArray.length <= 1) continue; + + for(String cptCd : cptCdArray){ + + // 기존 Map에 없는 값일 경우 CptCd를 Key로 새로 만들어 put + if(currentMap.get(cptCd) == null){ + AllStatDataRS.GroupModel node = new AllStatDataRS.GroupModel(); + node.setGroupName(Collections.singletonList(cptCd)); + node.setAll(entry.getValue().getAll()); + node.setYear(entry.getValue().getYear()); + node.setMonth(entry.getValue().getMonth()); + node.setDay(entry.getValue().getDay()); + + currentMap.put(cptCd, node); + continue; + } + + // 기존 값이 있을 경우 객체를 새로운 메모리에 할당하여 put + AllStatDataRS.GroupModel node = new AllStatDataRS.GroupModel(); + node.setGroupName(Collections.singletonList(cptCd)); + node.setAll(currentMap.get(cptCd).getAll() + entry.getValue().getAll()); + node.setYear(currentMap.get(cptCd).getYear() + entry.getValue().getYear()); + node.setMonth(currentMap.get(cptCd).getMonth() + entry.getValue().getMonth()); + node.setDay(currentMap.get(cptCd).getDay() + entry.getValue().getDay()); + + currentMap.put(cptCd, node); + } + + currentMap.remove(entry.getKey()); + } + + // Key의 맞는 GroupName Set + currentMap.forEach((key, value) -> { + value.setGroupName(Collections.singletonList(key)); + }); + + // 총 카운트가 가장많은 값 추출 + Long max = currentMap.values().stream() + .mapToLong(AllStatDataRS.GroupModel::getAll) + .max() + .orElse(0); + + List result = new ArrayList<>(); + + // 가장 많은 값만 반환 리스트에 ADD + for(Map.Entry entry : currentMap.entrySet()){ + if(entry.getValue().getAll().equals(max)){ + result.add(entry.getValue()); + } + } + + if(result.isEmpty()) result.add(new AllStatDataRS.GroupModel()); + + return result; + } + private String getFormat(String category){ String format = null; @@ -204,4 +313,5 @@ public class DosFltPlanAreaQueryRepository { return format; } + }