diff --git a/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java b/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java index 800a1d6..85ee965 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/EmailAuthService.java @@ -28,10 +28,6 @@ public void signUpEmailSend(String email) { verifyEmail(email); - if(email.contains("+")) { - throw new PostNotFoundException("잘못된 이메일 형식입니다."); - } - String authToken = AuthTokenUtil.getAuthToken(); signUpEmailSendService.send(email, authToken); diff --git a/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java b/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java index 66307c0..c23d45c 100644 --- a/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java +++ b/module-api/src/main/java/inspiration/domain/emailauth/SignUpEmailSendService.java @@ -3,27 +3,49 @@ import inspiration.aws.AwsSesService; import inspiration.enumeration.ExceptionType; import inspiration.exception.PostNotFoundException; -import inspiration.infrastructure.mail.MailProperties; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; - +import org.springframework.web.util.UriComponentsBuilder; import java.util.List; +import javax.mail.internet.MimeMessage; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; @Service @RequiredArgsConstructor @Slf4j public class SignUpEmailSendService implements EmailSendService { + private final MailProperties mailProperties; private final static String SUBJECT = "이메일 인증"; - private final AwsSesService awsSesService; + private final JavaMailSender mailSender; + private final static String SUBJECT = "이메일 인증"; + + @Value("${ygtang.server.scheme}") + private String scheme; + @Value("${ygtang.server.host}") + private String host; + @Value("${ygtang.server.port}") + private String port; + @Override public void send(String email, String authToken) { - String link = mailProperties.getSignUpEmailSendMail() + email + "&authToken=" + authToken; + String link = UriComponentsBuilder.newInstance() + .scheme(scheme) + .host(host) + .port(port) + .path("/api/v1/auth/email/signup") + .queryParam("email", URLEncoder.encode(email, StandardCharsets.UTF_8)) + .queryParam("authToken", authToken) + .build(false) + .toUriString(); try { awsSesService.send(SUBJECT, setHtml(link), List.of(email)); diff --git a/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java b/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java index 46f178d..6c82a8c 100644 --- a/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java +++ b/module-api/src/main/java/inspiration/domain/inspiration/InspirationService.java @@ -25,15 +25,16 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Slf4j @@ -50,6 +51,7 @@ public class InspirationService { private final InspirationTagRepository inspirationTagRepository; private final TagRepository tagRepository; private final OpenGraphService openGraphService; + private final ThreadPoolTaskExecutor threadPoolTaskExecutor; @Transactional(readOnly = true) public RestPage findInspirations(Pageable pageable, Long memberId) { @@ -268,16 +270,19 @@ private Inspiration getInspiration(Long id) { } private RestPage toRestPage(Page inspirationPage) { - final Map inspirationOpenGraphMap; - inspirationOpenGraphMap = inspirationPage.getContent() - .parallelStream() - .collect( - Collectors.toMap( - Inspiration::getId, - it -> getOpenGraphResponse(it.getType(), it.getContent()) - ) - ); - + final Map inspirationOpenGraphMap = new ConcurrentHashMap<>(); + // executor 에 작업 할당 + final List> completableFutures = inspirationPage.map( + inspiration -> CompletableFuture.runAsync( + () -> inspirationOpenGraphMap.put( + inspiration.getId(), + getOpenGraphResponse(inspiration.getType(), inspiration.getContent()) + ), + threadPoolTaskExecutor + ) + ).toList(); + // 비동기 작업 끝날때까지 대기 + completableFutures.forEach(CompletableFuture::join); return new RestPage<>( inspirationPage.stream() .peek(it -> it.setFilePath(getFilePath(it.getType(), it.getContent()))) diff --git a/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java b/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java index 6199666..20fe6dd 100644 --- a/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java +++ b/module-api/src/main/java/inspiration/domain/member/request/ExtraInfoRequest.java @@ -18,7 +18,6 @@ public class ExtraInfoRequest { private GenderType gender; @ApiModelProperty( notes = "UNDER_20S | EARLY_20S | LATE_20S | EARLY_30S | OLDER_35") - @NotNull(message = "나이는 필수 입력입니다.") private AgeGroupType age; @NotNull(message = "관심 직무는 필수 입력입니다.") diff --git a/module-batch/src/main/java/inspiration/application/member/count/MemberCountJobConfig.java b/module-batch/src/main/java/inspiration/application/member/count/MemberCountJobConfig.java index 6f02af4..f2c50a8 100644 --- a/module-batch/src/main/java/inspiration/application/member/count/MemberCountJobConfig.java +++ b/module-batch/src/main/java/inspiration/application/member/count/MemberCountJobConfig.java @@ -40,12 +40,12 @@ public class MemberCountJobConfig { static final String JOB_NAME = "member-count-job"; static final String STEP_NAME = "member-count-step"; - private static final int CHUNK_SIZE = 1000; + private static final int CHUNK_SIZE = 10000; private final JobBuilderFactory jobBuilderFactory; private final StepBuilderFactory stepBuilderFactory; private final SlackService slackService; - @Value("ygtang.temporary-directory-path") + @Value("${ygtang.temporary-directory-path}") private String temporaryDirectoryPath; @Bean diff --git a/module-domain/src/main/java/inspiration/domain/member/Member.java b/module-domain/src/main/java/inspiration/domain/member/Member.java index 97def6e..556e54d 100644 --- a/module-domain/src/main/java/inspiration/domain/member/Member.java +++ b/module-domain/src/main/java/inspiration/domain/member/Member.java @@ -28,15 +28,18 @@ public class Member extends BaseTimeEntity { @Column(nullable = false, length = 60) private String password; - @Column(nullable = true) + @Column @Enumerated(EnumType.STRING) private GenderType gender; - @Column(nullable = true) + /** + * iOS 에서 나이가 필수값이라 때문에 리젝당함 (2023-04-04) + */ + @Column @Enumerated(EnumType.STRING) - private AgeGroupType age_group; + private AgeGroupType ageGroup; - @Column(nullable = true) + @Column private String job; public void updatePassword(String password) { @@ -49,10 +52,10 @@ public void updateNickname(String nickname) { this.nickname = nickname; } - public void updateExtraInfo(GenderType gender, AgeGroupType age_group, String job) { + public void updateExtraInfo(GenderType gender, AgeGroupType ageGroup, String job) { this.gender = gender; - this.age_group = age_group; + this.ageGroup = ageGroup; this.job = job; } diff --git a/module-domain/src/main/java/inspiration/infrastructure/mail/MailProperties.java b/module-domain/src/main/java/inspiration/infrastructure/mail/MailProperties.java index 6732c60..ad4eab8 100644 --- a/module-domain/src/main/java/inspiration/infrastructure/mail/MailProperties.java +++ b/module-domain/src/main/java/inspiration/infrastructure/mail/MailProperties.java @@ -8,7 +8,5 @@ public class MailProperties { private int port; private String userName; private String password; - private String signUpEmailSendMail; private String resetPasswordForAuthSendMail; - private String authToken; } diff --git a/module-domain/src/main/java/inspiration/infrastructure/spring/ExecutorConfig.java b/module-domain/src/main/java/inspiration/infrastructure/spring/ExecutorConfig.java new file mode 100644 index 0000000..8a98fc8 --- /dev/null +++ b/module-domain/src/main/java/inspiration/infrastructure/spring/ExecutorConfig.java @@ -0,0 +1,23 @@ +package inspiration.infrastructure.spring; + +import org.springframework.boot.task.TaskExecutorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.time.Duration; + +@Configuration +public class ExecutorConfig { + @Bean + public ThreadPoolTaskExecutor threadPoolTaskExecutor() { + return new TaskExecutorBuilder() + .corePoolSize(12) + .maxPoolSize(12) + .queueCapacity(200) + .awaitTermination(true) + .awaitTerminationPeriod(Duration.ofSeconds(10)) + .threadNamePrefix("task-executor-") + .build(); + } +} diff --git a/module-domain/src/main/resources/application-dev.yml b/module-domain/src/main/resources/application-dev.yml index 81005f9..8901451 100644 --- a/module-domain/src/main/resources/application-dev.yml +++ b/module-domain/src/main/resources/application-dev.yml @@ -9,7 +9,6 @@ spring: user: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) mail: - sign-up-email-send-mail: ENC(v8vfcMSRTQlJR/sRIpLnWAuYzJz0trJL6yKadnmztGC9N6dD1Bo1nh9xMSdFnHZky/TRWW9uf0oVteh6Z57R0YjLYnOEJAyDmCgrv+x5JqLTPlFlCPpVAwCFx29nJc/m) reset-password-for-auth-send-mail: ENC(GeC90mA3UZZDdttZYktCwjx5gl00nT+ZGOOlCmT3UuTzwgvnGBqby+LjMl1BVfJ6B0leFjbJVUbkkykNQ0i/9/f6MPYopOgJy0sCaL8QlypSmzJy5Vkq98KTbAu18nsg) logging: @@ -31,6 +30,10 @@ ygtang: username: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) driver-class-name: com.mysql.cj.jdbc.Driver + server: + scheme: https + host: ENC(nhcx6FgoC9f1sw8x+IXZxUeA7jVGC00faq6kqZ9qJK5ZgKS/ayDPiY+swiTOrb1e) + port: 443 uri: webhook: ENC(KSZZjsE2e2j/6aQIb1EFZmIqM+S2qW2hY6/k9X97URbVEUcE6jNH60ZF186hVIpfmx3u4kZ3MgLboXvzId3CCFg3uIovOW5iiEh9T2zwf+wtK7GvcD0DaJ+H+xhRadViI5xoKD1aR6OMcVvDOwJurBVa6BFy0VemoIuSt+nQNYM=) diff --git a/module-domain/src/main/resources/application-local.yml b/module-domain/src/main/resources/application-local.yml index a7709e6..3aef2fb 100644 --- a/module-domain/src/main/resources/application-local.yml +++ b/module-domain/src/main/resources/application-local.yml @@ -9,7 +9,6 @@ spring: user: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) mail: - sign-up-email-send-mail: ENC(TxvKgsUqqpgYeMzecLx4+iVEt2RQL1J8F24/ttMYgkboMeX2e1PE9L5VNk85RKVgZttx5x8p6dNin6mZaSZLomOSMlIiN2B5STL5LBGgmqhR8lYnKfVtyANliIit9pdH) reset-password-for-auth-send-mail: ENC(2HO9hovu42xvdGvyYqoh2VhBxxl5Hp8gqopWj2vGlAiVrIBC0Bj0Be6iEbBVWuIWxgim0Uoznu3wRdT5LmbaKc/iLdluzHsB6xDbxiN5JA7PT3AxgLwZNAQ2q5bQ2v8w) cloud: @@ -27,3 +26,7 @@ ygtang: username: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) driver-class-name: com.mysql.cj.jdbc.Driver + server: + scheme: http + host: localhost + port: 8080 diff --git a/module-domain/src/main/resources/application-prod.yml b/module-domain/src/main/resources/application-prod.yml index e8fea44..fc5680c 100644 --- a/module-domain/src/main/resources/application-prod.yml +++ b/module-domain/src/main/resources/application-prod.yml @@ -4,7 +4,6 @@ spring: user: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) mail: - sign-up-email-send-mail: ENC(zyANvPjCAYiH+b6CSKvf0B3vMD1eU7NaOfqeUWVs2Mj4eCMvDa7Y6qpAV5h02jbihEC/I6zmjIZqsPu1VTdUadTTuVPVcLyqTJGlRYS5rLlEcXGhZ4zg+Nu8+3S8otsn) reset-password-for-auth-send-mail: ENC(TtTKtnwv64ttIONkMuT1SF6r/iU2NWV+hI17DTupgugbuj50RsiIsEXowUUmFoVz7fh01FBNkq0dKhkaDEK+BLWtRchs9Z+koSAMSNqvGhby2+8rHzRfIL3FMTzEGeHt) cloud: @@ -26,6 +25,10 @@ ygtang: username: ENC(pD8lmKr4PXMRCUto6pMF9Ub/iJCxKveTODoqXQm1fDEB4rVN1Nzs6sftmidgYWOP) password: ENC(MJISIAE2hpfyzQjAFQwM8o+Wtv5rHP5lJDwBq3mEIUkr93OEMj+uqYmXDjasb7El) driver-class-name: com.mysql.cj.jdbc.Driver + server: + scheme: https + host: ENC(cm4VELioPGIOdzRjWigLUeo7GDiwRwUK4CDjibCSt9ODl1CMwfUolW+eqeXKJ8Hj) + port: 443 uri: webhook: ENC(d6rn2vUIq/fnrv6k5m9EhuVtKxJGXm6Qf5NPGgaPMHa1yBld8nFPwxTh/XX9iVqweVSvnLXj0RNYQ9vNsoOiR1YtfqQi7jBFDtGYT7T8KEHZ6FVnwx499LoHKHAZBKF4GoX1kawUV7iPP+tSr5UvNkZkjGEYUqBzqmVgXvTXZng=) diff --git a/module-domain/src/test/resources/application.yml b/module-domain/src/test/resources/application.yml index af0981e..adc62de 100644 --- a/module-domain/src/test/resources/application.yml +++ b/module-domain/src/test/resources/application.yml @@ -17,9 +17,7 @@ spring: port: 587 username: username password: password - sign-up-email-send-mail: reset-password-for-auth-send-mail: - auth-token: jwt: secret: @@ -43,4 +41,8 @@ ygtang: jdbcUrl: jdbc:h2:mem:;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE username: sa password: - driver-class-name: org.h2.Driver \ No newline at end of file + driver-class-name: org.h2.Driver + server: + scheme: http + host: localhost + port: 8080 diff --git a/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java b/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java index c8d5a9f..b21be4f 100644 --- a/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java +++ b/module-web/src/test/java/inspiration/v1/auth/AuthControllerTest.java @@ -16,6 +16,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -48,9 +49,26 @@ void sendEmailForSignup() throws Exception { // then 1 .andExpect(status().isCreated()); // then 2 - //String authToken = redisService.getData(RedisKey.EAUTH_SIGN_UP.getKey() + email); - //assertThat(authToken).isNotBlank(); - //verify(signUpEmailSendService).send(any(), any()); + verify(signUpEmailSendService).send(any(), any()); + } + + @DisplayName("인증 메일 발송: '+ '문자 포함된 이메일도 허용") + @Test + void sendEmailForSignup_withPlusCharacter() throws Exception { + // given + String email = "localpart+1@domain"; + SendEmailRequest sendEmailRequest = new SendEmailRequest(); + sendEmailRequest.setEmail(email); + doNothing().when(signUpEmailSendService).send(any(), any()); + // when + mockMvc.perform( + post("/api/v1/auth/sends-email/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(sendEmailRequest))) + // then 1 + .andExpect(status().isCreated()); + // then 2 + verify(signUpEmailSendService).send(any(), any()); } @DisplayName("인증 메일 링크 통해 이메일 소유 확인") @@ -72,4 +90,4 @@ void authenticateEmailOfSignUp() throws Exception { // then .andExpect(status().isFound()); } -} \ No newline at end of file +} diff --git a/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java b/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java index 555dc35..41dbbb5 100644 --- a/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java +++ b/module-web/src/test/java/inspiration/v1/signup/SignUpControllerTest.java @@ -6,6 +6,7 @@ import inspiration.domain.member.Member; import inspiration.domain.member.MemberRepository; import inspiration.domain.member.request.SignUpRequest; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -21,8 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -75,4 +75,38 @@ void signUp() throws Exception { assertThat(member.get().getEmail()).isEqualTo(email); assertThat(member.get().getNickname()).isEqualTo("nickname"); } -} \ No newline at end of file + + @DisplayName("회원 추가정보 수정: ageGroup이 null 이어도 성공한다.") + @Test + void testAgeGroupIsNull() throws Exception { + // given + String email = "localpart@domain"; + SendEmailRequest sendEmailRequest = new SendEmailRequest(); + sendEmailRequest.setEmail(email); + doNothing().when(signUpEmailSendService).send(any(), any()); + mockMvc.perform(post("/api/v1/auth/sends-email/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(sendEmailRequest))) + .andExpect(status().isCreated()); + mockMvc.perform(get("/api/v1/auth/email/signup") + .queryParam("email", email)) + .andExpect(status().isFound()); + SignUpRequest signUpRequest = new SignUpRequest(); + signUpRequest.setEmail(email); + signUpRequest.setNickName("nickname"); + signUpRequest.setPassword("password"); + signUpRequest.setConfirmPassword("password"); + mockMvc.perform( + post("/api/v1/signup") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsBytes(signUpRequest))) + .andExpect(status().isCreated()); + // when + mockMvc.perform(patch("/api/v1/signup/extra-informations") + .queryParam("email", email) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"gender\":\"ETC\",\"ageGroup\":null,\"job\":\"job\"}") + // then + ).andExpect(status().isOk()); + } +} diff --git a/module-web/src/test/resources/application.yml b/module-web/src/test/resources/application.yml index 27ab3f1..3f4c94d 100644 --- a/module-web/src/test/resources/application.yml +++ b/module-web/src/test/resources/application.yml @@ -14,9 +14,7 @@ spring: port: 587 username: username password: password - sign-up-email-send-mail: reset-password-for-auth-send-mail: - auth-token: jwt: secret: servlet: @@ -37,7 +35,9 @@ cloud: ygtang: server: - host: ygtang.server.host + scheme: http + host: localhost + port: 8080 domain: datasource: jdbcUrl: jdbc:h2:mem:;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE @@ -51,4 +51,4 @@ ygtang: management: server: - port: 8081 \ No newline at end of file + port: 8081