使用阿里云短信服务收发短信

短信通知或者验证码等内容的短信推送可以说在业务系统以及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; 
    }
}

到这里获取到了上行的短信消息后,我们就是要更新我们的业务的数据,如更改状态,提醒管理员等等。当然了如果想让我们的程序更加的健全,日志等还是要补全的。这里我们没有接收短信的状态报告,但这都比较简单了,以后更新再说。

上面我们主要介绍使用阿里云的短信服务来发送短信以及接收短信。功能实现了,下次遇到支付宝发红包的事情,我们也可以搞一搞事情了。

云通信消息接收

One Comment on “使用阿里云短信服务收发短信

  1. 请问你的alicom-mns-receive-sdk和aliyun-java-sdk-dybaseapi两个jar包是怎么通过Maven引入的,我怎么引不到呢?并且也无法上传到私服上去。

发表评论

电子邮件地址不会被公开。 必填项已用*标注