From f9f450a9c24a6ca5796429842a4a01023244f66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dhji=28=EC=A7=80=EB=8C=80=ED=95=9C=29?= Date: Sun, 23 Jun 2024 23:16:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=93=9C=EB=A1=A0=EC=9B=90=EC=8A=A4?= =?UTF-8?q?=ED=86=B1=20=EC=8B=A0=EC=B2=AD=EB=AA=A9=EB=A1=9D=20=EC=97=91?= =?UTF-8?q?=EC=85=80=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/server/server.http | 6 +- pav-server/build.gradle | 4 + .../bas/dos/controller/BasDosController.java | 13 +- .../bas/dos/model/BasDosPlanDownloadRq.java | 20 ++ .../bas/dos/model/DosExcelCellStyleType.java | 5 + .../biz/api/bas/dos/model/PlanSelectType.java | 5 + .../api/bas/dos/service/BasDosService.java | 232 ++++++++++++++++-- .../java/com/palnet/comn/code/ErrorCode.java | 1 + .../java/com/palnet/comn/utils/HttpUtils.java | 5 + 9 files changed, 271 insertions(+), 20 deletions(-) create mode 100644 pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/BasDosPlanDownloadRq.java create mode 100644 pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/DosExcelCellStyleType.java create mode 100644 pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/PlanSelectType.java diff --git a/http/server/server.http b/http/server/server.http index d2ccf69b..5e3ba12d 100644 --- a/http/server/server.http +++ b/http/server/server.http @@ -105,4 +105,8 @@ Content-Type: application/json ] } ] -} \ No newline at end of file +} + +### dos(드론원스톱) excel download +GET {{appHost}}/api/bas/dos/plan/download/excel?searchStDt=2024-06-14&searchEndDt=2024-06-14 +Authorization: {{accessToken}} diff --git a/pav-server/build.gradle b/pav-server/build.gradle index d7ca5fbc..a66d566b 100644 --- a/pav-server/build.gradle +++ b/pav-server/build.gradle @@ -93,6 +93,10 @@ dependencies { implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr353:2.15.2' + // excel + implementation 'org.apache.poi:poi:5.2.5' + implementation 'org.apache.poi:poi-ooxml:5.2.5' + // geometry implementation 'com.esri.geometry:esri-geometry-api:2.2.4' implementation 'org.locationtech.proj4j:proj4j:1.3.0' diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/BasDosController.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/BasDosController.java index 73719380..b7a7f548 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/BasDosController.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/controller/BasDosController.java @@ -2,6 +2,7 @@ package com.palnet.biz.api.bas.dos.controller; import com.palnet.biz.api.bas.dos.model.BasDosPlanRq; import com.palnet.biz.api.bas.dos.model.BasDosPlanRs; +import com.palnet.biz.api.bas.dos.model.PlanSelectType; import com.palnet.biz.api.bas.dos.model.UpdatePlanRq; import com.palnet.biz.api.bas.dos.service.BasDosService; import com.palnet.comn.code.ErrorCode; @@ -32,7 +33,7 @@ public class BasDosController { */ @GetMapping("/plan") public ResponseEntity getDosPlan(BasDosPlanRq rq) { - List rs = basDosService.getDosPlan(rq); + List rs = basDosService.getDosPlan(rq, PlanSelectType.LIST); return ResponseEntity.ok(rs); } @@ -51,4 +52,14 @@ public class BasDosController { return ResponseEntity.ok().build(); } + /** + * 엑셀 다운로드 + * @param rq + */ + @GetMapping("/plan/download/excel") + public void downloadExcel(BasDosPlanRq rq) { + List rs = basDosService.getDosPlan(rq, PlanSelectType.DOWNLOAD); + basDosService.createExcel(rs); + } + } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/BasDosPlanDownloadRq.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/BasDosPlanDownloadRq.java new file mode 100644 index 00000000..c4eccdf8 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/BasDosPlanDownloadRq.java @@ -0,0 +1,20 @@ +package com.palnet.biz.api.bas.dos.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BasDosPlanDownloadRq { + private LocalDate searchStDt; + private LocalDate searchEndDt; + private String applyNo; + private String approvalCd; + private String selectZone; +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/DosExcelCellStyleType.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/DosExcelCellStyleType.java new file mode 100644 index 00000000..cea26632 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/DosExcelCellStyleType.java @@ -0,0 +1,5 @@ +package com.palnet.biz.api.bas.dos.model; + +public enum DosExcelCellStyleType { + HEADER_CELL, BODY_CELL +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/PlanSelectType.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/PlanSelectType.java new file mode 100644 index 00000000..9b08af75 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/model/PlanSelectType.java @@ -0,0 +1,5 @@ +package com.palnet.biz.api.bas.dos.model; + +public enum PlanSelectType { + LIST, DOWNLOAD +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/BasDosService.java b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/BasDosService.java index fa0b34cd..65ae611d 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/BasDosService.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/dos/service/BasDosService.java @@ -1,9 +1,7 @@ package com.palnet.biz.api.bas.dos.service; -import com.palnet.biz.api.bas.dos.model.BasDosPlanAreaRs; -import com.palnet.biz.api.bas.dos.model.BasDosPlanRq; -import com.palnet.biz.api.bas.dos.model.BasDosPlanRs; -import com.palnet.biz.api.bas.dos.model.UpdatePlanRq; +import com.palnet.biz.api.bas.dos.model.*; +import com.palnet.biz.api.external.model.ApprovalCd; import com.palnet.biz.api.external.model.DosApprovalResult; import com.palnet.biz.api.external.service.DronOneStopService; import com.palnet.biz.jpa.entity.DosFltPlanArea; @@ -12,16 +10,26 @@ import com.palnet.biz.jpa.entity.DosFltPlanResult; import com.palnet.biz.jpa.repository.dos.DosFltPlanAreaRepository; import com.palnet.biz.jpa.repository.dos.DosFltPlanQueryRepository; import com.palnet.biz.jpa.repository.dos.DosFltPlanResultRepository; +import com.palnet.comn.code.ErrorCode; +import com.palnet.comn.exception.CustomException; import com.palnet.comn.utils.AirspaceUtils; import com.palnet.comn.utils.AreaUtils; +import com.palnet.comn.utils.HttpUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFCellStyle; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.time.Instant; import java.time.LocalDate; import java.util.ArrayList; @@ -50,7 +58,7 @@ public class BasDosService { * @return List */ @Transactional(readOnly = true) - public List getDosPlan(BasDosPlanRq rq) { + public List getDosPlan(BasDosPlanRq rq, PlanSelectType type) { List planBasList = dosFltPlanQueryRepository.findPlanByBasSearch(rq); @@ -110,22 +118,24 @@ public class BasDosService { continue; } - List coordBuffers = areaUtils.createCircle(new Coordinate(area.getLon(), area.getLat()), area.getBufferZone()); - if ("GIMPO".equalsIgnoreCase(rq.getSelectZone())) { - AirspaceUtils airspaceUtils = AirspaceUtils.getInstance(); - Geometry rqGeometry = airspaceUtils.createGeometryByCoordinate(coordBuffers); + List> bufferCoordList = null; + if (type == PlanSelectType.LIST) { + List coordBuffers = areaUtils.createCircle(new Coordinate(area.getLon(), area.getLat()), area.getBufferZone()); - AirspaceUtils.FeatureInfo targetfeatureInfo = new AirspaceUtils.FeatureInfo(null, null, 0.0, area.getFltElev(), rqGeometry); - boolean isDuplicatedAirspace = airspaceUtils.isDuplicatedAirspace(targetfeatureInfo, AirspaceUtils.AirspaceType.GIMPO); - if (!isDuplicatedAirspace) { - continue; + if ("GIMPO".equalsIgnoreCase(rq.getSelectZone())) { + AirspaceUtils airspaceUtils = AirspaceUtils.getInstance(); + Geometry rqGeometry = airspaceUtils.createGeometryByCoordinate(coordBuffers); + + AirspaceUtils.FeatureInfo targetfeatureInfo = new AirspaceUtils.FeatureInfo(null, null, 0.0, area.getFltElev(), rqGeometry); + boolean isDuplicatedAirspace = airspaceUtils.isDuplicatedAirspace(targetfeatureInfo, AirspaceUtils.AirspaceType.GIMPO); + if (!isDuplicatedAirspace) { + continue; + } } + bufferCoordList = coordBuffers.stream().map(coord -> Map.of("lat", coord.y, "lon", coord.x)).collect(Collectors.toList()); } - - List> bufferCoordList = coordBuffers.stream().map(coord -> Map.of("lat", coord.y, "lon", coord.x)).collect(Collectors.toList()); - // TODO 추후 Utils 생성 // 주소 분할 - 임시 순서대로 split String addr = area.getAddr(); @@ -231,7 +241,7 @@ public class BasDosService { if (planRq.getFltElev() != null || planRq.getBufferZone() != null) { Optional optionalResult = dosFltPlanResultRepository.findById(area.getPlanAreaSno()); DosFltPlanResult dosFltPlanResult = null; - if(optionalResult.isPresent()) { + if (optionalResult.isPresent()) { dosFltPlanResult = optionalResult.get(); } // Circle만 체크 @@ -244,7 +254,7 @@ public class BasDosService { DosApprovalResult approvalResult = dronOneStopService.getApprovalResult(bufferZone, fltElev, centerPoint); // 재검증 수정 - if(dosFltPlanResult != null){ + if (dosFltPlanResult != null) { dosFltPlanResult.setApprovalCd(approvalResult.getApprovalCd().getCode()); dosFltPlanResult.setFltElevMax(approvalResult.getFltElevMax()); } else { @@ -286,4 +296,190 @@ public class BasDosService { }).collect(Collectors.toList()); } + public void createExcel(List rs) { + + try (XSSFWorkbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("sheet1"); + int cellWidth53 = getCellWidthByPixel(53); + int cellWidth70 = getCellWidthByPixel(70); + int cellWidth73 = getCellWidthByPixel(73); + int cellWidth82 = getCellWidthByPixel(82); + int cellWidth113 = getCellWidthByPixel(113); + int cellWidth145 = getCellWidthByPixel(145); + int cellWidth174 = getCellWidthByPixel(174); + sheet.setColumnWidth(0, cellWidth53); + sheet.setColumnWidth(1, cellWidth53); + sheet.setColumnWidth(2, cellWidth53); + sheet.setColumnWidth(3, cellWidth53); + sheet.setColumnWidth(4, cellWidth53); + sheet.setColumnWidth(5, cellWidth113); + sheet.setColumnWidth(6, cellWidth53); + sheet.setColumnWidth(7, cellWidth70); + sheet.setColumnWidth(8, cellWidth174); + sheet.setColumnWidth(9, cellWidth73); + sheet.setColumnWidth(10, cellWidth82); + sheet.setColumnWidth(11, cellWidth145); + + Row row = null; + Cell cell = null; + int rowNum = 1; + + // Header + XSSFCellStyle headerStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.HEADER_CELL); + XSSFCellStyle firstHeaderStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.HEADER_CELL); + firstHeaderStyle.setBorderLeft(BorderStyle.MEDIUM); + XSSFCellStyle lastHeaderStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.HEADER_CELL); + lastHeaderStyle.setBorderRight(BorderStyle.MEDIUM); + + row = sheet.createRow(rowNum++); + row.setHeightInPoints(33); // 높이를 포인트 단위로 설정 + + + cell = row.createCell(0); + cell.setCellValue("월"); + cell.setCellStyle(firstHeaderStyle); + cell = row.createCell(11); + cell.setCellValue("비고"); + cell.setCellStyle(lastHeaderStyle); + + + cell = row.createCell(1); + cell.setCellValue("일"); + cell.setCellStyle(headerStyle); + cell = row.createCell(2); + cell.setCellValue("신청자"); + cell.setCellStyle(headerStyle); + cell = row.createCell(3); + cell.setCellValue("행정구역1"); + cell.setCellStyle(headerStyle); + cell = row.createCell(4); + cell.setCellValue("행정구역2"); + cell.setCellStyle(headerStyle); + cell = row.createCell(5); + cell.setCellValue("상세주소"); + cell.setCellStyle(headerStyle); + cell = row.createCell(6); + cell.setCellValue("비행반경"); + cell.setCellStyle(headerStyle); + cell = row.createCell(7); + cell.setCellValue("최고비행\n해발고도(m)"); + cell.setCellStyle(headerStyle); + cell = row.createCell(8); + cell.setCellValue("세부사항"); + cell.setCellStyle(headerStyle); + cell = row.createCell(9); + cell.setCellValue("비행목적"); + cell.setCellStyle(headerStyle); + cell = row.createCell(10); + cell.setCellValue("긴급구조기관"); + cell.setCellStyle(headerStyle); + + XSSFCellStyle bodyStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.BODY_CELL); + XSSFCellStyle firstBodyStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.BODY_CELL); + firstHeaderStyle.setBorderLeft(BorderStyle.MEDIUM); + XSSFCellStyle lastBodyStyle = getDefaultCellStyle(workbook, DosExcelCellStyleType.BODY_CELL); + lastBodyStyle.setBorderRight(BorderStyle.MEDIUM); + XSSFCellStyle bodyStyleFontRed = getDefaultCellStyle(workbook, DosExcelCellStyleType.BODY_CELL); + bodyStyleFontRed.getFont().setColor(IndexedColors.RED.getIndex()); + + // Body + for (BasDosPlanRs plan : rs) { + for (BasDosPlanAreaRs area : plan.getAreaList()) { + ApprovalCd approvalCd = ApprovalCd.fromCode(area.getApprovalCd()); + + row = sheet.createRow(rowNum++); + cell = row.createCell(0); + cell.setCellValue(plan.getApplyDtMonth() != null ? plan.getApplyDtMonth() + "월" : ""); + cell.setCellStyle(firstBodyStyle); + cell = row.createCell(1); + cell.setCellValue(plan.getApplyDtDay() != null ? plan.getApplyDtDay() + "일" : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(2); + cell.setCellValue(plan.getApplyNm() != null ? plan.getApplyNm() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(3); + cell.setCellValue(area.getAddr1() != null ? area.getAddr1() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(4); + cell.setCellValue(area.getAddr2() != null ? area.getAddr2() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(5); + cell.setCellValue(area.getAddr3() != null ? area.getAddr3() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(6); + cell.setCellValue(area.getFltElev() != null ? area.getFltElev() + "" : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(7); + cell.setCellValue(area.getFltElevMax() != null ? area.getFltElevMax() + "" : "불가"); + if (approvalCd == ApprovalCd.UNAPPROVED) { + cell.setCellStyle(bodyStyleFontRed); + } else { + cell.setCellStyle(bodyStyle); + } + + cell = row.createCell(8); + cell.setCellValue(area.getDtl() != null ? area.getDtl() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(9); + cell.setCellValue(plan.getPurpose() != null ? plan.getPurpose() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(10); + cell.setCellValue(area.getEra() != null ? area.getEra() : ""); + cell.setCellStyle(bodyStyle); + cell = row.createCell(11); + cell.setCellValue(area.getRm() != null ? area.getRm() : ""); + cell.setCellStyle(lastBodyStyle); + } + } + + HttpServletResponse response = HttpUtils.getResponse(); + // 컨텐츠 타입과 파일명 지정 + response.setContentType("ms-vnd/excel"); + response.setHeader("Content-Disposition", "attachment;filename=example.xlsx"); + + // Excel File Output + workbook.write(response.getOutputStream()); + } catch (IOException e) { + throw new CustomException(ErrorCode.FILE_CREATE_FAIL); + } + } + + private XSSFCellStyle getDefaultCellStyle(XSSFWorkbook workbook, DosExcelCellStyleType type) { + XSSFCellStyle style = workbook.createCellStyle(); + + // font + XSSFFont font = workbook.createFont(); + font.setFontName("맑은 고딕"); + font.setFontHeightInPoints((short) 10); + if (type == DosExcelCellStyleType.HEADER_CELL) { + font.setBold(true); + } + style.setFont(font); + + // border + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + + // color + if (type == DosExcelCellStyleType.HEADER_CELL) { + XSSFColor myColor = new XSSFColor(new java.awt.Color(233, 224, 208), null); // 예: 빨간색 + style.setFillForegroundColor(myColor); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setBorderTop(BorderStyle.MEDIUM); + style.setBorderBottom(BorderStyle.MEDIUM); + } + + // alignment + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + + return style; + } + + private int getCellWidthByPixel(int pixelWidth) { +// return (pixelWidth * 256) / 8; + return (int) ((pixelWidth / 6.0) * 256); + } } diff --git a/pav-server/src/main/java/com/palnet/comn/code/ErrorCode.java b/pav-server/src/main/java/com/palnet/comn/code/ErrorCode.java index 092cdf67..4580b9d8 100644 --- a/pav-server/src/main/java/com/palnet/comn/code/ErrorCode.java +++ b/pav-server/src/main/java/com/palnet/comn/code/ErrorCode.java @@ -19,6 +19,7 @@ public enum ErrorCode { PLAN_LAANC_NOT_VALID("FT502", "LAANC를 통과하지 못한 비행계획서입니다."), EXTERNAL_API_ERROR("EA500", "외부서버 호출에 실패하였습니다."), AUTH_NAUTHORIZED("AU001", "권한이 없습니다."), + FILE_CREATE_FAIL("FI001", "파일을 생성에 실패하였습니다."), LIMIT_CALL("LT001", "호출횟수가 초과되었습니다."), diff --git a/pav-server/src/main/java/com/palnet/comn/utils/HttpUtils.java b/pav-server/src/main/java/com/palnet/comn/utils/HttpUtils.java index e7e1a45c..8ac430bb 100644 --- a/pav-server/src/main/java/com/palnet/comn/utils/HttpUtils.java +++ b/pav-server/src/main/java/com/palnet/comn/utils/HttpUtils.java @@ -235,6 +235,11 @@ public class HttpUtils { ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return sra.getRequest(); } + + public static HttpServletResponse getResponse() { + ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return sra.getResponse(); + } /** * 클라이언트 IP 를 가져온다