JAVA中调用阿里云语音通知Api并接收消息回执
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dyvmsapi</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.aliyun.mns</groupId>
<artifactId>aliyun-sdk-mns</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dyvmsapi20170525</artifactId>
<version>0.0.2</version>
</dependency>
<dependency>
<groupId>com.meiyuan</groupId>
<artifactId>alicom-mns-receive-sdk-0.0.1-SNAPSHOT</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.meiyuan</groupId>
<artifactId>aliyun-java-sdk-dybaseapi-1.0.0-SNAPSHOT</artifactId>
<version>1.0.0</version>
</dependency>
1.获取配置文件中的参数
package com.meiyuan.reservation.dto.call;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.Serializable;
/**
* 功能描述:ali云 语音通知辅助类实体
*
* @author tc
* @version 1.0
* @since 2020/12/15 17:24
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun")
public class ClientConfigDto implements Serializable {
private static final long serialVersionUID = 7736942314547915021L;
/**
* 您的AccessKey ID
*/
private String accessKeyId;
/**
* 您的AccessKey Secret
*/
private String accessKeySecret;
/**
* 语音文件的语音ID
*/
private String ttsCode;
private String product;
private String domain;
/**
* 主叫号码
*/
private String calledShowNumber;
/**
* 相应产品的消息类型
*/
private String messageType;
/**
* 在云通信页面开通相应业务消息后,就能在页面上获得对应的queueName
*/
private String queueName;
}
2.调用阿里云语音Api,三种不同的呼叫方式,根据需要选择调用
package com.meiyuan.reservation.call;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dyvmsapi.model.v20170525.IvrCallRequest;
import com.aliyuncs.dyvmsapi.model.v20170525.IvrCallResponse;
import com.aliyuncs.dyvmsapi.model.v20170525.SingleCallByTtsRequest;
import com.aliyuncs.dyvmsapi.model.v20170525.SingleCallByTtsResponse;
import com.aliyuncs.dyvmsapi.model.v20170525.SingleCallByVoiceRequest;
import com.aliyuncs.dyvmsapi.model.v20170525.SingleCallByVoiceResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.meiyuan.reservation.dto.call.CallMsgDto;
import com.meiyuan.reservation.dto.call.ClientConfigDto;
import com.meiyuan.reservation.mq.sender.ReservationMqSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 功能描述:ali云 语音通知辅助类
*
* @author tc
* @version 1.0
* @since 2020/12/15 17:17
*/
@Slf4j
@Component
public class VmsDemo {
/**
* 文本转语音外呼
*
* @return
* @throws ClientException
*/
@Autowired
private ReservationMqSender reservationMqSender;
public SingleCallByTtsResponse singleCallByTts(ClientConfigDto dto, String managerPhone, String reservationId, String tailNumber, String time) throws ClientException {
//设置访问超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient 暂时不支持多region
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", dto.getAccessKeyId(), dto.getAccessKeySecret());
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", dto.getProduct(), dto.getDomain());
IAcsClient acsClient = new DefaultAcsClient(profile);
SingleCallByTtsRequest request = new SingleCallByTtsRequest();
// 被叫显号,若您使用的模板为公共号池号码外呼模板,则该字段值必须为空;
// 若您使用的模板为专属号码外呼模板,则必须传入已购买的号码,仅支持一个号码,您可以在语音服务控制台上查看已购买的号码。
if ("numberPool".equals(dto.getCalledShowNumber())) {
request.setCalledShowNumber("");
} else {
request.setCalledShowNumber(dto.getCalledShowNumber());
}
//必填-被叫号码(店长)
request.setCalledNumber(managerPhone);
//必填-Tts模板ID
request.setTtsCode(dto.getTtsCode());
// 这里主要是填写我们语音文字模板里面的参数 ${param}
System.out.println(time);
request.setTtsParam("{\"time\":\"" + time + "\",\"tailNumber\":\"" + tailNumber + "\"}");
//可选-音量 取值范围 0--200
request.setVolume(200);
//可选-播放次数
request.setPlayTimes(2);
//可选-外部扩展字段,此ID将在回执消息中带回给调用方
request.setOutId(reservationId + ":" + managerPhone);
SingleCallByTtsResponse singleCallByTtsResponse = acsClient.getAcsResponse(request);
if (singleCallByTtsResponse.getCode() != null && singleCallByTtsResponse.getCode().equals("OK")) {
//请求成功
log.info("请求成功");
} else {
log.info("语音呼叫失败,code{}", singleCallByTtsResponse.getCode());
if (singleCallByTtsResponse.getCode().equals("isv.BUSINESS_LIMIT_CONTROL")) {
log.info("流控限制,3分钟后重试");
CallMsgDto callMsgDto = new CallMsgDto();
callMsgDto.setManagerPhone(Arrays.asList(managerPhone));
callMsgDto.setReservationId(reservationId);
callMsgDto.setContactPhone(tailNumber);
callMsgDto.setReservationTime(time);
reservationMqSender.sendLaunchMsg(callMsgDto, 3 * 60 * 1000L);
}
}
return singleCallByTtsResponse;
}
/**
* 语音文件外呼
*
* @return
* @throws ClientException
*/
public SingleCallByVoiceResponse singleCallByVoice(ClientConfigDto dto) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", dto.getAccessKeyId(), dto.getAccessKeySecret());
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", dto.getProduct(), dto.getDomain());
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
SingleCallByVoiceRequest request = new SingleCallByVoiceRequest();
//必填-被叫显号,可在语音控制台中找到所购买的显号
request.setCalledShowNumber("025000000");
//必填-被叫号码
request.setCalledNumber("15000000000");
//必填-语音文件ID
request.setVoiceCode("3a7c382b-ee87-493f-bfa0-b9fd6f31f8bb.wav");
//可选-外部扩展字段
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
SingleCallByVoiceResponse singleCallByVoiceResponse = acsClient.getAcsResponse(request);
return singleCallByVoiceResponse;
}
/**
* 交互式语音应答
*
* @return
* @throws ClientException
*/
public IvrCallResponse ivrCall(ClientConfigDto dto) throws ClientException {
//可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", dto.getAccessKeyId(), dto.getAccessKeySecret());
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", dto.getProduct(), dto.getDomain());
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象-具体描述见控制台-文档部分内容
IvrCallRequest request = new IvrCallRequest();
//必填-被叫显号,可在语音控制台中找到所购买的显号
request.setCalledShowNumber("057156210000");
//必填-被叫号码
request.setCalledNumber("15000000000");
request.setPlayTimes(3L);
//必填-语音文件ID或者tts模板的模板号,有参数的模板需要设置模板变量的值
//request.setStartCode("ebe3a2b5-c287-42a4-8299-fc40ae79a89f.wav");
request.setStartCode("TTS_713900000");
request.setStartTtsParams("{\"product\":\"aliyun\",\"code\":\"123\"}");
List<IvrCallRequest.MenuKeyMap> menuKeyMaps = new ArrayList<IvrCallRequest.MenuKeyMap>();
IvrCallRequest.MenuKeyMap menuKeyMap1 = new IvrCallRequest.MenuKeyMap();
menuKeyMap1.setKey("1");
menuKeyMap1.setCode("9a9d7222-670f-40b0-a3af.wav");
menuKeyMaps.add(menuKeyMap1);
IvrCallRequest.MenuKeyMap menuKeyMap2 = new IvrCallRequest.MenuKeyMap();
menuKeyMap2.setKey("2");
menuKeyMap2.setCode("44e3e577-3d3a-418f-932c.wav");
menuKeyMaps.add(menuKeyMap2);
IvrCallRequest.MenuKeyMap menuKeyMap3 = new IvrCallRequest.MenuKeyMap();
menuKeyMap3.setKey("3");
menuKeyMap3.setCode("TTS_71390000");
menuKeyMap3.setTtsParams("{\"product\":\"aliyun\",\"code\":\"123\"}");
menuKeyMaps.add(menuKeyMap3);
request.setMenuKeyMaps(menuKeyMaps);
//结束语可以使一个无参模板或者一个语音文件ID
request.setByeCode("TTS_71400007");
//可选-外部扩展字段
request.setOutId("yourOutId");
//hint 此处可能会抛出异常,注意catch
IvrCallResponse ivrCallResponse = acsClient.getAcsResponse(request);
return ivrCallResponse;
}
}
3.把监听器交给spring管理,服务启动的时候就去监听阿里的回执队列,获取回执消息
package com.meiyuan.reservation;
import com.alicom.mns.tools.DefaultAlicomMessagePuller;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import com.meiyuan.marsh.jetcache.anno.config.EnableAdvancedCreateCacheAnnotation;
import com.meiyuan.reservation.dto.call.CallMsgDto;
import com.meiyuan.reservation.dto.call.ClientConfigDto;
import com.meiyuan.reservation.dto.call.ReservationProperties;
import com.meiyuan.reservation.mq.listener.MyMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author admin
*/
@SpringBootApplication(scanBasePackages = "com.meiyuan")
@EnableDiscoveryClient
@EnableMethodCache(basePackages = "com.meiyuan")
@EnableAdvancedCreateCacheAnnotation
@EnableFeignClients(basePackages = {"com.meiyuan"})
@EnableConfigurationProperties
@EnableAsync
public class SaasReservationServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaasReservationServerApplication.class, args);
}
@Autowired
private MyMessageListener messageListener;
@Autowired
private ReservationProperties reservationProperties;
@Bean
public CallMsgDto listenerBean (ClientConfigDto dto) throws Exception {
DefaultAlicomMessagePuller puller = new DefaultAlicomMessagePuller();
if (reservationProperties.getReceive().equals("true")) {
puller.startReceiveMsg(dto.getAccessKeyId(), dto.getAccessKeySecret(), dto.getMessageType(), dto.getQueueName(), messageListener);
}
return new CallMsgDto();
}
}
4.获取到回执消息后的业务逻辑处理
package com.meiyuan.reservation.mq.listener;
import com.alicom.mns.tools.MessageListener;
import com.alicp.jetcache.anno.CreateCache;
import com.aliyun.mns.model.Message;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.gson.Gson;
import com.meiyuan.commons.tools.redis.JetcacheNames;
import com.meiyuan.marsh.jetcache.AdvancedCache;
import com.meiyuan.marsh.jetcache.anno.AdvancedCreateCache;
import com.meiyuan.reservation.call.VmsDemo;
import com.meiyuan.reservation.dao.ReservationDao;
import com.meiyuan.reservation.dao.VoiceReportDao;
import com.meiyuan.reservation.dto.call.CallMsgDto;
import com.meiyuan.reservation.dto.call.ClientConfigDto;
import com.meiyuan.reservation.dto.call.ReservationProperties;
import com.meiyuan.reservation.entity.ReservationEntity;
import com.meiyuan.reservation.entity.VoiceReportEntity;
import com.meiyuan.reservation.enums.ResCallStatusEnum;
import com.meiyuan.reservation.enums.TimeEnum;
import com.meiyuan.reservation.mq.sender.ReservationMqSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 功能描述:回执消息监听器
*
* @author tiancheng
* @version V1.0.0
* @since 2020/12/24 9:30
*/
@Slf4j
@Component
public class MyMessageListener implements MessageListener {
@AdvancedCreateCache(@CreateCache(name = JetcacheNames.PREFIX_SEQ_RESERVATION))
private AdvancedCache cache;
@Resource
private VoiceReportDao voiceReportDao;
@Autowired
private ReservationDao reservationDao;
@Autowired
private ReservationProperties reservationProperties;
@Autowired
private ReservationMqSender reservationMqSender;
private Gson gson = new Gson();
@Override
public boolean dealMessage(Message message) {
//消息的几个关键值
log.info("消息接收时间[{}],message handle[{}],body[{}],id[{}],dequeue count[{}]", null, message.getReceiptHandle(),
message.getMessageBodyAsString(), message.getMessageId(), message.getDequeueCount());
log.info("监听到语音回执消息------------------------------{}", message);
try {
Map<String, Object> contentMap = gson.fromJson(message.getMessageBodyAsString(), HashMap.class);
//依据自己的消息类型,获取对应的字段
String callId = (String) contentMap.get("call_id");
String startTime = (String) contentMap.get("start_time");
String endTime = (String) contentMap.get("end_time");
String caller = (String) contentMap.get("caller");
String callee = (String) contentMap.get("callee");
String duration = (String) contentMap.get("duration");
String statusCode = (String) contentMap.get("status_code");
String earlyMediaCode = (String) contentMap.get("early_media_code");
String hangupDirection = (String) contentMap.get("hangup_direction");
String statusMsg = (String) contentMap.get("status_msg");
String outId = (String) contentMap.get("out_id");
String dtmf = (String) contentMap.get("dtmf");
String voiceType = (String) contentMap.get("voice_type");
String dialogId = (String) contentMap.get("dialog_id");
String tollType = (String) contentMap.get("toll_type");
VoiceReportEntity voiceReportEntity = new VoiceReportEntity(IdWorker.getIdStr(), callId, startTime, endTime, caller, callee, duration, statusCode,
earlyMediaCode, hangupDirection, statusMsg,
outId, dtmf, voiceType, dialogId, tollType);
log.info("消息回执的code++++++++{}", statusCode);
//业务逻辑
//将回执信息存入数据库
voiceReportDao.insert(voiceReportEntity);
String phoneNumber = voiceReportEntity.getCallee();
String[] split = outId.split(":");
String reservationId = split[0];
String phone = split[1];
ReservationEntity reservationEntity = reservationDao.selectById(reservationId);
//根据状态码判断下一步处理逻辑
//1.未接听,占线,关机(10分钟后重试,重试一次)
if (statusCode.equals(ResCallStatusEnum.USER_NOT_ANSWER.getValue()) || statusCode.equals(ResCallStatusEnum.USER_BUSY.getValue())
|| statusCode.equals(ResCallStatusEnum.USER_SWITCH_OFF.getValue()) || statusCode.equals(ResCallStatusEnum.USER_NOT_CONNECT.getValue())) {
Integer num = (Integer) cache.get(outId);
if (num == null || reservationEntity == null) {
log.error("消息错误{}", outId);
} else if (num <= 1) {
cache.set(outId, 2);
long millis;
if (reservationProperties.getRetry().equals(true)) {
millis = TimeEnum.INTERVAL_TIME.getValue();
} else {
millis = 90 * 1000L;
}
Date date = reservationEntity.getReservationTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String reservationTime = sdf.format(date);
CallMsgDto callMsgDto = new CallMsgDto();
ArrayList<String> managerPhone = new ArrayList<>();
managerPhone.add(phoneNumber);
callMsgDto.setManagerPhone(managerPhone);
callMsgDto.setReservationId(reservationId);
callMsgDto.setContactPhone(reservationEntity.getContactPhone());
callMsgDto.setReservationTime(reservationTime);
reservationMqSender.sendLaunchMsg(callMsgDto, millis);
log.info("用户忙{}", phone);
}
} else if (statusCode.equals(ResCallStatusEnum.USER_SUCCESS.getValue()) || statusCode.equals(ResCallStatusEnum.USER_HANG_UP.getValue())) {
log.info("用户应答{}", phone);
} else {
log.error("用户无法接通{}", phone);
}
} catch (com.google.gson.JsonSyntaxException e) {
log.error("error_json_format:" + message.getMessageBodyAsString(), e);
//理论上不会出现格式错误的情况,所以遇见格式错误的消息,只能先delete,否则重新推送也会一直报错
return true;
} catch (Throwable e) {
//您自己的代码部分导致的异常,应该return false,这样消息不会被delete掉,而会根据策略进行重推
log.error("系统内部异常,消息重试");
return false;
}
//消息处理成功,返回true, SDK将调用MNS的delete方法将消息从队列中删除掉
return true;
}
}