팀프로젝트_PetHarmony

🌈 아이디 찾기_SMS API(coolsms)

이채림 2024. 8. 12. 17:54

아이디를 찾을 때

1. 사용자는 전화번호를 입력한 후 [인증번호 요청] 버튼을 누른다.

2. 서버는 전화번호가 DB에 있는지 확인한다.

2-1. 전화번호가 존재할 때 : "인증번호가 전송되었습니다."

2-2. 전화번호가 존재하지 않을 때 : "가입되지 않은 번호입니다."

2-3. 전화번호는 존재하지만 요청을 실패했을 때 :"인증번호 전송에 실패하였습니다."

3. 인증번호를 사용자 번호로 보낸다. (문자 인증 API 사용)

4. 사용자가 입력한 인증번호와 서버에서 보낸 인증번호가 같으면 아이디와 가입일자를 알려준다.

 

문자 인증 API를 찾다가 Spring과 연동이 되는 coolsms를 알게 되었다.

coolsms

https://coolsms.co.kr/

 

세상에서 가장 안정적이고 빠른 메시지 발송 플랫폼 - 쿨에스엠에스

손쉬운 결제 전용계좌, 신용카드, 계좌이체 등 국내 결제 뿐만 아니라 해용신용카드로 한번의 카드번호 등록으로 자동충전까지 지원합니다. 전용계좌, 신용카드, 계좌이체 등 다양한 결제 방식

coolsms.co.kr

 

회원가입을 하면 서비스 튜토리얼로 들어온다.

그 중 우리에게 필요한 카테고리는 개발 / 연동 이다.

[API Key 관리] > API Key 목록 - 새로운 API KEY

 

[SDK 다운로드] > Java용 SDK > Github Repo [🔗] https://github.com/coolsms/coolsms-java-examples

 

나는 gradle 프로젝트를 진행하고 있기 때문에 아래와 같이 작업했다.

 

Spring Boot 연동 (gradle)

1) build.gradle

dependencies {
	implementation 'net.nurigo:sdk:4.3.0'
}

 

2) application.properties

# coolsms
coolsms.api.key="내 API KEY"
coolsms.api.secret="내 API SECRET"


petharmony.phone.number="발신번호"

 

properties에는 민감한 내용이 있기 때문에 .gitignore에 설정하여 공개되지 않도록 한다.

3) .gitignore

### IntelliJ IDEA ###
application.properties

 

4-0) SmsUtil

// coolsms API
final DefaultMessageService messageService;

public ExampleController() {
        // 반드시 계정 내 등록된 유효한 API 키, API Secret Key를 입력해주셔야 합니다!
        this.messageService = NurigoApp.INSTANCE.initialize("INSERT_API_KEY", "INSERT_API_SECRET_KEY", "https://api.coolsms.co.kr");
}
    
/**
* 단일 메시지 발송 예제
*/
@PostMapping("/send-one")
public SingleMessageSentResponse sendOne() {
        Message message = new Message();
        // 발신번호 및 수신번호는 반드시 01012345678 형태로 입력되어야 합니다.
        message.setFrom("발신번호 입력");
        message.setTo("수신번호 입력");
        message.setText("한글 45자, 영자 90자 이하 입력되면 자동으로 SMS타입의 메시지가 추가됩니다.");

        SingleMessageSentResponse response = this.messageService.sendOne(new SingleMessageSendingRequest(message));
        System.out.println(response);

        return response;
}

coolsms에서 제공하는 sendOne()을 사용할 것이다.

 

외부 API 호출 로직도 내 기존 서비스나 컨트롤러에서 사용해도 되지만

코드 가독성이나 유지보수를 위해 따로 Util 폴더를 파서 작업하기로 한다.

 

4-1) util/SmsUtil

@Component
public class SmsUtil {
    @Value("${coolsms.api.key}")
    private String apiKey;
    @Value("${coolsms.api.secret}")
    private String apiSecretKey;
    @Value("${petharmony.phone.number}")
    private String phoneNumber;

