注册页面上有短信发送的按钮,当用户点击发送短信,我们需要生成验证码,发送给用户。我们将使用阿里提供的阿里大于来实现短信发送。
因为系统中不止注册一个地方需要短信发送,因此我们将短信发送抽取为微服务:learn-sms-service
,凡是需要的地方都可以使用。
另外,因为短信发送API调用时长的不确定性,为了提高程序的响应速度,短信发送我们都将采用异步发送方式,即:
短信服务监听MQ消息,收到消息后发送短信。
其它服务要发送短信时,通过MQ通知短信微服务。
learn
com.learn.parent
1.0.0-SNAPSHOT
4.0.0
com.learn.sms
learn-sms-service
1.0.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
com.aliyun
aliyun-java-sdk-core
3.3.1
com.aliyun
aliyun-java-sdk-dysmsapi
1.0.0
@SpringBootApplication
public class SmsApplication {
public static void main(String[] args) {
SpringApplication.run(SmsApplication.class, args);
}
}
server:
port: 8086
spring:
application:
name: sms-service
rabbitmq:
host: 192.168.56.101
username: learn
password: learn
virtual-host: /learn
项目结构:
我们首先把一些常量抽取到application.yml中:
learn:
sms:
accessKeyId: JWffwFJIwada # 你自己的accessKeyId
accessKeySecret: aySRliswq8fe7rF9gQyy1Izz4MQ # 你自己的AccessKeySecret
signName: 碧溪商城 # 签名名称
verifyCodeTemplate: SMS_133976814 # 模板名称
然后注入到属性类中:
@ConfigurationProperties(prefix = "learn.sms")
public class SmsProperties {
String accessKeyId;
String accessKeySecret;
String signName;
String verifyCodeTemplate;
public String getAccessKeyId() {
return accessKeyId;
}
public void setAccessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
}
public String getAccessKeySecret() {
return accessKeySecret;
}
public void setAccessKeySecret(String accessKeySecret) {
this.accessKeySecret = accessKeySecret;
}
public String getSignName() {
return signName;
}
public void setSignName(String signName) {
this.signName = signName;
}
public String getVerifyCodeTemplate() {
return verifyCodeTemplate;
}
public void setVerifyCodeTemplate(String verifyCodeTemplate) {
this.verifyCodeTemplate = verifyCodeTemplate;
}
}
我们把阿里提供的demo进行简化和抽取,封装一个工具类:
@Component
@EnableConfigurationProperties(SmsProperties.class)
public class SmsUtils {
@Autowired
private SmsProperties prop;
//产品名称:云通信短信API产品,开发者无需替换
static final String product = "Dysmsapi";
//产品域名,开发者无需替换
static final String domain = "dysmsapi.aliyuncs.com";
static final Logger logger = LoggerFactory.getLogger(SmsUtils.class);
public SendSmsResponse sendSms(String phone, String code, String signName, String template) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou",
prop.getAccessKeyId(), prop.getAccessKeySecret());
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest request = new SendSmsRequest();
request.setMethod(MethodType.POST);
//必填:待发送手机号
request.setPhoneNumbers(phone);
//必填:短信签名-可在短信控制台中找到
request.setSignName(signName);
//必填:短信模板-可在短信控制台中找到
request.setTemplateCode(template);
//可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
request.setTemplateParam("{\"code\":\"" + code + "\"}");
//选填-上行短信扩展码(无特殊需求用户请忽略此字段)
//request.setSmsUpExtendCode("90997");
//可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId("123456");
//hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
logger.info("发送短信状态:{}", sendSmsResponse.getCode());
logger.info("发送短信消息:{}", sendSmsResponse.getMessage());
return sendSmsResponse;
}
}
接下来,编写消息监听器,当接收到消息后,我们发送短信。
@Component
@EnableConfigurationProperties(SmsProperties.class)
public class SmsListener {
@Autowired
private SmsUtils smsUtils;
@Autowired
private SmsProperties prop;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "learn.sms.queue", durable = "true"),
exchange = @Exchange(value = "learn.sms.exchange",
ignoreDeclarationExceptions = "true"),
key = {"sms.verify.code"}))
public void listenSms(Map msg) throws Exception {
if (msg == null || msg.size() <= 0) {
// 放弃处理
return;
}
String phone = msg.get("phone");
String code = msg.get("code");
if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) {
// 放弃处理
return;
}
// 发送消息
SendSmsResponse resp = this.smsUtils.sendSms(phone, code,
prop.getSignName(),
prop.getVerifyCodeTemplate());
}
}
我们注意到,消息体是一个Map,里面有两个属性:
phone:电话号码
code:短信验证码