SpringBoot 使用阿里云语音互动(语音IVR)示例

阿里云语音互动(语音IVR)

官网教程https://help.aliyun.com/document_detail/150017.html?spm=a2c4g.11186623.0.0.3168637fiVsJeJ

建议先看一遍官网教程。上面得每一步下面都会用到

需要准备得东西有:

1.使用AK&SK初始化账号Client。前提得开通服务

2.购买号码(进行拨号使用)。

3.提交自己需要得语音模板,每个语音模板创建后会有个模板ID。

4.确定自己用的是MNS得Queue模型还是发送网络请求得方式获取回执消息。

准备好以上东西好导入对应得依赖,下面依赖得版本根据自己拿到得jar版本进行改变。

相关依赖

<!--    阿里语音相关依赖    -->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea-console</artifactId>
            <version>0.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>darabonba-env</artifactId>
            <version>0.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea-openapi</artifactId>
            <version>0.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dyvmsapi20170525</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea-util</artifactId>
            <version>0.2.13</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>darabonba-number</artifactId>
            <version>0.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>tea</artifactId>
            <version>[1.1.13, 2.0.0)</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun.alicom</groupId>
            <artifactId>alicom-mns-receive-sdk</artifactId>
            <version>1.0.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/alicom-mns-receive-sdk-1.0.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>com.aliyun.mns</groupId>
            <artifactId>aliyun-sdk-mns</artifactId>
            <version>1.1.8</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/aliyun-sdk-mns-1.1.8.jar
            </systemPath>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>3.2.2</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/aliyun-java-sdk-core-3.2.2.jar
            </systemPath>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-dybaseapi</artifactId>
            <version>1.0.0</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/aliyun-java-sdk-dybaseapi-1.0.0.jar
            </systemPath>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/commons-logging-1.1.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/commons-lang3-3.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpasyncclient</artifactId>
            <version>4.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/httpasyncclient-4.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/httpcore-4.4.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore-nio</artifactId>
            <version>4.4.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/httpcore-nio-4.4.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4.1</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/httpclient-4.4.1.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
            <scope>system</scope>
            <systemPath>${project.basedir}/src/main/resources/lib/commons-codec-1.9.jar</systemPath>
        </dependency>
        <!--  end 阿里语音依赖      -->

 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--如果是打jar包,则需在build的plugins中添加如下配置-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <!--值为true是指打包时包含scope为system的第三方Jar包-->
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
            </plugin>
        </plugins>
    </build>

建议去下载个官方Demo项目,将其中得jar包获取出来放入自己得Spring项目中


在这里插入图片描述
将jar包搬入到自己得SpringBoot项目
在这里插入图片描述

业务层代码

建议创建一个实体类专门处理语音功能

@Component
@ConfigurationProperties(prefix = "voice")
@Data
@Slf4j
public class Voice implements MessageListener, CommandLineRunner {
    @Value("${voice.accessKeyId}")
    private String accessKeyId;
    @Value("${voice.accessKeySecret}")
    private String accessKeySecret;
    @Value("${voice.calledShowNumber}")
    private String calledShowNumber;  //主叫号码
    @Value("${voice.enable:false}")
    private boolean enable;			 //是否开启语音功能
    private String templateFirst;    //模板
    private String templateConfirm;  //确认模板
    private String templateRefuse;   //拒绝模板
    private String templateSecond;   //模板XX
    private static Log logger = LogFactory.getLog(Voice.class);
    
    /**
     * 使用AK&SK初始化账号Client
     * @param accessKeyId
     * @param accessKeySecret
     * @return Client
     * @throws Exception
     */
    public Client createClient(String accessKeyId, String accessKeySecret) throws Exception{
        Config config=new Config()
                //您得AccessKey ID
                .setAccessKeyId(accessKeyId)
                //您得AccessKey Secret
                .setAccessKeySecret(accessKeySecret);
        //访问得域名
        config.endpoint="dyvmsapi.aliyuncs.com";
        return new Client(config);
    }
    
    /**
     * @Author: OUO
     * @DateTime: 2022/4/11 11:48
     * @Description: 因为实现了CommandLineRunner接口。启动spring项目时会执行下面run方法得内容,用于注册回执消息用到得
     */
    @Override
    public void run(String... args) throws Exception {
        if (enable){
            DefaultAlicomMessagePuller puller = new DefaultAlicomMessagePuller();

            //设置异步线程池大小及任务队列的大小,还有无数据线程休眠时间
            puller.setConsumeMinThreadSize(6);
            puller.setConsumeMaxThreadSize(16);
            puller.setThreadQueueSize(200);
            puller.setPullMsgThreadSize(1);
            //和服务端联调问题时开启,平时无需开启,消耗性能
            puller.openDebugLog(false);

            /*
             * TODO 将messageType和queueName替换成您需要的消息类型名称和对应的队列名称
             *云通信产品下所有的回执消息类型:
             *1:短信回执:SmsReport,
             *2:短息上行:SmsUp
             *3:语音呼叫:VoiceReport
             *4:流量直冲:FlowReport
             */
            String messageType = "VoiceReport";//此处应该替换成相应产品的消息类型
            String queueName = "Alicom-Queue-XXXXXXXXXXX-VoiceReport";//在云通信页面开通相应业务消息后,就能在页面上获得对应的queueName,格式类似Alicom-Queue-xxxxxx-SmsReport
            puller.startReceiveMsg(accessKeyId, accessKeySecret, messageType, queueName, this);
        }
    }
    
    /**
     * @Author: OUO
     * @DateTime: 2022/3/14 14:36
     * @Description:  语音IVR  类型为1的模板
     * CalledShowNumber被叫显号--专门买了号码就填,否则不填
     * CalledNumber接收语音通知的手机号码
     * TtsCode已通过审核的语音验证码模板ID
     * TtsParam模板中的变量参数
     * PlayTimes一通电话内语音通知内容的播放次数
     * Volume语音通知的播放音量
     * Speed语速控制
     * OutId发起请求时预留给调用方的自定义ID
     * no此处是我自己这边业务需求所加,实际根据自己需求变更参数内容即可
     */
    private void sendVoice(String phoneNumber, TestDO test,int no) throws Exception{
        Map<String, String> map = new HashMap<>();
        map.put("proName", "测试一"); //模板中变量参数1
        map.put("proNameTwo", "测试二");//模板中变量参数2
        map.put("proDate", DateUtil.format(new Date(),"yyyy-MM-dd HH:mm"));//模板中变量参数3
        String params = JSON.toJSONString(map);
        boolean mobile = Validator.isMobile(phoneNumber);
        if (!mobile){
            log.error("{}不是有效手机号码");
            return;
        }
        IvrCallResponseBody body;
        Date now=new Date();
        try {
            log.info("发送语音消息,OUO");
            Client client=createClient(accessKeyId,accessKeySecret);
            IvrCallRequest request=new IvrCallRequest();

            //设置等待用户按键超时时间
            request.setTimeout(1000*5);
            //必填-被叫显号,可在语音控制台找到所购买的显号
//            request.setCalledShowNumber(calledShowNumber);
            request.setCalledShowNumber(calledShowNumber);
            //必填-被叫号码
            request.setCalledNumber(phoneNumber);
            //设置播放次数
            request.setPlayTimes(3L);

            voiceSendRecordDO.setPlayTimes(3);
            //必填-语音文件ID或者tts模板的模板号,有参数的模板需要设置模板变量的值
            request.setStartCode(templateFirst);
            //如果有模板里面包含变量参数,下面这个必填变量参数值
            request.setStartTtsParams(params);

            //设置按键触发得语音模板
            List<IvrCallRequest.IvrCallRequestMenuKeyMap> menuKeyMaps=new ArrayList<>();
            IvrCallRequest.IvrCallRequestMenuKeyMap menuKeyMap1=new IvrCallRequest.IvrCallRequestMenuKeyMap();
            menuKeyMap1.setKey("1");
            menuKeyMap1.setCode(templateConfirm);
            menuKeyMaps.add(menuKeyMap1);
            IvrCallRequest.IvrCallRequestMenuKeyMap menuKeyMap2=new IvrCallRequest.IvrCallRequestMenuKeyMap();
            menuKeyMap2.setKey("2");
            menuKeyMap2.setCode(templateRefuse);
            menuKeyMaps.add(menuKeyMap2);
//            IvrCallRequest.IvrCallRequestMenuKeyMap menuKeyMap3=new IvrCallRequest.IvrCallRequestMenuKeyMap();
//            menuKeyMap3.setKey("3");
//            menuKeyMap3.setTtsParams(params);
//            menuKeyMap3.setCode(templateFirst);//再次播放一遍(待确认)
//            menuKeyMaps.add(menuKeyMap3);
            request.setMenuKeyMap(menuKeyMaps);
            //可选-外部扩展字段-一般用于携带自己业务那个实体类得主键ID
            request.setOutId(testDo.getId()+":"+no);
            voiceSendRecordDO.setParams(JSON.toJSONString(request));
            IvrCallResponse response=client.ivrCall(request);
            //此处有可能有异常
            body =response.getBody();
            String code=body.code;
            log.info("状态码:{}",code);
            //todo  发送成功或者失败都给管理员发送信息
            if ("OK".equalsIgnoreCase(code)){
                log.info("状态码为OK");
                //语音发送成功
                log.info("语音发送成功,手机:{}",phoneNumber);
                //todo 发送成功或者失败都给管理员发送信息
            }else {
                //发送失败

            }

        }catch (Exception e){
            //发送失败,给超级管理员发送消息
            e.printStackTrace();
//            Map<String, String> stringStringMap = JSON.parseObject(params, new TypeReference<Map<String, String>>() {});
            String format = StrFormatter
                    .format("系统错误,发送语音给{}失败,号码:{},报错信息:{}",
                            "********","**********",e.getMessage()+"\n"+e.getCause());
            //todo 给管理员发送通知,此处代码省略,根据自己得业务需求来
        }finally {
            //todo 记录日志,此处代码省略,根据自己得业务需求来

        }

    }
    
    
    /**
     * @Author: OUO
     * @DateTime: 2022/4/11 12:09
     * @Description: 此处我选择得是 阿里MNS的Queue模型来处理回执消息
     */
    @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);
            //TODO 根据文档中具体的消息格式进行消息体的解析
            String callId = (String) contentMap.get("call_id");                  //呼叫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");               //通话时长,单位为秒,未接通为0秒。
            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");                       //DTMF按键。
            String voiceType = (String) contentMap.get("voice_type");            //话单类型。取值voice为普通话单
            String dialogId = (String) contentMap.get("dialog_id");              //话术ID,智能外呼SAAS助手专有。
            String tollType = (String) contentMap.get("toll_type");              //通话类型。取值:LOCAL:市话~~


            log.info("消息回执的code++++++++{}", statusCode);
            // 这里开始编写业务代码
            if (StrUtil.isEmpty(outId)){
                return true;
            }
            //将绘制信息存入数据库
            String[] split=outId.split(":");
            String Id=split[0];
            String no=split[1];
            log.info("回执携带得参数是否正常:主键ID:{},no:{},statusCode:{}",Id,no,statusCode);
            /** 判断是否呼叫结果返回正常 */
            if (USER_CALL_SUCCESS.equals(statusCode)||USER_CALL_HANGUP.equals(statusCode)){
                log.info("用户应答,状态码:{},用户按键操作:{}",statusCode,dtmf);

                if ("1".equals(dtmf)){
                    //同意
                    log.info("用户同意操作,状态:{}",Constant.EXTRACT_NOTICE_ACCEPT_CODE);
                    //业务代码此处省略
                }else if ("2".equals(dtmf)){
                    //不同意
                    log.info("用户同意操作,状态:{}",Constant.EXTRACT_NOTICE_REFUSE_CODE);
                    //业务代码此处省略
                }else if ("3".equals(dtmf)){
                    //重听一遍语音
                    log.info("用户重听操作,啥都不干");
                    //业务代码此处省略
                }else{
                    //其他情况
                    //业务代码此处省略
                    
                }
                //业务代码此处省略
            } else {
                log.info("用户无法接通或者拒接{}",callee);
                //用户无法接通,业务代码省略
                
            }

        } catch (com.google.gson.JsonSyntaxException e) {
            logger.error("error_json_format:" + message.getMessageBodyAsString(), e);
            //理论上不会出现格式错误的情况,所以遇见格式错误的消息,只能先delete,否则重新推送也会一直报错
            return true;
        } catch (Throwable e) {
            e.printStackTrace();
            //您自己的代码部分导致的异常,应该return false,这样消息不会被delete掉,而会根据策略进行重推
            log.error("系统内部异常,消息重试.");
            return false;
        }finally {
            //存入到日志表中
            //业务代码此处省略
        }
        log.info("清除本条回执消息");
        //消息处理成功,返回true, SDK将调用MNS的delete方法将消息从队列中删除掉
        return true;
    }
    

}

以上就是示例代码了,若有疑问可在评论区留言,我看到有空会回复

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OUO~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值