短信通知或者验证码等内容的短信推送可以说在业务系统以及APP中都是很常用的功能。最近我们要给不同的人推送不同的消息,因此使用了下阿里云的短信服务,结合SDK以及Demo在api的调用上还是非常方便的。业务场景很简单,要针对不同的旅客推送给他们接送机时刻。我们依然使用Spring boot快速开发,这里我已经申请了并审核通过的签名及模板,来看看详细的推送过程吧。
引入阿里云短信SDK
首先我们的POM文件里需要引用到如下的jar包,可以看到,其中包含了阿里云的短信接口以及消息服务mns的JDK包。在官方的Demo中,使用gson来序列化,而我因为之前已经使用了jackson所以继续使用jackson代替了gson。
<properties>
<aliyun-java-sdk-core.version>3.3.1</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>1.0.0</aliyun-java-sdk-dysmsapi.version>
<alicom-mns-receive-sdk.version>1.0.0</alicom-mns-receive-sdk.version>
<aliyun-sdk-mns.version>1.1.8</aliyun-sdk-mns.version>
<aliyun-java-sdk-dybaseapi.version>1.0.0-SNAPSHOT</aliyun-java-sdk-dybaseapi.version>
<commons-lang3.version>3.1</commons-lang3.version>
<httpasyncclient.version>4.1</httpasyncclient.version>
<apache-httpcomponents.version>4.4.1</apache-httpcomponents.version>
<commons-codec.version>1.9</commons-codec.version>
</properties>
<!-- aliyun msg send -->
<dependency>
<groupid>com.aliyun</groupid>
<artifactid>aliyun-java-sdk-core</artifactid>
<version>${aliyun-java-sdk-core.version}</version>
</dependency>
<dependency>
<groupid>com.aliyun</groupid>
<artifactid>aliyun-java-sdk-dysmsapi</artifactid>
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<!-- aliyun msg recieve -->
<dependency>
<groupid>com.aliyun.alicom</groupid>
<artifactid>alicom-mns-receive-sdk</artifactid>
<version>${alicom-mns-receive-sdk.version}</version>
</dependency>
<dependency>
<groupid>com.aliyun.mns</groupid>
<artifactid>aliyun-sdk-mns</artifactid>
<version>${aliyun-sdk-mns.version}</version>
</dependency>
<dependency>
<groupid>com.aliyun</groupid>
<artifactid>aliyun-java-sdk-dybaseapi</artifactid>
<version>${aliyun-java-sdk-dybaseapi.version}</version>
</dependency>
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-lang3</artifactid>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupid>org.apache.httpcomponents</groupid>
<artifactid>httpasyncclient</artifactid>
<version>${httpasyncclient.version}</version>
</dependency>
<dependency>
<groupid>org.apache.httpcomponents</groupid>
<artifactid>httpcore</artifactid>
<version>${apache-httpcomponents.version}</version>
</dependency>
<dependency>
<groupid>org.apache.httpcomponents</groupid>
<artifactid>httpcore-nio</artifactid>
<version>${apache-httpcomponents.version}</version>
</dependency>
<dependency>
<groupid>org.apache.httpcomponents</groupid>
<artifactid>httpclient</artifactid>
<version>${apache-httpcomponents.version}</version>
</dependency>
<dependency>
<groupid>commons-codec</groupid>
<artifactid>commons-codec</artifactid>
<version>${commons-codec.version}</version>
</dependency>
系统用到的参数及配置
再来看下我们短信服务的一些参数及系统配置:
mns:
accesskey: ******
accesskey-secret: ******
#短信模板编号
sms-template-code: ******
#签名
sign-name: ******
#短信状态报告接收
smsReportQueueName: Alicom-Queue-******-SmsReport
#短信上行消息接收
smsUpQueueName: Alicom-Queue-******-SmsUp
发送短信
首先已经申请了签名,以及模板,这里我申请的模板如下:
那我们要做的便是将需要替换的参数在调用发送接口时带上即可。官方的Demo注释已经很清楚了,可以直接拿来调用,代码如下:
public interface AlicomMessageSendService{
/**
* 短信发送
* @param phoneNumber 收信号码
* @param templateParam 模板参数
* @param outId 业务ID
* @return
* @throws StoryServiceException
*/
public RespCodeEnum smsSendForTrip(String phoneNumber,Map<string,object> paramMap,Long outId)throws StoryServiceException,JsonProcessingException;
}
/**
* 短信发送
* @author
*/
@Service
public class AlicomMessageSendServiceImpl implements AlicomMessageSendService {
// 短信API产品名称(短信产品名固定,无需修改)
final String product = "Dysmsapi";
// 短信API产品域名(接口地址固定,无需修改)
final String domain = "dysmsapi.aliyuncs.com";
@Autowired
private StoryProperties storyProperties;
@Value("${mns.accesskey}")
String accessKeyId;// accessKeyId
@Value("${mns.accesskey-secret}")
String accessKeySecret;// accessKeySecret
@Value("${mns.sign-name}")
String signName;//签名
@Value("${mns.sms-template-code}")
String templateCode;//短信模板
@Autowired
ShortMessageService shortMessageService;
@Autowired
MessageTemplateService messageTemplateService;
/**
* 行程短信发送
* 尊敬的${name},${driver}将于[${time}]在[${from}]接您去往[${to}],联系电话:[${tel}],回复SD确认收到。
*/
@Override
public RespCodeEnum smsSendForTrip(String phoneNumber,Map<String,Object> paramMap,Long outId)throws StoryServiceException,JsonProcessingException{
//获取模板信息
MessageTemplateDTO template= messageTemplateService.findById(templateCode);
if(template!=null){
//序列化参数
ObjectMapper mapper = new ObjectMapper();
String strParams=mapper.writeValueAsString(paramMap);
//发送短信
RespCodeEnum result= smsSend(signName,templateCode,phoneNumber,strParams,outId.toString());
//发送后,记录log
ShortMessageDTO entity= new ShortMessageDTO();
entity.setMsgDate(DateUtils.currentDate());
entity.setMobile(phoneNumber);
entity.setMsgSignName(signName);
entity.setMsgTemplate(templateCode);
entity.setMsgType(ShortMessageMsgTypeEnum.MSG_SEND.code());
if(RespCodeEnum.SUCCESS.equals(result)){
entity.setSuccessFlag(true);
}else{
entity.setSuccessFlag(false);
entity.setCause(result.Val());
}
entity.setMsgParams(strParams);
entity.setMsgContent(MessageFormat.format("【{0}】", signName)+StringUtils.renderString(template.getTempContent(),paramMap));
entity.setTrip(outId);
shortMessageService.persist(entity);
return result;
}else{
return RespCodeEnum.FORMAT_PARAM;
}
}
/**
* 短信发送
* @param phoneNumber
* @param templateParam
* @return
*/
private RespCodeEnum smsSend(String signName,String templateCode, String phoneNumber,String templateParam,String outId) {
if(storyProperties.getSmsOnOff()) {
try{
// 设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
// 初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
// 组装请求对象
SendSmsRequest request = new SendSmsRequest();
// 使用post提交
request.setMethod(MethodType.POST);
// 必填:待发送手机号。
// 支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,
// 批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
request.setPhoneNumbers(phoneNumber);
// 必填:短信签名-可在短信控制台中找到
request.setSignName(signName);
// 必填:短信模板-可在短信控制台中找到
request.setTemplateCode(templateCode);
// 可选:模板中的变量替换JSON串,
// 如模板内容为"亲爱的${name},您的验证码为${code}"时
// 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,
// 如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
request.setTemplateParam(templateParam);
// 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
// request.setSmsUpExtendCode("90997");
// 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
request.setOutId(outId);
// 请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
if (sendSmsResponse.getCode() != null
&& sendSmsResponse.getCode().equals("OK")) {
return RespCodeEnum.SUCCESS;
}else{
return RespCodeEnum.FAILURE;
}
}catch(ClientException ex){
return RespCodeEnum.REMOTE_FAILURE;
}
}else{
return RespCodeEnum.FUNC_CLOSED;
}
}
}
到这里,短信发送接口的调用就简单了。我们将业务对象转化为Map之后,发送短信之后,对日期格式化,就可以直接调用上面的接口,之后我们要做的就是更新业务上系统短信发送的状态。这里的状态其实只是我们执行发送是否完成的状态,而非短信接口的短信发送成功的状态。
try{
// 将实体转化为Map做为调用短信接口时的参数
Map<string,object> paramMap=com.story.storywebeasy.utils.BeanUtils.objectToMap(trip);
// 对日期格式单独处理
paramMap.put("time", DateUtils.formatDate(trip.getTime(), TimeFormat.LONG_DATE_PATTERN_LINE_NO_SECONDS));
// 发送短信
RespCodeEnum respCodeEnum =alicomMessageSendService.smsSendForTrip(trip.getPhone(), paramMap,trip.getOutId());
if(respCodeEnum.equals(RespCodeEnum.SUCCESS)){
// 更新发送状态
alterMsgStatus(trip.getOutId(),TripMsgStatusEnum.SEND_SUCCESS.code());
}else{
// 更新发送状态
alterMsgStatus(trip.getOutId(),TripMsgStatusEnum.SEND_FAILTURE.code());
}
}catch(Exception ex){
//logger;
}
接收短信
在我们前面开始的的地方,我们已经引入了阿里云消息服务mns的SDK包。
我们采用消息队列消费模式,主动去mns的消息队列上消费消息,因为我希望当我们的业务系统启动时,就启动一个监听程序开始监听消息队列的状态。一旦消息服务收到一条上行的短信回复之后放在消息队列中,我们的监听服务就可以及时的拉取消息及时的处理。
还在我们之前引用的消息服务组件已经帮我们做了监听部分,而我们只需要处理接收后的业务逻辑部分就可以了。因此就可以直接使用@Component注解定义一个组件如下:
/**
* Ali短信回复消息收取
* @author admin
*
*/
@Component
@Order(value = 1)
public class AliMnsApplicationRunner implements ApplicationRunner {
@Value("${mns.accesskey}")
String accessKeyId;// accessKeyId
@Value("${mns.accesskey-secret}")
String accessKeySecret;// accessKeySecret
@Value("${mns.smsReportQueueName}")
//在云通信页面开通相应业务消息后,就能在页面上获得对应的queueName
String smsReportQueueName;
@Value("${mns.smsUpQueueName}")
//短信回执:SmsReport,短信上行:SmsUp
String smsUpQueueName;
@Autowired
private AlicomMessageListenerService alicomMessageListenerService;
/**
* 只能用于接收云通信的消息,不能用于接收其他业务的消息
* 短信上行消息接收
*/
@Override
public void run(ApplicationArguments var1) throws Exception{
DefaultAlicomMessagePuller puller=new DefaultAlicomMessagePuller();
String messageType="SmsUp";
if(alicomMessageListenerService!=null){
puller.startReceiveMsg(accessKeyId,accessKeySecret ,messageType,smsUpQueueName , alicomMessageListenerService);
}
}
}
这个会在我们的系统启动时执行一次,可以看到我们的组件实现了·ApplicationRunner·接口,而CommandLineRunner、ApplicationRunner 接口是在容器启动成功后之后回调,因此我们在这里启动我们消息的监听任务。
/**
* 上行短信接收
* @author admin
*/
@Slf4j
@Service
public class AlicomMessageListenterServiceImpl implements AlicomMessageListenerService{
@Autowired
private TripService tripService;
@Autowired
ShortMessageService shortMessageService;
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
@Override
public boolean dealMessage(Message message) {
try{
Map<String,Object> contentMap= objectMapper.readValue(message.getMessageBodyAsString(), HashMap.class);
String phoneNumber=(String)contentMap.get("phone_number");
String sendTime=(String)contentMap.get("send_time");
String content=(String)contentMap.get("content");
// 更新行程消息通知状态
tripService.alterMsgStatus(phoneNumber, TripMsgStatusEnum.RECIEVE_SUCCESS.code(), TripMsgStatusEnum.SEND_SUCCESS.code());
// 写入短信日志
ShortMessageDTO entity= new ShortMessageDTO();
// 格式化消息日期
if(!StringUtils.isEmpty(sendTime)){
entity.setMsgDate(DateUtils.parseDateTime(sendTime, TimeFormat.LONG_DATE_PATTERN_NO_SPLIT));
}else{
entity.setMsgDate(DateUtils.currentDate());
}
entity.setMobile(phoneNumber);
entity.setMsgType(ShortMessageMsgTypeEnum.MSG_RECIEVE.code());
entity.setSuccessFlag(true);
entity.setMsgContent(content);
shortMessageService.persist(entity);
}catch(JsonMappingException e){
}catch(JsonParseException e){
}catch(IOException e){
}catch(StoryServiceException e) {
e.printStackTrace();
}
// 返回true,则工具类自动删除已拉取的消息。
Boolean dealResult=true;
return dealResult;
}
}
到这里获取到了上行的短信消息后,我们就是要更新我们的业务的数据,如更改状态,提醒管理员等等。当然了如果想让我们的程序更加的健全,日志等还是要补全的。这里我们没有接收短信的状态报告,但这都比较简单了,以后更新再说。
上面我们主要介绍使用阿里云的短信服务来发送短信以及接收短信。功能实现了,下次遇到支付宝发红包的事情,我们也可以搞一搞事情了。
请问你的alicom-mns-receive-sdk和aliyun-java-sdk-dybaseapi两个jar包是怎么通过Maven引入的,我怎么引不到呢?并且也无法上传到私服上去。
您好,请问现在这个包还有嘛?
新的SDK请参考最新的版本:https://help.aliyun.com/document_detail/112148.html