先下载sdk。

model:

<?php
    namespace app\alicall\model;
    require_once ROOT_PATH . DS . 'alicall'. DS .'api_sdk'. DS .'vendor'. DS .'autoload.php';
 
    use think\Model;
 
    use Aliyun\Core\Config;
    use Aliyun\Core\Exception\ClientException;
    use Aliyun\Core\Profile\DefaultProfile;
    use Aliyun\Core\DefaultAcsClient;
    use Aliyun\Api\Dypls\Request\V20170525\BindAxbRequest;
    use Aliyun\Api\Dypls\Request\V20170525\BindAxnRequest;
    use Aliyun\Api\Dypls\Request\V20170525\UnbindSubscriptionRequest;
    use Aliyun\Api\Dypls\Request\V20170525\UpdateSubscriptionRequest;
    use Aliyun\Api\Dypls\Request\V20170525\QueryRecordFileDownloadUrlRequest;
    use Aliyun\Api\Dypls\Request\V20170525\QuerySubscriptionDetailRequest;
    use Aliyun\Api\Dypls\Request\V20170525\BindAxnExtensionRequest;
    // 加载区域结点配置
    Config::load();
    class AlicallApi extends Model
    {
        static $acsClient = null;
        static $ak;
        static $aks;
 
        public function setAk($ak)
        {
            static::$ak = $ak;
        }
        public function setAks($aks)
        {
            static::$aks = $aks;
        }
 
        /**
         * 取得AcsClient
         *
         * @return DefaultAcsClient
         */
        public static function getAcsClient() {
            //产品名称:云通信号码隐私保护服务API产品,开发者无需替换
            $product = "Dyplsapi";
 
            //产品域名,开发者无需替换
            $domain = "dyplsapi.aliyuncs.com";
 
            // TODO 此处需要替换成开发者自己的AK (https://ak-console.aliyun.com/)
            $accessKeyId = static::$ak; // AccessKeyId
 
            $accessKeySecret = static::$aks; // AccessKeySecret
 
 
            // 暂时不支持多Region
            $region = "cn-hangzhou";
 
            // 服务结点
            $endPointName = "cn-hangzhou";
 
 
            if(static::$acsClient == null) {
 
                //初始化acsClient,暂不支持region化
                $profile = DefaultProfile::getProfile($region, $accessKeyId, $accessKeySecret);
 
                // 增加服务结点
                DefaultProfile::addEndpoint($endPointName, $region, $product, $domain);
 
                // 初始化AcsClient用于发起请求
                static::$acsClient = new DefaultAcsClient($profile);
            }
            return static::$acsClient;
        }
 
 
        /**
         * AXB绑定接口
         * @param  string $aPhone          绑定电话a
         * @param  string $bPhone         绑定电话b
         * @param  string $xPhone         中间号x
         * @param  string $expirationTime 自动解除绑定时间 2018-10-10 15:15:15 当前时间加上8小时
         * @return [type]                 [description]
         */
        public static function bindAxb($aPhone='',$bPhone='',$xPhone='',$expirationTime='') {
 
            //组装请求对象-具体描述见控制台-文档部分内容
            $request = new BindAxbRequest();
 
            //可选-启用https协议
            //$request->setProtocol("https");
 
            //必填:号池Key
            $request->setPoolKey("FC123456");
 
            //必填:AXB关系中的A号码
            $request->setPhoneNoA($aPhone);
 
            //必填:AXB关系中的B号码
            $request->setPhoneNoB($bPhone);
 
            //可选:指定X号码进行绑定
            $request->setPhoneNoX($xPhone);
 
            //可选:期望分配X号码归属的地市(省去地市后缀后的城市名称)
            //$request->setExpectCity("北京");
 
            //必填:绑定关系对应的失效时间-不能早于当前系统时间
            $request->setExpiration(date('Y-m-d H:i:s',$expirationTime));
 
            //可选:是否需要录制音频-默认是false
            //$request->setIsRecordingEnabled(false);
 
            //可选:外部业务自定义ID属性
            //$request->setOutId("yourOutId");
 
            //hint 此处可能会抛出异常,注意catch
            $response = static::getAcsClient()->getAcsResponse($request);
 
            return $response;
        }
 
        /**
         * 解绑接口
         *
         * @return stdClass
         * @throws ClientException
         */
        public static function unbindSubscription($subsId, $secretNo) {
 
            //组装请求对象
            $request = new UnbindSubscriptionRequest();
 
            //可选-启用https协议
            //$request->setProtocol("https");
 
            //必填:号池Key
            $request->setPoolKey("FC123456");
 
            //必填:对应的产品类型
            $request->setProductType("AXB_170");
 
            //必填-分配的X小号-对应到绑定接口中返回的secretNo;
            $request->setSecretNo($secretNo);
 
            //必填-绑定关系对应的ID-对应到绑定接口中返回的subsId;
            $request->setSubsId($subsId);
 
            //hint 此处可能会抛出异常,注意catch
            $response = static::getAcsClient()->getAcsResponse($request);
 
            return $response;
        }
 
        /**
         * 更新绑定关系
         *
         * @return stdClass
         * @throws ClientException
         */
        public static function updateSubscription() {
 
            //组装请求对象
            $request = new UpdateSubscriptionRequest();
 
            //可选-启用https协议
            //$request->setProtocol("https");
 
            //必填:号池Key
            $request->setPoolKey("FC123456");
 
            //必填: 您所选择的产品类型,目前支持AXB_170、AXN_170、AXN_95三种产品类型
            $request->setProductType("AXB_170");
 
            //必填: 创建绑定关系API接口所返回的订购关系ID
            $request->setSubsId("123456");
 
            //必填: 创建绑定关系API接口所返回的X号码
            $request->setPhoneNoX("170000000");
 
 
            // todo 以下操作三选一, 目前支持三种类型: updateNoA(修改A号码)、updateNoB(修改B号码)、updateExpire(更新绑定关系有效期)
 
            // -------------------------------------------------------------------
 
            // 1. 修改A号码示例:
            // 必填: 操作类型
            $request->setOperateType("updateNoA");
 
            // OperateType为updateNoA时必选: 需要修改的A号码
            $request->setPhoneNoA("150000000");
 
            // -------------------------------------------------------------------
 
            // 2. 修改B号码示例:
            // 必填: 操作类型
            // $request->setOperateType("updateNoB");
 
            // OperateType为updateNoB时必选: 需要修改的B号码
            // $request->setPhoneNoB("150000000");
 
            // -------------------------------------------------------------------
 
            // 3. 更新绑定关系有效期示例:
            // 必填: 操作类型
            // $request->setOperateType("updateExpire");
 
            // OperateType为updateExpire时必选: 需要修改的绑定关系有效期
            // $request->setExpiration("2017-09-05 12:00:00");
 
            // -------------------------------------------------------------------
 
            // 此处可能会抛出异常,注意catch
            $response = static::getAcsClient()->getAcsResponse($request);
 
            return $response;
        }
 
        /**
         * 获取录音文件下载链接
         *
         * @return stdClass
         * @throws ClientException
         */
        public static function queryRecordFileDownloadUrl() {
 
            //组装请求对象
            $request = new QueryRecordFileDownloadUrlRequest();
 
            //可选-启用https协议
            //$request->setProtocol("https");
 
            //必填:号池Key
            $request->setPoolKey("FC123456");
 
            //必填: 对应的产品类型,目前一共支持三款产品AXB_170,AXN_170,AXN_95
            $request->setProductType("AXB_170");
 
            //必填: 话单回执中返回的标识每一通唯一通话行为的callId
            $request->setCallId("abcedf1234");
 
            //必填: 话单回执中返回的callTime字段
            $request->setCallTime("2017-09-05 12:00:00");
 
            //hint 此处可能会抛出异常,注意catch
            $response = static::getAcsClient()->getAcsResponse($request);
 
            return $response;
        }
 
        /**
         * 查询绑定关系详情
         *
         * @return stdClass
         * @throws ClientException
         */
        public static function querySubscriptionDetail() {
 
            //组装请求对象
            $request = new QuerySubscriptionDetailRequest();
 
            //可选-启用https协议
            //$request->setProtocol("https");
 
            //必填:号池Key
            $request->setPoolKey("FC123456");
 
            //必填: 产品类型,目前一共支持三款产品AXB_170,AXN_170,AXN_95
            $request->setProductType("AXB_170");
 
            //必填: 绑定关系ID
            $request->setSubsId("123456");
 
            //必填: 绑定关系对应的X号码
            $request->setPhoneNoX("170000000");
 
            //hint 此处可能会抛出异常,注意catch
            $response = static::getAcsClient()->getAcsResponse($request);
 
            return $response;
        }
 
 
 
    }

