UI 수정, SMS 클래스 추가

This commit is contained in:
comicgum
2026-09-10 07:03:11 +09:00
parent 4187407e96
commit 4a6c9d2afe
9 changed files with 356 additions and 62 deletions
@@ -0,0 +1,197 @@
package com.handong.smartservice.component;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Munja114 (www.munja114.co.kr) SMS/LMS URL integration.
*
* Posts an application/x-www-form-urlencoded body to the Munja114 remote endpoint.
* SMS or LMS is chosen automatically by the EUC-KR byte length of the message:
* up to 90 bytes is sent as SMS, longer text is sent as LMS (2000 bytes max).
*
* The response body is a pipe separated string: code|msg|nums|cols|etc1|etc2
*/
public class SMSSender {
private static final Logger logger = LoggerFactory.getLogger(SMSSender.class);
// Munja114 account, issued and authorized through the Munja114 customer center
private static final String REMOTE_ID = "";
private static final String REMOTE_PASS = "";
private static final String URL_SMS = "https://www.munja114.co.kr/Remote/RemoteSms.html";
private static final String URL_LMS = "https://www.munja114.co.kr/Remote/RemoteMms.html";
// Munja114 sends and expects EUC-KR encoded text
private static final Charset CHARSET = Charset.forName("EUC-KR");
private static final int SMS_MAX_BYTES = 90;
private static final int LMS_MAX_BYTES = 2000;
// Subject applies to LMS only, 20 characters max, no special characters
private static final String LMS_SUBJECT = "알림";
private static final Duration TIMEOUT = Duration.ofSeconds(10);
// Result codes returned by the gateway
public static final String CODE_SUCCESS = "0000";
public static final String CODE_CONNECT_ERROR = "0001";
public static final String CODE_AUTH_ERROR = "0002";
public static final String CODE_NO_CALL = "0003";
public static final String CODE_MSG_FORMAT_ERROR = "0004";
public static final String CODE_CALLBACK_ERROR = "0005";
public static final String CODE_PHONE_COUNT_ERROR = "0006";
public static final String CODE_RESERVE_TIME_ERROR = "0007";
public static final String CODE_NOT_ENOUGH_CALL = "0008";
public static final String CODE_SEND_FAIL = "0009";
public static final String CODE_MSG_TOO_LONG = "0012";
public static final String CODE_CALLBACK_NOT_REGISTERED = "0030";
public static final String CODE_CALLBACK_TYPE_FAIL = "0033";
public static final String CODE_SEND_LIMITED = "0080";
public static final String CODE_BLOCKED = "6666";
public static final String CODE_UNPAID = "9999";
// Local codes, never returned by the gateway
public static final String CODE_INVALID_PARAMETER = "9001";
public static final String CODE_EXCEPTION = "9002";
private static final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(TIMEOUT)
.build();
/** Send result parsed from the gateway response. */
public static class Result {
public final String code; // result code, "0000" on success
public final String msg; // result message
public final int sentCount; // number of messages sent
public final int remainCount; // remaining call count of the account
public final String rawResponse;
public Result(String code, String msg, int sentCount, int remainCount, String rawResponse) {
this.code = code;
this.msg = msg;
this.sentCount = sentCount;
this.remainCount = remainCount;
this.rawResponse = rawResponse;
}
public boolean isSuccess() {
return CODE_SUCCESS.equals(code);
}
@Override
public String toString() {
return "SMSSender.Result{code=" + code + ", msg=" + msg + ", sentCount=" + sentCount + ", remainCount=" + remainCount + "}";
}
}
/**
* Send a text message.
*
* @param callback sender number, must be pre-registered with the carrier (digits only, other characters are stripped)
* @param phone receiver number, comma separated for multiple receivers
* @param msg message body, sent as SMS when it fits in 90 EUC-KR bytes, otherwise as LMS
*/
public static Result send(String callback, String phone, String msg) {
if (!hasText(callback) || !hasText(phone) || !hasText(msg)) {
logger.warn("send: missing parameter, callback={}, phone={}", callback, phone);
return new Result(CODE_INVALID_PARAMETER, "invalid parameter", 0, 0, "");
}
String callbackNum = callback.replaceAll("[^0-9]", "");
String phoneList = phone.replaceAll("[^0-9,]", "");
int receiverCount = phoneList.split(",").length;
byte[] msgBytes = msg.getBytes(CHARSET);
boolean isLms = msgBytes.length > SMS_MAX_BYTES;
if (msgBytes.length > LMS_MAX_BYTES) {
logger.warn("send: message too long, bytes={}", msgBytes.length);
return new Result(CODE_MSG_TOO_LONG, "message too long", 0, 0, "");
}
StringBuilder body = new StringBuilder();
appendParam(body, "remote_id", REMOTE_ID);
appendParam(body, "remote_pass", REMOTE_PASS);
appendParam(body, "remote_num", String.valueOf(receiverCount));
appendParam(body, "remote_reserve", "0");
appendParam(body, "remote_phone", phoneList);
appendParam(body, "remote_callback", callbackNum);
appendParam(body, "remote_msg", msg);
if (isLms)
appendParam(body, "remote_subject", LMS_SUBJECT);
String url = isLms ? URL_LMS : URL_SMS;
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(TIMEOUT)
.header("Content-Type", "application/x-www-form-urlencoded;charset=ko")
.POST(HttpRequest.BodyPublishers.ofString(body.toString(), CHARSET))
.build();
HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
logger.warn("send: http status={}, url={}", response.statusCode(), url);
return new Result(CODE_CONNECT_ERROR, "http status " + response.statusCode(), 0, 0, "");
}
String raw = new String(response.body(), CHARSET).trim();
Result result = parseResult(raw);
if (result.isSuccess())
logger.info("send: ok, phone={}, type={}, remain={}", phoneList, isLms ? "lms" : "sms", result.remainCount);
else
logger.warn("send: failed, phone={}, result={}", phoneList, result);
return result;
}
catch (Exception e) {
logger.warn("send: exception, url={}, msg={}", url, e.getMessage());
return new Result(CODE_EXCEPTION, e.getMessage(), 0, 0, "");
}
}
// Response format: code|msg|nums|cols|etc1|etc2
private static Result parseResult(String raw) {
String[] arr = raw.split("\\|");
String code = arr.length > 0 ? arr[0].trim() : "";
String msg = arr.length > 1 ? arr[1].trim() : "";
int sentCount = arr.length > 2 ? toInt(arr[2]) : 0;
int remainCount = arr.length > 3 ? toInt(arr[3]) : 0;
return new Result(code, msg, sentCount, remainCount, raw);
}
private static void appendParam(StringBuilder body, String key, String value) {
if (body.length() > 0)
body.append('&');
body.append(key).append('=').append(URLEncoder.encode(value == null ? "" : value, CHARSET));
}
private static boolean hasText(String str) {
return str != null && !str.trim().isEmpty();
}
private static int toInt(String str) {
try {
return Integer.parseInt(str.trim());
}
catch (NumberFormatException e) {
return 0;
}
}
}