Browse Source

Merge commit '7fcee082cb5a22b4c58e20e101a7b5146fe81004' into develop

develop
lkd9125(이경도) 16 hours ago
parent
commit
8408433d06
  1. 103
      pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java
  2. 37
      pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java
  3. 19
      pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java
  4. 44
      pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRS.java
  5. 69
      pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java
  6. 3
      pav-server/src/main/java/com/palnet/biz/jpa/repository/ctr/CtrCntrlQueryRepository.java
  7. 317
      pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java

103
pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/StatisticsDosController.java

@ -0,0 +1,103 @@
package com.palnet.biz.api.bas.dos.controller;
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("/top-data")
@Operation(summary = "통계 페이지 상단 데이터 조회", description = "가장 많은 비행승인 데이터가 들어온 관할기관 데이터를 조회합니다.")
public ResponseEntity<?> allData(){
AllStatDataRS result = null;
try {
result = statisticsDosService.allData();
} catch(CustomException e){
ErrorCode errorCode = ErrorCode.fromCode(e.getSourceErrorCode());
String paramMessage = (String) e.getParamArray()[0];
Map<String, Object> 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 = null;
try {
result = statisticsDosService.cptStatData(rq);
} catch(CustomException e){
ErrorCode errorCode = ErrorCode.fromCode(e.getSourceErrorCode());
String paramMessage = (String) e.getParamArray()[0];
Map<String, Object> 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);
}
}

37
pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/AllStatDataRS.java

@ -0,0 +1,37 @@
package com.palnet.biz.api.bas.dos.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class AllStatDataRS {
private List<GroupModel> fullApproval;
private List<GroupModel> controlApproval;
private List<GroupModel> nonControlApproval;
@Data
public static class GroupModel{
private List<String> groupName;
private Long all;
private Long year;
private Long month;
private Long day;
public GroupModel(){
this.groupName = new ArrayList<>();
}
}
}

19
pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRQ.java

@ -0,0 +1,19 @@
package com.palnet.biz.api.bas.dos.model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
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;
}

44
pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/CptStatRS.java

@ -0,0 +1,44 @@
package com.palnet.biz.api.bas.dos.model;
import lombok.Data;
import java.util.List;
@Data
public class CptStatRS {
private List<CptStat> cptList;
@Data
public static class CptStat{
private String cptName; // 관할청 이름
private String cptCd;
private List<DateCountModel> countModel; // 카운트
private List<CoordinateModel> coordinateModels; // 중심좌표
}
@Data
public static class DateCountModel{
private String date;
private Long count;
}
@Data
public static class CoordinateModel{
private Double lat; // 위도 36.. ~~
private Double lon; // 경도 126.. ~~
}
}

69
pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/StatisticsDosService.java

@ -0,0 +1,69 @@
package com.palnet.biz.api.bas.dos.service;
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.jpa.repository.dos.DosFltPlanAreaQueryRepository;
import com.palnet.biz.jpa.repository.dos.DosFltPlanAreaRepository;
import com.palnet.biz.jpa.repository.dos.DosFltPlanBasRepository;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
@RequiredArgsConstructor
public class StatisticsDosService {
private final DosFltPlanAreaQueryRepository dosFltPlanAreaQueryRepository;
public AllStatDataRS allData() {
List<AllStatDataRS.GroupModel> fullApproval = dosFltPlanAreaQueryRepository.allApplyTopData();
List<AllStatDataRS.GroupModel> controlApproval = dosFltPlanAreaQueryRepository.controlApplyTopData(true);
List<AllStatDataRS.GroupModel> nonControlApproval = dosFltPlanAreaQueryRepository.controlApplyTopData(false);
AllStatDataRS result = new AllStatDataRS();
result.setFullApproval(fullApproval);
result.setControlApproval(controlApproval);
result.setNonControlApproval(nonControlApproval);
return result;
}
public CptStatRS cptStatData(CptStatRQ rq) {
List<CptStatRS.CptStat> cptList = dosFltPlanAreaQueryRepository.cptStatData(rq);
CptStatRS result = new CptStatRS();
result.setCptList(cptList);
return result;
}
@Getter
@RequiredArgsConstructor
public enum CompetentAgency{
F0002("김포항공관리사무소"),
F0001("서울지방항공청"),
F0003("양양공항출장소"),
F0004("원주공항출장소"),
F0006("군산공항출장소"),
F0007("부산지방항공청"),
F0010("울진공항출장소"),
C0001("울산공항출장소"),
C0002("여수공항출장소"),
C0003("무안공항출장소"),
;
private final String desc;
}
}

3
pav-server/src/main/java/com/palnet/biz/jpa/repository/ctr/CtrCntrlQueryRepository.java

