본문 바로가기
개발/Spring Batch

[Spring Batch] 환율 정보 SMS API 로 문자 보내기 (2023.10.18 이후 불가) - 예제편

by seopport 2023. 12. 2.
728x90
반응형

들어가기 전에

필자가 환율 정보 관련 프로젝트를 만든 이유는 원하는 환율에 거래를 하기 위해서 제작하였습니다. 지난 글에서 Scheduler를 활용하여 매 10초마다 환율에 대한 정보를 가져오는 프로젝트를 만들었습니다. '그렇다면 원하는 환율 정보가 들어왔을 때 나에게 알려줄 수 있는 방법이 뭐가 있을까?' 고민을 하다가 메시지를 통해서 제공을 해주고 싶었습니다. 물론 카카오톡 알림 API, 위젯 알림 등 도 하나의 방법일 수 있습니다.

 

일단 개인 프로젝트이기도 하니 무엇보다도 API 선정에 중요한 것은 "무료" 였습니다. 무료 SMS API를 검색하게 되었고, 현재 제공해주고 있는 무료 SMS API 서비스는 네이버 클라우드에서 제공해 주는 Simple & Easy Notification Service 였습니다. 

 

실제로 네이버 클라우드에서 제공해 주는 SENS API를 제외하고는 다른 무료 문자 발송 API를 발견하지 못했습니다. 다른 것들이 있다면 언제든지 댓글 부탁드립니다.

 

네이버 클라우드에서 제공해주는 API 들은 예제들도 잘 작성되어 있고 개발자 입장에서 접근하기 굉장히 쉽고, 편리하게 사용할 수 있습니다.

 

Spring Batch Logo
Spring Batch Logo

 

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)

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

 

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

JSON DATA
JSON DATA

 

응답 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 페이지에서 사용 언어별로 생성하는 방법을 알려주고 있습니다. 아래 페이지를 참고해서 생성하시면 됩니다.

 

Ncloud API

 

Ncloud API

 

api.ncloud-docs.com

 

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'

 

스택오버플로우 페이지에 저와 같은 오류에 대한 질문을 발견할 수 있었고, 출처를 남깁니다.

 

stackoverflow Q&A

 

Error: package org.apache.commons.codec.binary does not exist

I've added commons-codec-1.4.jar in my project file and also added import org.apache.commons.codec.binary.Base64; But whenever I build it it gives me an error:package org.apache.commons.codec.bi...

stackoverflow.com

 

GitHub Code

https://github.com/SeoYounSeok/exchangeBatch

 

GitHub - SeoYounSeok/exchangeBatch: THE EXPORT-IMPORT BANK OF KOREA's Open API Exchange Rate Batch

THE EXPORT-IMPORT BANK OF KOREA's Open API Exchange Rate Batch - GitHub - SeoYounSeok/exchangeBatch: THE EXPORT-IMPORT BANK OF KOREA's Open API Exchange Rate Batch

github.com

 

차후 개발 예정 사항

먼저 말씀드린 것처럼 2023년 10월 18일 부로 해당 서비스(SENS)가 제공되지 않고 있습니다. 테스트 코드를 작성하긴 했지만, 실행할 수 없는 상황이기 때문에 실행이 될 때 네이버 클라우드 SMS API 서비스 생성 및 사용법까지 함께 작성할 예정입니다.

 

참고 페이지

SENS API

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

 

Naver Cloud SMS API 가이드

 

SMS API

 

api.ncloud-docs.com

 

네이버 클라우드 플랫폼 유저 API 활용 방법 - 1편

 

[이렇게 사용하세요!] 네이버 클라우드 플랫폼 유저 API 활용 방법 — 1편

네이버 클라우드 플랫폼 유저API를 이용하면, 콘솔에 접속하지 않고 클라우드 서비스를 이용 할 수 있습니다.

medium.com

 

개발자 되버리기 블로그

 

Springboot + NAVER S.E.N.S 보내기 (V2 헤더 세팅)

사내에서 문자 서비스를 구축해야해서 네이버 nens를 다루게 되었는데 제가 미숙한 탓에 헤더 세팅하는데 어려움이 있었습니다. (혼자 핑계로 네이버 API 헤더 세팅 어렵다고.. 꿍시렁) 부디 이 글

koogood.tistory.com

 

728x90
반응형