diff --git a/server/.gradle/7.6.1/checksums/checksums.lock b/server/.gradle/7.6.1/checksums/checksums.lock index dec75957..52b0bdcf 100644 Binary files a/server/.gradle/7.6.1/checksums/checksums.lock and b/server/.gradle/7.6.1/checksums/checksums.lock differ diff --git a/server/.gradle/7.6.1/executionHistory/executionHistory.bin b/server/.gradle/7.6.1/executionHistory/executionHistory.bin index b8dd684f..3e3decae 100644 Binary files a/server/.gradle/7.6.1/executionHistory/executionHistory.bin and b/server/.gradle/7.6.1/executionHistory/executionHistory.bin differ diff --git a/server/.gradle/7.6.1/executionHistory/executionHistory.lock b/server/.gradle/7.6.1/executionHistory/executionHistory.lock index 5d432a7f..c0221a4c 100644 Binary files a/server/.gradle/7.6.1/executionHistory/executionHistory.lock and b/server/.gradle/7.6.1/executionHistory/executionHistory.lock differ diff --git a/server/.gradle/7.6.1/fileHashes/fileHashes.bin b/server/.gradle/7.6.1/fileHashes/fileHashes.bin index 01c4630b..557973bf 100644 Binary files a/server/.gradle/7.6.1/fileHashes/fileHashes.bin and b/server/.gradle/7.6.1/fileHashes/fileHashes.bin differ diff --git a/server/.gradle/7.6.1/fileHashes/fileHashes.lock b/server/.gradle/7.6.1/fileHashes/fileHashes.lock index 8aad27b4..2f7c38ff 100644 Binary files a/server/.gradle/7.6.1/fileHashes/fileHashes.lock and b/server/.gradle/7.6.1/fileHashes/fileHashes.lock differ diff --git a/server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin b/server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin index d1460727..b6201805 100644 Binary files a/server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin and b/server/.gradle/7.6.1/fileHashes/resourceHashesCache.bin differ diff --git a/server/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/server/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 25a65f96..88c84b1a 100644 Binary files a/server/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/server/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterPdfSendEventHandler.java b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterPdfSendEventHandler.java new file mode 100644 index 00000000..1f696efe --- /dev/null +++ b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterPdfSendEventHandler.java @@ -0,0 +1,30 @@ +package com.econovation.recruit.api.applicant.handler; + +import com.econovation.recruitdomain.domains.applicant.event.ApplicantRegisterEvent; +import com.econovation.recruitdomain.domains.applicant.helper.ApplicantPdfHelper; +import javax.mail.MessagingException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +@Slf4j +public class ApplicantRegisterPdfSendEventHandler { + private final ApplicantPdfHelper applicantPdfHelper; + + @Async + @TransactionalEventListener( + classes = ApplicantRegisterEvent.class, + phase = TransactionPhase.AFTER_COMMIT) + @Transactional + public void handle(ApplicantRegisterEvent applicantRegistEvent) throws MessagingException { + // TODO Log 제거 예정 + log.info("applicantRegistEvent: " + applicantRegistEvent.toString()); + applicantPdfHelper.sendToInterviewers(applicantRegistEvent.getAnswers()); + } +} diff --git a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/slack/handler/ApplicantRegisterEventHandler.java b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterSlackEventHandler.java similarity index 80% rename from server/Recruit-Api/src/main/java/com/econovation/recruit/api/slack/handler/ApplicantRegisterEventHandler.java rename to server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterSlackEventHandler.java index 027e8d30..54467782 100644 --- a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/slack/handler/ApplicantRegisterEventHandler.java +++ b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/handler/ApplicantRegisterSlackEventHandler.java @@ -1,19 +1,21 @@ -package com.econovation.recruit.api.slack.handler; +package com.econovation.recruit.api.applicant.handler; import com.econovation.recruitdomain.domains.applicant.event.ApplicantRegisterEvent; import com.econovation.recruitinfrastructure.slack.SlackMessageProvider; import com.econovation.recruitinfrastructure.slack.config.SlackProperties; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @Component @RequiredArgsConstructor @Slf4j -public class ApplicantRegisterEventHandler { +public class ApplicantRegisterSlackEventHandler { private final SlackMessageProvider slackMessageProvider; private final SlackProperties slackProperties; @@ -21,16 +23,18 @@ public class ApplicantRegisterEventHandler { @TransactionalEventListener( classes = ApplicantRegisterEvent.class, phase = TransactionPhase.AFTER_COMMIT) + @Transactional public void handle(ApplicantRegisterEvent applicantRegistEvent) { String message = generateApplicantRegisterMessage(applicantRegistEvent); slackMessageProvider.sendMessage(slackProperties.getChannel(), message); } private String generateApplicantRegisterMessage(ApplicantRegisterEvent applicantRegistEvent) { + Map answers = applicantRegistEvent.getAnswers(); return String.format( ":clapping: 지원자가 등록되었습니다.:clapping:\n" + ":chikorita: 지원자 이름: %s\n" + ":chikorita:희망 분야: %s", - applicantRegistEvent.getUserName(), applicantRegistEvent.getHopeField()); + answers.get("name"), answers.get("field")); } } diff --git a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/AnswerService.java b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/AnswerService.java index c3362eaf..bf9da6b9 100644 --- a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/AnswerService.java +++ b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/AnswerService.java @@ -80,7 +80,7 @@ private List> splitByAnswersInApplicantId( .collect(Collectors.toList()); } - private List> splitByAnswersInApplicantId(List answers) { + public List> splitByAnswersInApplicantId(List answers) { return answers.stream() .collect( Collectors.groupingBy( diff --git a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/ApplicantService.java b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/ApplicantService.java index 584dff2a..0afd1c8f 100644 --- a/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/ApplicantService.java +++ b/server/Recruit-Api/src/main/java/com/econovation/recruit/api/applicant/service/ApplicantService.java @@ -3,29 +3,29 @@ import com.econovation.recruit.api.applicant.usecase.ApplicantRegisterUseCase; import com.econovation.recruitcommon.utils.Result; import com.econovation.recruitdomain.common.aop.domainEvent.Events; -import com.econovation.recruitdomain.common.events.applicant.SubmitApplicantEvent; import com.econovation.recruitdomain.domain.applicant.Applicant; import com.econovation.recruitdomain.domains.applicant.adaptor.AnswerAdaptor; -import com.econovation.recruitdomain.domains.applicant.adaptor.ApplicantAdaptor; import com.econovation.recruitdomain.domains.applicant.adaptor.QuestionAdaptor; import com.econovation.recruitdomain.domains.applicant.domain.Answer; import com.econovation.recruitdomain.domains.applicant.domain.Question; import com.econovation.recruitdomain.domains.applicant.dto.BlockRequestDto; +import com.econovation.recruitdomain.domains.applicant.event.ApplicantRegisterEvent; import com.econovation.recruitdomain.domains.applicant.exception.QuestionNotFoundException; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class ApplicantService implements ApplicantRegisterUseCase { private final QuestionAdaptor questionAdaptor; - private final ApplicantAdaptor applicantAdaptor; private final AnswerAdaptor answerAdaptor; + private final AnswerService answerService; @Transactional @Override @@ -64,13 +64,24 @@ public UUID execute(List blocks) { .collect(Collectors.toList()); // Result 를 save 하게 된다. answerAdaptor.saveAll(results); - Events.raise( - SubmitApplicantEvent.of(applicantId, convertToSubmitApplicantEventTitle(results))); + Events.raise(ApplicantRegisterEvent.from(splitByAnswers(results))); + // SubmitApplicantEvent.of(applicantId, + // convertToSubmitApplicantEventTitle(results))); // TODO: 추가될지 말지 결정해야 함 // applicantRegister(results); return applicantId; } + public Map splitByAnswers(List answers) { + return answers.stream() + .collect( + Collectors.toMap( + answer -> answer.getQuestion().getName(), + Answer::getAnswer, + (existing, replacement) -> existing, + HashMap::new)); + } + private String convertToSubmitApplicantEventTitle(List results) { final String HOPE_FIELD_QUESTION_KEY = "hopeField"; final String NAME_QUESTION_KEY = "name"; diff --git a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/domain/Answer.java b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/domain/Answer.java index 5c00a481..a21b9b09 100644 --- a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/domain/Answer.java +++ b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/domain/Answer.java @@ -35,9 +35,6 @@ public class Answer { @Column(name = "answer") private String answer; - // @Column(name = "applicant_id", nullable = false - // , columnDefinition = "BINARY(16)") - // private UUID applicantId; @Column(name = "applicant_id", nullable = false) private String applicantId; diff --git a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/ApplicantPdfDto.java b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/ApplicantPdfDto.java new file mode 100644 index 00000000..9ff1f0c7 --- /dev/null +++ b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/ApplicantPdfDto.java @@ -0,0 +1,36 @@ +package com.econovation.recruitdomain.domains.applicant.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ApplicantPdfDto { + private String field; + private String field1; + private String field2; + private String name; + private String contacted; + private String classOf; + private String grade; + private String semester; + private String major; + private String doubleMajor; + private String minor; + private String activity; + private String channel; + private String reason; + private String future; + private String experience; + private String experienceTextarea; + private String goal; + private String deep; + private String collaboration; + private String portfolio; + private String fileUrl; + private String fileUrlforPlanner; + private String email; + private String check; + private String personalInformationAgree; + private String personalInformationAgreeForPortfolio; +} diff --git a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/BlockRequestDto.java b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/BlockRequestDto.java index 383052d1..63666172 100644 --- a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/BlockRequestDto.java +++ b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/dto/BlockRequestDto.java @@ -6,7 +6,6 @@ @Getter @RequiredArgsConstructor public class BlockRequestDto { - // private String type; private String name; private String answer; } diff --git a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/event/ApplicantRegisterEvent.java b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/event/ApplicantRegisterEvent.java index 591dc3fd..fca47887 100644 --- a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/event/ApplicantRegisterEvent.java +++ b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/event/ApplicantRegisterEvent.java @@ -1,6 +1,7 @@ package com.econovation.recruitdomain.domains.applicant.event; import com.econovation.recruitdomain.common.aop.domainEvent.DomainEvent; +import java.util.Map; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -9,10 +10,14 @@ @Builder @ToString public class ApplicantRegisterEvent extends DomainEvent { - private final String hopeField; - private final String userName; + private final Map answers; - public static ApplicantRegisterEvent of(String userName, String hopeField) { - return ApplicantRegisterEvent.builder().hopeField(hopeField).userName(userName).build(); + public static ApplicantRegisterEvent from(Map answers) { + return ApplicantRegisterEvent.builder().answers(answers).build(); + } + + @Override + public String toString() { + return "ApplicantRegisterEvent{" + "answers=" + answers + '}'; } } diff --git a/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/helper/ApplicantPdfHelper.java b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/helper/ApplicantPdfHelper.java new file mode 100644 index 00000000..49e29539 --- /dev/null +++ b/server/Recruit-Domain/src/main/java/com/econovation/recruitdomain/domains/applicant/helper/ApplicantPdfHelper.java @@ -0,0 +1,95 @@ +package com.econovation.recruitdomain.domains.applicant.helper; + +import com.econovation.recruitcommon.annotation.Helper; +import com.econovation.recruitdomain.domains.applicant.dto.ApplicantPdfDto; +import com.econovation.recruitinfrastructure.mail.EmailSenderService; +import com.econovation.recruitinfrastructure.pdf.PdfRender; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.istack.ByteArrayDataSource; +import java.time.LocalDateTime; +import java.util.Map; +import javax.mail.IllegalWriteException; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; + +@Helper +@RequiredArgsConstructor +public class ApplicantPdfHelper { + private final PdfRender pdfRender; + + private final ObjectMapper objectMapper; + + private final SpringTemplateEngine templateEngine; + private final EmailSenderService mailSender; + + private Context getPdfHtmlContext(ApplicantPdfDto settlementPDFDto) { + Map result = objectMapper.convertValue(settlementPDFDto, Map.class); + + Context context = new Context(null, result); + context.setVariable("now", LocalDateTime.now()); + return context; + } + + public void sendToInterviewers(Map answer) throws MessagingException { + ApplicantPdfDto applicantPdfDto = getApplicantPDFDto(answer); + try { + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8"); + + messageHelper.setFrom("ymecca730135@gmail.com"); + // messageHelper.setTo(applicantPdfDto.getEmail()); + messageHelper.setTo("ymecca12@naver.com"); + messageHelper.setCc("Econovation_Recruit_Team"); + messageHelper.setSubject("Econovation 신입모집 지원서"); + messageHelper.setText("귀역뛰 ( 귀하의 역량은 뛰어나나... )"); + String applicant = + templateEngine.process("applicant", getPdfHtmlContext(applicantPdfDto)); + messageHelper.addAttachment( + "Econovation_Recruit_Team_신입모집_지원서.pdf", + new ByteArrayDataSource( + pdfRender.generatePdfFromHtml(applicant).toByteArray(), + "application/pdf")); + mailSender.sendEmail(messageHelper.getMimeMessage()); + + } catch (Exception e) { + throw new IllegalWriteException("메일발송에 실패하였습니다."); + } + } + + private ApplicantPdfDto getApplicantPDFDto(Map result) { + return ApplicantPdfDto.builder() + .field(result.get("field")) + .field1(result.get("field1")) + .field2(result.get("field2")) + .name(result.get("name")) + .contacted(result.get("contacted")) + .classOf(result.get("classOf")) + .grade(result.get("grade")) + .semester(result.get("semester")) + .major(result.get("major")) + .doubleMajor(result.get("doubleMajor")) + .minor(result.get("minor")) + .activity(result.get("activity")) + .channel(result.get("channel")) + .reason(result.get("reason")) + .future(result.get("future")) + .experience(result.get("experience")) + .experienceTextarea(result.get("experienceTextarea")) + .goal(result.get("goal")) + .deep(result.get("deep")) + .collaboration(result.get("collaboration")) + .portfolio(result.get("portfolio")) + .fileUrl(result.get("fileUrl")) + .fileUrlforPlanner(result.get("fileUrlforPlanner")) + .email(result.get("email")) + .check(result.get("check")) + .personalInformationAgree(result.get("personalInformationAgree")) + .personalInformationAgreeForPortfolio( + result.get("personalInformationAgreeForPortfolio")) + .build(); + } +} diff --git a/server/Recruit-Domain/src/main/resources/templates/applicant.html b/server/Recruit-Domain/src/main/resources/templates/applicant.html new file mode 100644 index 00000000..eec56ccd --- /dev/null +++ b/server/Recruit-Domain/src/main/resources/templates/applicant.html @@ -0,0 +1,192 @@ + + + + + Applicant Information + + + +

Applicant Information

+
+
+ en-banner-black +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ The Econovation Developers +
+
+ yyyy년 MM월 dd일 +
+
+
이름${applicantPdfDto.name}
연락처${applicantPdfDto.contacted}
학번${applicantPdfDto.classOf}
학년${applicantPdfDto.grade}
학기${applicantPdfDto.semester}
전공${applicantPdfDto.major}
복수전공${applicantPdfDto.doubleMajor}
부전공${applicantPdfDto.minor}
활동${applicantPdfDto.activity}
지원경로${applicantPdfDto.channel}
지원동기${applicantPdfDto.reason}
지원후 포부${applicantPdfDto.future}
지원경험${applicantPdfDto.experience}
지원경험 기타${applicantPdfDto.experienceTextarea}
지원목표${applicantPdfDto.goal}
지원분야${applicantPdfDto.deep}
협업경험${applicantPdfDto.collaboration}
포트폴리오${applicantPdfDto.portfolio}
포트폴리오 파일${applicantPdfDto.fileUrl}
포트폴리오 파일(기획자용)${applicantPdfDto.fileUrlforPlanner}
이메일${applicantPdfDto.email}
지원동의${applicantPdfDto.check}
개인정보동의${applicantPdfDto.personalInformationAgree}
개인정보동의(포트폴리오)${applicantPdfDto.personalInformationAgreeForPortfolio}
+ + + + \ No newline at end of file diff --git a/server/Recruit-Infrastructure/build.gradle b/server/Recruit-Infrastructure/build.gradle index fdb943ce..aa74aef6 100644 --- a/server/Recruit-Infrastructure/build.gradle +++ b/server/Recruit-Infrastructure/build.gradle @@ -17,11 +17,14 @@ dependencies { //for email api 'org.springframework.boot:spring-boot-starter-thymeleaf' api 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' - api 'com.sun.mail:javax.mail:1.6.2' // api project(':Recruit-Domain') testImplementation 'org.springframework.boot:spring-boot-starter-thymeleaf' testImplementation "org.springframework.cloud:spring-cloud-starter-contract-stub-runner:3.1.5" testImplementation "org.springframework.cloud:spring-cloud-contract-wiremock:3.1.5" + + // pdf + api 'org.xhtmlrenderer:flying-saucer-pdf:9.1.20' + } \ No newline at end of file diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/ConfigurationInfrastructurePropertiesConfig.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/ConfigurationInfrastructurePropertiesConfig.java index 97723f18..9b57363f 100644 --- a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/ConfigurationInfrastructurePropertiesConfig.java +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/ConfigurationInfrastructurePropertiesConfig.java @@ -1,10 +1,15 @@ package com.econovation.recruitinfrastructure; +import com.econovation.recruitinfrastructure.mail.GoogleMailProperties; import com.econovation.recruitinfrastructure.slack.config.SlackProperties; import com.econovation.recruitinfrastructure.slack.config.SlackTFProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -@EnableConfigurationProperties({SlackProperties.class, SlackTFProperties.class}) +@EnableConfigurationProperties({ + SlackProperties.class, + SlackTFProperties.class, + GoogleMailProperties.class +}) @Configuration public class ConfigurationInfrastructurePropertiesConfig {} diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/EmailSenderService.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/EmailSenderService.java new file mode 100644 index 00000000..06b70835 --- /dev/null +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/EmailSenderService.java @@ -0,0 +1,29 @@ +package com.econovation.recruitinfrastructure.mail; + +import javax.mail.internet.MimeMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +@Service +public class EmailSenderService { + @Autowired private JavaMailSender javaMailSender; + @Autowired + public EmailSenderService(JavaMailSender javaMailSender) { + this.javaMailSender = javaMailSender; + } + @Async + public void sendEmail(SimpleMailMessage email) { + javaMailSender.send(email); + } + @Async + public void sendEmail(MimeMessage email) { + javaMailSender.send(email); + } + + public MimeMessage createMimeMessage() { + return javaMailSender.createMimeMessage(); + } +} diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/GoogleMailProperties.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/GoogleMailProperties.java new file mode 100644 index 00000000..d8e95bbd --- /dev/null +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/GoogleMailProperties.java @@ -0,0 +1,17 @@ +package com.econovation.recruitinfrastructure.mail; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@Getter +@AllArgsConstructor +@ConstructorBinding +@ConfigurationProperties(prefix = "spring.mail") +public class GoogleMailProperties { + private String host; + private Integer port; + private String username; + private String password; +} diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/MailConfig.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/MailConfig.java new file mode 100644 index 00000000..7bb7056d --- /dev/null +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/mail/MailConfig.java @@ -0,0 +1,23 @@ +package com.econovation.recruitinfrastructure.mail; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +@Configuration +@RequiredArgsConstructor +public class MailConfig { + private final GoogleMailProperties mailProperties; + + @Bean + public JavaMailSenderImpl mailSender() { + JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); + javaMailSender.setProtocol("smtp"); + javaMailSender.setHost(mailProperties.getHost()); + javaMailSender.setPort(mailProperties.getPort()); + javaMailSender.setUsername(mailProperties.getUsername()); + javaMailSender.setPassword(mailProperties.getPassword()); + return javaMailSender; + } +} diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/B64ImgReplacedElementFactory.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/B64ImgReplacedElementFactory.java new file mode 100644 index 00000000..dd259f3a --- /dev/null +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/B64ImgReplacedElementFactory.java @@ -0,0 +1,75 @@ +package com.econovation.recruitinfrastructure.pdf; + +import com.lowagie.text.BadElementException; +import com.lowagie.text.Image; +import java.io.IOException; +import java.util.Base64; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.xhtmlrenderer.extend.FSImage; +import org.xhtmlrenderer.extend.ReplacedElement; +import org.xhtmlrenderer.extend.ReplacedElementFactory; +import org.xhtmlrenderer.extend.UserAgentCallback; +import org.xhtmlrenderer.layout.LayoutContext; +import org.xhtmlrenderer.pdf.ITextFSImage; +import org.xhtmlrenderer.pdf.ITextImageElement; +import org.xhtmlrenderer.render.BlockBox; +import org.xhtmlrenderer.simple.extend.FormSubmissionListener; + +/** + * 이미지 가져오는 방식수정 + * https://www.tothenew.com/blog/using-data-urls-for-embedding-images-in-flying-saucer-generated-pdfs/ + */ +@Component +public class B64ImgReplacedElementFactory implements ReplacedElementFactory { + + public ReplacedElement createReplacedElement( + LayoutContext c, BlockBox box, UserAgentCallback uac, int cssWidth, int cssHeight) { + Element e = box.getElement(); + if (e == null) { + return null; + } + String nodeName = e.getNodeName(); + if (nodeName.equals("img")) { + String attribute = e.getAttribute("src"); + FSImage fsImage; + try { + fsImage = buildImage(attribute, uac); + } catch (BadElementException e1) { + fsImage = null; + } catch (IOException e1) { + fsImage = null; + } + if (fsImage != null) { + if (cssWidth != -1 || cssHeight != -1) { + fsImage.scale(cssWidth, cssHeight); + } + return new ITextImageElement(fsImage); + } + } + return null; + } + + protected FSImage buildImage(String srcAttr, UserAgentCallback uac) + throws IOException, BadElementException { + FSImage fsImage; + if (srcAttr.startsWith("data:image/")) { + String b64encoded = + srcAttr.substring( + srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length()); + + byte[] decodedBytes = Base64.getDecoder().decode(b64encoded); + fsImage = new ITextFSImage(Image.getInstance(decodedBytes)); + } else { + fsImage = uac.getImageResource(srcAttr).getImage(); + } + return fsImage; + } + + public void remove(Element e) {} + + public void reset() {} + + @Override + public void setFormSubmissionListener(FormSubmissionListener listener) {} +} diff --git a/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/PdfRender.java b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/PdfRender.java new file mode 100644 index 00000000..9d8e2bd4 --- /dev/null +++ b/server/Recruit-Infrastructure/src/main/java/com/econovation/recruitinfrastructure/pdf/PdfRender.java @@ -0,0 +1,50 @@ +package com.econovation.recruitinfrastructure.pdf; + +import com.lowagie.text.DocumentException; +import com.lowagie.text.pdf.BaseFont; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; +import org.xhtmlrenderer.layout.SharedContext; +import org.xhtmlrenderer.pdf.ITextRenderer; + +@Component +@RequiredArgsConstructor +@Slf4j +public class PdfRender { + + private final B64ImgReplacedElementFactory b64ImgReplacedElementFactory; + + public ByteArrayOutputStream generatePdfFromHtml(String html) + throws DocumentException, IOException { + String outputFolder = + System.getProperty("~/econo-recruit/applicant") + File.separator + "thymeleaf.pdf"; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + log.info(outputFolder); + ITextRenderer renderer = new ITextRenderer(); + SharedContext sharedContext = renderer.getSharedContext(); + sharedContext.setPrint(true); + sharedContext.setInteractive(false); + sharedContext.setReplacedElementFactory(b64ImgReplacedElementFactory); + sharedContext.getTextRenderer().setSmoothingThreshold(0); + + renderer.getFontResolver() + .addFont( + new ClassPathResource("/layouts/NanumBarunGothic.ttf").getURL().toString(), + BaseFont.IDENTITY_H, + BaseFont.EMBEDDED); + renderer.setDocumentFromString(html); + renderer.layout(); + + renderer.createPDF(outputStream); + + // save to outputFolder + + outputStream.close(); + return outputStream; + } +} diff --git a/server/Recruit-Infrastructure/src/main/resources/application-infrastructure.yml b/server/Recruit-Infrastructure/src/main/resources/application-infrastructure.yml index 227db7c5..00ed82c8 100644 --- a/server/Recruit-Infrastructure/src/main/resources/application-infrastructure.yml +++ b/server/Recruit-Infrastructure/src/main/resources/application-infrastructure.yml @@ -3,6 +3,17 @@ spring: host: ${REDIS_HOST:localhost} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} + mail: + host: ${MAIL_HOST:127.0.0.1} + port: 587 + username: ${GMAIL_ADDRESS} + password: ${GMAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true slack: webhook: token: ${SLACK_WEBHOOK_TOKEN:} diff --git a/server/Recruit-Infrastructure/src/main/resources/layouts/NanumBarunGothic.ttf b/server/Recruit-Infrastructure/src/main/resources/layouts/NanumBarunGothic.ttf new file mode 100644 index 00000000..c3148683 Binary files /dev/null and b/server/Recruit-Infrastructure/src/main/resources/layouts/NanumBarunGothic.ttf differ diff --git a/server/docker-compose.yml b/server/docker-compose.yml index 400ff16a..b46e1475 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml @@ -11,6 +11,8 @@ services: network_mode: "host" environment: - TZ=Asia/Seoul + volumes: + - ~/econo-recruit/server/volumes/mysql:/var/lib/mysql backend: image: blackbean99/econo-recruit:0.0.1 container_name: backend