<?php namespace app\mobile\controller; use think\Validate; use think\Db; use app\common\controller\Api; use app\mobile\model\Question; use app\mobile\model\Simulation; use app\mobile\model\Old; use app\mobile\model\Everyday; use app\mobile\model\Secret; use app\mobile\model\QuestionAnswer; use app\mobile\model\QuestionNote; use app\mobile\model\QuestionWrong; use app\mobile\model\QuestionCollect; use addons\qiniu\library\Auth; use app\common\model\Attachment; /** * 全能题库接口 * @ApiWeigh (98) */ class Almighty extends Api { protected $noNeedLogin = ['']; protected $noNeedRight = ['*']; public function _initialize() { parent::_initialize(); } /** * @ApiTitle (全能题库-题目列表) * @ApiSummary (全能题库-题目列表) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="answer_type", type="string", required=true, description="作答类型:0=全部,1=已答题,2=未答题,3=错题") * @ApiParams (name="question_type", type="string", required=true, description="试题题型:0=全部,1=单选题,2=多选题,3=判断题,4=简答题") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": { "total": 1, //题目总数 "list": [{ //题目列表 "id": 1, //题目ID "title": "测定混凝土立方体抗压强度时,标准试件的尺寸是( )㎜。", //题目 "option": "[{\"name\":\"A\",\"gender\":\"100\\u00d7100\\u00d7100\"},{\"name\":\"B\",\"gender\":\"150\\u00d7150\\u00d7150\"},{\"name\":\"C\",\"gender\":\"200\\u00d7200\\u00d7200\"},{\"name\":\"D\",\"gender\":\"70.7\\u00d770.7\\u00d770.7\"}]", //题目选项 "type": "1", //题目类型:1=单选题,2=多选题,3=判断题,4=简答题 "answer": "A", //答案 "note": "笔记", //笔记 "is_collect": "1" //是否收藏:0=否,1=是 }] } }) */ public function questionList() { $user_id = $this->auth->id; $answer_type = $this->request->param('answer_type'); $question_type = $this->request->param('question_type'); $where['q.target_type'] = '1'; switch ($answer_type) { case '1': $question_id_arr = QuestionAnswer::where('user_id',$user_id)->column('question_id'); $where['q.id'] = !empty($question_id_arr) ? ['in',$question_id_arr] : 0; break; case '2': $question_id_arr = QuestionAnswer::where('user_id',$user_id)->column('question_id'); if(!empty($question_id_arr)){ $where['q.id'] = ['notin',$question_id_arr]; } break; case '3': $question_id_arr = QuestionAnswer::where('user_id',$user_id)->where('is_wrong','1')->column('question_id'); $where['q.id'] = !empty($question_id_arr) ? ['in',$question_id_arr] : 0; break; } if(!empty($question_type)){ $where['q.type'] = $question_type; } $list = Question::alias('q') ->join('mobile_question_note qn','q.id = qn.question_id and qn.user_id='.$user_id,'left') ->join('mobile_question_collect qc','q.id = qc.question_id and qc.user_id='.$user_id,'left') ->where($where) ->field(' q.id, q.title, q.option, q.type, q.answer, qn.content note, if(qc.id > 0,1,0) is_collect ')->orderRaw('rand()')->limit(100) // 随机100道题目 ->select(); $total = count($list); $this->success('成功',compact('total','list')); } /** * @ApiTitle (全能题库-题目列表-详情) * @ApiSummary (全能题库-题目列表-详情) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": { "id": 1, //题目ID "title": "测定混凝土立方体抗压强度时,标准试件的尺寸是( )㎜。", //题目 "option": "[{\"name\":\"A\",\"gender\":\"100\\u00d7100\\u00d7100\"},{\"name\":\"B\",\"gender\":\"150\\u00d7150\\u00d7150\"},{\"name\":\"C\",\"gender\":\"200\\u00d7200\\u00d7200\"},{\"name\":\"D\",\"gender\":\"70.7\\u00d770.7\\u00d770.7\"}]", //题目选项 "type": "1", //题目类型:1=单选题,2=多选题,3=判断题,4=简答题 "answer": "A", //答案 "note": "笔记", //笔记 "is_collect": "1", //是否收藏:0=否,1=是 "status": "1" //状态:0=未答,1=答对,2=答错 "my_answer": "1", //我的答案 } }) */ public function questionInfo() { $user_id = $this->auth->id; $question_id = $this->request->param('question_id'); empty($question_id) && $this->error('缺少必要参数'); $info = Question::alias('q') ->join('mobile_question_note qn','q.id = qn.question_id and qn.user_id='.$user_id,'left') ->join('mobile_question_collect qc','q.id = qc.question_id and qc.user_id='.$user_id,'left') ->join('mobile_question_answer qa','q.id = qa.question_id and qa.user_id='.$user_id,'left') ->where('q.id',$question_id) ->field(' q.id, q.title, q.option, q.type, q.answer, q.analysis_video, q.analysis_text, qn.content note, if(qc.id > 0,1,0) is_collect, if(qa.id > 0,if(qa.is_wrong="0",1,2),0) status, qa.content my_answer ')->find(); $this->success('成功',$info); } /** * @ApiTitle (全能题库-答题) * @ApiSummary (全能题库-答题) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * @ApiParams (name="answer", type="string", required=true, description="回答内容") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": null }) */ public function answer() { $question_id = $this->request->param('question_id'); $answer = $this->request->param('answer'); if(empty($question_id) || empty($answer)){ $this->error('缺少必要参数'); } $question = Question::get($question_id); empty($question) && $this->error('题目信息不存在'); $info = QuestionAnswer::get(['question_id'=>$question_id,'user_id'=>$this->auth->id]); if(empty($info)){ $info = new QuestionAnswer(); } // 记录答案 $score = 0; $is_wrong = '0'; if($question['type'] != '4'){ if($question['answer'] == $answer){ $score = $question['score']; }else{ $is_wrong = '1'; } } $info->allowField(true)->save([ 'question_id' => $question_id, 'user_id' => $this->auth->id, 'content' => $answer, 'is_wrong' => $is_wrong, 'get_score' => $score ]); $this->success('回答成功'); } /** * @ApiTitle (全能题库-查看解析) * @ApiSummary (全能题库-查看解析) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1600675472", "data": { "analysis_video": { //解析视频 "cover": "http://qizhibang.brotop.cn/uploads/20200921/Fkp1Dv0c4dyYfVzAFBjhTuv25BNv.mp4?vframe/jpg/offset/1/w/1280/h/720", //封面图 "video": "http://qizhibang.brotop.cn/uploads/20200921/Fkp1Dv0c4dyYfVzAFBjhTuv25BNv.mp4" //视频 }, "analysis_text": "" //解析文字 } }) */ public function analysis() { $question_id = $this->request->param('question_id'); empty($question_id) && $this->error('缺少必要参数'); $info = Question::get($question_id); empty($info) && $this->error('题目信息不存在'); $info->visible(['analysis_video','analysis_text']); $this->success('成功',$info); } /** * @ApiTitle (全能题库-笔记) * @ApiSummary (全能题库-笔记) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * @ApiParams (name="content", type="int", required=true, description="笔记内容") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": null }) */ public function note() { $question_id = $this->request->param('question_id'); $content = $this->request->param('content'); empty($question_id) && $this->error('缺少必要参数'); empty($content) && $this->error('请填写笔记'); $info = QuestionNote::get(['question_id'=>$question_id,'user_id'=>$this->auth->id]); if(empty($info)){ $info = new QuestionNote(); } $info->allowField(true)->save([ 'question_id' => $question_id, 'user_id' => $this->auth->id, 'content' => $content ]); $this->success('添加笔记成功'); } /** * @ApiTitle (全能题库-报错) * @ApiSummary (全能题库-报错) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * @ApiParams (name="type", type="string", required=true, description="报错类型:1=答案不正确,2=题目不正确,3=解析不完整,4=其他错误") * @ApiParams (name="content", type="string", required=true, description="问题描述") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": null }) */ public function wrong() { $question_id = $this->request->param('question_id'); $type = $this->request->param('type'); $content = $this->request->param('content'); empty($question_id) && $this->error('缺少必要参数'); empty($type) && $this->error('请选择报错类型'); empty($content) && $this->error('请填写问题描述'); $info = QuestionWrong::get(['question_id'=>$question_id,'user_id'=>$this->auth->id]); if(empty($info)){ $info = new QuestionWrong(); } $info->allowField(true)->save([ 'question_id' => $question_id, 'user_id' => $this->auth->id, 'type' => $type, 'content' => $content ]); $this->success('报错成功'); } /** * @ApiTitle (全能题库-收藏) * @ApiSummary (全能题库-收藏) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="question_id", type="int", required=true, description="题目ID") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599032660", "data": null }) */ public function collect() { $question_id = $this->request->param('question_id'); empty($question_id) && $this->error('缺少必要参数'); $info = QuestionCollect::get(['question_id'=>$question_id,'user_id'=>$this->auth->id]); if(!empty($info)){ $info->delete(); $this->success('取消收藏成功'); } QuestionCollect::create([ 'question_id' => $question_id, 'user_id' => $this->auth->id, ]); $this->success('收藏成功'); } /** * @ApiTitle (智能搜题-语音转文字) * @ApiSummary (智能搜题-语音转文字) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="filename", type="string", required=true, description="文件地址") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599130815", "data": [{ "id": 1, //题目ID "title": "测定混凝土立方体抗压强度时,标准试件的尺寸是( )㎜。", //题目 "type": "4", //题目类型:1=单选题,2=多选题,3=判断题,4=简答题 "option": "[{\"name\":\"A\",\"gender\":\"100\\u00d7100\\u00d7100\"},{\"name\":\"B\",\"gender\":\"150\\u00d7150\\u00d7150\"},{\"name\":\"C\",\"gender\":\"200\\u00d7200\\u00d7200\"},{\"name\":\"D\",\"gender\":\"70.7\\u00d770.7\\u00d770.7\"}]" //题目选项 }] }) */ public function getText() { $filename = $this->request->param('filename'); empty($filename) && $this->error('请输入音频地址'); $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($suffix, ['pcm', 'wav', 'amr', 'm4a', 'mp3'])) { $this->error('不支持该音频格式'); } if($suffix == 'mp3'){ $filename = $this->mp3ToPcm($filename); } $result = $this->asrJson($filename); $this->success('成功',['text'=>$result]); } /** * @ApiTitle (智能搜题) * @ApiSummary (智能搜题) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiParams (name="keyword", type="string", required=true, description="关键词") * @ApiParams (name="page", type="inter", required=false, description="当前页(默认1)") * @ApiParams (name="page_num", type="inter", required=false, description="每页显示数据个数(默认10)") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1599130815", "data": [{ "id": 1, //题目ID "title": "测定混凝土立方体抗压强度时,标准试件的尺寸是( )㎜。", //题目 "type": "4", //题目类型:1=单选题,2=多选题,3=判断题,4=简答题 "option": "[{\"name\":\"A\",\"gender\":\"100\\u00d7100\\u00d7100\"},{\"name\":\"B\",\"gender\":\"150\\u00d7150\\u00d7150\"},{\"name\":\"C\",\"gender\":\"200\\u00d7200\\u00d7200\"},{\"name\":\"D\",\"gender\":\"70.7\\u00d770.7\\u00d770.7\"}]" //题目选项 }] }) */ public function search() { $page = $this->request->param('page', 1, 'intval'); $page_num = $this->request->param('page_num', 10, 'intval'); $keyword = $this->request->param('keyword'); empty($keyword) && $this->error('缺少必要参数'); $list = Question::where('target_type','1') ->where('title','like','%'.$keyword.'%') ->field('id,title,type,option') ->page($page,$page_num) ->select(); $this->success('成功',$list); } /** * @ApiTitle (错题训练) * @ApiSummary (错题训练) * @ApiMethod (POST) * * @ApiHeaders (name=token, type=string, required=true, description="请求的Token") * * @ApiReturn({ "code": 1, "msg": "成功", "time": "1600678092", "data": { "banner": "http://www.enterprise.top/uploads/20200911/8894d62100f2f920ffb2f38063b63f2d.jpg", //广告图 "list": [{ // 试卷列表 "0": { "id": 11, "target_type": "1", "target_id": 0, "type": "3" }, "pan": 1, //判断题数 "duo": 0, //多选题数 "dan": 0, //单选题数 "title": "《全能题库》", //试卷标题 "type": "1", //题目归属类型:1=全能题库,2=模拟试题,3=历年真题,4=每日一练,5=通关密卷 "id": 0 //试卷ID(全能题库为0) }] } }) */ public function wrongQuestion() { $banner = Db::name('mobile_config')->where('id',1)->value('wrong_adver'); $banner = !empty($banner) ? cdnurl($banner,true) : ''; $question_list = Question::alias('q') ->join('mobile_question_answer qa','q.id = qa.question_id and user_id='.$this->auth->id,'left') ->where('qa.is_wrong','1') ->field('q.id,q.target_type,q.target_id,q.type') ->select(); $list = []; foreach ($question_list as $v) { $list[$v['target_id']][] = $v; } // 统计题目数 foreach ($list as &$v) { $v['dan'] = $v['duo'] = $v['pan'] = 0; foreach ($v as &$value) { switch ($value['type']) { case '1': $v['dan']++; break; case '2': $v['duo']++; break; case '3': $v['pan']++; break; } } switch ($v[0]['target_type']) { case '1': $v['title'] = '《全能题库》'; break; case '2': $v['title'] = Simulation::where('id',$v[0]['target_id'])->value('title'); break; case '3': $v['title'] = Old::where('id',$v[0]['target_id'])->value('title'); break; case '4': $v['title'] = Everyday::where('id',$v[0]['target_id'])->value('title'); break; case '5': $v['title'] = Secret::where('id',$v[0]['target_id'])->value('title'); break; } $v['type'] = $v[0]['target_type']; $v['id'] = $v[0]['target_id']; } $this->success('成功',compact('banner','list')); } /** * mp3转pcm * @ApiInternal */ public function mp3ToPcm($filename) { $key = ltrim($filename,'/'); $config = get_addon_config('qiniu'); $auth = new Auth($config['app_key'], $config['secret_key']); //设置转码参数 $fops = "avthumb/s16le/acodec/pcm_s16le/ac/1/ar/16000"; //通过添加'|saveas'参数,指定处理后的文件保存的bucket和key 不指定默认保存在当前空间,bucket为目标空间,后一个参数为转码之后文件名 $filename = str_replace(strrchr($key, "."),"",$key); $entry = $config['bucket'] . ':' . $filename . '.pcm'; $saveas_key = $auth->base64_urlSafeEncode($entry); $fops = $fops.'|saveas/'.$saveas_key; $policy = array( 'persistentOps' => $fops, 'persistentNotifyUrl' => $this->request->root(true) . '/mobile/notify/notifyQiniuAvthumb', ); $token = $auth->uploadToken($config['bucket'], null, $config['expire'], $policy); $multipart = [ ['name' => 'token', 'contents' => $token], [ 'name' => 'file', 'contents' => fopen(ROOT_PATH . '/public/' .$key, 'r'), 'filename' => $key, ] ]; try { $client = new \GuzzleHttp\Client(); $res = $client->request('POST', $config['uploadurl'], [ 'multipart' => $multipart ]); $code = $res->getStatusCode(); //成功不做任何操作 } catch (\GuzzleHttp\Exception\ClientException $e) { $this->error("上传失败"); } // 返回文件地址 $url = '/' . $filename . '.pcm'; if($this->file_exists($url)){ return cdnurl($url); } $this->error('接口错误'); } /** * 判断文件是否存在 * @ApiInternal */ public function file_exists($url) { $info = Db::name('attachment')->where('url',$url)->field('id')->find(); if(!$info){ $this->file_exists($url); }else{ return true; } } /** * 语音转文字 * @ApiInternal */ public function asrJson($filename){ dump($filename); define('DEMO_CURL_VERBOSE', false); // 打印curl debug信息 # 填写网页上申请的appkey 如 $apiKey="g8eBUMSokVB1BHGmgxxxxxx" $API_KEY = "kVcnfD9iW2XVZSMaLMrtLYIz"; # 填写网页上申请的APP SECRET 如 $secretKey="94dc99566550d87f8fa8ece112xxxxx" $SECRET_KEY = "O9o1O213UgG5LFn0bDGNtoRN3VWl2du6"; # 需要识别的文件 $AUDIO_FILE = preg_match("/^http(s)?:\\/\\/.+/",$filename) ? $filename : ROOT_PATH . '/public' . $filename; # 文件格式 $FORMAT = substr($AUDIO_FILE, -3); // 文件后缀 pcm/wav/amr 格式 极速版额外支持m4a 格式 $CUID = "123456PHP"; # 采样率 $RATE = 16000; // 固定值 # 普通版 $ASR_URL = "http://vop.baidu.com/server_api"; # 根据文档填写PID,选择语言及识别模型 $DEV_PID = 1537; // 1537 表示识别普通话,使用输入法模型。 $SCOPE = 'audio_voice_assistant_get'; // 有此scope表示有语音识别普通版能力,没有请在网页里开通语音识别能力 #测试自训练平台需要打开以下信息, 自训练平台模型上线后,您会看见 第二步:“”获取专属模型参数pid:8001,modelid:1234”,按照这个信息获取 dev_pid=8001,lm_id=1234 //$DEV_PID = 8001 ; //$LM_ID = 1234 ; # 极速版需要打开以下信息 打开注释的话请填写自己申请的appkey appSecret ,并在网页中开通极速版(开通后可能会收费) //$ASR_URL = "http://vop.baidu.com/pro_api"; //$DEV_PID = 80001; //$SCOPE = 'brain_enhanced_asr'; // 有此scope表示有极速版能力,没有请在网页里开通极速版 $SCOPE = false; // 部分历史应用没有加入scope,设为false忽略检查 /** 公共模块获取token开始 */ $auth_url = "http://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=".$API_KEY."&client_secret=".$SECRET_KEY; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $auth_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //信任任何证书 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 检查证书中是否设置域名,0不验证 curl_setopt($ch, CURLOPT_VERBOSE, DEMO_CURL_VERBOSE); $res = curl_exec($ch); if(curl_errno($ch)) { $this->error(curl_error($ch)); } curl_close($ch); $response = json_decode($res, true); if (!isset($response['access_token'])){ $this->error('ERROR TO OBTAIN TOKEN'); } if (!isset($response['scope'])){ $this->error('ERROR TO OBTAIN scopes'); } if ($SCOPE && !in_array($SCOPE, explode(" ", $response['scope']))){ $this->error('CHECK SCOPE ERROR');// 请至网页上应用内开通语音识别权限 } $token = $response['access_token']; /** 公共模块获取token结束 */ /** 拼接参数开始 **/ $audio = file_get_contents($AUDIO_FILE); $base_data = base64_encode($audio); $params = array( "dev_pid" => $DEV_PID, //"lm_id" => $LM_ID, //测试自训练平台开启此项 "format" => $FORMAT, "rate" => $RATE, "token" => $token, "cuid"=> $CUID, "speech" => $base_data, "len" => strlen($audio), "channel" => 1, ); $json_array = json_encode($params); $headers[] = "Content-Length: ".strlen($json_array); $headers[] = 'Content-Type: application/json; charset=utf-8'; /** 拼接参数结束 **/ /** asr 请求开始 **/ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $ASR_URL); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); curl_setopt($ch, CURLOPT_TIMEOUT, 60); // 识别时长不超过原始音频 curl_setopt($ch, CURLOPT_POSTFIELDS, $json_array); curl_setopt($ch, CURLOPT_VERBOSE, DEMO_CURL_VERBOSE); $res = curl_exec($ch); if(curl_errno($ch)) { $this->error(curl_error($ch)); } curl_close($ch); /** asr 请求结束 **/ // 解析结果 $response = json_decode($res, true); if (isset($response['err_no']) && $response['err_no'] == 0){ return $response['result'][0]; }else{ $this->error($response['err_msg']); } } }