이번 편에서는 1편에서 세팅한 내용들을 바탕으로 스프링 부트 프로젝트에서 알림톡을 발송 코드 작성을 다룹니다.


NCP 알림톡 API 가이드

https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2

 

알림톡 API

 

api.ncloud-docs.com

공식 docs 링크는 위와 같으며 위 문서를 참고하여 작성했습니다!

 

1. 메시지 발송 API 기본 정보

얼핏보면 내용이 많아 복잡해보일 수 있지만 해당 요청 주소로 필수로 요구하는 정보들을 채워 보내면 됩니다!

아래는 필요한 정보들을 정리한 것으로 마지막에 코드 예시로도 보여드리겠습니다.

 

요청 URL

POST https://sens.apigw.ntruss.com/alimtalk/v2/services/{serviceId}/messages

필수 path variables

serviceId String

 

필수 API header 

Content-Type 요청 Body Content Type을 application/json으로 지정 (POST)
x-ncp-apigw-timestamp - 1970년 1월 1일 00:00:00 협정 세계시(UTC)부터의 경과 시간을 밀리초(Millisecond)로 나타냄
- API Gateway 서버와 시간 차가 5분 이상 나는 경우 유효하지 않은 요청으로 간주
x-ncp-iam-access-key 포탈 또는 Sub Account에서 발급받은 Access Key ID
x-ncp-apigw-signature-v2 - 위 예제의 Body를 Access Key Id와 맵핑되는 SecretKey로 암호화한 서명
- HMAC 암호화 알고리즘은 HmacSHA256 사용

요청 body (필수+옵션)

{
    "plusFriendId":"string",
    "templateCode":"string",
    "messages":[
        {
            "countryCode":"string",
            "to":"string",
            "title":"string",
            "content":"string",
            "headerContent":"string",
            "itemHighlight":{
                "title":"string",
                "description":"string"
            },
            "item":{
                "list":[
                    {
                        "title":"string",
                        "description":"string"
                    }
                ],
                "summary":{
                    "title":"string",
                    "description":"string"
                }
            },
            "buttons":[
                {
                    "type":"string",
                    "name":"string",
                    "linkMobile":"string",
                    "linkPc":"string",
                    "schemeIos":"string",
                    "schemeAndroid":"string"
                }
            ],
            "useSmsFailover": "boolean",
            "failoverConfig": {
                "type": "string",
                "from": "string",
                "subject": "string",
                "content": "string"
            }
        }
    ],
    "reserveTime": "yyyy-MM-dd HH:mm",
    "reserveTimeZone": "string",
    "scheduleCode": "string"
}

 

plusFriendId Mandatory  String 카카오톡 채널명
((구)플러스친구 아이디)
 
