使用 Twilio 在 Spring Boot 中發送 WhatsApp 訊息
1. 概述
WhatsApp Messenger 是全球領先的訊息平台,使其成為企業與用戶聯繫的重要工具。
透過 WhatsApp 進行溝通,我們可以提高客戶參與度、提供高效支援並與用戶建立更牢固的關係。
在本教程中,我們將探討如何在 Spring Boot 應用程式中使用Twilio發送 WhatsApp 訊息。我們將逐步完成必要的配置,並實現發送訊息和處理用戶回應的功能。
2. 設定 Twilio
要學習本教程,我們首先需要一個 Twilio 帳戶和一個WhatsApp Business 帳戶 (WABA) 。
我們需要透過創建 WhatsApp Sender 將這兩個帳戶連接在一起。 Twilio 提供了詳細的設定教程,可以參考它來引導我們完成此過程。
成功設定 WhatsApp Sender 後,我們可以繼續向用戶發送訊息和從用戶接收訊息。
3. 設定項目
在使用 Twilio 發送 WhatsApp 訊息之前,我們需要包含 SDK 依賴項並正確配置我們的應用程式。
3.1.依賴關係
讓我們先將Twilio SDK 依賴項加入專案的pom.xml
檔中:
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>10.4.1</version>
</dependency>
3.2.定義 Twilio 配置屬性
現在,為了與 Twilio 服務互動並向用戶發送 WhatsApp 訊息,我們需要配置帳戶 SID 和身份驗證令牌來驗證 API 請求。我們還需要訊息服務 SID 來指定我們希望使用支援 WhatsApp 的 Twilio 電話號碼發送訊息的訊息服務。
我們將這些屬性儲存在專案的application.yaml
檔案中,並使用@ConfigurationProperties
將值對應到 POJO,我們的服務層在與 Twilio 互動時引用該 POJO:
@Validated
@ConfigurationProperties(prefix = "com.baeldung.twilio")
class TwilioConfigurationProperties {
@NotBlank
@Pattern(regexp = "^AC[0-9a-fA-F]{32}$")
private String accountSid;
@NotBlank
private String authToken;
@NotBlank
@Pattern(regexp = "^MG[0-9a-fA-F]{32}$")
private String messagingSid;
// standard setters and getters
}
我們還添加了驗證註釋,以確保正確配置所有必需的屬性。如果任何定義的驗證失敗,都會導致 Spring ApplicationContext
無法啟動。這使我們能夠符合快速失敗原則。
下面是我們的application.yaml
檔案的片段,它定義了將自動映射到我們的TwilioConfigurationProperties
類別的所需屬性:
com:
baeldung:
twilio:
account-sid: ${TWILIO_ACCOUNT_SID}
auth-token: ${TWILIO_AUTH_TOKEN}
messaging-sid: ${TWILIO_MESSAGING_SID}
因此,此設定允許我們外部化 Twilio 屬性並在我們的應用程式中輕鬆存取它們。
3.3.啟動時初始化 Twilio
為了成功呼叫SDK暴露的方法,我們需要在啟動時初始化一次。為此,我們將建立一個實作ApplicationRunner
介面的TwilioInitializer
類別:
@Component
@EnableConfigurationProperties(TwilioConfigurationProperties.class)
class TwilioInitializer implements ApplicationRunner {
private final TwilioConfigurationProperties twilioConfigurationProperties;
// standard constructor
@Override
public void run(ApplicationArguments args) {
String accountSid = twilioConfigurationProperties.getAccountSid();
String authToken = twilioConfigurationProperties.getAuthToken();
Twilio.init(accountSid, authToken);
}
}
使用建構函式註入,我們注入先前建立的TwilioConfigurationProperties
類別的實例。然後我們在run()
方法中使用配置的帳戶 SID 和身份驗證令牌來初始化 Twilio SDK。
這確保了當我們的應用程式啟動時,Twilio 已準備好使用。這種方法比每次需要發送訊息時在服務層中初始化 Twilio 用戶端更好。
4. 發送 WhatsApp 訊息
現在我們已經定義了屬性,讓我們建立一個WhatsAppMessageDispatcher
類別並引用它們以與 Twilio 互動。
對於此演示,我們將舉一個範例,每當我們在網站上發布新文章時,我們都希望通知使用者。我們將向他們發送一條包含該文章連結的 WhatsApp 訊息,以便他們可以輕鬆查看。
4.1.配置內容SID
為了限制企業發送未經請求的訊息或垃圾郵件, WhatsApp 要求所有企業發起的通知都進行模板化並預先註冊。這些模板由唯一的內容 SID 進行標識,該 SID 必須經過 WhatsApp 的批准才能在我們的應用程式中使用。
對於我們的範例,我們將配置以下訊息範本:
New Article Published. Check it out : {{ArticleURL}}
這裡, {{ArticleURL}}
是一個佔位符,當我們發送通知時,它將被替換為新發布文章的實際 URL。
現在,讓我們在TwilioConfigurationProperties
類別中定義一個新的巢狀類別來保存內容 SID:
@Valid
private NewArticleNotification newArticleNotification = new NewArticleNotification();
class NewArticleNotification {
@NotBlank
@Pattern(regexp = "^HX[0-9a-fA-F]{32}$")
private String contentSid;
// standard setter and getter
}
我們再次添加驗證註釋,以確保我們正確配置內容 SID 並且它與預期格式相符。
同樣,讓我們將相應的內容SID屬性添加到我們的application.yaml
檔案中:
com:
baeldung:
twilio:
new-article-notification:
content-sid: ${NEW_ARTICLE_NOTIFICATION_CONTENT_SID}
4.2.實現訊息調度程序
現在我們已經配置了內容 SID,讓我們實作服務方法來向使用者發送通知:
public void dispatchNewArticleNotification(String phoneNumber, String articleUrl) {
String messagingSid = twilioConfigurationProperties.getMessagingSid();
String contentSid = twilioConfigurationProperties.getNewArticleNotification().getContentSid();
PhoneNumber toPhoneNumber = new PhoneNumber(String.format("whatsapp:%s", phoneNumber));
JSONObject contentVariables = new JSONObject();
contentVariables.put("ArticleURL", articleUrl);
Message.creator(toPhoneNumber, messagingSid)
.setContentSid(contentSid)
.setContentVariables(contentVariables.toString())
.create();
}
在我們的dispatchNewArticleNotification()
方法中,我們使用配置的訊息SID和內容SID向指定的電話號碼發送通知。我們也將文章 URL 作為內容變數傳遞,該變數將用於取代訊息範本中的佔位符。
需要注意的是,我們也可以設定不帶任何占位符的靜態訊息範本。在這種情況下,我們可以簡單地省略對setContentVariables()
方法的呼叫。
5. 處理 WhatsApp 回复
一旦我們發出通知,我們的用戶可能會回覆他們的想法或問題。當用戶回覆我們的 WhatsApp 企業帳戶時,就會啟動一個 24 小時會話窗口,在此期間我們可以使用自由格式的消息與用戶進行通信,而無需預先批准的模板。
為了自動處理來自應用程式的使用者回复,我們需要在 Twilio 訊息服務中配置一個webhook端點。每當用戶發送訊息時,Twilio 服務都會呼叫此端點。我們在配置的 API 端點中收到多個參數,可以使用它們來自訂回應。
讓我們看看如何在 Spring Boot 應用程式中建立這樣的 API 端點。
5.1.實現回覆訊息調度程序
首先,我們將在WhatsAppMessageDispatcher
類別中建立一個新的服務方法來傳送自由格式的回覆訊息:
public void dispatchReplyMessage(String phoneNumber, String username) {
String messagingSid = twilioConfigurationProperties.getMessagingSid();
PhoneNumber toPhoneNumber = new PhoneNumber(String.format("whatsapp:%s", phoneNumber));
String message = String.format("Hey %s, our team will get back to you shortly.", username);
Message.creator(toPhoneNumber, messagingSid, message).create();
}
在我們的dispatchReplyMessage()
方法中,我們向用戶發送個人化訊息,透過他們的用戶名來稱呼他們,並讓他們知道我們的團隊將很快回覆他們。
值得注意的是,我們甚至可以在 24 小時會話期間向使用者發送彩信。
5.2.公開 Webhook 端點
接下來,我們將在應用程式中公開 POST API 端點。此端點的路徑應與我們在 Twilio 訊息服務中配置的 Webhook URL 相符:
@PostMapping(value = "/api/v1/whatsapp-message-reply")
public ResponseEntity<Void> reply(@RequestParam("ProfileName") String username,
@RequestParam("WaId") String phoneNumber) {
whatsappMessageDispatcher.dispatchReplyMessage(phoneNumber, username);
return ResponseEntity.ok().build();
}
在我們的控制器方法中,我們接受來自 Twilio 的ProfileName
和WaId
參數。這些參數分別包含發送訊息的使用者的使用者名稱和電話號碼。然後,我們將這些值傳遞給dispatchReplyMessage()
方法,以將回應傳送回使用者。
我們在範例中使用了ProfileName
和WaId
參數。但如前所述,Twilio 會向我們配置的 API 端點發送多個參數。例如,我們可以存取Body
參數來檢索使用者訊息的文字內容。我們可以將此訊息儲存在佇列中,並將其路由到適當的支援團隊進行進一步處理。
6. 測試 Twilio 集成
現在我們已經實現了使用 Twilio 發送 WhatsApp 訊息的功能,讓我們看看如何測試此整合。
測試外部服務可能具有挑戰性,因為我們不想在測試期間對 Twilio 進行實際的 API 呼叫。這是我們將使用 MockServer 的地方,它允許我們模擬傳出的 Twilio 呼叫。
6.1.配置 Twilio REST 用戶端
為了將我們的 Twilio API 請求路由到 MockServer,我們需要為 Twilio SDK 設定自訂 HTTP 用戶端。
我們將在測試套件中建立一個類,該類別使用自訂HttpClient
創建TwilioRestClient
的實例:
class TwilioProxyClient {
private final String accountSid;
private final String authToken;
private final String host;
private final int port;
// standard constructor
public TwilioRestClient createHttpClient() {
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial((chain, authType) -> true)
.build();
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
.setSSLContext(sslContext)
.setProxy(new HttpHost(host, port));
HttpClient httpClient = new NetworkHttpClient(clientBuilder);
return new Builder(accountSid, authToken)
.httpClient(httpClient)
.build();
}
}
在我們的TwilioProxyClient
類別中,我們建立一個自訂HttpClient
,它透過由host
和port
參數指定的代理伺服器路由所有請求。我們也將 SSL 上下文配置為信任所有證書,因為 MockServer 預設使用自簽名證書。
6.2.配置測試環境
在編寫測試之前,我們將在src/test/resources
目錄中建立application-integration-test.yaml
文件,其中包含以下內容:
com:
baeldung:
twilio:
account-sid: AC123abc123abc123abc123abc123abc12
auth-token: test-auth-token
messaging-sid: MG123abc123abc123abc123abc123abc12
new-article-notification:
content-sid: HX123abc123abc123abc123abc123abc12
這些虛擬值繞過了我們先前在TwilioConfigurationProperties class
中配置的驗證。
現在,讓我們使用@BeforeEach
註解來設定測試環境
@Autowired
private TwilioConfigurationProperties twilioConfigurationProperties;
private MockServerClient mockServerClient;
private String twilioApiPath;
@BeforeEach
void setUp() {
String accountSid = twilioConfigurationProperties.getAccountSid();
String authToken = twilioConfigurationProperties.getAuthToken();
InetSocketAddress remoteAddress = mockServerClient.remoteAddress();
String host = remoteAddress.getHostName();
int port = remoteAddress.getPort();
TwilioProxyClient twilioProxyClient = new TwilioProxyClient(accountSid, authToken, host, port);
Twilio.setRestClient(twilioProxyClient.createHttpClient());
twilioApiPath = String.format("/2010-04-01/Accounts/%s/Messages.json", accountSid);
}
在setUp()
方法中,我們建立TwilioProxyClient
類別的實例,並傳入正在執行的 MockServer 實例的host
和port
。然後,該客戶端用於為 Twilio SDK 設定自訂RestClient
。我們也將用於發送訊息的 API 路徑儲存在twilioApiPath
變數中。
6.3.驗證 Twilio 請求
最後,讓我們編寫一個測試案例來驗證我們的dispatchNewArticleNotification()
方法是否向Twilio發送了預期的請求:
// Set up test data
String contentSid = twilioConfigurationProperties.getNewArticleNotification().getContentSid();
String messagingSid = twilioConfigurationProperties.getMessagingSid();
String contactNumber = "+911001001000";
String articleUrl = RandomString.make();
// Configure mock server expectations
mockServerClient
.when(request()
.withMethod("POST")
.withPath(twilioApiPath)
.withBody(new ParameterBody(
param("To", String.format("whatsapp:%s", contactNumber)),
param("ContentSid", contentSid),
param("ContentVariables", String.format("{\"ArticleURL\":\"%s\"}", articleUrl)),
param("MessagingServiceSid", messagingSid)
))
)
.respond(response()
.withStatusCode(200)
.withBody(EMPTY_JSON));
// Invoke method under test
whatsAppMessageDispatcher.dispatchNewArticleNotification(contactNumber, articleUrl);
// Verify the expected request was made
mockServerClient.verify(request()
.withMethod("POST")
.withPath(twilioApiPath)
.withBody(new ParameterBody(
param("To", String.format("whatsapp:%s", contactNumber)),
param("ContentSid", contentSid),
param("ContentVariables", String.format("{\"ArticleURL\":\"%s\"}", articleUrl)),
param("MessagingServiceSid", messagingSid)
)),
VerificationTimes.once()
);
在我們的測試方法中,我們首先設定測試資料並配置 MockServer 以期望對 Twilio API 路徑發出 POST 請求,並在請求正文中包含特定參數。當發出此請求時,我們也指示 MockServer 以200
狀態碼和空 JSON 正文回應。
接下來,我們使用測試資料呼叫dispatchNewArticleNotification()
方法,並驗證是否向MockServer發出了一次預期的請求。
透過使用 MockServer 模擬 Twilio API,我們確保我們的整合能如預期運作,而不會實際發送任何訊息或產生任何成本。
七、結論
在本文中,我們探討如何從 Spring Boot 應用程式使用 Twilio 發送 WhatsApp 訊息。
我們完成了必要的配置並實現了使用動態佔位符向用戶發送模板通知的功能。
最後,我們透過公開 Webhook 端點來接收來自 Twilio 的回复資料來處理用戶對通知的回复,並創建一個服務方法來分派通用的非模板化回复訊息。
與往常一樣,本文中使用的所有程式碼範例都可以在 GitHub 上找到。