Important
1.2.0์ 1.1.x์ ํธํ๋์ง ์์ต๋๋ค. Maven Central์ io.github.icomm-api:bizgo-sdk-comm-java 1.1.1 ์ดํ(net.bizgo.client.BizgoClient)์ ํจํค์งยท์ง์
์ ยท์ธ์ฆยท์๋ต ํ์
์ด ๋ชจ๋ ๋ค๋ฅด๋ฏ๋ก, ๋ฒ์ ๋ฒํธ๋ง ์ฌ๋ ค์๋ ์ปดํ์ผ๋์ง ์์ต๋๋ค. ์ฌ๋ฆฌ๊ธฐ ์ ์ ์ด์ ๋ฒ์ ์์ ์ฎ๊ธฐ๊ธฐ๋ฅผ ๋ณด์ธ์.
๋น์ฆ๊ณ (Bizgo) ์ปค๋ฎค๋์ผ์ด์ API์ Java SDK์ ๋๋ค. SMS/LMS/MMS, ๊ตญ์ ๋ฌธ์, RCS, ์นด์นด์ค ์๋ฆผํกยท๋ธ๋๋๋ฉ์์ง, ๋ค์ด๋ฒ ํกํก์ ํ๋์ ํด๋ผ์ด์ธํธ๋ก ๋ฐ์กํ๊ณ , ๋ฆฌํฌํธยท์ด๋ ฅยทํต๊ณ๋ฅผ ์กฐํํ๊ณ , ์นํ ์ ๊ฒ์ฆํฉ๋๋ค.
- Java 17 ์ด์ ยท ๋ฐํ์ ์์กด์ฑ์ Jackson(
jackson-databind) ํ๋ ยท HTTP๋ JDK ๋ด์ฅjava.net.http.HttpClient - ์์ฒญ ๋ชจ๋ธ์ ๋ณด๋ด๊ธฐ ์ ์ ๊ฒ์ฆํฉ๋๋ค: ํ์ ํ๋, ์ ์ ์๋ ํ๋, ๋ฐ์ดํธ ๊ธธ์ด(SMS 90byte ๋ฑ, EUC-KR ๊ธฐ์ค), EUC-KR ๋ฒ์ ๋ฐ ๋ฌธ์(์ด๋ชจ์ง), ํ์ฉ ๊ฐ, ํญ๋ชฉ ์
- ๋ชจ๋ธ๊ณผ ์ ์ฒด API(146๊ฐ operation)์ ๋ฉ์๋๋ฅผ ์คํ์์ ์์ฑํฉ๋๋ค: bizgo-api-spec (OpenAPI 3.1) ยท ์๋ฌธ: API ๋ ํผ๋ฐ์ค
- ํธ์ ๊ธฐ๋ฅ: ๋๋ ๋ฐ์ก, ํด๋ผ์ด์ธํธ ์๋ ์ ํ, ํ
์คํธ ๋๊ตฌ(
-testing์ํฐํฉํธ), ๊ด์ธก(hook, ์ ํ OpenTelemetry ์ํฐํฉํธ)
Gradle:
implementation 'io.github.icomm-api:bizgo-sdk-comm-java:1.2.0'Maven:
<dependency>
<groupId>io.github.icomm-api</groupId>
<artifactId>bizgo-sdk-comm-java</artifactId>
<version>1.2.0</version>
</dependency>์ ํ ์ํฐํฉํธ(๊ฐ์ ๋ฒ์ ): bizgo-sdk-comm-java-testing(ํ
์คํธ ๋๊ตฌ, ํ
์คํธ), bizgo-sdk-comm-java-opentelemetry(tracing, ๊ด์ธก).
- ์ฝ์
๋ฐ์ก๊ด๋ฆฌ > ์ฐ๋๊ด๋ฆฌ์์ API Key๋ฅผ ๋ฐ๊ธํ๊ณ , ํธ์ถํ ์๋ฒ์ ๊ณต์ธ IP๋ฅผ ๋ฑ๋กํฉ๋๋ค. - ํค๋ฅผ ํ๊ฒฝ๋ณ์๋ก ์ค์ ํฉ๋๋ค. ํค๋ ์ฝ๋๋ ์ ์ฅ์์ ์ฐ์ง ์์ต๋๋ค.
export BIZGO_API_KEY=...- sandbox(์ค์ ๋ฐ์ก ์์)์์ ๋จผ์ ํ์ธํฉ๋๋ค.
import io.github.icommapi.bizgo.Bizgo;
import io.github.icommapi.bizgo.Environment;
import io.github.icommapi.bizgo.models.SendResult;
try (Bizgo client = Bizgo.builder().environment(Environment.SANDBOX).build()) { // ํค๋ BIZGO_API_KEY์์ ์ฝ์
SendResult result = client.send().sms("01000000000", "01000000000", "[๋น์ฆ๊ณ ] ์ธ์ฆ๋ฒํธ๋ 123456 ์
๋๋ค.");
System.out.println(result.getMsgKeys()); // ์ ์๋ ๋ฉ์์ง ํค
System.out.println(result.getFailed()); // ์ ์ ๋จ๊ณ์์ ๊ฑฐ์ ๋ ์์ ์ (์์ผ๋ฉด []). ๊ฐ์ ๋ฉฑ๋ฑ์ฑ ํค๋ก ์ด๋ฏธ ์ ์๋ ์์ ์๋ getDuplicates()
}์ด์์ ๋ณด๋ผ ๋๋ environment(...)๋ฅผ ์๋ตํ๊ฑฐ๋ Environment.PRODUCTION์ ์๋๋ค.
Bizgo๋ ์ค๋ ๋ ์์ ํ๋ฏ๋ก API Key๋ง๋ค ํ๋๋ฅผ ๋ง๋ค์ด ์ฌ์ฌ์ฉํ์ธ์.
์ ์ โ ๋ฐ์ก ์๋ฃ.
send()์ ๊ฒฐ๊ณผ๋ ์ ์ ๊ฒฐ๊ณผ์ ๋๋ค. ์ต์ข ๊ฒฐ๊ณผ๋ ๋ฆฌํฌํธ๋ก ๋ฐ์ต๋๋ค.
๋ชจ๋ ์ฑ๋์ client.send().omni(...) ํ๋๋ก ๋ณด๋
๋๋ค. ๋ฉ์์ง๋ฅผ ์ฌ๋ฌ ๊ฐ ๋ฃ์ผ๋ฉด ์ ๋ฉ์์ง๊ฐ ์คํจํ ๋ ๋ค์ ๋ฉ์์ง๋ก ๋์ฒด๋ฐ์ก๋ฉ๋๋ค.
import io.github.icommapi.bizgo.SendOptions;
import io.github.icommapi.bizgo.models.*;
SendResult result = client.send().omni(
List.of(Destination.builder().to("01000000000").replaceWords(Map.of("name", "ํ๊ธธ๋")).build()), // ์ต๋ 200๋ช
List.of(
AlimtalkMessage.builder().senderKey("SENDER_KEY_EXAMPLE").templateCode("TEMPLATE_CODE_EXAMPLE")
.msgType("AT").text("#{name}๋, ์ฃผ๋ฌธ์ด ์ ์๋์์ต๋๋ค.").build(),
SmsMessage.builder().from("01000000000").text("#{name}๋, ์ฃผ๋ฌธ์ด ์ ์๋์์ต๋๋ค.").build()), // ์๋ฆผํก ์คํจ ์
SendOptions.builder()
.idempotencyKey("order-20260923-0001") // ๊ถ์ฅ: ์ฌ์๋ํด๋ ์ค๋ณต ๋ฐ์ก๋์ง ์์ (idempotencyTtl ๊ธฐ๋ณธ 86400)
.ref("order-20260923-0001") // ๋ฆฌํฌํธ์ ๊ทธ๋๋ก ๋์์ค๋ ์ฐธ์กฐ๊ฐ
.build());idempotencyKey๋ฅผ ์ฃผ๊ณ idempotencyTtl์ ์๋ตํ๋ฉด SDK๊ฐ idempotencyTtl์ Bizgo.DEFAULT_IDEMPOTENCY_TTL(86400์ด = 24์๊ฐ)๋ก ์ฑ์ ๋ณด๋
๋๋ค(๋น์ฆ๊ณ ๋ ํค๋ง ์๊ณ TTL์ด ์์ผ๋ฉด A309๋ก ๊ฑฐ์ ). ์ง์ ์ค ๊ฐ(0~86400, 0 ํฌํจ)์ ๊ทธ๋๋ก ๋ณด๋ด๊ณ , ํค๊ฐ ์์ผ๋ฉด TTL๋ ๋ณด๋ด์ง ์์ต๋๋ค. send().request(...)(fromJson/fromMap์ผ๋ก ๋ง๋ ์์ฒญ ํฌํจ), send().bulk์ ์ฒญํฌ ํค, ๋ณธ๋ฌธ์ idempotencyKeyยทidempotencyTtl์ด ๋ชจ๋ ์๋ ์์ฑ ๋ฉ์๋์๋ ๊ฐ์ ๊ท์น์ด ์ ์ฉ๋๋ฉฐ, ๋๊ธด ์์ฒญ ๊ฐ์ฒดยทMap์ ๋ฐ๊พธ์ง ์์ต๋๋ค.
๊ฐ๋จํ ๊ฒฝ์ฐ:
client.send().omni("01000000000", alimtalk, sms); // ์์ ์ 1๋ช
, ๋์ฒด๋ฐ์ก ์์๋๋ก
client.send().omni(List.of("01000000000", "01000001234"), sms); // ์ฌ๋ฌ ๋ช
client.send().request(SendOmniRequest.fromJson(json)); // API ๋ฌธ์์ JSON ๊ทธ๋๋ก (๊ฒ์ฆ ํ ๋ฐ์ก)| ์ฑ๋ | ๋ชจ๋ธ | ๊ฐํธ ๋ฉ์๋ |
|---|---|---|
| SMS (90byte) | SmsMessage |
send().sms() |
| LMS (2,000byte) | MmsMessage (fileKey ์์) |
send().lms() |
| MMS | MmsMessage (fileKey ์ต๋ 3๊ฐ) |
send().mms() |
| ๊ตญ์ ๋ฌธ์ | InternationalMessage |
|
| RCS | RcsMessage |
|
| ์นด์นด์ค ์๋ฆผํก | AlimtalkMessage |
|
| ์นด์นด์ค ๋ธ๋๋๋ฉ์์ง | BrandMessage |
|
| ๋ค์ด๋ฒ ํกํก | NaverTalkMessage |
- ๋ชจ๋ธ์ ๋ถ๋ณ ๊ฐ์ฒด์ด๊ณ
X.builder()...build()๋ก ๋ง๋ญ๋๋ค.build()์์ ๊ฒ์ฆํ๋ฉฐ, ์๋ชป๋๋ฉดValidationException์ด ๋ฉ๋๋ค(๋ณด๋ด๊ธฐ ์ ). - ์กฐ๊ฑด๋ถ ํ์ ํ๋(์คํ์
x-sdk-required-if)๋ ๋ณด๋ด๊ธฐ ์ ์ ๊ฒ์ฌํฉ๋๋ค. ์๋ฆผํก ์ ๋ฌธ ๋ฐ์ก(sendType์ดtemplate์ด ์๋)์msgType(ATํ ์คํธํยทAI์ด๋ฏธ์งํ)๊ณผtext๊ฐ ํ์์ ๋๋ค(๋น ์ง๋ฉด ์๋ฒ๊ฐ A523์ผ๋ก ๊ฑฐ์ ).msgType์ ํ ํ๋ฆฟ ์กฐํ ๊ฒฐ๊ณผ(AlimtalkTemplate.getMsgType())์ ๊ฐ์ ๊ทธ๋๋ก ์ฐ๋ฉด ๋ฉ๋๋ค. ํ ํ๋ฆฟ ์๋ ์นํ(sendType("template"))์msgTypeยทtext์์ด ๋ณด๋ด๊ณ , ๋์ ๋ชจ๋destinations[].replaceWords๊ฐ ํ์ํฉ๋๋ค. ๊ทธ ๋ฐ์ ๋ธ๋๋๋ฉ์์งsendType๋ณ ํ๋, ๋ฒํผWL(urlPcยทurlMobile)ยทAL(urlMobile), RCSheader("1")โfooter, ์๋ดํก PlainFILEยทRich ํ์ ๋ณ ์ฒจ๋ถ๊ฐ ์์ต๋๋ค. ์์ฒญ ์ ์ฒด๊ฐ ํ์ํ ๊ท์น($.destinations[]...)์SendOmniRequestยทReservationCreateRequest๋ฅผ ๋ง๋ค ๋(omni,bulk,fromJson/fromMap) ๊ฒ์ฌํ๋ฉฐ, ์ค๋ฅ์๋ ๊ฒฝ๋ก(SendOmniRequest.destinations[1].replaceWords)์ ์กฐ๊ฑด๋ง ๋ด๊น๋๋ค. - ํ๋ ์ด๋ฆ์ API์ ๊ฐ์ต๋๋ค(
senderKey,from). ์ค์ ํ ํ๋๋ง ์ ์ก๋๊ณ , ์คํ ๊ธฐ๋ณธ๊ฐ์ ๋ณด๋ด์ง ์์ต๋๋ค(์๋ฒ ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ). - SMS๊ฐ 90byte๋ฅผ ๋๋์ง ๋ฏธ๋ฆฌ ์๊ณ ์ถ์ผ๋ฉด
MessageBytes.euckr(text)๋ฅผ ์๋๋ค.
FileUploadResult uploaded = client.files().uploadMms(Path.of("banner.jpg")); // jpg, ์ต๋ 300KB (๋์ผ๋ฉด ๋ณด๋ด๊ธฐ ์ ์ค๋ฅ)
client.send().mms("01000000000", "01000000000", "...", List.of(uploaded.getFileKey()));
client.files().uploadRcs(Path.of("card.png")); // -> getMedia()
client.files().uploadBrandMessage(Path.of("wide.jpg"), BrandImageKind.WIDE); // -> getImgUrl()
client.files().uploadMms(FileUpload.of(bytes, "banner.jpg").imageName("event")); // ๋ฐ์ดํธ๋ก ์ฌ๋ฆฌ๊ธฐ์ฝ์์์ API Key๋ณ๋ก ๋ฆฌํฌํธ ์์ ๋ฐฉ์(POLLING ๋๋ WEBHOOK)์ ์ ํฉ๋๋ค.
Polling โ ์ฒ๋ฆฌ์ ์ฑ๊ณตํ ๋ฐฐ์น๋ง ์์ ํ์ธ(ack)ํฉ๋๋ค. ์ฒ๋ฆฌ ํจ์๊ฐ ์์ธ๋ฅผ ๋ด๋ฉด ๊ฐ์ ๋ฐฐ์น๋ฅผ ๋ค์ ๋ฐ์ต๋๋ค.
int handled = client.reports().consume(reports -> {
for (Report r : reports) {
db.upsert(r.getMsgKey(), r.getReportCode()); // ๊ฐ์ ๋ฆฌํฌํธ๊ฐ ๋ค์ ์ฌ ์ ์์ผ๋ upsert
}
});Webhook โ ์๋ช
์ ๊ฒ์ฆํ๊ณ 5์ด ์์ {"msgKey": ...}๋ก ์๋ตํฉ๋๋ค. ์นํ
secret์ ๋น์ฆ๊ณ ์ ์์ฒญํด ๋ณ๋๋ก ๋ฐ์ต๋๋ค.
WebhookReceiver receiver = new WebhookReceiver(System.getenv("BIZGO_WEBHOOK_SECRET"));
// ์ฌ์ฉํ๋ ์น ํ๋ ์์ํฌ์ ํธ๋ค๋ฌ ์์์ (headers: Map<String, String> ๋๋ Map<String, List<String>>)
try {
ReportWebhookPayload report = receiver.report(headers, rawBody);
enqueue(report); // ๋ฌด๊ฑฐ์ด ์ฒ๋ฆฌ๋ ๋น๋๊ธฐ๋ก
return ok(Webhooks.ackJson(report.getMsgKey())); // {"msgKey":"..."}
} catch (WebhookVerificationException e) {
return status(401);
}์นํ ์๋ช ๊ณผ timestamp ํ์ฉ ์ค์ฐจ๋ฅผ ๊ฒ์ฆํ์ธ์. ์ด์ ํ๊ฒฝ์์๋ HTTPS, ๋น์ฆ๊ณ ์นํ ๋ฐ์ IP ํ์ฉ ๋ชฉ๋ก,
msgKey๊ธฐ์ค ์ค๋ณต ์ ๊ฑฐ๋ฅผ ํจ๊ป ์ ์ฉํ๊ณ , ์ค์ํ ํ๋จ์ ๋ฆฌํฌํธยท์ํ ์กฐํ API๋ก ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์.
๊ฐ๋ณ ์กฐํ โ client.reports().inquiry(msgKey) (30์ผ ์ด๋ด)
client.messages().status(msgKey); // ๋จ๊ฑด ์ํ (๋์ฒด๋ฐ์ก ์ ์ฑ๋๋ณ ํญ๋ชฉ)
client.messages().statusByRequestId(requestId); // ๋๋ณด ์์ฒญ ์ ์ฒด (msgKey์์ ๋ 3์๋ฆฌ๋ฅผ ๋บ ๊ฐ)
client.messages().statistics(LocalDate.of(2026, 9, 1), LocalDate.of(2026, 9, 23), "SMS", null);
// ํ์ด์ง(lastSeq)๋ฅผ ์๋์ผ๋ก ๋ฐ๋ผ๊ฐ. LocalDateTime์ KST, ์๊ฐ๋๊ฐ ์๋ ๊ฐ์ KST๋ก ๋ณํ
for (MessageStatus m : client.messages().iterHistory(
HistoryQuery.since(LocalDateTime.of(2026, 9, 23, 9, 0)).serviceTypes("SMS", "ALIMTALK"))) {
...
}
client.messages().streamHistory(HistoryQuery.since(Instant.now().minus(1, ChronoUnit.HOURS)))
.filter(m -> !"10000".equals(m.getReportCode()))
.forEach(...);
// MO(์์ ) ์ด๋ ฅ. occurredTime์ +09:00 ์คํ์
์ผ๋ก ๋ณด๋
client.messages().iterMoHistory(MoHistoryQuery.since(LocalDateTime.of(2026, 9, 23, 9, 0)).limit(500));์กฐํ API์ ๊ธฐ๋ณธ ํธ์ถ ํ๋๋ ์ด๋น 5ํ์
๋๋ค. limit์ 1~1000์ด๋ฉฐ ๋ฒ์ ๋ฐ์ด๋ฉด ๋ณด๋ด๊ธฐ ์ ์ ์ค๋ฅ๊ฐ ๋ฉ๋๋ค.
์์ ๋ฐ์กยท์
๋ก๋ยท๋ฆฌํฌํธยท์กฐํ ์ธ์ ์คํ์ ๋ชจ๋ operation(146๊ฐ) ์ ์ธ ์ ์์ต๋๋ค. ๋ฉ์๋๋ ์คํ์ x-sdk-* ๋ฉํ๋ฐ์ดํฐ์์ ์์ฑ๋๋ฉฐ, ์์น๋ client.<๋ฆฌ์์ค>().<ํ์ ๋ฆฌ์์ค>().<๋ฉ์๋>(...)์
๋๋ค.
| ๋ฆฌ์์ค | ์ |
|---|---|
reservations() ยท reservations().recipients() |
์์ฝ ๋ฐ์ก ๋ฑ๋กยท์กฐํยท์์ ยท์ทจ์ยท์ผ์์ ์งยท์ฌ๊ฐ, ์์ฝ ์์ ์ |
insights().alimtalk() ยท insights().brandMessage() ยท insights().rcs() |
์ฑ๋๋ณ ์ธ์ฌ์ดํธ(ํต๊ณ) |
kakao().senders() ยท categories() ยท groups() ยท sanctions() |
์นด์นด์ค ๋ฐ์ ํ๋กํยท์นดํ ๊ณ ๋ฆฌยท๊ทธ๋ฃนยท์ ์ฌ |
alimtalk().templates() ยท templateCategories() ยท publicTemplates() |
์๋ฆผํก ํ ํ๋ฆฟ ๊ด๋ฆฌยท๊ฒ์ |
brandMessage().groupSends() ยท templates() ยท friendGroups() ยท videos() โฆ |
๋ธ๋๋๋ฉ์์ง ๋๋ณดยทํ ํ๋ฆฟยท์น๊ตฌ ๊ทธ๋ฃน |
rcs().brands() ยท chatbots() ยท templates() ยท templateForms() โฆ |
RCS ๋ธ๋๋ยท์ฑ๋ดยทํ ํ๋ฆฟ |
counsel().messages() ยท sessions() ยท channels() ยท systemMessages() โฆ |
์นด์นด์ค ์๋ดํก |
files() |
์์ ์ ๋ก๋ + ์๋ฆผํก ํ ํ๋ฆฟยท์์ดํ ํ์ด๋ผ์ดํธ ์ด๋ฏธ์ง, ๋ธ๋๋๋ฉ์์ง ์นดํ๋ก๊ทธ ์ด๋ฏธ์ง |
์ธ์ ์์๋ ๊ฒฝ๋ก ํ๋ผ๋ฏธํฐ โ ์์ฒญ ๋ณธ๋ฌธ ๋ชจ๋ธ โ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด์
๋๋ค. ์์ฒญ ๋ณธ๋ฌธ์ ๋ฐ์ก ๋ชจ๋ธ์ฒ๋ผ build()์์ ๊ฒ์ฆ๋๊ณ , ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ io.github.icommapi.bizgo.params.<OperationId>Params ๋น๋๋ก ๋ง๋ญ๋๋ค(ํ์ ๊ฐยทํ์ฉ ๊ฐ ๊ฒ์ฆ).
๋ฐํ๊ฐ์ ์คํ์ x-sdk-result๊ฐ ๊ฐ๋ฆฌํค๋ ์๋ต ๋ถ๋ถ์
๋๋ค(๊ธฐ๋ณธ data.data, ๋ฐ์ดํฐ๊ฐ ์๋ ์๋ต์ void).
import io.github.icommapi.bizgo.models.*;
import io.github.icommapi.bizgo.params.ListAlimtalkTemplatesParams;
// ์๋ฆผํก ํ
ํ๋ฆฟ ๋ชฉ๋ก (offset ํ์ด์ง๋ฅผ ์๋์ผ๋ก ์ํ)
for (AlimtalkTemplate t : client.alimtalk().templates().iterList(
ListAlimtalkTemplatesParams.builder().senderKey("SENDER_KEY_EXAMPLE").limit(100).build())) {
System.out.println(t.getTemplateCode() + " " + t.getInspectionStatus());
}
// ์์ฝ ๋ฐ์ก: ์ฑ๋ ๋ฉ์์ง๋ฅผ ReservationMessageFlowItem์ผ๋ก ๊ฐ์๋๋ค
ReservationCreateServiceResult created = client.reservations().create(ReservationCreateRequest.builder()
.destinations(Destination.builder().to("01000000000").build())
.messageFlow(ReservationMessageFlowItem.of(SmsMessage.builder().from("01000000000").text("์์ฝ ์๋ด").build()))
.resvSendTime("2026-12-31 09:00:00")
.build());
String resvKey = created.getResvKey(); // ์์ฝ ํค (์ทจ์ยท์์ ์ ์ฌ์ฉ)
created.getData().getDestinations(); // ์์ ์๋ณ ์ ์ ๊ฒฐ๊ณผ
Reservation cancelled = client.reservations().cancel("RESV_KEY_EXAMPLE");
// ๊ฒฝ๋ก ํ๋ผ๋ฏธํฐ๋ ์์, ์๋์ผ๋ก URL ์ธ์ฝ๋ฉ๋ฉ๋๋ค
client.alimtalk().templates().delete("SENDER_KEY_EXAMPLE", "TEMPLATE_CODE_EXAMPLE");- ํ์ด์ง ์ํ: ์คํ์
x-sdk-pagination์ด ์๋ ๋ชฉ๋ก์iter<๋ฉ์๋>(...)(Iterable, ๊ฒ์ผ๋ฅด๊ฒ ์กฐํ)์stream<๋ฉ์๋>(...)(Stream)๊ฐ ํจ๊ป ์์ฑ๋ฉ๋๋ค. cursor๋hasNext๊ฐ false์ด๊ฑฐ๋ ์ปค์๊ฐ ์๊ฑฐ๋ ์์ง์ด์ง ์์ผ๋ฉด, pageยทoffset์ ๋น ํ์ด์งยท์์ฒญํ ํฌ๊ธฐ๋ณด๋ค ์์ ํ์ด์งยทtotal๋๋ฌ ์ ๋ฉ์ถฅ๋๋ค. ๊ฐ์ ํ์ด์ง๊ฐ ๋ฐ๋ณต๋๋ฉด ๋ฉ์ถฅ๋๋ค. - ์ฌ์๋: operation๋ง๋ค ์คํ์
x-sdk-retry๋ฅผ ๋ฐ๋ฆ ๋๋ค.safe(์กฐํ, ๋ฉฑ๋ฑ PUT/DELETE)๋ 429ยท5xxยท๋คํธ์ํฌ ์ค๋ฅ๋ฅผ,rate_limit_only(์์ฑยท๋ฐ์กยท์ ๋ก๋)๋ 429๋ง ์ฌ์๋ํฉ๋๋ค. - ์
๋ก๋:
file(+fileKey/imageName)๋ง ๋ฐ๋ ์ ๋ก๋๋FileUpload/Path๋ฅผ, ๊ทธ ๋ฐ์ multipart ์์ฒญ(rcs().brands().update(...)๋ฑ)์ ์์ฑ๋ ์์ฒญ ๋ชจ๋ธ(ํ์ผ ํ๋๋FileUpload)์ ๋ฐ์ต๋๋ค. ํ์ผ์ ํ ๋ฒ ์ฝ์ด ์ฌ์๋์ ์ฌ์ฌ์ฉํฉ๋๋ค. - ์ง์ ๋ง๋ ํธ์ ๋ฉ์๋(
send().sms,reports().consume,messages().iterHistory๋ฑ)๋ ๊ทธ๋๋ก์ด๋ฉฐ, ๊ฐ์ ์ด๋ฆ์ด๋ฉด ํธ์ ๋ฉ์๋๊ฐ ์ฐ์ ํฉ๋๋ค. - ๋ชจ๋ operation ๋ชฉ๋ก๊ณผ ๋ฉํ๋ฐ์ดํฐ๋
Operation.all()๋ก ๋ณผ ์ ์์ต๋๋ค.
WebhookReceiver์ ์๋ดํก ์นํ
ํ์๊ฐ ์์ฑ๋์ด ์์ต๋๋ค: counselMessage, counselReference, counselExpiredSession, counselSeenInfo, counselPersonalInfo, counselCertResult, counselResult. ์๋ต์ Webhooks.counselAckJson()({"code":"A000","result":"Success"})์
๋๋ค.
์๋ดํก ์นํ ์๋ ์๋ช ์ด ์์ต๋๋ค(์๋ช ์ ๋ฆฌํฌํธยทMO ์นํ ์๋ง ์ ์ฉ). ๊ทธ๋์ ์๋ดํก ํ์๋ ์๋ช ์ ์๊ตฌํ๊ฑฐ๋ ๊ฒ์ฌํ์ง ์๊ณ (์๋ช ํค๋๊ฐ ์๋ ๋ฌด์), ๋ณธ๋ฌธ ํฌ๊ธฐ ์ ํ(1 MiB)ยทJSON ๊น์ด 64ยทํ์ ๊ฒ์ฌ๋ง ํ ๋ค ํ์ ์ด ์๋ ํ์ด๋ก๋๋ฅผ ๋๋ ค์ค๋๋ค. ์นํ secret๋ ํ์ ์์ต๋๋ค.
์๋ดํก๋ง ๋ฐ๋ ์๋ํฌ์ธํธ๋ secret ์์ด ์ ์ ํ์๋ฅผ ์๋๋ค.
CounselMessageWebhookPayload message = WebhookReceiver.parseCounselMessage(rawBody);
return Webhooks.counselAckJson();์ ์ ํ์๋ 7์ข
๋ชจ๋ ์์ต๋๋ค: parseCounselMessage, parseCounselReference, parseCounselExpiredSession, parseCounselSeenInfo, parseCounselPersonalInfo, parseCounselCertResult, parseCounselResult. ๋ฆฌํฌํธยทMO์ฉ WebhookReceiver(secret ํ์)๊ฐ ์ด๋ฏธ ์๋ค๋ฉด receiver.counselMessage(headers, rawBody)์ฒ๋ผ ์ธ์คํด์ค ๋ฉ์๋๋ฅผ ์จ๋ ๊ฒฐ๊ณผ๋ ๊ฐ์ต๋๋ค. ๋ฆฌํฌํธยทMO ์นํ
์ ํญ์ ์๋ช
์ด ํ์ํฉ๋๋ค.
์๋ดํก ์์ ์๋ํฌ์ธํธ์๋ HTTPS์ ๋น์ฆ๊ณ ์นํ
๋ฐ์ IP ํ์ฉ ๋ชฉ๋ก์ ์ ์ฉํ๊ณ , ์ฌ์ ์ก์ผ๋ก ๊ฐ์ ์ด๋ฒคํธ๊ฐ ๋ค์ ์ฌ ์ ์์ผ๋ msgKey๊ฐ ์์ผ๋ฉด ๊ทธ๊ฒ์ผ๋ก ์ค๋ณต์ ์ ๊ฑฐํ์ธ์. ์๋ดํก ์นํ
์๋ ์ฌ์ฉ์ ์๋ณ์ยท์๋ด ๋ด์ฉยท์ํธํ๋ ์ธ์ฆ ๊ฒฐ๊ณผ๊ฐ ์์ผ๋ ๋ก๊ทธ์ ๋จ๊ธฐ์ง ๋ง์ธ์.
send().bulk(...)๋ ์์ ์ ์ ์ ํ ์์ด chunkSize(1~200, ๊ธฐ๋ณธ 200)์ฉ ๋๋ omni๋ฅผ ํธ์ถํ๊ณ , ์ต๋ concurrency(๊ธฐ๋ณธ 4)๊ฐ๋ฅผ ๋์์ ๋ณด๋
๋๋ค. ํ ์์ฒญ์ด ์คํจํด๋ ๋๋จธ์ง๋ ๊ณ์ ๋ณด๋
๋๋ค.
BulkSendResult result = client.send().bulk(numbers, List.of(sms),
BulkOptions.builder()
.idempotencyKeyPrefix("campaign-20260923") // ํค: campaign-20260923-<chunkSize>-<์์ ์ธ๋ฑ์ค>-<hash8>
.concurrency(4)
.build());
System.out.println(result.getSucceeded().size() + " accepted, " + result.getFailed().size() + " rejected");
for (BulkSendResult.ChunkError e : result.getErrors()) { // ์์ฒญ ์ ์ฒด๊ฐ ์คํจํ ์ฒญํฌ (์ธ๋ฑ์ค ๋ฒ์๋ง)
retryLater(numbers.subList(e.fromIndex(), e.toIndex()), e.error());
}idempotencyKeyPrefix๊ฐ ์์ผ๋ฉด ์์ฒญ๋ง๋ค<prefix>-<chunkSize>-<์์ ์ธ๋ฑ์ค>-<hash8>์ ๋ฉฑ๋ฑ์ฑ ํค๋ก ์๋๋ค.hash8์ ๊ทธ ์ฒญํฌ์ ์์ ๋ฒํธ ๋ชฉ๋ก(์์ ํฌํจ)์ SHA-256์ผ๋ก ํด์ํ ์ 8์๋ฆฌ์ ๋๋ค(BulkOptions.idempotencyKey(...)). ๊ทธ๋์ ๊ฐ์ ํค๊ฐ ๋ค๋ฅธ ์์ ์ ๋ฌถ์์ ์ฐ์ด๋ ์ผ์ด ์๊ณ , ํ์์์๋ ์ฌ์๋ํฉ๋๋ค.idempotencyTtl์ ์ฃผ์ง ์์ผ๋ฉด ์ฒญํฌ๋ง๋ค 86400(Bizgo.DEFAULT_IDEMPOTENCY_TTL)์ ๋ณด๋ ๋๋ค. ์์ผ๋ฉด ์ฌ์๋ ์ ์ฑ ์ ์ผ๋ฐ ๋ฐ์ก๊ณผ ๊ฐ์ต๋๋ค(429๋ง).- ์ฌ์คํ์ ๊ฐ์ ๋ชฉ๋กยท๊ฐ์ prefixยท๊ฐ์
chunkSize๋ก ํ์ธ์. ๊ทธ๋ฌ๋ฉด ํค๊ฐ ๊ฐ์ ์ด๋ฏธ ์ ์๋ ์ฒญํฌ๋ ๋ค์ ๋ฐ์ก๋์ง ์์ต๋๋ค(๋ชฉ๋กยท์์ยทchunkSize๋ฅผ ๋ฐ๊พธ๋ฉด ํค๊ฐ ๋ฌ๋ผ์ ธ ์ค๋ณต ๋ฐ์ก๋ ์ ์์). ์คํจํ ์ฒญํฌ๋ง ๋ค์ ๋ณด๋ด๋ ค๋ฉดgetErrors()์ ์ฒญํฌ ๋ฒํธ์ ์ธ๋ฑ์ค ๋ฒ์(fromIndex~toIndex)๋ฅผ ์ฐ์ธ์. prefix๋ 176์ ์ดํ(์์ฑ๋ ํค๋ 200์ ์ดํ)์ฌ์ผ ํฉ๋๋ค. - ํ ์ฒญํฌ์ ์ด๋ค ์คํจ๋ ๋ค๋ฅธ ์ฒญํฌ์ ์ด๋ฏธ ๋ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์๊ฒ ํ์ง ์์ต๋๋ค. ์น๋ช
์ ์ค๋ฅ(
Error)๋ ์ค๋ ๋ ์ธํฐ๋ฝํธ๊ฐ ๋๋ฉดBulkSendException์ด ๋๊ณgetPartialResult()์ ์ ์๋ ์ฒญํฌ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค(์ธํฐ๋ฝํธ ํ๋๊ทธ๋ ๋ณต์). - ๊ฒฐ๊ณผ:
getResults()(์ฒญํฌ ๋ฒํธ โSendResult),getErrors()(์ฒญํฌ ๋ฒํธยท์์ ์ ์ธ๋ฑ์ค ๋ฒ์ยท์์ธ), ์ ์ฒด ํฉ๊ณgetSucceeded()/getDuplicates()/getFailed()/getMsgKeys(). ๊ฐ์ prefix๋ก ๋ค์ ์คํํ๋ฉด ์ด๋ฏธ ์ ์๋ ์์ ์๋getDuplicates()์ ๋ชจ์ด๊ณ , ๋ชจ๋ ์์ ์๊ฐA301์ธ ์ฒญํฌ๋ ์ค๋ฅ๊ฐ ์๋ ์ ์๋ ์์ฒญ์ผ๋ก ์ ๋๋ค(isComplete()์ ์ํฅ ์์). ๋ก๊ทธยท์์ธยทtoString()์๋ ์์ ์ ๋ฒํธ๋ฅผ ๋ฃ์ง ์์ต๋๋ค. - ์๋ ์๋ ์ ํ(send ๋ฒํท)์ด ํจ๊ป ์ ์ฉ๋ฉ๋๋ค.
ํด๋ผ์ด์ธํธ๋ง๋ค ํ ํฐ ๋ฒํท 2๊ฐ๋ก ์์ฒญ ์๋๋ฅผ ์ ํํฉ๋๋ค(๊ธฐ๋ณธ ์ผ์ง). ํ ํฐ์ด ๋ชจ์๋ผ๋ฉด ์์ฒญ ์ ์ ๊ธฐ๋ค๋ฆฌ๋ฉฐ, ์ฌ์๋๋ ์๋๋ง๋ค ๋ค์ ๊ธฐ๋ค๋ฆฝ๋๋ค.
| ๋ฒํท | ๊ธฐ๋ณธ | ์์ฒญ 1๊ฑด์ ๋น์ฉ | ๋์ operation (์คํ์ x-sdk-rate: send) |
|---|---|---|---|
| send | ์ด๋น 200 ๋ฉ์์ง(์์ ๋ฒํธ ๊ธฐ์ค) | ์์ฒญ์ destinations ์(์ต์ 1, ์์ ์ ๋ชฉ๋ก์ด ์๋ ๋ฐ์ก์ 1) |
send.omni(send().sms/lms/mms/omni/request/bulk), reservations.create, reservations.recipients.create, brandMessage.groupSends.create, counsel.messages.sendPlain, counsel.messages.sendRich |
| other | ์ด๋น 5 ์์ฒญ | 1 | ๊ทธ ๋ฐ์ ์ ๋ถ(์กฐํ, ์ ๋ก๋, ํ ํ๋ฆฟ ๊ด๋ฆฌ ๋ฑ) |
- ์์ ์ 200๋ช
์ง๋ฆฌ ์์ฒญ์ ํ ํฐ 200๊ฐ๋ฅผ ์ฐ๋ฏ๋ก ๊ธฐ๋ณธ ํ๋์์ ์ด๋น 1๊ฑด๋ง ๋๊ฐ๋๋ค.
bulk๋ ๊ฐ์์ 1,000๋ช ์ ์ฝ 4~5์ด์ ๊ฑธ์ณ ๋๊ฐ๋๋ค. - ๋ฒํท ์ฉ๋(์๊ฐ ํ์ฉ๋)์ ์ด๋น ํ๋์ ๊ฐ์ต๋๋ค. ํ๋๋ฅผ ์์ฒญ ํฌ๊ธฐ๋ณด๋ค ๋ฎ๊ฒ ์ก์๋(์: 100/์ด์ 200๋ช ์์ฒญ) ๋ฒํท์ด ๊ฐ๋ ์ฐฐ ๋๊น์ง ๊ธฐ๋ค๋ฆฐ ๋ค ๋ณด๋ด๊ณ ๋ถ์กฑ๋ถ์ ๋ค์ ์์ฒญ์ด ๊ฐ์ผ๋ฏ๋ก, ๋ฉ์ถ์ง ์๊ณ ํ๊ท ์๋๋ฅผ ์งํต๋๋ค.
- ์ด๋ค operation์ด send ๋ฒํท์ธ์ง๋ SDK์ ๋ชฉ๋ก์ผ๋ก ๋์ง ์๊ณ ์คํ์
x-sdk-rate์์ ์์ฑํฉ๋๋ค(Operation.getRateBucket()).
Bizgo.builder().rateLimit(RateLimit.of(100, 5)).build(); // ๊ฐ ๋ณ๊ฒฝ
Bizgo.builder().rateLimit(null).build(); // ๋๊ธฐํ๋ก์ธ์ค ๋จ์์ ๋๋ค. ๋น์ฆ๊ณ ์ ํ๋(๋ฐ์ก ์ด๋น 200 ๋ฉ์์ง, ๊ทธ ์ธ ์ด๋น 5 ์์ฒญ)๋ ๊ณ์ (API Key) ๋จ์์ ๋๋ค. ์ฌ๋ฌ ํ๋ก์ธ์คยท์๋ฒยทํด๋ผ์ด์ธํธ ์ธ์คํด์ค๊ฐ ๊ฐ์ ํค๋ฅผ ์ฐ๋ฉด ์ด ์ ํ๋ง์ผ๋ก๋ ํ๋๋ฅผ ์งํฌ ์ ์์ต๋๋ค. ์ธ์คํด์ค ์๋ก ๋๋ ๊ฐ์ ์ค์ ํ๊ฑฐ๋ ๊ณต์ ์ ํ๊ธฐ๋ฅผ ์ฐ์ธ์. ํ๋๋ฅผ ๋์ด ๋ฐ์ 429(
A020)๋ ๊ณ์ ์ฌ์๋ํฉ๋๋ค. operation๋ณ ๋ฒํท์Operation.getRateBucket()์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค.
๋น์ฆ๊ณ ๊ฐ SDK ์ฌ์ฉ ํํฉ(์ธ์ดยท๋ฒ์ ยท๋ฐํ์)์ ํ์ ํ ์ ์๋๋ก ๋ชจ๋ ์์ฒญ์ ๋ค์ ํค๋๋ฅผ ๋ถ์ ๋๋ค.
| ํค๋ | ๊ฐ |
|---|---|
User-Agent |
bizgo-sdk-comm-java/<SDK ๋ฒ์ > java/<Java ๋ฒ์ > (<OS>; <CPU>)[ app/<์ฑ ์ด๋ฆ>-<์ฑ ๋ฒ์ >] ์: bizgo-sdk-comm-java/1.2.0 java/21.0.11 (linux; x64) |
X-Bizgo-Client |
bizgo-sdk-comm-java/<SDK ๋ฒ์ > (ํ๋ก์๊ฐ User-Agent๋ฅผ ๋ฐ๊ฟ๋ ๋จ๋๋ก) |
- OS๋
linux/windows/darwin/freebsd/other, CPU๋x64/arm64/x86/arm/other๋ง ๋ณด๋ ๋๋ค. ํธ์คํธ๋ช ยท์ฌ์ฉ์๋ช ยท์ปค๋ ๋ฒ์ ยทIP ๊ฐ์ ๊ฐ์ ๋ฃ์ง ์๊ณ , ์ด ํค๋ ์ธ์ SDK๊ฐ ๋ฐ๋ก ์์งํ๊ฑฐ๋ ์ ์กํ๋ ์ ๋ณด๋ ์์ต๋๋ค(phone-home ์์). - ์ ํ:
Bizgo.builder().appInfo("myshop", "1.4.2")๋ก ์ฑ ์ด๋ฆยท๋ฒ์ ์ ๋ง๋ถ์ผ ์ ์์ต๋๋ค. ์ด๋ฆ์[A-Za-z0-9._-]150์, ๋ฒ์ ์30์๋ง ํ์ฉํ๊ณ ๊ทธ ๋ฐ(๊ณต๋ฐฑ, ์ค๋ฐ๊ฟ,[A-Za-z0-9._+-]1@๋ฑ)์ ์ค์ ์ค๋ฅ์ ๋๋ค. ์ด๋ฉ์ผยท์ ํ๋ฒํธ ๊ฐ์ ๊ฐ์ธ์ ๋ณด๋ฅผ ๋ฃ์ง ๋ง์ธ์. Authorization,User-Agent,X-Bizgo-Client๋ ๋ค๋ฅธ ์ต์ ์ด๋HttpTransport๋ก ๋ฎ์ด์ธ ์ ์์ต๋๋ค.
bizgo-sdk-comm-java-testing ์ํฐํฉํธ(ํ
์คํธ ์์กด์ฑ์ผ๋ก๋ง)๋ก ๋คํธ์ํฌยทAPI Key ์์ด SDK๋ฅผ ์ฐ๋ ์ฝ๋๋ฅผ ํ
์คํธํฉ๋๋ค.
testImplementation 'io.github.icomm-api:bizgo-sdk-comm-java-testing:1.2.0'import io.github.icommapi.bizgo.testing.*;
FakeTransport fake = new FakeTransport();
Bizgo client = fake.client(); // ๊ฐ์ง ํค(test-api-key-not-real), ์๋ ์ ํ ๋, ๋คํธ์ํฌ ์ฐ๊ฒฐ ์์
client.send().sms("01000000000", "01000000000", "hello"); // ๊ธฐ๋ณธ: ์์ ์๋ง๋ค A000 + ๊ฐ์ง msgKey
RecordedRequest request = fake.lastRequest();
assertEquals("sendOmni", request.getOperationId());
assertEquals("hello", request.getJsonBody().at("/messageFlow/0/sms/text").asText());
fake.on("sendOmni")
.thenFail(ErrorLayer.SERVICE, 429, "A020") // ๋ค์ ํธ์ถ: ์๋ ์ ํ (Retry-After: 0์ด๋ผ ๋ฐ๋ก ์ฌ์๋)
.thenSendResult("A000", "A306"); // ๊ทธ๋ค์: ๋ ๋ฒ์งธ ์์ ์ ๊ฑฐ์
fake.on("getReportPolling").thenFail(ErrorLayer.GATEWAY, 401, "A401");
fake.on("listReservations").thenData("{\"reservations\":[],\"hasNext\":false}");
// ์นํ
ํ
์คํธ ์์ฒญ (์๋ช
๋ฐฉ์์ ์ค์ ์ ๊ฐ์)
WebhookSigner.SignedWebhook hook = WebhookSigner.sign("test-webhook-secret", "{\"msgKey\":\"K001\"}");
ReportWebhookPayload report = new WebhookReceiver("test-webhook-secret").report(hook.headers(), hook.body());- ๊ธฐ๋ก(
requests(),requests(operationId),lastRequest())์๋ operation, method, ๊ฒฝ๋ก, ์ฟผ๋ฆฌ, JSON ๋ณธ๋ฌธ์ด ์๊ณ ํค๋ ๊ฐ(API Key ํฌํจ)์ ๊ธฐ๋กํ์ง ์์ต๋๋ค. - ์๋ต์ ์ง์ ํ์ง ์์ operation์ ์ฑ๊ณต ๋ดํฌ๋ฅผ ๋๋ ค์ค๋๋ค(๋ฐ์กยท์์ฝ ๋ฑ๋ก์ ์์ ์๋ง๋ค
A000+FAKE-MSGKEY-000001โฆ, ๊ทธ ๋ฐ์๋ ๋นdata.data).thenRespond(status, json),thenData(json),thenFail(layer, status, code),thenNetworkError(),thenTimeout(),thenDefault()๋ฅผ ์์๋๋ก ์ฐ๊ณ ๋ง์ง๋ง ์๋ต์ด ๋ฐ๋ณต๋ฉ๋๋ค. fake.clientBuilder()๋กmaxRetriesยทhook ๋ฑ ๋ค๋ฅธ ์ค์ ์ ๋ํ ์ ์์ต๋๋ค. ์ง์ ๋ง๋HttpTransport๋ SDK๊ฐ ์์ ์ค์ (๋ฆฌ๋ค์ด๋ ํธยท์ฌ์๋ยท์์ฒด ์ธ์ฆยท์ฟ ํค ๋)์ ํ์ธํ ์ ์์ด ๊ธฐ๋ณธ์ผ๋ก ๊ฑฐ๋ถํฉ๋๋ค. ๊ตฌํ์ด ์ด๋ฅผ ์งํค๊ณ API Key ํค๋ยทURL์ ๋ก๊ทธ์ ๋จ๊ธฐ์ง ์๋๋ค๋ฉดBizgo.builder().httpTransport(t).trustHttpTransport(true)๋ก ๋ช ์์ ์ผ๋ก ํ์ฉํ์ธ์.FakeTransport๋ ์ด ์ค์ ์ด ํ์ ์์ต๋๋ค.
RequestHook์ผ๋ก ํธ์ถ ์์ยท์ข
๋ฃ๋ฅผ ๋ฐ์ต๋๋ค. ์ ๋ฌ๋๋ ๊ฐ์ operationId, resource.method, HTTP method, ๊ฒฝ๋ก ํ
ํ๋ฆฟ(์: /api/comm/v1/report/inquiry/{msgKey}), ์ํ ์ฝ๋, ์ค๋ฅ layer/code, ์๋ ํ์, ์์ ์๊ฐ๋ฟ์
๋๋ค. ๋ณธ๋ฌธยท์ฟผ๋ฆฌยทํค๋ ๊ฐยท์ค์ ๊ฒฝ๋ก ๊ฐยท์ ํ๋ฒํธยทAPI Key๋ ๋๊ธฐ์ง ์์ต๋๋ค. hook์์ ๋ ์์ธ๋ ๋ฌด์๋ฉ๋๋ค.
Bizgo client = Bizgo.builder()
.hook(new RequestHook() {
@Override
public void onRequestEnd(RequestEvent e) {
metrics.record(e.getOperationId(), e.getStatus(), e.getErrorCode(), e.getAttempts(), e.getDuration());
}
})
.build();OpenTelemetry๋ ์ ํ ์ํฐํฉํธ bizgo-sdk-comm-java-opentelemetry๋ก ์ ๊ณตํฉ๋๋ค(์ฝ์ด์ ๋ฐํ์ ์์กด์ฑ์ Jackson ํ๋ ๊ทธ๋๋ก).
implementation 'io.github.icomm-api:bizgo-sdk-comm-java-opentelemetry:1.2.0'Bizgo client = Bizgo.builder().hook(BizgoTracing.create(GlobalOpenTelemetry.get())).build();span ์ด๋ฆ์ bizgo <resource>.<method>(์: bizgo alimtalk.templates.list, kind CLIENT), ์์ฑ์ http.request.method, url.template, http.response.status_code, bizgo.operation_id, bizgo.code, bizgo.layer, bizgo.retry_count, error.type์
๋๋ค. ๋น์ฆ๊ณ ๋ก trace ํค๋๋ฅผ ๋ณด๋ด์ง ์์ต๋๋ค.
๋ชจ๋ ์์ธ๋ unchecked์ด๋ฉฐ BizgoException์ ์์ํฉ๋๋ค(io.github.icommapi.bizgo.errors).
try {
client.send().omni(...);
} catch (ValidationException e) { // ๋ณด๋ด๊ธฐ ์ ๊ฒ์ฆ ์คํจ (ํ๋, ๊ธธ์ด, ๋ฌธ์). e.getViolations()
} catch (AuthenticationException e) { // ํค๊ฐ ํ๋ ธ๊ฑฐ๋ IP๊ฐ ๋ฑ๋ก๋์ง ์์
} catch (RateLimitException e) { // ์๋ ์ฌ์๋ ํ์๋ ํ๋ ์ด๊ณผ. e.getRetryAfter()
} catch (DuplicateRequestException e) { // ๊ฐ์ idempotencyKey๊ฐ ์ด๋ฏธ ์ ์๋จ (๋ค์ ๋ฐ์ก๋์ง ์์)
} catch (ApiException e) { // ๊ทธ ๋ฐ์ ๊ฑฐ์ . e.getCode(), e.getLayer(), e.getDescription(), e.getTrackingId()
} catch (ApiConnectionException e) { // ๋คํธ์ํฌ ์ค๋ฅยทํ์์์. ๋ฐ์ก์ ์ ์๋์ ์๋ ์์ โ ์ํ ์กฐํ๋ก ํ์ธ
}BizgoException
โโโ ConfigurationException โโโ ValidationException
โโโ ApiConnectionException โโ ApiTimeoutException
โโโ ApiException {httpStatus, code, serverMessage, layer, description, trackingId, body}
โ โโโ BadRequestException (400) โโโ AuthenticationException (401) โโโ PermissionDeniedException (403)
โ โโโ NotFoundException (404) โโโ DuplicateRequestException (A301)
โ โโโ RateLimitException (429 / A020) โโโ InternalServerException (5xx)
โโโ InvalidResponseException
โโโ WebhookVerificationException
- ์๋ต์ ๋ ๋จ๊ณ๋ก ํ์ ํฉ๋๋ค:
common.authCode(๊ฒ์ดํธ์จ์ด: ์ธ์ฆยทํ์) โdata.code(์ํ ์ฒ๋ฆฌ).e.getLayer()๊ฐGATEWAY/SERVICE์ ๋๋ค. ๊ฐ์ ์ฝ๋๋ผ๋ ๋จ๊ณ๋ง๋ค ๋ป์ด ๋ค๋ฆ ๋๋ค(์:A401). - HTTP 200์ธ๋ฐ
data.code๊ฐ ์คํจ๋ฉด ์๋ฌ์ฝ๋ ํ์ ๋ฌธ์์ HTTP ์ํ๋ก ์์ธ๋ฅผ ๊ณ ๋ฆ ๋๋ค(์:A306โBadRequestException). ์ค๋ช ์ServiceCodes.lookup(code)๋ก๋ ๋ณผ ์ ์์ต๋๋ค. - ๋ฐ์ก ์์ฒญ์ด ์ฑ๊ณตํด๋ ์ผ๋ถ ์์ ์๋ ๊ฑฐ์ ๋ ์ ์์ต๋๋ค. ํญ์
result.getFailed()๋ฅผ ํ์ธํ์ธ์. - ๊ฐ์
idempotencyKey๋ก ์ ํจ์๊ฐ ์์ ๋ค์ ๋ณด๋ด๋ฉด ์์ฒญ์ ์ฑ๊ณต(HTTP 200)ํ๊ณ ์ด๋ฏธ ์ ์๋ ์์ ์๋ ์์ ์๋ณ ์ฝ๋A301๋ก ์ต๋๋ค. SDK๋ ์ด ์์ ์๋ฅผgetFailed()๊ฐ ์๋๋ผresult.getDuplicates()๋ก ๋๋ ค์ค๋๋ค(getSucceeded()ยทgetMsgKeys()์๋ ์์, ์ด ์์ฒญ์ผ๋ก ์๋ก ์ ์๋ ๊ฒ์ด ์๋). ๋ฐ๋ผ์ ๊ฐ์ ํค๋ก ๋ค์ ๋ณด๋ด๋ ์์ ํ๊ณ , ์ด๋ฏธ ์ ์๋ ์์ ์๋getDuplicates()์์ ํ์ธํฉ๋๋ค. ์์ฒญ ์ ์ฒด๊ฐdata.codeA301์ด๋ฉด ๊ธฐ์กด์ฒ๋ผDuplicateRequestException์ ๋๋ค.
| ์์ฒญ | ์๋ ์ฌ์๋ |
|---|---|
| ์กฐํ, ๋ฆฌํฌํธ ์์ ํ์ธ | 429, 500/502/503/504, ๋คํธ์ํฌ ์ค๋ฅ |
๋ฐ์ก (idempotencyKey ์์) |
429, 500/502/503/504, ๋คํธ์ํฌ ์ค๋ฅ |
๋ฐ์ก (idempotencyKey ์์), ์
๋ก๋ |
429๋ง (์ค๋ณต ๋ฐ์ก ๋ฐฉ์ง) |
๊ธฐ๋ณธ๊ฐ์ ์ต๋ 2ํ ์ฌ์๋(์ง์ ๋ฐฑ์คํ 0.5sยท1sโฆ ์ต๋ 8s, jitter, Retry-After ์ฐ์ ยท์ต๋ 60s), ์์ฒญ ํ์์์ 30์ด, ์ฐ๊ฒฐ ํ์์์ 5์ด์
๋๋ค.
์ฌ์๋ํ ์์ฒญ(๋คํธ์ํฌ ์ค๋ฅยท5xxยท429 ๋ค)์ด A301์ ๋ฐ์ผ๋ฉด DuplicateRequestException.isAlreadyAccepted()๊ฐ true์ด๊ณ "์ด์ ์๋๊ฐ ์ด๋ฏธ ์ ์๋จ" ์๋ด๊ฐ ๋ถ์ต๋๋ค. ๋ฉ์์ง๋ ์ด๋ฏธ ๋ฐ์ก ์ค์ด๋ ๋ค์ ๋ณด๋ด์ง ๋ง์ธ์.
์๋ต์ด ๋ฌธ์์ ๋ค๋ฅด๋ฉด(๋ชฉ๋ก์ null, ํํ ์ค๋ฅ, ๋ฆฌ๋ค์ด๋ ํธ ๋ฑ) InvalidResponseException(HTTP ์ํ, getTrackingId(), getBody())์ด ๋ฉ๋๋ค. ๋ฐ์ก์ด๋ฉด "์์ฒญ์ ์ ์๋์ ์ ์์ผ๋ ์ํ ์กฐํ๋ก ํ์ธํ์ธ์"๊ฐ ๋ฉ์์ง์ ๋ค์ด ์์ผ๋, ๋ค์ ๋ณด๋ด๊ธฐ ์ ์ ์กฐํ API๋ก ํ์ธํ์ธ์.
Bizgo.builder().maxRetries(3).timeout(Duration.ofSeconds(10)).build();
// ํ๋ก์ยท์ฌ๋ด CA๊ฐ ํ์ํ๋ฉด SDK ์ต์
์ ์ฐ์ธ์ (TLS ๊ฒ์ฆ์ ๋๋ ์ต์
์ ์์ต๋๋ค)
Bizgo.builder()
.proxy(new InetSocketAddress("proxy.example.internal", 3128)) // ๋๋ proxy(ProxySelector)
.sslContext(companySslContext) // ์ฌ๋ด CA๋ฅผ ๋ฃ์ SSLContext
.build();
// HttpClient๋ฅผ ์ง์ ๋๊ธธ ์๋ ์์ง๋ง, ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ๋ฐ๋ฅด๊ฑฐ๋ AuthenticatorยทCookieHandler๊ฐ ์์ผ๋ฉด ์ค์ ์ค๋ฅ์
๋๋ค.
// (์๋ ์ธ์ฆ์ SDK์ Authorization(API Key)์ ๋ฐ๊พธ๊ณ 401 ๋ค ์์ฒญ(๋ฐ์ก ํฌํจ)์ ์ฌ๋ฌ ๋ฒ ๋ค์ ๋ณด๋
๋๋ค.)
// ๋๊ธด HttpClient์ TLS ์ค์ ์ด ๊ทธ๋๋ก ์ ์ฉ๋๊ณ , close()ํ์ง ์์ต๋๋ค.
Bizgo.builder().httpClient(HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build()).build();System.Logger(io.github.icommapi.bizgo)๋ฅผ ์๋๋ค. ๋ณ๋ ๋ก๊น
์์กด์ฑ์ด ์์ผ๋ฉฐ, SLF4J/Log4j ๋ฑ์ ๊ฐ ํ๋ ์์ํฌ์ System.Logger ๋ธ๋ฆฌ์ง๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
DEBUG์์ ์๋๋ง๋ค GET /api/comm/v1/message/history -> 200 (35 ms, attempt 1) ํ ์ค๋ง ๋จ๊น๋๋ค. ํค, ๋ณธ๋ฌธ, ์ฟผ๋ฆฌ ๋ฌธ์์ด, ์ ํ๋ฒํธ๋ ๋จ๊ธฐ์ง ์์ต๋๋ค.
- API Key๋ ์ ๋์ด ์์ด ํค ๊ฐ๋ง ์๋๋ค(
Bearer/ApiKey์ ๋์ด๋ 401). ์๋ค ๊ณต๋ฐฑยท์ค๋ฐ๊ฟ๋ ์ง์ฐ์ง ์๊ณ ์ค์ ์ค๋ฅ๋ก ์ฒ๋ฆฌํฉ๋๋ค(ํ๊ฒฝ๋ณ์ ํฌํจ). ํ๊ฒฝ๋ณ์๋ ์ํฌ๋ฆฟ ์ ์ฅ์์์ ์ฝ๊ณ , ์ฝ๋ยท์ ์ฅ์ยทํด๋ผ์ด์ธํธ ์ฑ์ ๋ฃ์ง ์์ต๋๋ค. - SDK๋ ํค, ์์ฒญ ๋ณธ๋ฌธ, ์ฟผ๋ฆฌ ๋ฌธ์์ด(์ ํ๋ฒํธ๊ฐ ๋ค์ด๊ฐ ์ ์์)์ ๋ก๊ทธ๋ ์์ธ ๋ฉ์์ง์ ๋จ๊ธฐ์ง ์์ต๋๋ค. ๊ฒ์ฆ ์ค๋ฅ์๋ ์ ๋ ฅ๊ฐ์ ์ฃ์ง ์๊ณ , HTTP ํด๋ผ์ด์ธํธ ์์ธ(URL ํฌํจ)๋ ์์ธ์ผ๋ก ์ฐ๊ฒฐํ์ง ์์ต๋๋ค.
- ๋ชจ๋ธยท์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ(
*Params)ยท์ต์ ๊ฐ์ฒด์toString()์ ์ ํ๋ฒํธ ๊ฐ์ด๋ฐ๋ฅผ ๊ฐ๋ฆฌ๊ณ (010****0000, 8~10์๋ฆฌ๋158****4์ฒ๋ผ ์ ๋ฐ ์ด์, 7์๋ฆฌ ์ดํ๋ ์ ๋ถ), ์ด๋ฆยท๋๋ค์ยท์ด๋ฉ์ผ์ ์ฒซ ๊ธ์๋ง(ํ**), ๋ณธ๋ฌธ ๊ฐ์ ๊ฐ์ ๊ธธ์ด๋ง(***(12์)), ํคยทํ ํฐ์ ์ ๋ถ ๊ฐ๋ฆฝ๋๋ค. getter ๊ฐ์ ๊ทธ๋๋ก์ ๋๋ค.SendOmniRequest.toJson()๊ณผApiException.getBody()์๋ ์๋ฌธ(์ ํ๋ฒํธ ํฌํจ ๊ฐ๋ฅ)์ด ์์ผ๋ ๊ทธ๋๋ก ๋ก๊ทธ์ ๋จ๊ธฐ์ง ๋ง์ธ์. - JDK HTTP ํด๋ผ์ด์ธํธ ๋๋ฒ๊ทธ ๋ก๊ทธ(
-Djdk.httpclient.HttpClient.log=headers๋ฑ)๋Authorizationํค๋๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅํ๋ฏ๋ก ์ด์์์ ์ผ์ง ๋ง์ธ์. http://base URL, userinfoยทqueryยทfragment๊ฐ ์๋ base URL์ ๊ฑฐ๋ถํฉ๋๋ค(ํ ์คํธ์ฉ localhost ์ ์ธ). SDK ์์ฒด์๋ TLS ๊ฒ์ฆ์ ๋๋ ์ต์ ์ด ์์ต๋๋ค. ๋จ,httpClient(...)๋ก ๋๊ธดHttpClient๋ ๊ทธ ํด๋ผ์ด์ธํธ์ TLS ์ค์ (SSLContext)์ด ๊ทธ๋๋ก ์ ์ฉ๋๋ฏ๋ก ๊ฒ์ฆ์ ๋ ํด๋ผ์ด์ธํธ๋ฅผ ๋๊ธฐ์ง ๋ง์ธ์. ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ๋ฐ๋ฅด๊ฑฐ๋Authenticator๊ฐ ์๋HttpClient๋ ๋ฐ์ง ์์ต๋๋ค(ํค๊ฐ ๋ค๋ฅธ ์๋ฒ๋ก ๊ฐ๊ฑฐ๋ ๋ฐ๋ ์ ์์). 3xx ์๋ต์ ๋ฐ๋ฅด์ง ์๊ณHTTP 302: ๋ฆฌ๋ค์ด๋ ํธโฆ์ค๋ฅ๋ก ์๋ฆฝ๋๋ค.- ์นํ
:
X-IB-Timestamp๋ ASCII ์ซ์ 1~16์๋ฆฌ๋ง, secret์ ๊ณต๋ฐฑ๋ง ์๋ ๊ฐ๋ ๊ฑฐ๋ถ,tolerance๋ 0๋ณด๋ค ํฌ๊ฑฐ๋null(๋)๋ง ํ์ฉํฉ๋๋ค. ๊น๊ฒ ์ค์ฒฉ๋๊ฑฐ๋ ํํ๊ฐ ํ๋ฆฐ ๋ณธ๋ฌธ์WebhookVerificationException(ํธ๋ค๋ฌ์์ 4xx๋ก ์๋ต)์ ๋๋ค. ์๋ตยท์นํ JSON์ ์ค์ฒฉ์ 64๋จ๊ณ๊น์ง์ ๋๋ค. ApiExceptionยทInvalidResponseException(๋ณธ๋ฌธ ํฌํจ)๊ณผValidationException(์๋ฐ ๋ชฉ๋ก ํฌํจ)์ Java ์ง๋ ฌํ๋ก ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค(์์ ํ ๋ฑ).- ์ทจ์ฝ์ ์ ๊ณ ๋ SECURITY.md๋ฅผ ์ฐธ๊ณ ํ์ธ์.
1.2.0์ bizgo-api-spec์ ๊ธฐ์ค์ผ๋ก ์๋ก ์์ฑํ์ต๋๋ค. 1.1.x ์ฝ๋๋ git ๊ธฐ๋ก์ ๋จ์ ์์ต๋๋ค. ํธํ๋์ง ์๋ ๋ณ๊ฒฝ:
| 1.1.x | 1.2.0 |
|---|---|
ํจํค์ง net.bizgo.client |
io.github.icommapi.bizgo (Maven groupId io.github.icomm-api์ ๋ง์ถค) |
BizgoClient.builder().apiKey(...) |
Bizgo.builder().apiKey(...) (์๋ต ์ ํ๊ฒฝ๋ณ์ BIZGO_API_KEY) |
ID/PW ํ ํฐ ์ธ์ฆ(clientId/password, /v1/auth/token) |
์ ๊ฑฐ. API Key๋ง ์ง์ |
HttpConfig.baseUrl("https://mars.ibapi.kr/api/comm") |
environment(Environment.SANDBOX) ๋๋ baseUrl("https://mars.ibapi.kr") (/api/comm ์์ด) |
send(OmniRequest) |
send().omni(to, messages...), send().request(SendOmniRequest), send().sms/lms/mms(...) |
upload(FileRequest) |
files().uploadMms / uploadRcs / uploadBrandMessage(file, BrandImageKind) |
get(ReportPollingRequest) / remove(...) |
reports().poll() / reports().ack(reportId), ๋๋ reports().consume(handler) |
get(ReportInquiryRequest) |
reports().inquiry(msgKey) |
inquireStatusByMsgKey / inquireStatusByRequestId |
messages().status(msgKey) / messages().statusByRequestId(requestId) |
inquireStatistics(...) / inquireHistory(...) |
messages().statistics(...) / messages().history(HistoryQuery), iterHistory, streamHistory |
| (์์) | MO ์กฐํ(messages().mo, moHistory), ์นํ
๊ฒ์ฆ(WebhookReceiver) |
๋ชจ๋ ์๋ต์ด BizgoResponse ํ๋ |
์๋ํฌ์ธํธ๋ณ ํ์
(SendResult, ReportBatch, List<Report>, MessagePage โฆ) |
data.code != A000์ด์ด๋ ์ ์ ๋ฐํ |
์์ธ(ApiException ๊ณ์ด)๋ก ๋์ง |
๋ชจ๋ ๋ฉ์๋ throws Exception, ์ฌ๋ฌ ๊ณ์ด์ ์์ธ |
unchecked BizgoException ๊ณ์ธต ํ๋ |
OmniRequest, ์์ผ๋ก ์ด ๋ฉ์์ง ํด๋์คยทenum(MsgType ๋ฑ) |
์คํ์์ ์์ฑํ ๋ถ๋ณ ๋ชจ๋ธ(SendOmniRequest, SmsMessage โฆ). ์ฝ๋ ๊ฐ์ ๋ฌธ์์ด์ด๊ณ build()์์ ํ์ฉ ๊ฐ ๊ฒ์ฆ |
์นด์นด์ค ๋ฒํผ ์๋ธํด๋์ค(WLButton ๋ฑ) |
AlimtalkButton / BrandMessageButton ํ๋์ type("WL") |
idempotencyTtl(String) |
SendOptions.builder().idempotencyTtl(Integer) |
Apache HttpClient 4, Tika, shadow jar, Jackson compileOnly |
JDK HttpClient, ์ผ๋ฐ jar, Jackson jackson-databind๊ฐ ์ ์(api) ์์กด์ฑ |
| ํ์์์ ์์, ์ฌ์๋ ์์ | ๊ธฐ๋ณธ ํ์์์ 30์ด(์ฐ๊ฒฐ 5์ด), ์ฌ์๋ 2ํ(์ ์ฑ ์ ์ ํ) |
1.1.x์์ ๊ณ ์น ๊ฒฐํจ: ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ์ธ์ฆ ํ ํฐ์ static์ผ๋ก ๊ณต์ ํ๋ ๋ฌธ์ (์ด์ ํคยท์ค์ ์ ์ธ์คํด์ค๋ณ), RCS ๋ฒํผ์ด ์ ์ก๋์ง ์๋ ๋ฌธ์ , ๋ณธ๋ฌธ ์๋ ์ค๋ฅ ์๋ต์์ NPE, ๊ฒฝ๋ก ํ๋ผ๋ฏธํฐ ๋ฏธ์ธ์ฝ๋ฉ, README์ ์ฝ๋ ๋ถ์ผ์น, POM์ scm/url์ด ๋ค๋ฅธ ์ ์ฅ์๋ฅผ ๊ฐ๋ฆฌํค๋ ๋ฌธ์ .
./gradlew build # ์ปดํ์ผ(-Xlint:all -Werror), ํ
์คํธ(mock HTTP, ๋คํธ์ํฌยทํค ๋ถํ์), javadoc, ์์ฑ ์ฝ๋ ๊ฒ์ฌ
./gradlew generateModels # spec/openapi.yaml์ด ๋ฐ๋์์ ๋ ๋ชจ๋ธ ์ฌ์์ฑ
./gradlew checkGeneratedModels # ์์ฑ ๊ฒฐ๊ณผ(๋ชจ๋ธยท๋ฆฌ์์คยทํ๋ผ๋ฏธํฐยท์นํ
ํ์)๊ฐ ์ปค๋ฐ๊ณผ ๊ฐ์์ง (CI)๋ชจ๋: ๋ฃจํธ(์ฝ์ด, bizgo-sdk-comm-java), testing/(bizgo-sdk-comm-java-testing), opentelemetry/(bizgo-sdk-comm-java-opentelemetry).
AI ์ฝ๋ฉ ๋๊ตฌ๋ก ์ด ์ ์ฅ์๋ฅผ ์์ ํ ๋์ ๊ท์น์ AGENTS.md์ ์์ต๋๋ค. SDK๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ AI๋ก ์์ฑํ ๋๋ llms.txt๋ฅผ ์ปจํ ์คํธ๋ก ๋ฃ์ผ๋ฉด ์ ํ๋๊ฐ ๋์์ง๋๋ค.