1. 概述
WhatsApp Messenger 是全球領先的即時通訊平台,是企業連接用户的重要工具。
通過在 WhatsApp 上進行溝通,我們可以提升客户參與度,提供高效支持,並與用户建立更緊密的聯繫。
在本教程中,我們將探討如何使用 Twilio 在 Spring Boot 應用程序中發送 WhatsApp 消息。 我們將逐步講解必要的配置,並實現發送消息和處理用户回覆的功能。
2. 設置 Twilio
為了遵循本教程,我們首先需要一個 Twilio 賬户和一個 WhatsApp Business Account (WABA)。
我們需要將這兩個賬户連接起來,通過創建 WhatsApp 發送者來完成。Twilio 提供了一個 詳細的設置教程,可以參考該教程指導我們完成這個過程。
在成功設置 WhatsApp 發送者後,我們就可以開始向用户發送消息並接收來自用户的消息。
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 請求的身份驗證。 我們還需要使用我們的 WhatsApp 啓用 Twilio 電話號碼用於發送消息的 消息傳遞服務 SID。
我們將這些屬性存儲在我們的項目的 <em >application.yaml</em> 文件中,並使用 <em >@ConfigurationProperties</em> 將值映射到 POJO,該 POJO 由我們的服務層在與 Twilio 交互時引用:
@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 中暴露的方法,我們需要在啓動時將其初始化一次。
為此,我們將創建一個 TwilioInitializer 類,該類實現了 ApplicationRunner 接口:
@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 類的一個實例。然後,我們使用配置好的 account SID 和 auth token 初始化 Twilio SDK 在 run() 方法中。
這確保了 Twilio 在我們的應用程序啓動時已準備好使用。 這種方法優於在每次需要發送消息時在我們的服務層中初始化 Twilio 客户端。
4. 發送 WhatsApp 消息
現在我們已經定義了我們的屬性,讓我們創建一個 WhatsAppMessageDispatcher 類並將其引用以與 Twilio 交互。
對於本次演示,我們將採用一個示例,即我們希望在網站上發佈新文章時通知我們的用户。 我們將向他們發送一條 WhatsApp 消息,其中包含指向文章的鏈接,以便他們可以輕鬆查看。
4.1. 配置內容 SID
為了限制企業發送垃圾信息或不想要的營銷信息,WhatsApp 要求所有企業發起的通知必須使用模板並預先註冊。這些 模板 通過一個唯一的 Content SID 進行標識,並且必須在 WhatsApp 批准之前才能在我們的應用程序中使用。
對於我們的示例,我們將配置以下消息模板:
New Article Published. Check it out : {{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 並使其與預期格式匹配。
同樣,讓我們在 application.yaml文件中添加相應的內容 SID 屬性:
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 客户端。
我們將創建一個類在我們的測試套件中,該類將創建一個 TwilioRestClient 實例,並使用自定義 HttpClient:
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();
}
}在我們的 <em >TwilioProxyClient</em> 類中,我們創建了一個自定義的 <em >HttpClient</em>,它會將所有請求路由到通過 <em >host</em> 和 <em >port</em> 參數指定的代理服務器。<strong >我們還配置了 SSL 上下文以信任所有證書,因為 MockServer 默認使用自簽名證書</strong>。
6.2. 配置測試環境
在編寫測試之前,我們將創建一個 <em >application-integration-test.yaml</em> 文件,並將其放置在我們的 <em >src/test/resources</em> 目錄下,內容如下:
com:
baeldung:
twilio:
account-sid: AC123abc123abc123abc123abc123abc12
auth-token: test-auth-token
messaging-sid: MG123abc123abc123abc123abc123abc12
new-article-notification:
content-sid: HX123abc123abc123abc123abc123abc12這些佔位值繞過了我們在 TwilioConfigurationProperties 類 中配置的驗證。
現在,讓我們使用 @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,我們確保我們的集成按照預期工作,而無需實際發送任何消息或產生任何費用.
7. 結論
本文介紹瞭如何使用 Twilio 從 Spring Boot 應用程序中發送 WhatsApp 消息。
我們詳細講解了必要的配置,並實現了將帶有動態佔位符的模板通知發送給用户的功能。
最後,我們通過暴露一個 Webhook 端點來接收 Twilio 從用户處發送的回覆數據,並創建了一個用於發送通用非模板回覆消息的服務方法。