    private DefaultMessageService messageService;

    @PostConstruct
    public void init() {
        this.messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecretKey, "https://api.coolsms.co.kr");
    }

    public SingleMessageSentResponse sendOne(String to, String certificationNumber) {
        Message message = new Message();
        message.setFrom(phoneNumber);
        message.setTo(to);
        message.setText(String.format("[PetHarmony] 인증번호 : %s를 입력해주세요.", certificationNumber));

        SingleMessageSentResponse response = this.messageService.sendOne(new SingleMessageSendingRequest(message));
        System.out.println(response);

        return response;
    }
}

 

 

 

 

위 사진과 같은 오류 메시지가 떴다.

먼저 Controller에 로그를 찍으며 확인해봤다.

@PostMapping("/api/public/send")
public ResponseEntity<String> sendingNumberToFindId(@RequestBody FindIdDTO findIdDTO) {
        try {
            System.out.println("111111");
            String resultMsg = userService.sendingNumberToFindId(findIdDTO);
            System.out.println("222222");
            return ResponseEntity.ok(resultMsg);
        } catch (Exception e) {
            System.out.println("333333");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("인증번호 전송에 실패하였습니다.");
        }
}

 로그 111111은 찍히고 222222는 찍히지 않으며, 대신 333333이 찍혔다.

userService.sendingNumberToFindId(findIdDTO) 메소드 호출에서 예외가 발생했다는 의미이다.

 

원인을 파악하기 위해

 e.printStackTrace();  // 예외 스택 트레이스를 출력

 

Caused by: net.nurigo.sdk.message.exception.NurigoBadRequestException: child "apiKey" fails because ["apiKey" length must be 16 characters long]

apiKey 길이가 16자 되어야 하는데, 그렇지 않아서 발생하는 오류라고 한다.

난 화가 났다. 당연히 16자리 정확히 복사했을텐데 왜 이러는건지,,,,,,,,,

properties를 확인한 난 죽고시펐다,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

쌍따옴표를 사용했네,,,,,,,,,, 응,,,,,,,,,,,,,,,,,,,,,,,,,,,,

난 컴공 자격이 없다

 

 

야호 성공 🩷🩷🩷🩷🩷🩷🩷🩷🩷🩷🩷🩷

 


성공한 코드

- 백엔드 (Service)

public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final AuthenticationManager authenticationManager;
    private final JwtTokenProvider jwtTokenProvider;
    private final SmsUtil smsUtil;
    private final CertificationRepository certificationRepository;

    public String sendingNumberToFindId(FindIdDTO findIdDTO) {
        Optional<User> optionalUser = userRepository.findByPhone(findIdDTO.getPhone());

        if (optionalUser.isPresent()) {
            String certificationNumber = String.format("%04d", (int)(Math.random() * 10000));
            SingleMessageSentResponse response = smsUtil.sendOne(optionalUser.get().getPhone(), certificationNumber);

            if (response != null && response.getStatusCode().equals("2000")) {
                Certification certification = Certification.builder()
                        .phone(findIdDTO.getPhone())
                        .certificationNumber(certificationNumber)
                        .build();

                certificationRepository.save(certification);
                return "인증번호가 전송되었습니다.";
            } else {
                return "인증번호 전송에 실패하였습니다.";
            }
        } else {
            return "가입되지 않은 번호입니다.";
        }
    }
}

 

- 백엔드 (Controller)

public class UserController {
    private final UserService userService;
    
    @PostMapping("/api/public/send-certification")
    public ResponseEntity<String> sendingNumberToFindId(@RequestBody FindIdDTO findIdDTO) {
        try {
            String resultMsg = userService.sendingNumberToFindId(findIdDTO);
            return ResponseEntity.ok(resultMsg);
        } catch (Exception e) {
            e.printStackTrace();  // 예외 스택 트레이스를 출력
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("인증번호 전송에 실패하였습니다.");
        }
    }
}