生活已经够复杂的了,还需要在朋友和家人之间转发信息,以确保每个人都能了解最新的情况。在这篇文章中,我将分享我如何设置一个Twilio号码,我把它作为 "我 "的电话号码给我孩子的学校,这样,所有发自该号码的信息都会自动转发给我和我妻子,我们中的任何一个都可以回复。我希望你能想到在你自己的生活中,这可能是很方便的情况:包裹交付、聚会计划、约会提醒,这个名单很长。
在这篇文章中,我将使用Java,但同样的方法也适用于任何你可以建立一个网络应用的语言。如果你对JavaScript很熟悉,那么Code Exchange上的SMS Forwarding to Multiple Numbers将是一个很好的起点。
我们正在建立什么?
这里的一切都基于一个单一的Twilio电话号码,以及你和你的家人或朋友有手机的群体。你可以把Twilio号码给别人,就像你自己的号码一样。
我们将设置Twilio号码,以便当有人给它发短信时,信息将被转发给你的小组中的每个人。在这个例子中,我将使用两个小组成员,但代码的设计使你可以使用你想要的数量:

当你的小组以外的人(左边的手机)向Twilio号码发送短信时,该信息将被转发给小组中的每个人(右边的手机)。这些信息看起来是由Twilio号码发出的,所以真正的发件人的电话号码会被加在信息的开头。
当你的小组中的任何人回复Twilio号码时,他们应该把真正的目的地号码放在信息的开头,在信息被转发之前,这个号码会被删除。你群里的每个人也会得到一份消息的副本:

请注意,如果你想把一个群组成员的信息发送给所有其他人,你可以通过在信息前加上自己的号码来实现。
使用Twilio的可编程消息系统
为了告诉Twilio如何响应传入的短信,我们将使用webhooks。当一条消息进入我们的电话号码时,Twilio将向我们提供的URL发出HTTP请求。我们将建立一个应用程序,在HTTP响应中发送指令,告诉Twilio接下来要做什么。

HTTP响应中的指令是用TwiML写的,包括多个Message标签,告诉Twilio发送新的文本信息。这些消息的内容和目的地取决于谁发来的消息以及他们说了什么;这些属性是HTTP请求的一部分。继续阅读,看看如何使用Java和Spring Boot构建这个应用。
构建应用程序
Spring Boot是在Java中构建Web应用程序的最流行的框架。我喜欢用Spring Initializr开始新项目。如果你想跟着编码,可以使用这个链接,它设置的选项和我用的一样,或者在GitHub上找到成品项目。
下载、解压并在你的IDE中打开生成的项目。在src/main/java 的com.example.smsgroupbroadcast 包中会有一个单独的类,叫做SmsGroupBroadcastApplication 。你不需要编辑该类,但它有一个main 方法,可以用来运行应用程序。
在同一个包中,在一个名为SmsHandler.java 的新文件中创建一个新的类,名为SmsHandler 。为了不让事情变得太复杂,我们将把所有的代码放在这个类里。当我们完成时,它将有大约100行长。
从我们需要在启动时运行的代码开始:
@RestController
public class SmsHandler {
private final Set<String> groupPhoneNumbers;
public SmsHandler() {
groupPhoneNumbers = Set.of(System.getenv("GROUP_PHONE_NUMBERS").split(","));
}
// more code will go in here
}
@RestController 注解告诉Spring,这个类应该被扫描出可以处理HTTP请求的方法。我们很快就会写一个。
第4行的Set<String> groupPhoneNumbers ,在构造函数中通过读取一个环境变量进行初始化,该变量的值是一个以逗号分隔的E.164格式的电话号码字符串。这些号码应该是你的手机和你小组中其他人的手机(上图右边的所有人)。你可以直接在你的IDE中设置环境变量。

IntelliJ IDEA环境变量配置
一种处理HTTP请求的方法
为了更容易写出正确的TwiML,我们将使用Twilio的Java辅助库。在pom.xml ,即项目顶层的Maven配置文件的<dependencies> 部分中添加以下代码:
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>8.18.0</version>
</dependency>
我们一直建议使用最新版本的Twilio辅助库。在撰写本文时,最新版本是8.18.0 ,你可以在mvnrepository.com上查看最新版本。
这时你可能需要告诉你的IDE重新加载Maven的变化。然后,在你的SmsHandler 类中加入这段代码:
@RequestMapping(
value = "/sms",
method = {RequestMethod.GET, RequestMethod.POST},
produces = "application/xml")
@ResponseBody
public String handleSmsWebhook(
@RequestParam("From") String fromNumber,
@RequestParam("To") String twilioNumber,
@RequestParam("Body") String messageBody) {
List<Message> outgoingMessages;
if (groupPhoneNumbers.contains(fromNumber)) {
outgoingMessages = messagesSentFromGroup(fromNumber, twilioNumber, messageBody);
} else {
outgoingMessages = messagesSentToGroup(fromNumber, twilioNumber, messageBody);
}
MessagingResponse.Builder responseBuilder = new MessagingResponse.Builder();
outgoingMessages.forEach(responseBuilder::message);
return responseBuilder.build().toXml();
}
这个方法的开头有很多注解,Spring会识别这些注解:
@RequestMapping告诉Spring这个方法应该在 和 的请求中调用, ,并且响应中的 是 。GETPOST/smsContent-typeapplication/xml@ResponseBody告诉Spring,这个方法的返回值应该被用作HTTP响应的主体。@RequestParam注解告诉Spring从HTTP请求中提取命名的参数,并将它们作为参数传递给方法。这对GET和POST都有效,尽管这些参数在HTTP请求的不同部分。
该方法的主体创建了一个Message 对象的列表,根据触发该webhook的传入短信是否来自一个组成员,该列表的填充方式不同(第12行)。我们稍后会定义messagesSentFromGroup 和messagesSentToGroup 方法,但首先要注意在第19-21行,信息列表是如何通过forEach 添加到MessagingResponse 。
处理来自非小组成员的消息
如果上述方法中的groupPhoneNumbers.contains(fromNumber) 检查返回false ,那么我们就知道这个消息是来自我们组以外的人。在这种情况下,messagesSentToGroup 方法被调用,以获得一个Message 对象的列表,该列表代表将转发给每个组员的传入消息的副本:
private List<Message> messagesSentToGroup(String fromNumber, String twilioNumber, String messageBody) {
List<Message> messages = new ArrayList<>();
String finalMessage = "From " + fromNumber + " " + messageBody;
groupPhoneNumbers.forEach(groupMemberNumber ->
messages.add(createMessageTwiml(groupMemberNumber, twilioNumber, finalMessage))
);
return messages;
}
我们建立finalMessage ,并为每个组员添加一个消息到列表中。我创建了一个名为createMessageTwiml 的小型辅助方法,将Twilio辅助库的构建模式代码变成了一个单行代码。我觉得这样做是值得的,因为我们将在本课中多次建立Message对象。这个方法看起来像这样:
private Message createMessageTwiml(String to, String from, String body) {
return new Message.Builder()
.to(to)
.from(from)
.body(new Body.Builder(body).build())
.build();
}
来自群组成员的消息
当小组成员向Twilio号码发送消息时,他们应该在前面加上真正的目的地号码。

