diff --git a/pav-server/build.gradle b/pav-server/build.gradle index 101ec16c..fb2a0292 100644 --- a/pav-server/build.gradle +++ b/pav-server/build.gradle @@ -43,6 +43,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-cache' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-jdbc' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + // thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + + // pdf create + implementation 'com.itextpdf:itextpdf:5.5.13' + implementation 'com.itextpdf.tool:xmlworker:5.5.13' + implementation 'com.itextpdf:pdfa:7.2.3' // db runtimeOnly 'mysql:mysql-connector-java' @@ -95,6 +104,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" + } tasks.named('test') { diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/flight/controller/BasFlightController.java b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/controller/BasFlightController.java index 1440779c..ccc71b26 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/flight/controller/BasFlightController.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/controller/BasFlightController.java @@ -25,13 +25,17 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.palnet.biz.api.bas.flight.service.BasFlightService; +import com.palnet.biz.api.bas.flight.template.service.TemplateService; +import com.palnet.biz.api.bas.flight.template.vo.LaancPdfVO; import com.palnet.biz.api.comn.model.ComnPagingRs; import com.palnet.biz.api.comn.response.BasicResponse; import com.palnet.biz.api.comn.response.ErrorResponse; import com.palnet.biz.api.comn.response.SuccessResponse; +import com.palnet.biz.jpa.entity.ComFileBas; import com.palnet.biz.scheduler.ctr.service.CtrTrnsLctnService; import com.palnet.comn.exception.CustomException; import com.palnet.comn.utils.AreaUtils; +import com.palnet.comn.utils.PdfUtils; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; @@ -48,13 +52,19 @@ public class BasFlightController { private final Environment env; private final CtrTrnsLctnService ctrTrnsLctnService; private final AreaUtils utils; + private final TemplateService templateService; + private final PdfUtils pdfUtils; + + @Autowired - public BasFlightController(BasFlightService basFlightService, Environment env, CtrTrnsLctnService ctrTrnsLctnService, AreaUtils AreaUtils) { + public BasFlightController(BasFlightService basFlightService, Environment env, CtrTrnsLctnService ctrTrnsLctnService, AreaUtils AreaUtils, TemplateService templateService, PdfUtils pdfUtils) { this.basFlightService = basFlightService; this.env = env; this.ctrTrnsLctnService = ctrTrnsLctnService; this.utils = AreaUtils; + this.templateService = templateService; + this.pdfUtils = pdfUtils; } @GetMapping("/area") @@ -383,4 +393,18 @@ public class BasFlightController { } } + @PostMapping("/laanc-pdf/create") + @Tag(name = "비행계획서", description = "비행계획서 관련 API") + @ApiOperation(value = "Laanc 공문 PDF생성") + public void createLancePDF(@RequestBody LaancPdfVO vo) { + + // PDF 생성 및 저장 + ComFileBas comFileBas = templateService.makeLaancPdf(vo); + + // DB 저장 + templateService.save(comFileBas); + } + + + } diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/service/TemplateService.java b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/service/TemplateService.java new file mode 100644 index 00000000..09b0a7b1 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/service/TemplateService.java @@ -0,0 +1,109 @@ +package com.palnet.biz.api.bas.flight.template.service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.Map; +import java.util.Map.Entry; + +import javax.transaction.Transactional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.palnet.biz.api.acnt.jwt.utils.JwtTokenUtil; +import com.palnet.biz.api.bas.flight.template.vo.LaancPdfVO; +import com.palnet.biz.jpa.entity.ComFileBas; +import com.palnet.biz.jpa.repository.com.ComFileBasRepository; +import com.palnet.comn.utils.InstantUtils; +import com.palnet.comn.utils.PdfUtils; + +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class TemplateService { + + @Autowired + private PdfUtils pdfUtils; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private ComFileBasRepository comFileBasRepository; + + @Value("${base-url}") + private String BASE_PATH; + + private final String FILE_EXTENSION = ".pdf"; + + /** + * 파일명 만들기, 앞에 기본으로 금일 날짜있음 + * [예시] 20230102_[추가파라미터] + * @param etcName + * @return + */ + private String getLaancSaveName(String ...etcName){ + + String date = InstantUtils.toDateString(Instant.now()).replace("-", ""); + + StringBuilder result = new StringBuilder(); + result.append(date); + + for(String name : etcName){ + result.append("-") + .append(name); + } + + result.append(this.FILE_EXTENSION); + + return result.toString(); + } + + /** + * PDF 생성 + * @param vo + * @return + */ + public ComFileBas makeLaancPdf(LaancPdfVO vo){ + + String airUser = vo.getUserName(); + String etc = String.valueOf(System.currentTimeMillis()); // 동명이인 방지 + String fileName = getLaancSaveName(airUser, etc); + + String htmlContent = pdfUtils.getHtmlToString(vo); + ComFileBas comFileBas = pdfUtils.generatePDF(htmlContent, fileName); + + String userId = jwtTokenUtil.getUserIdByToken(); + comFileBas.setCreateUserId(userId); + + return comFileBas; + } + + + + /** + * DB 인서트, 최신데이터 가져온 후 Group NO 1추가함 + * @param comFileBas + */ + @Transactional + public void save(ComFileBas comFileBas){ + + // DB Insert + ComFileBas lastComFileBas = comFileBasRepository.findFirstByOrderByFileSnoDesc(); + int fileGroupNo = (lastComFileBas == null) ? 1 : lastComFileBas.getFileGroupNo() + 1; + + comFileBas.setFileGroupNo(fileGroupNo); + + comFileBasRepository.save(comFileBas); + } + +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/LaancPdfVO.java b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/LaancPdfVO.java new file mode 100644 index 00000000..5ac4fed0 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/LaancPdfVO.java @@ -0,0 +1,54 @@ +package com.palnet.biz.api.bas.flight.template.vo; + +import java.lang.reflect.Field; +import java.util.HashMap; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +public class LaancPdfVO extends PdfBaseVO{ + + // TODO : 아직 파라미터가 정해지지 않음 + private String userName; // 조종사 이름 + + private int userAge; // 조종사 나이 + + private String userGender; // 조종사 성별 + + + public LaancPdfVO (){ + init(); + } + + public HashMap getParam(){ + + Field[] fields = getClass().getDeclaredFields(); + + HashMap result = new HashMap<>(); + + try { + for(Field field : fields){ + Object value = field.get(this); + String key = field.getName(); + + result.put(key, value); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e){ + e.printStackTrace(); + } + + return result; + } + + @Override + public void init() { + super.setTemplate("official_document"); + } + +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/PdfBaseVO.java b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/PdfBaseVO.java new file mode 100644 index 00000000..624c17bc --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/flight/template/vo/PdfBaseVO.java @@ -0,0 +1,16 @@ +package com.palnet.biz.api.bas.flight.template.vo; + +import java.util.Map; + +import lombok.Data; + +@Data +public abstract class PdfBaseVO { + + private String template; + + public abstract void init(); + + public abstract Map getParam(); + +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComFileBas.java b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComFileBas.java new file mode 100644 index 00000000..146f7bf9 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComFileBas.java @@ -0,0 +1,70 @@ +package com.palnet.biz.jpa.entity; + +import lombok.Data; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.Instant; + + +/** + * The persistent class for the COM_IDNTF_BAS database table. + * + */ +@Data +@Entity +@Table(name="COM_FILE_BAS") +@NamedQuery(name="ComFileBas.findAll", query="SELECT c FROM ComFileBas c") +public class ComFileBas implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @Column(name="FILE_SNO") + private int fileSno; + + @Column(name="FILE_GROUP_NO") + private int fileGroupNo; + + @Column(name="FILE_SAVE_NM") + private String fileSaveNm; + + @Column(name="FILE_ORI_NM") + private String fileOriNm; + + @Column(name = "FILE_PATH") + private String filePath; + + @Column(name="FILE_EXT") + private String fileExt; + +// @Temporal(TemporalType.TIMESTAMP) + @Column(name="FILE_SIZE") + private String fileSize; + +// @Temporal(TemporalType.TIMESTAMP) + @Column(name="DEL_YN", columnDefinition = "TIMESTAMP") + private String delYn = "N"; + + @Column(name="DEL_USER_ID") + private String delUserId; + + @Column(name="DEL_DT") + private Instant delDt; + + @Column(name="CREATE_USER_ID") + private String createUserId; + + @Column(name="CREATE_DT" , columnDefinition = "TIMESTAMP") + private Instant createDt; + +} + + + + + + + + + + diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java new file mode 100644 index 00000000..db35bd93 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java @@ -0,0 +1,12 @@ +package com.palnet.biz.jpa.repository.com; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.palnet.biz.jpa.entity.ComFileBas; + +@Repository +public interface ComFileBasRepository extends JpaRepository{ + + public ComFileBas findFirstByOrderByFileSnoDesc(); +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComIdntBasRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComIdntBasRepository.java index f705bb79..29a12d2c 100644 --- a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComIdntBasRepository.java +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComIdntBasRepository.java @@ -1,7 +1,6 @@ package com.palnet.biz.jpa.repository.com; import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; diff --git a/pav-server/src/main/java/com/palnet/comn/utils/PdfUtils.java b/pav-server/src/main/java/com/palnet/comn/utils/PdfUtils.java new file mode 100644 index 00000000..9217365f --- /dev/null +++ b/pav-server/src/main/java/com/palnet/comn/utils/PdfUtils.java @@ -0,0 +1,169 @@ +package com.palnet.comn.utils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import com.itextpdf.text.Document; +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.pdf.PdfWriter; +import com.itextpdf.tool.xml.XMLWorker; +import com.itextpdf.tool.xml.XMLWorkerFontProvider; +import com.itextpdf.tool.xml.XMLWorkerHelper; +import com.itextpdf.tool.xml.css.CssFile; +import com.itextpdf.tool.xml.css.StyleAttrCSSResolver; +import com.itextpdf.tool.xml.html.CssAppliers; +import com.itextpdf.tool.xml.html.CssAppliersImpl; +import com.itextpdf.tool.xml.html.Tags; +import com.itextpdf.tool.xml.parser.XMLParser; +import com.itextpdf.tool.xml.pipeline.css.CSSResolver; +import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline; +import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline; +import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline; +import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext; +import com.palnet.biz.api.acnt.jwt.utils.JwtTokenUtil; +import com.palnet.biz.api.bas.flight.template.vo.PdfBaseVO; +import com.palnet.biz.jpa.entity.ComFileBas; + +@Component +public class PdfUtils { + + + @Autowired + private TemplateEngine templateEngine; + + @Value("${base-url}") + private String BASE_PATH; + + private final String FILE_EXTENSION = ".pdf"; + + + /** + * Thymeleaf HTML 파일을 데이터 바인딩하여 String으로 변환 + * @param + * @param pdfDto + * @return + */ + public String getHtmlToString(T pdfDto){ + + Map param = pdfDto.getParam(); + + // Thymeleaf 방식 html에 입힐 데이터 바인딩 + Context context = new Context(); + + for(Entry entry : param.entrySet()){ + String key = entry.getKey(); + + context.setVariable(key, param.get(key)); + } + + // 앞 뒤 prefix, suffix는 yml에 정의해놓음 + // html에 바인딩할 데이터 넣고 파싱이후 String형식으로 뽑아옴 + String htmlContent = templateEngine.process(pdfDto.getTemplate(), context); + + return htmlContent; + } + + /** + * HTML 태그로 이루어진 String값을 PDF로 변환 + * @param htmlContent + * @param fileName + * @return + */ + public ComFileBas generatePDF(String htmlContent, String fileName){ + + // PDF 용지 설정하기 + Document pdfDocument = new Document(); + ComFileBas result = new ComFileBas(); + + String pdfPath = new StringBuilder() + .append(this.BASE_PATH) + .append(InstantUtils.toDateString(Instant.now()).replace("-", "")) + .append("/").toString(); + + try { + + // 폴더 생성 + new File(pdfPath).mkdirs(); + + // File 저장 기본경로는 main 아래로 기본으로 잡힘 + PdfWriter writer = PdfWriter.getInstance(pdfDocument, new FileOutputStream(pdfPath + fileName)); + writer.setInitialLeading(12.5f); + + pdfDocument.open(); + + /* + CSS 설정 + TODO : CSS 파일이 따로 있을시 임포트해주는 방법, CSS 파일 적용은 필수는 아니지만, 글꼴 적용은 꼭해야만 함 + jar변환 시 CSS파일을 못 찾을수도 있어서 InputStream 사용 + CSS의 색깔은 키워드로 표기 X, #e73a3a 등 16진법으로 표기 해야함 + */ + InputStream cssStream = getClass().getClassLoader().getResourceAsStream("static/css/pdf.css"); + CssFile cssFile = XMLWorkerHelper.getCSS(cssStream); + CSSResolver cssResolver = new StyleAttrCSSResolver(); + cssResolver.addCss(cssFile); + + // Font 설정 + XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS); + fontProvider.register("static/font/NanumGothic.ttf", "NanumGothic"); + CssAppliers cssAppliers = new CssAppliersImpl(fontProvider); + + // XML Worker에 넣을 PipeLine객체 생성, CssResolverPipeline 이 객체를 최종적으로 넣어야 Font,Css가 적용됨 + HtmlPipelineContext htmlPipelineContext = new HtmlPipelineContext(cssAppliers); + htmlPipelineContext.setTagFactory(Tags.getHtmlTagProcessorFactory()); + + PdfWriterPipeline pdfWriterPipeline = new PdfWriterPipeline(pdfDocument, writer); + HtmlPipeline htmlPipeline = new HtmlPipeline(htmlPipelineContext, pdfWriterPipeline); + CssResolverPipeline cssResolverPipeline = new CssResolverPipeline(cssResolver, htmlPipeline); + + StringReader stringReader = new StringReader(htmlContent); + + XMLWorker xmlWorker = new XMLWorker(cssResolverPipeline, true); + XMLParser xmlParser = new XMLParser(xmlWorker, Charset.forName("UTF-8")); + + xmlParser.parse(stringReader); + + } catch (IOException e) { + e.printStackTrace(); + } catch (DocumentException e){ + e.printStackTrace(); + } catch (Exception e){ + e.printStackTrace(); + } + finally{ + pdfDocument.close(); + } + + result.setFileGroupNo(0); + result.setFilePath(pdfPath); + result.setFileExt(this.FILE_EXTENSION); + result.setCreateDt(Instant.now()); + result.setFileSaveNm(fileName); + result.setFileOriNm(fileName); + + try { + String filePath = result.getFileOriNm(); + long fileSize = Files.size(Paths.get(pdfPath + filePath)) / 1024; + + if(fileSize >= 0) result.setFileSize(String.valueOf(fileSize)); + } catch (IOException e) { + e.printStackTrace(); + } + + return result; + } +} diff --git a/pav-server/src/main/resources/application.yml b/pav-server/src/main/resources/application.yml index cbf486e8..691dc309 100644 --- a/pav-server/src/main/resources/application.yml +++ b/pav-server/src/main/resources/application.yml @@ -30,6 +30,12 @@ spring: ddl-auto: none properties: hibernate: + thymeleaf: + mode: HTML5 + cache: false + encoding: UTF-8 + prefix: classpath:/templates/ + suffix: .html server: port: 8080 @@ -57,6 +63,8 @@ api: client-secret-key: Q4K4OtUYol search-url : https://openapi.naver.com/v1/search/local.json +base-url: + --- spring: @@ -102,6 +110,7 @@ api: client-secret-key: Q4K4OtUYol search-url : https://openapi.naver.com/v1/search/local.json +base-url: /data/server/files --- spring: @@ -156,6 +165,7 @@ api: client-secret-key: Q4K4OtUYol search-url : https://openapi.naver.com/v1/search/local.json +base-url: /data/server/files --- spring: @@ -204,4 +214,6 @@ api: naver: client-id: WGEct3bJhQC0pyMsP_GK client-secret-key: Q4K4OtUYol - search-url : https://openapi.naver.com/v1/search/local.json \ No newline at end of file + search-url : https://openapi.naver.com/v1/search/local.json + +base-url: /data/server/files \ No newline at end of file diff --git a/pav-server/src/main/resources/static/css/pdf.css b/pav-server/src/main/resources/static/css/pdf.css new file mode 100644 index 00000000..2567b876 --- /dev/null +++ b/pav-server/src/main/resources/static/css/pdf.css @@ -0,0 +1,12 @@ + +body{ + font-family: NanumGothic; +} + +p{ + background-color: #e73a3a; +} + +div { + background-color: #e73a3a; +} \ No newline at end of file diff --git a/pav-server/src/main/resources/static/font/NanumGothic.ttf b/pav-server/src/main/resources/static/font/NanumGothic.ttf new file mode 100644 index 00000000..75d010a5 Binary files /dev/null and b/pav-server/src/main/resources/static/font/NanumGothic.ttf differ diff --git a/pav-server/src/main/resources/templates/official_document.html b/pav-server/src/main/resources/templates/official_document.html new file mode 100644 index 00000000..ed34dda2 --- /dev/null +++ b/pav-server/src/main/resources/templates/official_document.html @@ -0,0 +1,12 @@ + + + + + + +

Thymeleaf Tutorials

+

이름 : [[${userName}]]

+

나이 : [[${userAge}]]

+

성별 : [[${userGender}]]

+ +