@ -1361,7 +1361,8 @@ public class CtrCntrlQueryRepository{
.from(qCntrlBasEntity)
.leftJoin(qCtrCntrHstry)
.on(qCntrlBasEntity.cntrlId.eq(qCtrCntrHstry.cntrlId))
.where(builder) .orderBy(qCtrCntrHstry.hstrySno.asc())
.where(builder)
.orderBy(qCtrCntrHstry.hstrySno.asc())
.fetch();
return result;

317
pav-server/src/main/java/com/palnet/biz/jpa/repository/dos/DosFltPlanAreaQueryRepository.java

@ -0,0 +1,317 @@
package com.palnet.biz.jpa.repository.dos;
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.jpa.entity.QDosFltPlanArea;
import com.palnet.biz.jpa.entity.QDosFltPlanBas;
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.*;
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.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Repository
@Slf4j
@RequiredArgsConstructor
public class DosFltPlanAreaQueryRepository {
private final JPAQueryFactory query;
/**
* 데이터 조회
* @return
*/
public List<AllStatDataRS.GroupModel> allApplyTopData() {
QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea;
QDosFltPlanBas qDosFltPlanBas = QDosFltPlanBas.dosFltPlanBas;
Integer year = LocalDate.now().getYear();
Integer month = LocalDate.now().getMonth().getValue();
NumberTemplate<Long> yearTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN YEAR({0}) = {1} THEN 1 END)", qDosFltPlanBas.applyDt, year);
NumberTemplate<Long> 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<Long> todayTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN DATE({0}) = CURDATE() THEN 1 END)", qDosFltPlanBas.applyDt);
BooleanBuilder builder = new BooleanBuilder();
builder.and(Expressions.booleanTemplate("{0} IS NOT NULL", qDosFltPlanArea.cptCd));
Map<String, AllStatDataRS.GroupModel> groupModel = query
.select(
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(
key -> {
if(key.getGroupName() == null) return "";
StringBuilder result = new StringBuilder();
key.getGroupName().forEach(node -> {
result.append(node);
result.append(",");
});
result.deleteCharAt(result.length() - 1);
return result.toString();
},
value -> value
));
return this.topDataParsing(groupModel);
}
/**
* 관제, 비관제 데이터 조회
* @param controlFlag TRUE : 관제권 조회, FALSE : 관제권 조회
* @return
*/
public List<AllStatDataRS.GroupModel> controlApplyTopData(Boolean controlFlag) {
QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea;
QDosFltPlanBas qDosFltPlanBas = QDosFltPlanBas.dosFltPlanBas;
Integer year = LocalDate.now().getYear();
Integer month = LocalDate.now().getMonth().getValue();
NumberTemplate<Long> yearTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN YEAR({0}) = {1} THEN 1 END)", qDosFltPlanBas.applyDt, year);
NumberTemplate<Long> 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<Long> todayTemplate = Expressions.numberTemplate(Long.class, "COUNT(CASE WHEN DATE({0}) = CURDATE() THEN 1 END)", qDosFltPlanBas.applyDt);
ListPath<String, StringPath> groupingColumn = null;
BooleanBuilder builder = new BooleanBuilder();
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;
}
Map<String, AllStatDataRS.GroupModel> groupModel = query
.select(
Projections.bean(
AllStatDataRS.GroupModel.class,
groupingColumn.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(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);
return result.toString();
},
value -> value
));
return this.topDataParsing(groupModel);
}
/**
* 공항별 데이터 통계
* @param rq
* @return
*/
public List<CptStatRS.CptStat> cptStatData(CptStatRQ rq) {
QDosFltPlanArea qDosFltPlanArea = QDosFltPlanArea.dosFltPlanArea;
QDosFltPlanBas qDosFltPlanBas = QDosFltPlanBas.dosFltPlanBas;
StatisticsDosService.CompetentAgency[] constants = StatisticsDosService.CompetentAgency.values();
List<CptStatRS.CptStat> cptStatList = new ArrayList<>();
for(StatisticsDosService.CompetentAgency competnetAgency : constants){
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()));
builder.and(qDosFltPlanBas.applyDt.loe(rq.getEndDt()));
}
List<CptStatRS.DateCountModel> countModel = query
.select(
Projections.bean(
CptStatRS.DateCountModel.class,
formattedDate.as("date"),
qDosFltPlanArea.count().as("count")
)
)
.from(qDosFltPlanArea)
.leftJoin(qDosFltPlanBas)
.on(qDosFltPlanArea.planSno.eq(qDosFltPlanBas.planSno))
.where(builder)
.groupBy(formattedDate)
.fetch();
List<CptStatRS.CoordinateModel> coordinateModels = query
.select(
Projections.bean(
CptStatRS.CoordinateModel.class,
qDosFltPlanArea.lat.as("lat"),
qDosFltPlanArea.lon.as("lon")
)
)
.from(qDosFltPlanArea)
.leftJoin(qDosFltPlanBas)
.on(qDosFltPlanArea.planSno.eq(qDosFltPlanBas.planSno))
.where(builder)
.fetch();
CptStatRS.CptStat cptStatModel = new CptStatRS.CptStat();
cptStatModel.setCptName(competnetAgency.getDesc());
cptStatModel.setCptCd(competnetAgency.name());
cptStatModel.setCountModel(countModel);
cptStatModel.setCoordinateModels(coordinateModels);
cptStatList.add(cptStatModel);
}
return cptStatList;
}
private List<AllStatDataRS.GroupModel> topDataParsing(Map<String, AllStatDataRS.GroupModel> groupModel){
Map<String, AllStatDataRS.GroupModel> currentMap = new ConcurrentHashMap<>(groupModel);
// CptCd가 한 개가 아닌 값들에 대한 로직
for(Map.Entry<String, AllStatDataRS.GroupModel> 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<AllStatDataRS.GroupModel> result = new ArrayList<>();
// 가장 많은 값만 반환 리스트에 ADD
for(Map.Entry<String, AllStatDataRS.GroupModel> 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;
switch (category){
case "year":
format = "%Y";
break;
case "month":
format = "%Y-%m";
break;
case "day":
format = "%Y-%m-%d";
break;
default:
throw new CustomException(ErrorCode.NON_VALID_PARAMETER);
}
return format;
}
}
Loading…
Cancel
Save