控制器:

class Api extends Common
    {
        protected $ak;
        protected $aks;
        protected function _initialize()
        {
            parent::_initialize();
            // 获取配置
            $this->ak = ConfigModel::where('name', 'alicall_ak')->value('value');
            $this->aks = ConfigModel::where('name', 'alicall_aks')->value('value');
        }
 
        /**
         * 绑定手机
         * 成功返回字段
         * Code=OK
         * Message=OK
         * RequestId=E35338B5-3EBD-42FB-881C-3D44F6591D2C
         * subsId=557203043042578393
         * secretNo=17099611704
         * 查询绑定关系详情(QuerySubscriptionDetail)接口返回的结果:
         * stdClass Object
         * (
         *    [Message] => isv.SUBS_NOT_EXIST
         *    [RequestId] => 3A1C2E2A-8A51-4207-B17F-1E361FA7B83A
         *    [Code] => isv.SUBS_NOT_EXIST
         * )
         * @return [type] [description]
         */
        public function makeCall()
        {
            try {
                Auth::login();
                if (empty($this->member_id))rstJson('error', '缺少必要字段!');
                if (input('is_store') != true)rstJson('error', '您无拨打电话权限!');
                // 二次验证是否为商户,否则无拨打电话权限
                $info = StoreModel::where('member_id',Auth::$member_id)->find();
                if(!$info)rstJson('error', '您无拨打电话权限!');
 
                //商家电话 主叫
                $aPhone = MemberModel::where('id',Auth::$member_id)->value('mobile');
                //用户电话 被叫
                $bPhone = MemberModel::where('id',$this->member_id)->value('mobile');
 
                // 查询钱够不够!!!
                // 查询 主叫phone_no商家 与被叫 peer_no ,状态为11的订单
                $money = OrderModel::getOrderMoney([
                    'store_member_phone'=>$aPhone, //商家电话
                    'member_phone'=>$bPhone,   // 用户电话
                    'status' => 11 //订单状态
                ]);
                if(empty($money))rstJson('error', '未找到相关订单。');
 
                $member_money = MemberModel::where('mobile',$aPhone)->value('money');
                if(floatval($money) > $member_money)rstJson('error', '您的余额不足,请先充值。');
 
                //中间号
                $xPhone_arr = ConfigModel::where('name', 'alicall_xphone')->value('value');
                $xPhone_arr =  array_filter(explode(',',$xPhone_arr));
 
                $alicall_api = new AlicallApi();
                $alicall_api->setAk($this->ak);
                $alicall_api->setAks($this->aks);
 
                // 循环绑定可用的中间号,若无可用好吗,则需要在阿里云接口那里购买新的中间号码,
                // 填写到后台-系统-配置管理-基础-阿里隐私保护_中间号 多个号码用“,”分割
                foreach ($xPhone_arr as $xPhone) {
                    // 自动解除绑定时间
                    $expirationTime = time() + 28800;
                    $ret = $alicall_api->bindAxb($aPhone,$bPhone,$xPhone,$expirationTime);
                    $ret = object_to_array($ret);
                    // 成功与否全写到数据库
                    BindModel::create([
                        'a_phone' => $aPhone,
                        'x_phone' => $xPhone,
                        'b_phone' => $bPhone,
                        'bind_status' => $ret['Code'],
                        'expire_time' => $expirationTime,
                        'request_id' => $ret['RequestId'],
                        'subs_id' => isset($ret['SecretBindDTO'])?$ret['SecretBindDTO']['SubsId']:'',
                        'secret_no' => isset($ret['SecretBindDTO'])?$ret['SecretBindDTO']['SecretNo']:'',
                        'status' => $ret['Code'] === 'OK'?2:1,
                        'add_time' => time()
                    ]);
                    if ($ret['Code'] === 'OK') {
                        //绑定成功,写到订单
                        $map = [
                            'store_member_phone'=>$aPhone, //商家电话
                            'member_phone'=>$bPhone,   // 用户电话
                            'status' => 11 //订单状态
                        ];
                        OrderModel::update(['x_phone'=>$xPhone],$map);
                        rstJson('success', '正在拨号...', ['tel'=>$xPhone]);
                    }
                }
                rstJson('error', alicallErrorStr($ret['Message']));
            } catch (\Exception $e) {
                rstJson('error', $e->getMessage());
            }
 
        }
 
        /**
         * 解除绑定 通话完毕解除绑定状态
         * @return [type] [description]
         */
        public function unBindPhone($aBind = '',$bBind = '',$xBind = '')
        {
            try {
                $aBind = $aBind?$aBind:input('abind/s');
                $bBind = $bBind?$bBind:input('bbind/s');
                $xBind = $xBind?$xBind:input('xbind/s');
                // 只查询绑定成功的  status 2
                $map = [
                    'a_phone' => $aBind,
                    'b_phone' => $bBind,
                    'x_phone' => $xBind,
                    'status' => 2
                ];
                // 查询绑定信息
                $bindInfo = BindModel::where($map)->field('request_id,subs_id,secret_no')->find();
                if (empty($bindInfo['subs_id']) || empty($bindInfo['secret_no'])) {
                    return false;
                }
                $alicall_api = new AlicallApi();
                $alicall_api->setAk($this->ak);
                $alicall_api->setAks($this->aks);
                $ret = $alicall_api->unbindSubscription($bindInfo['subs_id'],$bindInfo['secret_no']);
                $ret = object_to_array($ret);
                if ($ret['Message'] !== 'OK')return false;
 
                //修改绑定状态
                BindModel::update([
                    'status' => 3,
                    'unbind_msg'=>$ret['Message'],
                    'update_time'=>time()
                ],$map);
                return true;
            } catch (\Exception $e) {
                return false;
            }
        }
 
 
 
        /**
         * 呼叫状态报告接收
         * release_time - start_time = 0 未接通  否则已接通
         * free_ring_time > call_out_time  未振铃(被叫方手机关机等状态)
         *
         * release_dir 0代表平台释放 1代表主叫挂断 2代表被叫挂断
         * call_type 0:主叫(phone_no打给peer_no);1:被叫(peer_no打给phone_no);2:短信发送;3:短信接收; 4:呼叫拦截; 5:短信收发拦截
         * release_cause 释放原因
         * 1:未分配的号码
         * 2:无路由到指定的转接网
         * 3:无路由到目的地
         * 4:发送专用信息音
         * 16:正常的呼叫拆线
         * 17:用户忙
         * 18:用户未响应
         * 19:用户未应答
         * 20:用户缺席
         * 21:呼叫拒收
         * 22:号码改变
         * 27:目的地不可达
         * 28:无效的号码格式(地址不全)
         * 29:性能拒绝
         * 31:正常—未指定
         * 34: 无电路/通路可用
         * 42: 交换设备拥塞
         * 50:所请求的性能未预定
         * 53:CUG中限制去呼叫
         * 55: CUG中限制来呼叫
         * 57:承载能力无权
         * 58:承载能力目前不可用
         * 65:承载能力未实现
         * 69:所请求的性能未实现
         * 87:被叫用户不是CUG的成员
         * 88:不兼容的目的地
         * 90:不存在的CUG
         * 91:无效的转接网选择
         * 95:无效的消息,未指定
         * 97:消息类型不存在或未实现
         * 99:参数不存在或未实现
         * 102:定时器终了时恢复
         * 103:参数不存在或未实现—传递
         * 110:消息带有未被识别的参数—舍弃
         * 111:协议错误,未指定
         * 127:互通,未指定
         *
         * @return [type] [description]
         */
        public function callNotify()
        {
            try {
                $ret_arr = file_get_contents("php://input");
                //转为数组
                $data = json_decode($ret_arr,true);
                addlog('alicall_msg1',$data);
                $data = $data[0];
                addlog('alicall_msg2',$data);
                // 通话完毕,先解除绑定
                $this->unBindPhone($data['phone_no'],$data['peer_no'],$data['secret_no']);
                $data['add_time'] = time();
                //写入通话记录表
                CallmsgModel::create($data);
                // 如果打通了
                // 扣钱
                if ((int)strtotime($data['release_time']) - (int)strtotime($data['start_time']) != 0) {
                    $member_id = MemberModel::where('mobile',$data['phone_no'])->value('id');
                    // 查询 主叫phone_no商家 与被叫 peer_no ,状态为11的订单
                    $map = [
                        'store_member_phone'=>$data['phone_no'], //商家电话
                        'member_phone'=>$data['peer_no'],   // 用户电话
                        'status' => 11 //订单状态
                    ];
                    // 订单金额
                    $money = OrderModel::getOrderMoney($map);
                    // 开始扣钱
                    $balance = PaymentModel::moneyLess('-',$member_id,$money);
                    addlog('kouqian',$balance);
                    // 修改订单状态
                    $order_sn = OrderModel::changeStatus(12,$map,true);
                    // 添加到金额记录
                    addlog('order_sn',$order_sn);
                    AccountLogModel::create([
                        'order_sn' => $order_sn,
                        'member_id' => $member_id,
                        'add_time' => time(),
                        'info' => '给 '.$data['peer_no'].' 打电话。',
                        'status' => 'cut',
                        'price' => $money,
                        'balance' => $balance,
                        'pay_status' => 2
                    ]);
                }
                // 通知接口应答,固定格式。
                echo '{
                    "code": 0,
                    "msg": "成功"
                }';
                die;
            } catch (\Exception $e) {
                addlog('alicall_error_log',$e->getMessage());
            }
        }
 
        /**
         * 获取通话状态(查询订单状态,12为通话成功)
         * @return [type] [description]
         */
        public function getCallStatus()
        {
            try {
                Auth::login();
                $order_sn = input('order_sn/s');
                if(empty($order_sn))rstJson('success', '未找到订单号!');
                $orderInfo = OrderModel::where('order_sn',$order_sn)->find();
                // 解绑
                $this->unBindPhone($orderInfo['store_member_phone'],$orderInfo['member_phone'],$orderInfo['x_phone']);
                if ($orderInfo['status'] == 12) {
                    rstJson('success', '通话成功!');
                }else{
                    rstJson('error', '未接通!');
                }
            } catch (\Exception $e) {
                rstJson('error', $e->getMessage());
            }
 
        }
    }