templateCode Mandatory String 템플릿 코드  
messages Mandatory Object 메시지 정보 - 아래 항목 참조 (messages.XXX)
- 최대 100개
messages.countryCode Optional String 수신자 국가번호  
messages.to Mandatory String 수신자번호  
messages.title Optional String 알림톡 강조표시 내용 강조 표기 유형의 템플릿에서만 사용 가능
messages.content Mandatory String 알림톡 메시지 내용  
messages.headerContent Optional String 알림톡 헤더 내용 - 아이템 리스트 유형의 템플릿에서만 사용 가능
- 최대 16자까지 입력 가능
messages.itemHighlight Optional Object 아이템 하이라이트 아이템 리스트 유형의 템플릿에서만 사용 가능
messages.itemHighlight.title Mandatory String 아이템 하이라이트 제목 - 아이템 리스트 유형의 템플릿에서만 사용 가능
이미지가 없는 경우
- 최대 30자까지 입력 가능 (2줄)
- 1줄은 15자까지 입력 가능
이미지가 있는 경우
- 최대 21자까지 입력 가능 (2줄)
- 1줄은 10자까지 입력 가능
- 2줄 초과 시 말줄임 처리
messages.itemHighlight.description Mandatory String 아이템 하이라이트 설명 - 아이템 리스트 유형의 템플릿에서만 사용 가능
이미지가 없는 경우
- 최대 19자까지 입력 가능 (1줄)
이미지가 있는 경우
- 최대 13자까지 입력 가능 (1줄)
- 1줄 초과 시 말줄임 처리
messages.item Optional Object 아이템 리스트 아이템리스트 유형의 템플릿에서만 사용 가능
messages.messages.item.list Mandatory Array of Object 아이템 리스트 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최소 2개 이상, 최대 10개
messages.messages.item.list.title Mandatory String 아이템 리스트 제목 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 6자까지 입력 가능
messages.messages.item.list.description Mandatory String 아이템 리스트 설명 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 23자까지 입력 가능
messages.messages.summary Optional Object 아이템 요약 정보 아이템리스트 유형의 템플릿에서만 사용 가능
messages.messages.summary.title Mandatory String 아이템 요약 제목 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 최대 6자까지 입력 가능
messages.messages.summary.description Mandatory String 아이템 요약 설명 - 아이템리스트 유형의 템플릿에서만 사용 가능
- 허용되는 문자: 통화기호(유니코드 통화기호, 元, 円, 원), 통화코드 (ISO 4217), 숫자, 콤마, 소수점, 공백
- 소수점 2자리까지 허용
- 최대 23자까지 입력 가능
messages.buttons Optional Array of Object 알림톡 메시지 버튼 아래 템플릿 버튼 정보 참조
messages.buttons.type Mandatory String 버튼 Type 아래 템플릿 버튼 정보 참조
messages.buttons.name Mandatory String 버튼명 아래 템플릿 버튼 정보 참조
messages.useSmsFailover Optional Boolean SMS Failover 사용 여부 - Failover가 설정된 카카오톡 채널에서만 사용 가능
- 기본: 카카오톡 채널의 Failover 설정 여부를 따름
messages.failoverConfig Optional Object Failover 설정 아래 항목 참조
messages.failoverConfig.type Optional String Failover SMS 메시지 Type - SMS 또는 LMS
- 기본: content 길이에 따라 자동 적용 (90 bytes 이하 SMS, 초과 LMS)
messages.failoverConfig.from Optional String Failover SMS 발신번호 - 기본: Failover 설정 시 선택한 발신번호
- 승인되지 않은 발신번호 사용시 Failover 동작 안함
messages.failoverConfig.subject Optional String Failover SMS 제목 - LMS type으로 동작할 때 사용
- 기본: 카카오톡 채널명
messages.failoverConfig.content Optional String Failover SMS 내용 기본: 알림톡 메시지 내용 (버튼 제외)
reserveTime Optional String 예약 일시 메시지 발송 예약 일시 (yyyy-MM-dd HH:mm)
reserveTimeZone Optional String 예약 일시 타임존 - 예약 일시 타임존 (기본: Asia/Seoul)
- 지원 타임존 목록
* TZ database name 값 사용
scheduleCode Optional String 스케줄 코드 등록하려는 스케줄 코드

응답 status

202 Accepted (발송 요청 완료)
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
500 Internal Server Error

응답 body

{
    "requestId":"string",
    "requestTime":"string",
    "statusCode":"string",
    "statusName":"string",
    "messages":[
        {
            "messageId":"string",
            "countryCode":"string",
            "to":"string",
            "content":"string",
            "requestStatusCode":"string",
            "requestStatusName":"string",
            "requestStatusDesc":"string",
            "useSmsFailover":"boolean"
        }
    ]
}
requestId Mandatory String 발송 요청 아이디  
requestTime Mandatory DateTime 발송 요청 시간  
statusCode Mandatory String 요청 상태 코드 - 성공: 202
- 실패: 그 외
- HTTP Status 규격을 따름
statusName Mandatory String 요청 상태명 - 성공: success
- 처리 중: processing
- 예약 중: reserved
- 스케줄 중: scheduled
- 실패: fail
messages.messageId Mandatory String 메시지 아이디  
messages.countryCode Optional String 수신자 국가번호  
messages.to Mandatory String 수신자 번호  
messages.content Mandatory String 알림톡 메시지 내용  
messages.requestStatusCode Mandatory String 발송요청 상태 코드 - 성공: A000
- 실패: 그 외 코드(Desc 항목에 실패 사유가 명시)
messages.requestStatusName Mandatory String 발송 요청 상태명 - 성공: success
- 실패: fail
messages.requestStatusDesc Mandatory String 발송 요청 상태
내용
 
messages.useSmsFailover Mandatory Boolean SMS Failover
사용 여부

2. 메시지 발송 코드 작성 예시

크게 4가지 단계로 나눠볼 수 있습니다.

(1) 서명 생성

(2) 헤더 생성 

(3) 바디 생성

(4) api 요청

 

