短信通知或者验证码等内容的短信推送可以说在业务系统以及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引入的,我怎么引不到呢?并且也无法上传到私服上去。