表:

-- phpMyAdmin SQL Dump
-- version 4.7.9
-- https://www.phpmyadmin.net/
--
-- Host: localhost
-- Generation Time: 2019-12-03 16:44:51
-- 服务器版本: 5.6.44-log
-- PHP Version: 7.1.26

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `lincang`
--

-- --------------------------------------------------------

--
-- 表的结构 `lc_alicall_bindxphone`
--

CREATE TABLE `lc_alicall_bindxphone` (
  `id` bigint(20) UNSIGNED NOT NULL,
  `a_phone` varchar(20) NOT NULL DEFAULT '0' COMMENT 'A手机',
  `x_phone` varchar(20) NOT NULL DEFAULT '0' COMMENT '中间号',
  `b_phone` varchar(20) NOT NULL DEFAULT '0' COMMENT 'B手机',
  `bind_status` varchar(50) NOT NULL DEFAULT '' COMMENT '接口返回绑定状态',
  `status` tinyint(1) UNSIGNED NOT NULL DEFAULT '1' COMMENT '1 失败 2 成功 3 解绑',
  `expire_time` varchar(11) NOT NULL DEFAULT '0' COMMENT '失效时间',
  `request_id` varchar(100) NOT NULL DEFAULT '' COMMENT '接口返回(请求id号)',
  `subs_id` varchar(100) NOT NULL DEFAULT '' COMMENT '接口返回(三元绑定关系对应的绑定ID)',
  `secret_no` varchar(100) NOT NULL DEFAULT '' COMMENT '接口返回(调用绑定接口时分配的隐私号码)',
  `unbind_msg` varchar(255) NOT NULL DEFAULT '' COMMENT '接口返回(解绑返回信息)',
  `add_time` varchar(20) NOT NULL DEFAULT '0',
  `update_time` varchar(20) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='阿里云手机号绑定关系记录表' ROW_FORMAT=COMPACT;

--
-- Indexes for dumped tables
--


CREATE TABLE `lc_alicall_callmsg` (
  `msg_id` bigint(20) UNSIGNED NOT NULL,
  `phone_no` varchar(11) NOT NULL DEFAULT '' COMMENT '主叫',
  `pool_key` varchar(50) NOT NULL DEFAULT '' COMMENT '对应的号池Key',
  `city` varchar(50) NOT NULL DEFAULT '' COMMENT '隐私号码归属地',
  `sub_id` varchar(50) NOT NULL DEFAULT '' COMMENT '通话对应的三元组的绑定关系ID',
  `call_time` varchar(50) NOT NULL DEFAULT '' COMMENT '主叫拨打时间',
  `peer_no` varchar(11) NOT NULL DEFAULT '' COMMENT 'AXB中的B号码或者N号码',
  `called_display_no` varchar(50) NOT NULL DEFAULT '' COMMENT '被叫显号',
  `release_dir` varchar(50) NOT NULL DEFAULT '' COMMENT '通话释放方向,0代表平台释放 1代表主叫挂断 2代表被叫挂断',
  `call_out_time` varchar(50) NOT NULL DEFAULT '',
  `ring_time` varchar(50) NOT NULL DEFAULT '' COMMENT '呼叫送被叫端局时,被叫端局响应的时间',
  `call_id` varchar(50) NOT NULL DEFAULT '' COMMENT '唯一标识一通通话记录的ID',
  `start_time` varchar(50) NOT NULL DEFAULT '' COMMENT '被叫接听时间',
  `partner_key` varchar(50) NOT NULL DEFAULT '' COMMENT '商户Key',
  `free_ring_time` varchar(50) NOT NULL DEFAULT '' COMMENT '被叫手机真实的振铃时间,free_ring_time > call_out_time代表被叫真实发生了振铃事件, 相等代表未振铃',
  `control_msg` varchar(50) NOT NULL DEFAULT '',
  `id` varchar(50) NOT NULL DEFAULT '',
  `secret_no` varchar(50) NOT NULL DEFAULT '' COMMENT 'AXB中的X号码',
  `call_type` varchar(50) NOT NULL DEFAULT '' COMMENT '呼叫类型',
  `release_cause` varchar(50) NOT NULL DEFAULT '' COMMENT '释放原因',
  `control_type` varchar(50) NOT NULL DEFAULT '',
  `release_time` varchar(50) NOT NULL DEFAULT '' COMMENT '被叫挂断时间,release_time - start_time 代表通话时长 如果结果为0,代表呼叫未接通',
  `add_time` varchar(10) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='通话记录' ROW_FORMAT=COMPACT;


--
-- Indexes for table `lc_alicall_bindxphone`
--
ALTER TABLE `lc_alicall_bindxphone`
  ADD PRIMARY KEY (`id`) USING BTREE;

--
-- 在导出的表使用AUTO_INCREMENT
--

--
-- 使用表AUTO_INCREMENT `lc_alicall_bindxphone`
--
ALTER TABLE `lc_alicall_bindxphone`
  MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;