전체 코드 예시

변수들 설명 및 유의 사항

plusFriendId: 카톡플러스친구채널 id. @를 붙여서 넣어주세요.

                        ex) @chaech2929

to: 알림톡을 보낼 전화번호 String 타입

templateCode: 알림톡 템플릿 등록 시 설정했던 식별 코드

content: 알림톡 템플릿 등록 시 설정했던 글 내용과 동일하게 넣어주세요. 변수 값은 코드에서 사용하던 변수에 맞춰 알맞은 걸로 채워서                  넣어주시면 됩니다.

                ex) String content = userName + "님, 두둥에 가입하신 것을 환영합니다!\n"

 

signature 생성 시 사용하는 timestamp 값과 api 요청시 헤더에 넣는 timestamp 값이 동일해야합니다.

 

가장 기본 옵션으로만 이루어진 메시지를 기준으로 작성했습니다. 

package band.gosrock.infrastructure.config.AlilmTalk;


import java.io.IOException;
import java.sql.Timestamp;
import java.time.Instant;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
@Configuration
public class AlimTalk {
    private final String serviceID;
    private final String ncpAccessKey;
    private final String ncpSecretKey;
    private final String plusFriendId;

    public AlimTalk(
            @Value("${ncp.service-id}") String serviceID,
            @Value("${ncp.access-key}") String ncpAccessKey,
            @Value("${ncp.secret-key}") String ncpSecretKey,
            @Value("${ncp.plus-friend-id}") String plusFriendId) {
        this.serviceID = serviceID;
        this.ncpAccessKey = ncpAccessKey;
        this.ncpSecretKey = ncpSecretKey;
        this.plusFriendId = plusFriendId;
    }

    public void sendAlimTalk(String to, String templateCode, String content) {
        String alimTalkSendRequestUrl =
                "https://sens.apigw.ntruss.com/alimtalk/v2/services/" + serviceID + "/messages";
        String alimTalkSignatureRequestUrl = "/alimtalk/v2/services/" + serviceID + "/messages";
        CloseableHttpClient httpClient = null;
        try {
            // signature 생성
            String[] signatureArray =
                    makePostSignature(ncpAccessKey, ncpSecretKey, alimTalkSignatureRequestUrl);

            // http 통신 객체 생성
            httpClient = HttpClients.createDefault(); // http client 생성
            HttpPost httpPost = new HttpPost(alimTalkSendRequestUrl); // post 메서드와 URL 설정

            // 헤더 설정
            httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
            httpPost.setHeader("x-ncp-iam-access-key", ncpAccessKey);
            httpPost.setHeader("x-ncp-apigw-timestamp", signatureArray[0]);
            httpPost.setHeader("x-ncp-apigw-signature-v2", signatureArray[1]);

            // body 설정
            JSONObject msgObj = new JSONObject();
            msgObj.put("plusFriendId", plusFriendId);
            msgObj.put("templateCode", templateCode);

            JSONObject messages = new JSONObject();
            messages.put("to", to);
            messages.put("content", content);

            JSONArray messageArray = new JSONArray();
            messageArray.put(messages);
            msgObj.put("messages", messageArray);

            // api 전송 값 http 객체에 담기
            httpPost.setEntity(new StringEntity(msgObj.toString(), "UTF-8"));
            // api 호출
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

            // 응답 결과
            String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
            System.out.println(result);

        } catch (Exception ex) {
            // TODO: 에러 처리
            ex.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String[] makePostSignature(String accessKey, String secretKey, String url) {
        String[] result = new String[2];
        try {
            String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
            String space = " "; // space
            String newLine = "\n"; // new line
            String method = "POST"; // method

            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);

            result[0] = timeStamp;
            result[1] = encodeBase64String;

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    public String[] makeGetSignature(String accessKey, String secretKey, String url) {
        String[] result = new String[2];
        try {
            String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
            String space = " "; // space
            String newLine = "\n"; // new line
            String method = "GET"; // method

            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);

            result[0] = timeStamp;
            result[1] = encodeBase64String;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}

 

 

참고 사이트

https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2

https://honeystorage.tistory.com/191

https://wildeveloperetrain.tistory.com/77

https://blog.naver.com/PostView.naver?blogId=n_cloudplatform&logNo=222475388473&parentCategoryNo=&categoryNo=12&viewDate=&isShowPopularPosts=false&from=postView 

+ Recent posts