Skip to content

About

Bizgo Communication API SDK JAVA

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

bizgo-sdk-comm-java

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, ๊ด€์ธก).

์‹œ์ž‘ํ•˜๊ธฐ

  1. ์ฝ˜์†” ๋ฐœ์†ก๊ด€๋ฆฌ > ์—ฐ๋™๊ด€๋ฆฌ์—์„œ API Key๋ฅผ ๋ฐœ๊ธ‰ํ•˜๊ณ , ํ˜ธ์ถœํ•  ์„œ๋ฒ„์˜ ๊ณต์ธ IP๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.
  2. ํ‚ค๋ฅผ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ํ‚ค๋Š” ์ฝ”๋“œ๋‚˜ ์ €์žฅ์†Œ์— ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
export BIZGO_API_KEY=...
  1. 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), RCS header("1") โ†’ footer, ์ƒ๋‹ดํ†ก Plain FILEยท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์ด๋ฉฐ ๋ฒ”์œ„ ๋ฐ–์ด๋ฉด ๋ณด๋‚ด๊ธฐ ์ „์— ์˜ค๋ฅ˜๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค.

์ „์ฒด API

์œ„์˜ ๋ฐœ์†กยท์—…๋กœ๋“œยท๋ฆฌํฌํŠธยท์กฐํšŒ ์™ธ์— ์ŠคํŽ™์˜ ๋ชจ๋“  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 ์‹๋ณ„ ์ •๋ณด

๋น„์ฆˆ๊ณ ๊ฐ€ 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์ž, ๋ฒ„์ „์€ [A-Za-z0-9._+-] 130์ž๋งŒ ํ—ˆ์šฉํ•˜๊ณ  ๊ทธ ๋ฐ–(๊ณต๋ฐฑ, ์ค„๋ฐ”๊ฟˆ, @ ๋“ฑ)์€ ์„ค์ • ์˜ค๋ฅ˜์ž…๋‹ˆ๋‹ค. ์ด๋ฉ”์ผยท์ „ํ™”๋ฒˆํ˜ธ ๊ฐ™์€ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋„ฃ์ง€ ๋งˆ์„ธ์š”.
  • 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๋Š” ์ด ์„ค์ •์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

๊ด€์ธก(Observability)

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.code A301์ด๋ฉด ๊ธฐ์กด์ฒ˜๋Ÿผ 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๋ฅผ ์ปจํ…์ŠคํŠธ๋กœ ๋„ฃ์œผ๋ฉด ์ •ํ™•๋„๊ฐ€ ๋†’์•„์ง‘๋‹ˆ๋‹ค.

๋ผ์ด์„ ์Šค

Apache-2.0

About

Bizgo Communication API SDK JAVA

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages