Spring Boot 2.7.10, JDK 1.8 기준
아래 서비스 연동하는 방법입니다.
실제 호출은 비동기 형식으로 진행됩니다.
app:
ncloud:
accessKey: {accessKey}
secretKey: {secretKey}
mail:
url: https://mail.apigw.ntruss.com/api/v1/mails
alimtalk:
url: https://sens.apigw.ntruss.com/alimtalk/v2/services/{serviceId}/messages
plusFriendId: "@{plusFriendId}"
serviceId: {serviceId}
sms:
url: https://sens.apigw.ntruss.com/sms/v2/services/{serviceId}/messages
from: "{fromPhone}"
serviceId: {serviceId}
AlimTalkReqMessageCommon.java
@Data
public class AlimTalkReqMessageCommon {
private String title;
private String description;
}
AlimTalkReqMessageItem.java
@Data
public class AlimTalkReqMessageItem {
private List<AlimTalkReqMessageCommon> list;
private AlimTalkReqMessageCommon summary;
}
AlimTalkReqMessageFailoverConfig.java
@Data
public class AlimTalkReqMessageFailoverConfig {
private String reserveTime;
private String reserveTimeZone;
private String scheduleCode;
}
AlimTalkReqMessageButton.java
@Data
public class AlimTalkReqMessageButton {
private String type;
private String name;
private String linkMobile;
private String linkPc;
private String schemeIos;
private String schemeAndroid;
}
AlimTalkReqMessage.java
@Data
public class AlimTalkReqMessage {
private String countryCode; //수신자 국가번호
private String to; //수신자번호
private String title; //알림톡 강조표시 내용
private String content; //알림톡 메시지 내용
private String headerContent; //알림톡 헤더 내용
private AlimTalkReqMessageCommon itemHighlight; //아이템 하이라이트
private AlimTalkReqMessageItem item; //아이템 리스트
private List<AlimTalkReqMessageButton> buttons; //알림톡 메시지 버튼
private boolean useSmsFailover; //SMS Failover 사용 여부
private AlimTalkReqMessageFailoverConfig failoverConfig; //Failover 설정
}
AlimTalkReqBody.java
@Data
public class AlimTalkReqBody {
private String plusFriendId; //카카오톡 채널명 ((구)플러스친구 아이디)
private String templateCode; //템플릿 코드
private List<AlimTalkReqMessage> messages; //메시지 정보
// private String reserveTime; //예약 일시
// private String reserveTimeZone; //예약 일시 타임존
// private String scheduleCode; //스케줄 코드
}
RecipientForRequest.java
@Data
public class RecipientForRequest {
private String address = null;
private String name = null;
private String type = "R";
private Object parameters = null;
}
RecipientGroupFilter.java
public class RecipientGroupFilter {
private Boolean andFilter;
private List<String> groups;
}
CreateMailRequest.java
@Data
public class CreateMailRequest {
@Required
private String senderAddress; // 발송자 Email 주소
private String senderName; // 발송자 이름
private int templateSid; // 템플릿 ID
@Required
private String title; // 메일 제목
@Required
private String body = ""; // 메일 본문
private boolean individual = true; // 개인별 발송 혹은 일반 발송 여부
private boolean confirmAndSend; //확인 후 발송 여부
private boolean advertising = false; // 광고메일여부
private Object parameters; // 치환 파라미터
private String referencesHeader; // 특정 메일을 모아서 보기 위해 네이버 메일에서 지원하는 기능
// private long reservationUtc; // 예약 발송 일시
// private String reservationDateTime; // 예약 발송 일시 (reservationUtc 값 우선)
private List<String> attachFileIds; // 첨부파일 ID 목록
@Required
private List<RecipientForRequest> recipients; // 수신자목록
private RecipientGroupFilter recipientGroupFilter; // 수신자 그룹 조합 발송 조건
private boolean useBasicUnsubscribeMsg; // 광고 메일일 경우 기본 수신 거부 문구 사용 여부
@Required
private String unsubscribeMessage; // 사용자 정의 수신 거부 문구
}
SmsContentType.java
public enum SmsContentType {
COMM, //일반 메시지
AD // 광고메시지
}
SmsFiles.java
@Getter
@Setter
public class SmsFiles {
private String fileId;
}
SmsMessage.java
@Data
public class SmsMessage {
private String to;
private String subject;
private String content;
}
SmsRequestBody.java
@Data
public class SmsRequestBody {
private String requestId;
private String requestStartTime;
private String requestEndTime;
private String completeStartTime;
private String completeEndTime;
private SmsType type;
private SmsContentType contentType;
private String countryCode; // 국가코드 (한국 82)
private String from;
private String subject;
private String content;
private List<SmsMessage> messages;
private List<SmsFiles> files;
// private String reserveTime; // 예약발송 예)yyyy-MM-dd HH:mm
// private String reserveTimeZone;
// private String scheduleCode;
}
SmsType.java
public enum SmsType {
SMS, LMS, MMS
}
NCloudService.java
@EnableAsync
@Component
@Slf4j
public class NCloudService {
@Value("${app.ncloud.mail.url}") String mailUrl;
@Value("${app.ncloud.sms.url}") String smsUrl;
@Value("${app.ncloud.alimtalk.url}") String alimtalkUrl;
@Value("${app.ncloud.alimtalk.plusFriendId}") String alimtalkId;
@Value("${app.ncloud.alimtalk.serviceId}") String alimtalkServiceId;
@Value("${app.ncloud.sms.from}") String smsFrom;
@Value("${app.ncloud.sms.serviceId}") String smsServiceId;
@Value("${app.ncloud.accessKey}") String accessKey;
@Value("${app.ncloud.secretKey}") String secretKey;
public JSONObject callApi(String obj, String urlParam) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
return callApi(obj, urlParam, "POST");
}
/**
* 이메일 전송 요청
* @param tid nCloud에 등록되어있는 템플릿 ID
* @param recipientForRequest 수신자목록
*/
@Async
public void createMailRequest(int tid, List<RecipientForRequest> recipientForRequest) throws Exception {
CreateMailRequest createMailRequest = new CreateMailRequest();
createMailRequest.setTemplateSid(tid);
createMailRequest.setSenderAddress("test@gmail.com");
createMailRequest.setRecipients(recipientForRequest);
ObjectMapper mapper = new ObjectMapper();
JSONObject response = callApi(mapper.writeValueAsString(createMailRequest), mailUrl);
}
/**
* 카카오톡 메시지 전송 요청
* @param templateCode nCloud에 등록되어있는 템플릿 ID
* @param messages 수신자 및 수신내용 목록
*/
@Async
public void sendAlimTalkForKakao(String templateCode, List<AlimTalkReqMessage> messages) throws Exception {
AlimTalkReqBody body = new AlimTalkReqBody();
body.setPlusFriendId(alimtalkId);
if(templateCode != null)
body.setTemplateCode(templateCode);
body.setMessages(messages);
ObjectMapper mapper = new ObjectMapper();
callApi(mapper.writeValueAsString(body), alimtalkUrl.replaceAll("\\{serviceId}", alimtalkServiceId));
}
public JSONObject sendSms(SmsType type, List<SmsMessage> messages) throws Exception {
return sendSms(type, messages, "82");
}
/**
* SMS 전송 요청
* @param type SMS, MMS, LMS 여부
* @param messages 수신자 및 수신내용 목록
* @param countryCode 수신국가
*/
@Async
public JSONObject sendSms(SmsType type, List<SmsMessage> messages, String countryCode) throws Exception {
SmsRequestBody body = new SmsRequestBody();
body.setType(type);
body.setContentType(SmsContentType.COMM);
body.setCountryCode(countryCode);
body.setFrom(smsFrom);
body.setSubject("null");
body.setContent("null");
body.setMessages(messages);
ObjectMapper mapper = new ObjectMapper();
JSONObject response = callApi(mapper.writeValueAsString(body), smsUrl.replaceAll("\\{serviceId}", smsServiceId));
return response;
}
/**
* SMS전송 결과 조회
* @param messageId 메시지 ID
*/
public JSONObject getSendSmsResultInfo(String messageId) throws Exception {
String url = new StringBuilder()
.append(smsUrl.replaceAll("\\{serviceId}", smsServiceId))
.append("/")
.append(messageId).toString();
return callApi(null, url, "GET");
}
public JSONObject callApi(String obj, String urlParam, String method) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
HttpURLConnection conn = null;
JSONObject responseJson = null;
URL url = new URL(urlParam);
Long timestamp = System.currentTimeMillis();
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("cache-control", "no-cache");
conn.setRequestProperty("pragma", "no-cache");
conn.setRequestProperty("x-ncp-apigw-timestamp", Long.toString(timestamp));
conn.setRequestProperty("x-ncp-iam-access-key", accessKey);
conn.setRequestProperty("x-ncp-apigw-signature-v2", makeSignature(url, method, timestamp));
conn.setDoOutput(true);
if(obj != null) {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
try {
bw.write(obj);
bw.flush();
bw.close();
} catch (IOException e) {
log.error("Error : {}", e.getMessage());
} finally {
if (bw != null) {
bw.close();
}
}
}
int responseCode = conn.getResponseCode();
if (100 <= responseCode && responseCode <= 399) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
try {
br.close();
}catch (IOException e){
log.error("Error : {}", e.getMessage());
}finally {
if(br != null){
br.close();
}
}
responseJson = new JSONObject(sb.toString());
} else{
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
StringBuilder sb = new StringBuilder();
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
try {
br.close();
}catch (IOException e){
log.error("Error : {}", e.getMessage());
}finally {
if(br != null){
br.close();
}
}
responseJson = new JSONObject(sb.toString());
}
log.info("responseJson :: " + responseJson);
return responseJson;
}
public String makeSignature(URL paramUrl, String requestType, Long timestamp) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String space = " ";
String newLine = "\n";
String method = requestType;
String url = paramUrl.toString().replaceAll("^((http[s]?|ftp):\\/)?\\/?([^:\\/\\s]+)", "");
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.getEncoder().encodeToString(rawHmac);
return encodeBase64String;
}
}