From ec058ad3af3c983b42c6fd6c4d65b484b8a1bda5 Mon Sep 17 00:00:00 2001 From: namhyo01 Date: Tue, 17 Oct 2023 01:47:10 +0900 Subject: [PATCH 1/4] feat(BE): sns config complete #111 --- build.gradle | 7 ++ .../api/aws/adapter/in/AWSSNSController.java | 28 ++++++ .../application/port/in/SendSMSUsecase.java | 8 ++ .../com/example/api/aws/config/AWSConfig.java | 38 ++++++++ .../com/example/api/aws/domain/SendSMS.java | 6 ++ .../com/example/api/aws/dto/SendSMSDto.java | 16 ++++ .../api/aws/service/AWSSNSService.java | 89 +++++++++++++++++++ 7 files changed, 192 insertions(+) create mode 100644 src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java create mode 100644 src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java create mode 100644 src/main/java/com/example/api/aws/config/AWSConfig.java create mode 100644 src/main/java/com/example/api/aws/domain/SendSMS.java create mode 100644 src/main/java/com/example/api/aws/dto/SendSMSDto.java create mode 100644 src/main/java/com/example/api/aws/service/AWSSNSService.java diff --git a/build.gradle b/build.gradle index e0b3a12..2239dd9 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,13 @@ dependencies { implementation 'com.google.guava:guava:32.1.2-jre' // google core library 사용 implementation 'org.springframework.boot:spring-boot-starter-security' // security implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + +// implementation(platform("software.amazon.awssdk:bom:2.20.54")) +// implementation 'software.amazon.awssdk:sns' // SNS 모듈 추가 + implementation 'com.amazonaws:aws-java-sdk-core:1.11.927' + implementation 'com.amazonaws:aws-java-sdk-sns:1.11.927' +// Spring Boot SNS Starter +// implementation 'org.springframework.boot:spring-boot-starter-aws' // implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:2.7.11' // oauth2 // implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java b/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java new file mode 100644 index 0000000..ad9e983 --- /dev/null +++ b/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java @@ -0,0 +1,28 @@ +package com.example.api.aws.adapter.in; + + +import com.example.api.aws.application.port.in.SendSMSUsecase; +import com.example.api.aws.dto.SendSMSDto; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Validated +public class AWSSNSController { + private final SendSMSUsecase sendSMSUsecase; + + + /** + * 핸드폰 인증에 사용 + * @param sendSMSDto + */ + @GetMapping("/aws/sns/code") + public void sendCertificationPhone(@Validated SendSMSDto sendSMSDto){ + sendSMSUsecase.send(sendSMSDto); + } + + +} diff --git a/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java b/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java new file mode 100644 index 0000000..58be84a --- /dev/null +++ b/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java @@ -0,0 +1,8 @@ +package com.example.api.aws.application.port.in; + +import com.amazonaws.services.sns.model.PublishResult; +import com.example.api.aws.dto.SendSMSDto; + +public interface SendSMSUsecase { + PublishResult send(SendSMSDto sendSMSDto); +} diff --git a/src/main/java/com/example/api/aws/config/AWSConfig.java b/src/main/java/com/example/api/aws/config/AWSConfig.java new file mode 100644 index 0000000..d38d8ac --- /dev/null +++ b/src/main/java/com/example/api/aws/config/AWSConfig.java @@ -0,0 +1,38 @@ +package com.example.api.aws.config; + + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.AmazonSNSClientBuilder; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +@Getter +@Configuration +public class AWSConfig { + + @Value("${aws.accessKey}") + private String awsAccessKey; + + @Value("${aws.secretKey}") + private String awsSecretKey; + + @Value("${aws.region}") + private String awsRegion; + + @Primary + @Bean + public AmazonSNSClient getAWSSNSClient(){ + return (AmazonSNSClient) AmazonSNSClientBuilder.standard() + .withRegion(awsRegion) + .withCredentials( + new AWSStaticCredentialsProvider(new BasicAWSCredentials(awsAccessKey, awsSecretKey)) + ) + .build(); + } +} diff --git a/src/main/java/com/example/api/aws/domain/SendSMS.java b/src/main/java/com/example/api/aws/domain/SendSMS.java new file mode 100644 index 0000000..d25e3e9 --- /dev/null +++ b/src/main/java/com/example/api/aws/domain/SendSMS.java @@ -0,0 +1,6 @@ +package com.example.api.aws.domain; + +public class SendSMS { + private String phone; + private String message; +} diff --git a/src/main/java/com/example/api/aws/dto/SendSMSDto.java b/src/main/java/com/example/api/aws/dto/SendSMSDto.java new file mode 100644 index 0000000..d0c0129 --- /dev/null +++ b/src/main/java/com/example/api/aws/dto/SendSMSDto.java @@ -0,0 +1,16 @@ +package com.example.api.aws.dto; + + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Getter; + +@Getter +public class SendSMSDto { + @NotBlank(message = "핸드폰 번호를 입력해주세요") + @Pattern( + regexp = "(01[016789])(\\d{3,4})(\\d{4})", + message = "올바른 핸드폰 번호를 입력해주세요" + ) + String phone; +} diff --git a/src/main/java/com/example/api/aws/service/AWSSNSService.java b/src/main/java/com/example/api/aws/service/AWSSNSService.java new file mode 100644 index 0000000..0eeca7e --- /dev/null +++ b/src/main/java/com/example/api/aws/service/AWSSNSService.java @@ -0,0 +1,89 @@ +package com.example.api.aws.service; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClientBuilder; +import com.amazonaws.services.sns.model.MessageAttributeValue; +import com.amazonaws.services.sns.model.PublishRequest; +import com.amazonaws.services.sns.model.PublishResult; +import com.example.api.aws.application.port.in.SendSMSUsecase; +import com.example.api.aws.config.AWSConfig; +import com.example.api.aws.dto.SendSMSDto; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + + +@Slf4j +@RequiredArgsConstructor +@Service +public class AWSSNSService implements SendSMSUsecase { + private final AWSConfig awsConfig; + @Override + public PublishResult send(SendSMSDto senderDto) { + + PublishResult result = null; + String region = awsConfig.getAwsRegion(); + try { + + BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsConfig.getAwsAccessKey(), awsConfig.getAwsSecretKey()); + + AmazonSNSClientBuilder builder = + AmazonSNSClientBuilder.standard(); + + AmazonSNS sns = builder.withRegion(Regions.AP_NORTHEAST_1) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)).build(); + + Map smsAttributes = + new HashMap<>(); + + smsAttributes.put("AWS.SNS.SMS.SenderID", new MessageAttributeValue() + .withStringValue("BPP").withDataType("String")); + + smsAttributes.put("AWS.SNS.SMS.MaxPrice", new MessageAttributeValue() + .withStringValue("30").withDataType("String")); + + smsAttributes.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue() + .withStringValue("Promotional").withDataType("String")); + + result = this.sendSMSMessage(sns, + generateRandomSixDigitNumber(), + senderDto.getPhone(), + smsAttributes); + } catch (Exception ex) { + + log.error("The sms was not sent."); + log.error("Error message: " + ex.getMessage()); + throw new AmazonClientException(ex.getMessage(), ex); + } + return result; + } + + /** + * 6 랜덤 숫자 리턴 + * + * @return + */ + private String generateRandomSixDigitNumber() { + Random random = new Random(); + + int randomNumber = random.nextInt(1000000); // 0~ 999999 사이 랜덤 숫자 + + return String.format("%06d", randomNumber); + } + + private PublishResult sendSMSMessage(AmazonSNS sns, String message, String phone, Map messageAttributeValueMap) { + return sns.publish(new PublishRequest() + .withMessage(message) + .withPhoneNumber(phone) + .withMessageAttributes(messageAttributeValueMap) + ); + } +} From b8ea9624cd0cc69e1193baaff5ef10f901309676 Mon Sep 17 00:00:00 2001 From: namhyo01 Date: Tue, 17 Oct 2023 02:39:20 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(BE):=20=ED=95=B8=EB=93=9C=ED=8F=B0?= =?UTF-8?q?=EC=97=90=20=EC=A0=84=EC=86=A1=20=ED=99=95=EC=9D=B8=20#111?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/api/auth/config/SecurityConfig.java | 1 + .../api/aws/adapter/in/AWSSNSController.java | 9 ++++++--- .../aws/application/port/in/SendSMSUsecase.java | 2 +- .../example/api/aws/service/AWSSNSService.java | 15 +++++++++------ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/example/api/auth/config/SecurityConfig.java b/src/main/java/com/example/api/auth/config/SecurityConfig.java index e8bb8a4..8c197dd 100644 --- a/src/main/java/com/example/api/auth/config/SecurityConfig.java +++ b/src/main/java/com/example/api/auth/config/SecurityConfig.java @@ -56,6 +56,7 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .requestMatchers("/auth/**").permitAll() // auth로 오는 애들은 일단 인증 없이 가능 .requestMatchers("/actuator/**").permitAll() .requestMatchers(HttpMethod.POST, "/user/**").permitAll() + .requestMatchers("/aws/**").permitAll() .requestMatchers("/login/**").permitAll() .requestMatchers(PERMIT_URL_ARRAY).permitAll() .anyRequest().authenticated()); // 그 외는 전부 인증 필요 diff --git a/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java b/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java index ad9e983..d2fcc7f 100644 --- a/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java +++ b/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java @@ -6,10 +6,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor +@RequestMapping("/aws/sns") @Validated public class AWSSNSController { private final SendSMSUsecase sendSMSUsecase; @@ -19,9 +22,9 @@ public class AWSSNSController { * 핸드폰 인증에 사용 * @param sendSMSDto */ - @GetMapping("/aws/sns/code") - public void sendCertificationPhone(@Validated SendSMSDto sendSMSDto){ - sendSMSUsecase.send(sendSMSDto); + @GetMapping("/code/{phone}") + public void sendCertificationPhone(@PathVariable String phone){ + sendSMSUsecase.send(phone); } diff --git a/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java b/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java index 58be84a..a9ec2e6 100644 --- a/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java +++ b/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java @@ -4,5 +4,5 @@ import com.example.api.aws.dto.SendSMSDto; public interface SendSMSUsecase { - PublishResult send(SendSMSDto sendSMSDto); + PublishResult send(String phone); } diff --git a/src/main/java/com/example/api/aws/service/AWSSNSService.java b/src/main/java/com/example/api/aws/service/AWSSNSService.java index 0eeca7e..aef4911 100644 --- a/src/main/java/com/example/api/aws/service/AWSSNSService.java +++ b/src/main/java/com/example/api/aws/service/AWSSNSService.java @@ -27,9 +27,11 @@ public class AWSSNSService implements SendSMSUsecase { private final AWSConfig awsConfig; @Override - public PublishResult send(SendSMSDto senderDto) { + public PublishResult send(String phone) { PublishResult result = null; + StringBuilder koreaPhone = new StringBuilder("+82"); + koreaPhone.append(phone); String region = awsConfig.getAwsRegion(); try { @@ -48,14 +50,14 @@ public PublishResult send(SendSMSDto senderDto) { .withStringValue("BPP").withDataType("String")); smsAttributes.put("AWS.SNS.SMS.MaxPrice", new MessageAttributeValue() - .withStringValue("30").withDataType("String")); + .withStringValue("0.50").withDataType("String")); smsAttributes.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue() .withStringValue("Promotional").withDataType("String")); result = this.sendSMSMessage(sns, generateRandomSixDigitNumber(), - senderDto.getPhone(), + koreaPhone.toString(), smsAttributes); } catch (Exception ex) { @@ -73,10 +75,11 @@ public PublishResult send(SendSMSDto senderDto) { */ private String generateRandomSixDigitNumber() { Random random = new Random(); - + String message = "[여행파티]인증번호입니다 아래 6글자를 입력해주세요\n"; + StringBuilder newMessage = new StringBuilder(message); int randomNumber = random.nextInt(1000000); // 0~ 999999 사이 랜덤 숫자 - - return String.format("%06d", randomNumber); + newMessage.append(String.format("%06d", randomNumber)); + return newMessage.toString(); } private PublishResult sendSMSMessage(AmazonSNS sns, String message, String phone, Map messageAttributeValueMap) { From 8d27e2bd511fed01a88722083653f5b63f4f197e Mon Sep 17 00:00:00 2001 From: namhyo01 Date: Tue, 17 Oct 2023 18:31:34 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat(BE):=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20redis=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=ED=99=95=EC=9D=B8=EC=9A=A9=20#111?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/auth/adapter/in/AuthController.java | 2 +- .../api/auth/config/SecurityConfig.java | 2 +- .../MyAuthenticationSuccessHandler.java | 1 - .../example/api/auth/type/RefreshToken.java | 1 - .../api/aws/adapter/in/AWSSNSController.java | 31 ----------- .../application/port/in/SendSMSUsecase.java | 8 --- .../com/example/api/aws/domain/SendSMS.java | 6 --- .../api/{aws => common}/config/AWSConfig.java | 2 +- .../api/common/type/ErrorCodeEnum.java | 5 +- .../sms/adapter/in/rest/SmsController.java | 35 +++++++++++++ .../out/persistence/PhoneCertification.java | 23 ++++++++ .../PhoneCertificationPersistence.java | 24 +++++++++ .../port/in/SendCertificationCodeUsecase.java | 7 +++ .../port/in/VerifyCodeUsecase.java | 7 +++ .../port/out/CertificationCodePort.java | 9 ++++ .../dto/CheckSMSDto.java} | 9 +++- .../PhoneCertificationRepository.java | 11 ++++ .../service/SMSService.java} | 52 ++++++++++++++----- .../user/adapter/in/rest/UserController.java | 11 ++-- 19 files changed, 173 insertions(+), 73 deletions(-) delete mode 100644 src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java delete mode 100644 src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java delete mode 100644 src/main/java/com/example/api/aws/domain/SendSMS.java rename src/main/java/com/example/api/{aws => common}/config/AWSConfig.java (96%) create mode 100644 src/main/java/com/example/api/sms/adapter/in/rest/SmsController.java create mode 100644 src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java create mode 100644 src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java create mode 100644 src/main/java/com/example/api/sms/application/port/in/SendCertificationCodeUsecase.java create mode 100644 src/main/java/com/example/api/sms/application/port/in/VerifyCodeUsecase.java create mode 100644 src/main/java/com/example/api/sms/application/port/out/CertificationCodePort.java rename src/main/java/com/example/api/{aws/dto/SendSMSDto.java => sms/dto/CheckSMSDto.java} (56%) create mode 100644 src/main/java/com/example/api/sms/repository/PhoneCertificationRepository.java rename src/main/java/com/example/api/{aws/service/AWSSNSService.java => sms/service/SMSService.java} (63%) diff --git a/src/main/java/com/example/api/auth/adapter/in/AuthController.java b/src/main/java/com/example/api/auth/adapter/in/AuthController.java index e8e34d7..78e7973 100644 --- a/src/main/java/com/example/api/auth/adapter/in/AuthController.java +++ b/src/main/java/com/example/api/auth/adapter/in/AuthController.java @@ -31,7 +31,7 @@ public class AuthController { public ResponseEntity logout(@CookieValue String access_token, HttpServletResponse response) { logoutUsecase.removeToken(access_token); - Cookie cookie = new Cookie("accessToken", null); + Cookie cookie = new Cookie("access_token", null); cookie.setMaxAge(0); response.addCookie(cookie); return ResponseEntity.ok(StatusResponseDto.addStatus(200)); diff --git a/src/main/java/com/example/api/auth/config/SecurityConfig.java b/src/main/java/com/example/api/auth/config/SecurityConfig.java index 8c197dd..bb13a5c 100644 --- a/src/main/java/com/example/api/auth/config/SecurityConfig.java +++ b/src/main/java/com/example/api/auth/config/SecurityConfig.java @@ -56,7 +56,7 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti .requestMatchers("/auth/**").permitAll() // auth로 오는 애들은 일단 인증 없이 가능 .requestMatchers("/actuator/**").permitAll() .requestMatchers(HttpMethod.POST, "/user/**").permitAll() - .requestMatchers("/aws/**").permitAll() + .requestMatchers("/sms/**").permitAll() .requestMatchers("/login/**").permitAll() .requestMatchers(PERMIT_URL_ARRAY).permitAll() .anyRequest().authenticated()); // 그 외는 전부 인증 필요 diff --git a/src/main/java/com/example/api/auth/handler/MyAuthenticationSuccessHandler.java b/src/main/java/com/example/api/auth/handler/MyAuthenticationSuccessHandler.java index d02b33a..1dee839 100644 --- a/src/main/java/com/example/api/auth/handler/MyAuthenticationSuccessHandler.java +++ b/src/main/java/com/example/api/auth/handler/MyAuthenticationSuccessHandler.java @@ -59,7 +59,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .encode(StandardCharsets.UTF_8) .toUriString(); log.info("success redirecting"); - log.info(targetUrl); CookieUtils.addCookie(response, "access_token",token.getAccessToken(), 1000 * 60 * 60); clearAuthenticationAttributes(request, response); // response.addCookie(CookieUtils.addCookie();); diff --git a/src/main/java/com/example/api/auth/type/RefreshToken.java b/src/main/java/com/example/api/auth/type/RefreshToken.java index fadfe66..da95b00 100644 --- a/src/main/java/com/example/api/auth/type/RefreshToken.java +++ b/src/main/java/com/example/api/auth/type/RefreshToken.java @@ -24,5 +24,4 @@ public class RefreshToken implements Serializable { // 직렬화, 역 직렬화 public void updateAccessToken(String accessToken) { this.accessToken = accessToken; } - } diff --git a/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java b/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java deleted file mode 100644 index d2fcc7f..0000000 --- a/src/main/java/com/example/api/aws/adapter/in/AWSSNSController.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.api.aws.adapter.in; - - -import com.example.api.aws.application.port.in.SendSMSUsecase; -import com.example.api.aws.dto.SendSMSDto; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/aws/sns") -@Validated -public class AWSSNSController { - private final SendSMSUsecase sendSMSUsecase; - - - /** - * 핸드폰 인증에 사용 - * @param sendSMSDto - */ - @GetMapping("/code/{phone}") - public void sendCertificationPhone(@PathVariable String phone){ - sendSMSUsecase.send(phone); - } - - -} diff --git a/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java b/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java deleted file mode 100644 index a9ec2e6..0000000 --- a/src/main/java/com/example/api/aws/application/port/in/SendSMSUsecase.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.api.aws.application.port.in; - -import com.amazonaws.services.sns.model.PublishResult; -import com.example.api.aws.dto.SendSMSDto; - -public interface SendSMSUsecase { - PublishResult send(String phone); -} diff --git a/src/main/java/com/example/api/aws/domain/SendSMS.java b/src/main/java/com/example/api/aws/domain/SendSMS.java deleted file mode 100644 index d25e3e9..0000000 --- a/src/main/java/com/example/api/aws/domain/SendSMS.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.api.aws.domain; - -public class SendSMS { - private String phone; - private String message; -} diff --git a/src/main/java/com/example/api/aws/config/AWSConfig.java b/src/main/java/com/example/api/common/config/AWSConfig.java similarity index 96% rename from src/main/java/com/example/api/aws/config/AWSConfig.java rename to src/main/java/com/example/api/common/config/AWSConfig.java index d38d8ac..dc1c0d8 100644 --- a/src/main/java/com/example/api/aws/config/AWSConfig.java +++ b/src/main/java/com/example/api/common/config/AWSConfig.java @@ -1,4 +1,4 @@ -package com.example.api.aws.config; +package com.example.api.common.config; import com.amazonaws.auth.AWSStaticCredentialsProvider; diff --git a/src/main/java/com/example/api/common/type/ErrorCodeEnum.java b/src/main/java/com/example/api/common/type/ErrorCodeEnum.java index bcc8a4e..f4fe861 100644 --- a/src/main/java/com/example/api/common/type/ErrorCodeEnum.java +++ b/src/main/java/com/example/api/common/type/ErrorCodeEnum.java @@ -9,8 +9,9 @@ public enum ErrorCodeEnum { LOGIN_IS_NOT_DONE(HttpStatus.UNAUTHORIZED, "로그인 정보가 없습니다"), INVALID_PERMISSION(HttpStatus.UNAUTHORIZED, "권한이 없습니다"), - DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 오류"); - + DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "데이터베이스 오류"), + CODE_IS_NOT_VALID(HttpStatus.BAD_REQUEST, "잘못된 인증번호입니다"), + CODE_IS_EXPIRED(HttpStatus.BAD_REQUEST, "휴대전화를 인증해주세요"); private final HttpStatus httpStatus; private final String message; } \ No newline at end of file diff --git a/src/main/java/com/example/api/sms/adapter/in/rest/SmsController.java b/src/main/java/com/example/api/sms/adapter/in/rest/SmsController.java new file mode 100644 index 0000000..d990c54 --- /dev/null +++ b/src/main/java/com/example/api/sms/adapter/in/rest/SmsController.java @@ -0,0 +1,35 @@ +package com.example.api.sms.adapter.in.rest; + +import com.example.api.sms.application.port.in.SendCertificationCodeUsecase; +import com.example.api.sms.application.port.in.VerifyCodeUsecase; +import com.example.api.sms.dto.CheckSMSDto; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/sms") +@Validated +public class SmsController { + private final SendCertificationCodeUsecase sendCertificationCodeUsecase; + private final VerifyCodeUsecase verifyCodeUsecase; + + /** + * 핸드폰 인증에 사용 + * + * @param phone + */ + @Operation(summary = "certification phone", description = "핸드폰 인증") + @GetMapping("/code/{phone}") + public void sendCertificationPhone(@PathVariable String phone){ + sendCertificationCodeUsecase.send(phone); + } + + @PostMapping("/code") + public void certificatePhone(@RequestBody @Validated CheckSMSDto checkSMSDto){ + verifyCodeUsecase.verifyCertificationCode(checkSMSDto); + } + +} diff --git a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java new file mode 100644 index 0000000..1ef5bc2 --- /dev/null +++ b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java @@ -0,0 +1,23 @@ +package com.example.api.sms.adapter.out.persistence; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; +import org.springframework.data.redis.core.index.Indexed; + +import java.io.Serializable; + +/** + * 핸드폰 인증에 사용되는 클래스 + */ +@Getter +@AllArgsConstructor +@RedisHash(value = "phone_check", timeToLive = 360) // hash collection 명시 +public class PhoneCertification implements Serializable { + @Id + private String phone; + private String code; +} diff --git a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java new file mode 100644 index 0000000..6a79d66 --- /dev/null +++ b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java @@ -0,0 +1,24 @@ +package com.example.api.sms.adapter.out.persistence; + + +import com.example.api.sms.application.port.out.CertificationCodePort; +import com.example.api.sms.repository.PhoneCertificationRepository; +import com.example.api.common.exception.CustomException; +import com.example.api.common.type.ErrorCodeEnum; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class PhoneCertificationPersistence implements CertificationCodePort { + private final PhoneCertificationRepository repository; + @Override + public void saveCode(String phone, String code) { + repository.save(new PhoneCertification(phone, code)); + } + + @Override + public PhoneCertification findCode(String phone) { + return repository.findById(phone).orElseThrow(()-> new CustomException(ErrorCodeEnum.CODE_IS_EXPIRED)); + } +} diff --git a/src/main/java/com/example/api/sms/application/port/in/SendCertificationCodeUsecase.java b/src/main/java/com/example/api/sms/application/port/in/SendCertificationCodeUsecase.java new file mode 100644 index 0000000..aa8a315 --- /dev/null +++ b/src/main/java/com/example/api/sms/application/port/in/SendCertificationCodeUsecase.java @@ -0,0 +1,7 @@ +package com.example.api.sms.application.port.in; + +import com.amazonaws.services.sns.model.PublishResult; + +public interface SendCertificationCodeUsecase { + PublishResult send(String phone); +} diff --git a/src/main/java/com/example/api/sms/application/port/in/VerifyCodeUsecase.java b/src/main/java/com/example/api/sms/application/port/in/VerifyCodeUsecase.java new file mode 100644 index 0000000..2b60576 --- /dev/null +++ b/src/main/java/com/example/api/sms/application/port/in/VerifyCodeUsecase.java @@ -0,0 +1,7 @@ +package com.example.api.sms.application.port.in; + +import com.example.api.sms.dto.CheckSMSDto; + +public interface VerifyCodeUsecase { + void verifyCertificationCode(CheckSMSDto checkSMSDto); +} diff --git a/src/main/java/com/example/api/sms/application/port/out/CertificationCodePort.java b/src/main/java/com/example/api/sms/application/port/out/CertificationCodePort.java new file mode 100644 index 0000000..e370f9a --- /dev/null +++ b/src/main/java/com/example/api/sms/application/port/out/CertificationCodePort.java @@ -0,0 +1,9 @@ +package com.example.api.sms.application.port.out; + +import com.example.api.sms.adapter.out.persistence.PhoneCertification; + +public interface CertificationCodePort { + void saveCode(String phone, String code); + + PhoneCertification findCode(String phone); +} diff --git a/src/main/java/com/example/api/aws/dto/SendSMSDto.java b/src/main/java/com/example/api/sms/dto/CheckSMSDto.java similarity index 56% rename from src/main/java/com/example/api/aws/dto/SendSMSDto.java rename to src/main/java/com/example/api/sms/dto/CheckSMSDto.java index d0c0129..d1131c1 100644 --- a/src/main/java/com/example/api/aws/dto/SendSMSDto.java +++ b/src/main/java/com/example/api/sms/dto/CheckSMSDto.java @@ -1,16 +1,21 @@ -package com.example.api.aws.dto; +package com.example.api.sms.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.Getter; @Getter -public class SendSMSDto { +public class CheckSMSDto { @NotBlank(message = "핸드폰 번호를 입력해주세요") @Pattern( regexp = "(01[016789])(\\d{3,4})(\\d{4})", message = "올바른 핸드폰 번호를 입력해주세요" ) String phone; + + @NotBlank(message = "인증번호를 입력해주세요") + @Pattern(regexp = "^[0-9]{6}$", message = "숫자 6자리만 입력해야 합니다.") + String code; } diff --git a/src/main/java/com/example/api/sms/repository/PhoneCertificationRepository.java b/src/main/java/com/example/api/sms/repository/PhoneCertificationRepository.java new file mode 100644 index 0000000..0abd440 --- /dev/null +++ b/src/main/java/com/example/api/sms/repository/PhoneCertificationRepository.java @@ -0,0 +1,11 @@ +package com.example.api.sms.repository; + +import com.example.api.sms.adapter.out.persistence.PhoneCertification; +import org.springframework.data.repository.CrudRepository; + +import java.util.Optional; + +public interface PhoneCertificationRepository extends CrudRepository { +// Optional findByPhone(String phone); + +} diff --git a/src/main/java/com/example/api/aws/service/AWSSNSService.java b/src/main/java/com/example/api/sms/service/SMSService.java similarity index 63% rename from src/main/java/com/example/api/aws/service/AWSSNSService.java rename to src/main/java/com/example/api/sms/service/SMSService.java index aef4911..3e3bd15 100644 --- a/src/main/java/com/example/api/aws/service/AWSSNSService.java +++ b/src/main/java/com/example/api/sms/service/SMSService.java @@ -1,4 +1,4 @@ -package com.example.api.aws.service; +package com.example.api.sms.service; import com.amazonaws.AmazonClientException; import com.amazonaws.auth.AWSStaticCredentialsProvider; @@ -9,9 +9,13 @@ import com.amazonaws.services.sns.model.MessageAttributeValue; import com.amazonaws.services.sns.model.PublishRequest; import com.amazonaws.services.sns.model.PublishResult; -import com.example.api.aws.application.port.in.SendSMSUsecase; -import com.example.api.aws.config.AWSConfig; -import com.example.api.aws.dto.SendSMSDto; +import com.example.api.common.exception.CustomException; +import com.example.api.common.type.ErrorCodeEnum; +import com.example.api.sms.application.port.in.SendCertificationCodeUsecase; +import com.example.api.sms.application.port.in.VerifyCodeUsecase; +import com.example.api.sms.application.port.out.CertificationCodePort; +import com.example.api.common.config.AWSConfig; +import com.example.api.sms.dto.CheckSMSDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -24,17 +28,40 @@ @Slf4j @RequiredArgsConstructor @Service -public class AWSSNSService implements SendSMSUsecase { +public class SMSService implements SendCertificationCodeUsecase, VerifyCodeUsecase { private final AWSConfig awsConfig; + private final CertificationCodePort certificationCodePort; + + @Override + public void verifyCertificationCode(CheckSMSDto checkSMSDto) { + String phone = checkSMSDto.getPhone(); + String code = checkSMSDto.getCode(); + + String originalCode = certificationCodePort.findCode(phone).getCode(); + if (code.equals(originalCode)) { + // 이제 그 redis에 저장 => 인증 redis + }else{ + throw new CustomException(ErrorCodeEnum.CODE_IS_NOT_VALID); // 코드가 일치 하지 않음 + } + + } + + /** + * 코드 발급 받고, 전송 + * @param phone + * @return + */ @Override public PublishResult send(String phone) { PublishResult result = null; StringBuilder koreaPhone = new StringBuilder("+82"); koreaPhone.append(phone); - String region = awsConfig.getAwsRegion(); try { - + String message = "[여행파티]인증번호입니다 아래 6글자를 입력해주세요\n"; + String code = generateRandomSixDigitNumber(); + StringBuilder newMessage = new StringBuilder(message); + newMessage.append(code); BasicAWSCredentials awsCreds = new BasicAWSCredentials(awsConfig.getAwsAccessKey(), awsConfig.getAwsSecretKey()); AmazonSNSClientBuilder builder = @@ -56,9 +83,10 @@ public PublishResult send(String phone) { .withStringValue("Promotional").withDataType("String")); result = this.sendSMSMessage(sns, - generateRandomSixDigitNumber(), + newMessage.toString(), koreaPhone.toString(), smsAttributes); + certificationCodePort.saveCode(phone,code); } catch (Exception ex) { log.error("The sms was not sent."); @@ -75,11 +103,11 @@ public PublishResult send(String phone) { */ private String generateRandomSixDigitNumber() { Random random = new Random(); - String message = "[여행파티]인증번호입니다 아래 6글자를 입력해주세요\n"; - StringBuilder newMessage = new StringBuilder(message); + int randomNumber = random.nextInt(1000000); // 0~ 999999 사이 랜덤 숫자 - newMessage.append(String.format("%06d", randomNumber)); - return newMessage.toString(); + return String.format("%06d", randomNumber); +// newMessage.append(); +// return newMessage.toString(); } private PublishResult sendSMSMessage(AmazonSNS sns, String message, String phone, Map messageAttributeValueMap) { diff --git a/src/main/java/com/example/api/user/adapter/in/rest/UserController.java b/src/main/java/com/example/api/user/adapter/in/rest/UserController.java index 3b1c421..a29998c 100644 --- a/src/main/java/com/example/api/user/adapter/in/rest/UserController.java +++ b/src/main/java/com/example/api/user/adapter/in/rest/UserController.java @@ -4,10 +4,7 @@ import com.example.api.matching.application.port.in.FindMatchingUsecase; import com.example.api.matching.application.port.in.MatchingApplicationUsecase; import com.example.api.matching.dto.FindMatchingDto; -import com.example.api.user.application.port.in.DeleteUserUsecase; -import com.example.api.user.application.port.in.FindUserUsecase; -import com.example.api.user.application.port.in.RecommendedMatchingUsecase; -import com.example.api.user.application.port.in.SaveUserUsecase; +import com.example.api.user.application.port.in.*; import com.example.api.user.dto.CreateUserDto; import com.example.api.user.dto.FindUserDto; import com.example.api.user.dto.UpdateUserDto; @@ -17,16 +14,13 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.validation.BindingResult; -import org.springframework.validation.FieldError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; @RestController @RequiredArgsConstructor @@ -156,4 +150,7 @@ public void deleteAll() { public void deleteUser(@PathVariable Long userId) { deleteUserUsecase.deleteUser(userId); } + + + } \ No newline at end of file From 5cf6040df2215bffeed0e3da7d284d0dcc033497 Mon Sep 17 00:00:00 2001 From: namhyo01 Date: Tue, 17 Oct 2023 19:35:17 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat(BE):=20=EC=9D=B8=EC=A6=9D=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=B2=B4=ED=81=AC=20in=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20#111?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/PhoneCertification.java | 3 --- .../PhoneCertificationPersistence.java | 23 ++++++++++++++++++- .../persistence/VerifyPhoneCertification.java | 20 ++++++++++++++++ .../port/out/CheckVerifiedPhonePort.java | 7 ++++++ .../VerifyCertificationRepository.java | 8 +++++++ .../example/api/sms/service/SMSService.java | 3 +++ .../example/api/user/service/UserService.java | 4 +++- 7 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/example/api/sms/adapter/out/persistence/VerifyPhoneCertification.java create mode 100644 src/main/java/com/example/api/sms/application/port/out/CheckVerifiedPhonePort.java create mode 100644 src/main/java/com/example/api/sms/repository/VerifyCertificationRepository.java diff --git a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java index 1ef5bc2..c27620c 100644 --- a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java +++ b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertification.java @@ -5,9 +5,6 @@ import lombok.Getter; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.TimeToLive; -import org.springframework.data.redis.core.index.Indexed; - import java.io.Serializable; /** diff --git a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java index 6a79d66..894a270 100644 --- a/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java +++ b/src/main/java/com/example/api/sms/adapter/out/persistence/PhoneCertificationPersistence.java @@ -2,16 +2,19 @@ import com.example.api.sms.application.port.out.CertificationCodePort; +import com.example.api.sms.application.port.out.CheckVerifiedPhonePort; import com.example.api.sms.repository.PhoneCertificationRepository; import com.example.api.common.exception.CustomException; import com.example.api.common.type.ErrorCodeEnum; +import com.example.api.sms.repository.VerifyCertificationRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor -public class PhoneCertificationPersistence implements CertificationCodePort { +public class PhoneCertificationPersistence implements CertificationCodePort, CheckVerifiedPhonePort { private final PhoneCertificationRepository repository; + private final VerifyCertificationRepository verifyCertificationRepository; @Override public void saveCode(String phone, String code) { repository.save(new PhoneCertification(phone, code)); @@ -21,4 +24,22 @@ public void saveCode(String phone, String code) { public PhoneCertification findCode(String phone) { return repository.findById(phone).orElseThrow(()-> new CustomException(ErrorCodeEnum.CODE_IS_EXPIRED)); } + + /** + * 인증 번호 만료 여부 확인 + * @param phone + */ + @Override + public void findCheckedPhone(String phone) { + verifyCertificationRepository.findById(phone).orElseThrow(()-> new CustomException(ErrorCodeEnum.CODE_IS_EXPIRED)); + } + + /** + * 인증 여부 저장 + * @param phone + */ + @Override + public void saveCheckedPhone(String phone) { + verifyCertificationRepository.save(new VerifyPhoneCertification(phone)); + } } diff --git a/src/main/java/com/example/api/sms/adapter/out/persistence/VerifyPhoneCertification.java b/src/main/java/com/example/api/sms/adapter/out/persistence/VerifyPhoneCertification.java new file mode 100644 index 0000000..7354f51 --- /dev/null +++ b/src/main/java/com/example/api/sms/adapter/out/persistence/VerifyPhoneCertification.java @@ -0,0 +1,20 @@ +package com.example.api.sms.adapter.out.persistence; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; + +import java.io.Serializable; + + +/** + * 핸드폰 인증 성공시 한시간 동안 유효하게 냅둠 + */ +@Getter +@AllArgsConstructor +@RedisHash(value = "phone_check", timeToLive = 3600) // hash collection 명시 +public class VerifyPhoneCertification implements Serializable { + @Id + String phone; +} diff --git a/src/main/java/com/example/api/sms/application/port/out/CheckVerifiedPhonePort.java b/src/main/java/com/example/api/sms/application/port/out/CheckVerifiedPhonePort.java new file mode 100644 index 0000000..40a22b1 --- /dev/null +++ b/src/main/java/com/example/api/sms/application/port/out/CheckVerifiedPhonePort.java @@ -0,0 +1,7 @@ +package com.example.api.sms.application.port.out; + +public interface CheckVerifiedPhonePort { + void findCheckedPhone(String phone); + + void saveCheckedPhone(String phone); +} diff --git a/src/main/java/com/example/api/sms/repository/VerifyCertificationRepository.java b/src/main/java/com/example/api/sms/repository/VerifyCertificationRepository.java new file mode 100644 index 0000000..9a1387f --- /dev/null +++ b/src/main/java/com/example/api/sms/repository/VerifyCertificationRepository.java @@ -0,0 +1,8 @@ +package com.example.api.sms.repository; + +import com.example.api.sms.adapter.out.persistence.VerifyPhoneCertification; +import org.springframework.data.repository.CrudRepository; + +public interface VerifyCertificationRepository extends CrudRepository { + +} diff --git a/src/main/java/com/example/api/sms/service/SMSService.java b/src/main/java/com/example/api/sms/service/SMSService.java index 3e3bd15..7a1e08f 100644 --- a/src/main/java/com/example/api/sms/service/SMSService.java +++ b/src/main/java/com/example/api/sms/service/SMSService.java @@ -15,6 +15,7 @@ import com.example.api.sms.application.port.in.VerifyCodeUsecase; import com.example.api.sms.application.port.out.CertificationCodePort; import com.example.api.common.config.AWSConfig; +import com.example.api.sms.application.port.out.CheckVerifiedPhonePort; import com.example.api.sms.dto.CheckSMSDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -31,6 +32,7 @@ public class SMSService implements SendCertificationCodeUsecase, VerifyCodeUsecase { private final AWSConfig awsConfig; private final CertificationCodePort certificationCodePort; + private final CheckVerifiedPhonePort checkVerifiedPhonePort; @Override public void verifyCertificationCode(CheckSMSDto checkSMSDto) { @@ -40,6 +42,7 @@ public void verifyCertificationCode(CheckSMSDto checkSMSDto) { String originalCode = certificationCodePort.findCode(phone).getCode(); if (code.equals(originalCode)) { // 이제 그 redis에 저장 => 인증 redis + checkVerifiedPhonePort.saveCheckedPhone(phone); }else{ throw new CustomException(ErrorCodeEnum.CODE_IS_NOT_VALID); // 코드가 일치 하지 않음 } diff --git a/src/main/java/com/example/api/user/service/UserService.java b/src/main/java/com/example/api/user/service/UserService.java index e3c2d08..99fb901 100644 --- a/src/main/java/com/example/api/user/service/UserService.java +++ b/src/main/java/com/example/api/user/service/UserService.java @@ -4,6 +4,7 @@ import com.example.api.common.exception.CustomException; import com.example.api.common.type.ErrorCodeEnum; import com.example.api.common.utils.CustomBase64Utils; +import com.example.api.sms.application.port.out.CheckVerifiedPhonePort; import com.example.api.social.adapter.out.persistence.SocialEntity; import com.example.api.user.adapter.out.persistence.UserEntity; import com.example.api.user.adapter.out.persistence.UserMapperInterface; @@ -37,10 +38,11 @@ public class UserService implements SaveUserUsecase, FindUserUsecase, DeleteUser private final FindUserPort findUserPort; private final DeleteUserPort deleteUserPort; private final FindSocialPort findSocialPort; - + private final CheckVerifiedPhonePort checkVerifiedPhonePort; @Override @Transactional public void createUser(CreateUserDto userDto) { + checkVerifiedPhonePort.findCheckedPhone(userDto.getPhone()); // 인증여부 검증 추가 SocialEntity social = findSocialPort.findSocialUser(CustomBase64Utils.getBase64DecodeString(userDto.getSocialEmail()), CustomBase64Utils.getBase64DecodeString(userDto.getProvider())).orElseThrow(()->new CustomException(ErrorCodeEnum.LOGIN_IS_NOT_DONE)); userDto.setSocialId(social.getSocialId()); saveUserPort.createUser(userMapper.toDomain(userDto));