先下载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 */;