들어가기 전에
필자가 환율 정보 관련 프로젝트를 만든 이유는 원하는 환율에 거래를 하기 위해서 제작하였습니다. 지난 글에서 Scheduler를 활용하여 매 10초마다 환율에 대한 정보를 가져오는 프로젝트를 만들었습니다. '그렇다면 원하는 환율 정보가 들어왔을 때 나에게 알려줄 수 있는 방법이 뭐가 있을까?' 고민을 하다가 메시지를 통해서 제공을 해주고 싶었습니다. 물론 카카오톡 알림 API, 위젯 알림 등 도 하나의 방법일 수 있습니다.
일단 개인 프로젝트이기도 하니 무엇보다도 API 선정에 중요한 것은 "무료" 였습니다. 무료 SMS API를 검색하게 되었고, 현재 제공해주고 있는 무료 SMS API 서비스는 네이버 클라우드에서 제공해 주는 Simple & Easy Notification Service 였습니다.
실제로 네이버 클라우드에서 제공해 주는 SENS API를 제외하고는 다른 무료 문자 발송 API를 발견하지 못했습니다. 다른 것들이 있다면 언제든지 댓글 부탁드립니다.
네이버 클라우드에서 제공해주는 API 들은 예제들도 잘 작성되어 있고 개발자 입장에서 접근하기 굉장히 쉽고, 편리하게 사용할 수 있습니다.
Simple & Easy Notification Service의 SMS(LMS) API
짧은 문자 메시지 서비스인 SMS(Short Message Service)와 긴 문자 메시지 서비스인 LMS(Long Message Service)을 사용하여 알림 메시지를 전송할 수 있습니다. 네이버 클라우드 플랫폼 웹 콘솔에서 미리 메시지 전송 테스트를 하거나 전송 이력을 검색하여 조회할 수 있습니다.
SMS는 50건 이하까지, LMS는 10건 이하까지 무료로 제공해주고 있습니다.
그렇다면 왜 2023년 10월 18일 이후에는 불가라고 적어놓았을까요?
어뷰징 고객의 악의적인 스팸 발송 때문입니다. 한 고객당 50건으로 제한을 해놓고 있지만 고객들이 여러 명이 있다면 수 백건, 수 천건이 넘어갑니다. (설마 내 휴대폰에 여러 스팸 문자들도..?)
아래 이미지는 NAVAER CLOUD 에서 공지사항으로 올리신 글입니다.
[수정 공지] Simple and Easy Notification Service 및 Cloud Outbound Mailer 이용 제한 실행 안내 (10/18)
API 사이트와 여러 블로그에서 사용하신 예제들을 참고하면서 코드를 만들었는데, 네이버 클라우드 홈페이지에서 서비스를 만들려고 보니 오류가 났었고 공지를 확인했습니다. 만약에 언젠가 해당 서비스가 개인 사용자에게 풀리게 되면, 해당 코드로 작성을 할 예정입니다.
application-API.properties
이전에 말씀드린 것처럼 API 관련 Key 들은 별도로 관리하기 위한 파일입니다. Naver Cloud에 접근하기 위한 값들을 설정합니다.
# Naver-Cloud SMS API
ncloud-sms-serviceId=Service-ID
ncloud-sms-accessKey=Access-Key
ncloud-sms-secretKey=Secret-Key
ncloud-sms-fromPhoneNumber =01012341234
DTO 객체 생성
SMS 발송을 위한 객체들을 생성합니다. SMS API 페이지에서 JSON 데이터를 보여주고 있어서 동일하게 객체를 생성해 주면 됩니다.
JSON 객체
요청 Body
응답 Body
SmsMessageDTO
요청 Body 의 messages에 대한 내용에 관련된 객체입니다.
messages 내 subject 의 경우에는 LMS, MMS에서만 사용이 가능합니다. 즉 SMS에서는 to, content 만 사용합니다.
package com.main.exchangeBatch.dto;
import lombok.*;
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Builder
public class SmsMessageDTO {
String to;
String content;
}
SmsRequestDTO
요청 Body 에 맞게 객체를 구성해 줍니다.
package com.main.exchangeBatch.dto;
import lombok.*;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class SmsRequestDTO {
String type;
String contentType;
String countryCode;
String from;
String content;
List<SmsMessageDTO> messages;
}
SmsResponseDTO
응답 Body에 맞게 객체를 구성해 줍니다.
package com.main.exchangeBatch.dto;
import lombok.*;
import java.time.LocalDateTime;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString
public class SmsResponseDTO {
String requestId;
LocalDateTime requestTime;
String statusCode;
String statusName;
}
SmsService
본격적으로 서비스 코드를 작성해 보겠습니다.
변수 값 세팅
application-API.properties 에 설정한 값을 변수 값으로 세팅합니다.
@Value("${ncloud-sms-accessKey}")
private String accessKey;
@Value("${ncloud-sms-secretKey}")
private String secretKey;
@Value("${ncloud-sms-serviceId}")
private String serviceId;
@Value("${ncloud-sms-fromPhoneNumber}")
private String fromPhoneNumber;
makeSignature method
API를 사용하기 위해서는 Signature을 생성 해야합니다. makeSignature method 의 경우에는 Ncloud API 페이지에서 사용 언어별로 생성하는 방법을 알려주고 있습니다. 아래 페이지를 참고해서 생성하시면 됩니다.
public String makeSignature(Long currentTime) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
String space = " "; // one space
String newLine = "\n"; // new line
String method = "POST";
String url = "/sms/v2/services/" + this.serviceId + "/messages";
String timestamp = currentTime.toString();
String accessKey = this.accessKey;
String secretKey = this.secretKey;
String message = new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timestamp)
.append(newLine)
.append(accessKey)
.toString();
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
String encodeBase64String = Base64.encodeBase64String(rawHmac);
return encodeBase64String;
}
sendMessage method
메서드 명 그대로 메시지를 보내는 로직입니다. 위 함수에서 생성한 Signature 을 헤더에 담아야 합니다. 배치에서도 사용했던 Webclient를 사용해서 서비스를 구축했습니다.
매개 변수는 아래 2개입니다.
- String to - 수신 번호
- String content - 보내실 내용
public boolean sendMessage(String to, String content) throws JsonProcessingException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String smsURL = "https://sens.apigw.ntruss.com/sms/v2/services/"+ serviceId +"/messages";
Long currentTime = System.currentTimeMillis();
// SmsMessageDTO setting
SmsMessageDTO smsMessageDTO = new SmsMessageDTO(to, content);
List<SmsMessageDTO> messages = new ArrayList<>();
messages.add(smsMessageDTO);
SmsRequestDTO smsRequestDTO = SmsRequestDTO.builder()
.type("SMS")
.contentType("COMM")
.countryCode("82")
.from(fromPhoneNumber)
.content(smsMessageDTO.getContent())
.messages(messages)
.build();
String body = new ObjectMapper().writeValueAsString(smsRequestDTO);
webClient.post().uri(smsURL)
.contentType(MediaType.APPLICATION_JSON)
.header("x-ncp-apigw-timestamp", currentTime.toString())
.header("x-ncp-iam-access-key", accessKey)
.header("x-ncp-apigw-signature-v2", makeSignature(currentTime))
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(body))
.retrieve()
.bodyToMono(SmsResponseDTO.class).block();
return true;
}
SmsService 전체 코드
package com.main.exchangeBatch.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.main.exchangeBatch.dto.SmsMessageDTO;
import com.main.exchangeBatch.dto.SmsRequestDTO;
import com.main.exchangeBatch.dto.SmsResponseDTO;
import org.springframework.beans.factory.annotation.Value;
import org.apache.commons.codec.binary.Base64;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class SmsService {
private WebClient webClient;
@Value("${ncloud-sms-accessKey}")
private String accessKey;
@Value("${ncloud-sms-secretKey}")
private String secretKey;
@Value("${ncloud-sms-serviceId}")
private String serviceId;
@Value("${ncloud-sms-fromPhoneNumber}")
private String fromPhoneNumber;
public boolean sendMessage(String to, String message) throws JsonProcessingException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String smsURL = "https://sens.apigw.ntruss.com/sms/v2/services/"+ serviceId +"/messages";
Long currentTime = System.currentTimeMillis();
// SmsMessageDTO setting
SmsMessageDTO smsMessageDTO = new SmsMessageDTO(to, message);
List<SmsMessageDTO> messages = new ArrayList<>();
messages.add(smsMessageDTO);
SmsRequestDTO smsRequestDTO = SmsRequestDTO.builder()
.type("SMS")
.contentType("COMM")
.countryCode("82")
.from(fromPhoneNumber)
.content(smsMessageDTO.getContent())
.messages(messages)
.build();
String body = new ObjectMapper().writeValueAsString(smsRequestDTO);
webClient.post().uri(smsURL)
.contentType(MediaType.APPLICATION_JSON)
.header("x-ncp-apigw-timestamp", currentTime.toString())
.header("x-ncp-iam-access-key", accessKey)
.header("x-ncp-apigw-signature-v2", makeSignature(currentTime))
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(body))
.retrieve()
.bodyToMono(SmsResponseDTO.class).block();
return true;
}
public String makeSignature(Long currentTime) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
String space = " "; // one space
String newLine = "\n"; // new line
String method = "POST";
String url = "/sms/v2/services/" + this.serviceId + "/messages";
String timestamp = currentTime.toString();
String accessKey = this.accessKey;
String secretKey = this.secretKey;
String message = new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timestamp)
.append(newLine)
.append(accessKey)
.toString();
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
String encodeBase64String = Base64.encodeBase64String(rawHmac);
return encodeBase64String;
}
}
@Test 코드
public class SmsServiceTest {
@Autowired
private SmsService smsService;
@Test
void sendMessage() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
smsService.sendMessage("01077777777","exchageBatch message test");
}
}
Error: package org.apache.commons.codec.binary.does.not.exist #14
import org.apache.commons.codec.binary.Base64; 설정을 했지만, Base64에 오류가 발생했습니다.
해결 방안
아래 코드를 build.gradle 파일에 추가하면 해결됩니다.
implementation group: 'commons-codec', name: 'commons-codec', version: '1.7'
스택오버플로우 페이지에 저와 같은 오류에 대한 질문을 발견할 수 있었고, 출처를 남깁니다.
GitHub Code
https://github.com/SeoYounSeok/exchangeBatch
차후 개발 예정 사항
먼저 말씀드린 것처럼 2023년 10월 18일 부로 해당 서비스(SENS)가 제공되지 않고 있습니다. 테스트 코드를 작성하긴 했지만, 실행할 수 없는 상황이기 때문에 실행이 될 때 네이버 클라우드 SMS API 서비스 생성 및 사용법까지 함께 작성할 예정입니다.
참고 페이지
네이버 클라우드 플랫폼 유저 API 활용 방법 - 1편
'개발 > Spring Batch' 카테고리의 다른 글
[Spring Batch] 환율 정보 API 를 간단한 배치 스케줄러 추가 - 예제편 (2) | 2023.11.24 |
---|---|
[Spring Batch] 환율 정보 Open API를 활용한 간단한 배치 만들기 - 예제편 (3) | 2023.11.23 |
[Spring Batch] 대용량 데이터 처리를 위해 Batch 를 알아보자 - 이론편 (0) | 2023.11.15 |