信息中的 "谢谢你!"部分将从Twilio号码发送到信息开头的号码上。群里的每个人也会得到一份副本。要做到这一点,messagesSentFromGroup 方法必须拆分消息正文,检查它是否以电话号码开头(如果不是,则发送一个有用的提醒),并建立一个外发消息的列表,像这样:
private List<Message> messagesSentFromGroup(String fromNumber, String twilioNumber, String messageBody) {
List<Message> messages = new ArrayList<>();
String[] messageParts = messageBody.split("\\s+", 2);
String e164Regex = "\\+[0-9]+";
if (messageParts.length != 2 || !messageParts[0].matches(e164Regex)) {
return List.of(createHowToMessage(fromNumber, twilioNumber));
}
String realToNumber = messageParts[0];
String realMessageBody = messageParts[1];
// add the message to the non-group recipient
messages.add(
createMessageTwiml(realToNumber, twilioNumber, realMessageBody)
);
// send a copy of the message to everyone in the group except the sender
String groupCopyMessage = "To " + realToNumber + " " + realMessageBody;
groupPhoneNumbers.forEach(groupMemberNumber -> {
if (!groupMemberNumber.equals(fromNumber)) {
messages.add(
createMessageTwiml(groupMemberNumber, twilioNumber, groupCopyMessage));
}
});
return messages;
}
private Message createHowToMessage(String fromNumber, String twilioNumber){
return createMessageTwiml(fromNumber, twilioNumber,
"To send a message to someone outside your group, " +
"don't forget to include the destination phone number at the start, " +
"eg '+44xxxx Ahoy!'");
}
messagesSentFromGroup 方法可能看起来有很多代码,但它大致上分成了两半。第4-9行处理拆分输入并检查是否以电话号码开头。e164Regex 测试+ 后面的数字,这与E.164电话号码的格式相对应。
messagesSentFromGroup 的其余部分建立了一个我们需要发送的所有信息的列表并将其返回。
最后,有一个单独的方法用于createHowToMessage ,我认为这个方法值得拆分,以保持较长的方法更易读。
在本地运行你的代码
启动该应用程序的最简单方法是使用你的IDE来运行我们之前看到的SmsGroupBroadcasterApplication 类中的main 方法。如果你喜欢使用命令行终端,那么从项目的顶层运行./mvnw spring-boot:run 。无论哪种方式,都要记得设置GROUP_PHONE_NUMBERS 环境变量。
一旦应用程序启动,你可以浏览到http://localhost:8080/sms?From=__from__&To=__to__&Body=__body__,你应该看到一个类似的响应:
<Response>
<Message from="__to__" to="GROUP_MEMBER_1">
<Body>From __from__ __body__</Body>
</Message>
<Message from="__to__" to="GROUP_MEMBER_2">
<Body>From __from__ __body__</Body>
</Message>
</Response>
GROUP_MEMBER_1 而 将是你的 环境变量中的数字。GROUP_MEMBER_2 GROUP_PHONE_NUMBERS
用Twilio使用你的代码
为了让Twilio能够使用你的应用程序进行网络勾选,它将需要一个公共的URL。有很多方法可以在线部署Java代码,但为了简单起见,当你在工作时,我推荐使用ngrok。
安装ngrok后,你可以运行ngrok http 8080 ,你会看到一个https 转发URL,你需要在电话号码配置页面上为 "有消息进来 "时设置这个URL。不要忘记把/sms 的路径添加到URL上。

你也可以使用Twilio CLI为电话号码设置webhook URL。
twilio phone-numbers:update <PHONE_NUMBER> --sms-url=<URL>
如果URL是一个localhost 地址,Twilio CLI将为你创建一个ngrok隧道。
如果你想在真正给出号码之前测试一下工作情况,可以招募一些有电话的朋友,或者使用其他Twilio号码来试试。一旦你对它感到满意,就把应用移到永远在线的公共云上,这样你就不必让你的开发机器全天候运行。这不在这篇文章的范围内,但Spring的打包和部署文档有很多选择。你只需要为每条发送到Twilio号码的短信提供一个HTTP请求,所以要求非常低。