diff --git a/app/Console/Commands/callcenterListen.php b/app/Console/Commands/callcenterListen.php index 34968ac6..c8f03e52 100644 --- a/app/Console/Commands/callcenterListen.php +++ b/app/Console/Commands/callcenterListen.php @@ -103,7 +103,6 @@ class callcenterListen extends Command if ($cause == 'Cancel') { $billsec = 0; }else{ - if ($leaving_time && $answered_time){ $billsec = $leaving_time - $answered_time > 0 ? $leaving_time - $answered_time : 0; }else{ diff --git a/app/Console/Commands/swoole.php b/app/Console/Commands/swoole.php new file mode 100644 index 00000000..817bbf6c --- /dev/null +++ b/app/Console/Commands/swoole.php @@ -0,0 +1,235 @@ +url = config('app.url'); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + while (true){ + $data = Redis::lPop(config('freeswitch.fs_dial_key')); + if ($data == null){ + sleep(2); + continue; + } + $data = json_decode($data,true); + $process = new \Swoole\Process(function () use ($data) { + $fs = new \Freeswitchesl(); + $service = config('freeswitch.esl'); + if (!$fs->connect($service['host'], $service['port'], $service['password'])){ + Log::error("asr监听ESL未连接"); + return false; + } + //监听的事件 + $eventarr = [ + 'CHANNEL_CALLSTATE', + 'CHANNEL_ANSWER', + //'RECORD_START', + //'RECORD_STOP', + 'CHANNEL_HANGUP_COMPLETE', + ]; + if (isset($data['aleg_uuid'])){ + $fs->filteruuid($data['aleg_uuid']); + } + if (isset($data['bleg_uuid'])){ + $fs->filteruuid($data['bleg_uuid']); + } + if (isset($data['dial_str'])){ + $fs->bgapi(base64_decode($data['dial_str'])); + } + $answer_time = 0; + //录音目录 + $filepath = $this->fs_dir . '/recordings/' . date('Y') . '/' . date('m') . '/' . date('d') . '/'; + $fullfile = $filepath . 'full_' . md5($data['aleg_uuid'] . $data['bleg_uuid']) . '.wav'; + $fs->events('plain', implode(" ",$eventarr)); + while (true) { + $received_parameters = $fs->recvEvent(); + if (!empty($received_parameters)) { + $serialize = $fs->serialize($received_parameters,'json'); + $json = json_decode($serialize,true); + $eventname = Arr::get($json,'Event-Name',null); //事件名称 + $uuid = Arr::get($json,'Unique-ID',null);//当前信道leg的uuid + $otherUuid = Arr::get($json,'Other-Leg-Unique-ID',null); + $CallerCallerIDNumber = Arr::get($json,"Caller-Caller-ID-Number"); //主叫 + $CallerCalleeIDNumber = Arr::get($json,"Caller-Destination-Number"); //被叫 + switch ($eventname) { + //呼叫状态 + case 'CHANNEL_CALLSTATE': + //是分机号才记录 + if (preg_match('/\d{4,5}/',$CallerCallerIDNumber)){ + $state = Arr::get($json,'Channel-Call-State'); + $uniqueid = Arr::get($json,'Caller-Unique-ID'); + Redis::setex($CallerCallerIDNumber.'_uuid',1200, $uniqueid); + DB::table('sip')->where('username',$CallerCallerIDNumber)->update(['state'=>$state]); + } + break; + case 'CHANNEL_ANSWER': + if ($otherUuid) { //被叫应答后 + $answer_time = time(); + //开启全程录音 + $fs->bgapi("uuid_record {$uuid} start {$fullfile} 7200"); //录音 + if (Redis::get($this->asr_status_key)==1) { + + //记录A分段录音数据 + $halffile_a = $filepath . 'half_' . md5($otherUuid . time() . uniqid()) . '.wav'; + $fs->bgapi("uuid_record " . $otherUuid . " start " . $halffile_a . " 18"); + Redis::set($otherUuid,json_encode([ + 'uuid' => $otherUuid, + 'leg_uuid' => $otherUuid, + 'record_file' => $halffile_a, + 'full_record_file' => $fullfile, + 'start_at' => date('Y-m-d H:i:s'), + 'end_at' => null, + ])); + + //记录B分段录音数据 + $halffile_b = $filepath . 'half_' . md5($uuid . time() . uniqid()) . '.wav'; + $fs->bgapi("uuid_record " . $uuid . " start " . $halffile_b . " 18"); + Redis::set($uuid,json_encode([ + 'uuid' => $otherUuid, + 'leg_uuid' => $uuid, + 'record_file' => $halffile_b, + 'full_record_file' => $fullfile, + 'start_at' => date('Y-m-d H:i:s'), + 'end_at' => null, + ])); + unset($halffile_a); + unset($halffile_b); + } + } + break; + case 'RECORD_START': + $channel = Redis::get($uuid); + if ($channel){ + $data = array_merge(json_decode($channel,true),[ + 'start_time' => date('Y-m-d H:i:s'), + ]); + Redis::set($uuid,json_encode($data)); + } + break; + case 'RECORD_STOP': + if (Redis::get($this->asr_status_key)==1) { + $channel = Redis::get($uuid); + if ($channel){ + $data = json_decode($channel,true); + if (isset($data['record_file'])&&file_exists($data['record_file'])){ + DB::table('asr')->insert([ + 'uuid' => $data['uuid'], + 'leg_uuid' => $data['leg_uuid'], + 'start_at' => $data['start_at'], + 'end_at' => date('Y-m-d H:i:s'), + 'billsec' => strtotime(date('Y-m-d H:i:s'))-strtotime($data['start_at']), + 'record_file' => str_replace($this->fs_dir, $this->url, $data['record_file']), + 'created_at' => date('Y-m-d H:i:s'), + ]); + } + //结束说话 后接着开启分段录音 + $halffile = $filepath . 'half_' . md5($uuid . time() . uniqid()) . '.wav'; + $fs->bgapi("uuid_record " . $uuid . " start " . $halffile . " 18"); + Redis::set($uuid,json_encode(array_merge($data,[ + 'record_file' => $halffile, + 'start_at' => date('Y-m-d H:i:s'), + 'end_at' => null, + ]))); + unset($data); + unset($halffile); + } + unset($channel); + } + break; + case 'CHANNEL_HANGUP_COMPLETE': + $otherType = array_get($json,'Other-Type',null); + //A的挂机事件到来时线束进程 + if (empty($otherType) || $otherType == 'originatee') { + $src = array_get($json,'Caller-Caller-ID-Number',null); + $dst = array_get($json,'Caller-Callee-ID-Number',null); + $customer_caller = array_get($json,'variable_customer_caller',null); + $dst = !empty($customer_caller)?$customer_caller:$dst; + $start = array_get($json,'variable_start_stamp',null); + $user_data = array_get($json,'variable_user_data',null); + $record_file = str_replace($this->fs_dir,$this->url,$fullfile); + $billsec = $answer_time!=0?time()-$answer_time:0; + try{ + $user_data = decrypt($user_data); + }catch (\Exception $exception){ + $user_data = null; + } + try { + $model = DB::table('sip') + ->join('users','sip.id','=','users.sip_id') + ->where('sip.username',$src) + ->select(['users.id','users.department_id']) + ->first(); + if ($model == null) break 2; + $cdr = [ + 'user_id' => $model->id, + 'uuid' => $uuid, + 'aleg_uuid' => $uuid, + 'bleg_uuid' => $uuid, + 'direction' => 1, + 'src' => $src, + 'dst' => $dst, + 'duration' => 0, + 'billsec' => $billsec, + 'record_file' => $record_file, + 'user_data' => $user_data, + 'created_at' => date('Y-m-d H:i:s'), + ]; + DB::table('cdr')->insert($cdr); + }catch (\Exception $exception){ + Log::error('写入通话记录异常:'.$exception->getMessage(),$cdr); + } + break 2; + } + default: + # code... + break; + } + } + } + $fs->disconnect(); + }); + $process->start(); + } + } +} diff --git a/app/Http/Controllers/ApiController.php b/app/Http/Controllers/ApiController.php index f1f1b2b2..b0c43165 100644 --- a/app/Http/Controllers/ApiController.php +++ b/app/Http/Controllers/ApiController.php @@ -71,7 +71,10 @@ class ApiController extends Controller if ($data['exten'] == null || $data['phone'] == null) { return Response::json(['code'=>1,'msg'=>'号码不能为空']); } - + //验证手机号码 + if (!preg_match('/\d{4,12}/', $data['phone'])) { + return Response::json(['code'=>1,'msg'=>'客户电话号码格式不正确']); + } //检测10秒重复请求 if(Redis::get($data['exten'].'_check')!=null){ return Response::json(['code'=>1,'msg'=>'重复请求,请稍后再试']); @@ -90,20 +93,6 @@ class ApiController extends Controller return Response::json(['code'=>1,'msg'=>'当前外呼号未登录']); } - $fs = new \Freeswitchesl(); - $service = config('freeswitch.esl'); - try{ - $fs->connect($service['host'],$service['port'],$service['password']); - }catch (\Exception $exception){ - Log::info('拨打电话连接esl异常:'.$exception->getMessage()); - return Response::json(['code'=>1,'msg'=>'无法连接外呼服务']); - } - - //验证手机号码 - if (!preg_match('/\d{4,12}/', $data['phone'])) { - return Response::json(['code'=>1,'msg'=>'客户电话号码格式不正确']); - } - //呼叫字符串 $aleg_uuid = md5(\Snowflake::nextId(1).$data['exten'].$data['phone'].Redis::incr('fs_id')); $bleg_uuid = md5(\Snowflake::nextId(2).$data['phone'].$data['exten'].Redis::incr('fs_id')); @@ -148,13 +137,16 @@ class ApiController extends Controller } $dialStr .= $data['phone']."_".$bleg_uuid; }else{ //内部呼叫 - $dialStr .="user/".$sip->username." ".$data["phone"]; + $dialStr .="user/".$sip->username." ".$data["phone"]."_".$bleg_uuid; } $dialStr .=" XML default"; try{ - $fs->bgapi($dialStr); - $fs->disconnect(); + Redis::rPush(config('freeswitch.fs_dial_key'),json_encode([ + 'aleg_uuid' => $aleg_uuid, + 'bleg_uuid' => $bleg_uuid, + 'dial_str' => base64_encode($dialStr), + ])); //20分钟过期 Redis::setex($data['exten'].'_uuid',1200, $aleg_uuid); return Response::json(['code'=>0,'msg'=>'呼叫成功','data'=>['uuid'=>$aleg_uuid,'time'=>date('Y-m-d H:i:s')]]); diff --git a/config/freeswitch.php b/config/freeswitch.php index daa416c5..0da99321 100644 --- a/config/freeswitch.php +++ b/config/freeswitch.php @@ -40,7 +40,8 @@ return [ 'password' => 'dgg@1234.', 'port' => 8022, ], - + //拨打电话队列 + 'fs_dial_key' => 'fs_dial_list', //队列响铃模式 'strategy' => [ 'top-down' => '顺序振铃',