百度翻译 的文档是需要消费的,并且它的api请求是一个异步的过程,如果业务上用户不需要登录的时候,无法直接获取到翻译结果文档。因此,做了个异步返回的功能。
思路:
1、上传文档;
2、调用翻译接口;
3、通过kafka监听接口回调,处理返回数据,保存到本地(方便以后扩展,如加入用户信息,查看我的翻译记录)
4、前端监听后端服务,通过swoole的websocket实时请求文档数据
关键部分代码(赶工做的,未做优化):
//上传文件
public function upload()
{
header("Access-Control-Allow-Origin:*");
set_time_limit(300);
$file = $_FILES['file'];
if(!$file){
$show = array('status'=>422,'message'=>'请上传图片或者文档文件');
json($show);
}
$app = '2020bd';
if($_SERVER['SERVER_ADDR']=='127.0.0.1'){
$sPath = '/Users/kunyuan/www/activity/storage/'.$app.'/';
}else{
$sPath = '/var/www/html/activity/storage/'.$app.'/';
}
if(!mk_dir($sPath)){
$show = array('status'=>500,'message'=>'目录不存在');
json($show);
}
$path_parts = pathinfo($file['name']);
$ext = strtolower($path_parts["extension"]);
$extAry = array(
'jpg','jpeg','png','docx','xls','xlsx','ppt','pptx','pdf'
);
if(!in_array($ext, $extAry)){
$show = array('status'=>422,'message'=>'请上传图片或者文档文件');
json($show);
}
try{
//保存到服务器本地
$filename = date('Ymd'). '_' .rand(10000, 99999).'_'.time();
$tmpfile = $filename . '.'.$ext;
$tmp = $file["tmp_name"];
$img = file_get_contents($tmp);
$path1 = $sPath . $tmpfile;
$res = file_put_contents($path1, $img);
if(is_https()){
$url = 'https://'.$_SERVER['SERVER_NAME'].'/storage/'.$app.'/'.$tmpfile;
}else{
$url = 'http://'.$_SERVER['SERVER_NAME'].'/storage/'.$app.'/'.$tmpfile;
}
$error = '上传失败';
if($res){
//判断文件类型,为了文件调用百度的图片接口或者文档接口
if(in_array($ext,array('jpg','jpeg','png'))){
$resExt = 'pic';
}else{
$resExt = 'docs';
}
//保存数据库
$fileid = $this->common->setFiles(array(
'sourceFile' => $url,
'localFile' => $path1,
'type' => $resExt
));
$show = array('status'=>0,'fileUrl'=>$url,'fileSize'=>ceil(filesize($path1)/1000)."k",'ext'=>$resExt, 'id' => $fileid,'resExt'=>$resExt);
}else{
$show = array('status'=>500,'message'=>$error,'info'=>$info,'fileSize'=>ceil(filesize($path1)/1000)."k");
}
json($show);
}catch(Exception $e){
$show = array('status'=>$e->getCode(),'message'=>$e->getMessage());
json($show);
}
}
//调用百度接口
public function api()
{
//$type = I('type','docs');
$id = I('id',0);
$callback = I('callback');
//判断是否为上传的文件
$data = $this->common->getFiles(array('id'=>$id));
if(empty($data)){
$show = array('status'=>420,'message'=>'数据有误');
json($show, $callback);
}
$type = $data['type'];
switch($type){
case 'docs':
$this->docsTran($data);
break;
case 'pic':
$this->picTran($data);
break;
default:
$this->docsTran($data);
break;
}
}
//文档翻译接口
private function docsTran($data)
{
//die('ooo');
$file = $data['localFile'];//I('file');
$from = I('from', 'zh');
$to = I('to','en');
$callback = I('callback');
if(empty($file)){
$show = array('status'=>422,'message'=>'请上传文档文件');
json($show, $callback);
}
$path_parts = pathinfo($file);
$ext = strtolower($path_parts["extension"]);
$extAry = array(
'docx','xls','xlsx','ppt','pptx','pdf'
);
if(!in_array($ext, $extAry)){
$show = array('status'=>423,'message'=>'请上传文档文件');
json($show, $callback);
}
if($from == $to){
$show = array('status'=>424,'message'=>'翻译语言不能与原文档言语一样');
json($show, $callback);
}
$arr = array(
'requestId' => uniqid(),
'sourceFile' => $data['sourceFile'],
'localFile' => '',
'type' => 'docs',
'content' => '',
'create_time' => time()
);
$id = $this->common->set($arr);
$params = array(
'appid' => $this->appid, //你的appid
'from' => $from,
'to' => $to,
'timestamp' => time(), //10位时间戳
'type' => $ext, //文档类型,根据具体文档而定
);
$seckey = $this->seckey; //你的密钥
ksort($params);
$querySign = '';
foreach ($params as $key => $value) {
$querySign .= $key . '=' . $value . '&';
}
$filePath = $file; //文档路径
$params['sign'] = md5($querySign . '' . md5_file($filePath) . '' . $seckey);
$url = 'http://fanyi-api.baidu.com/api/trans/vip/doctrans';
$header = array(
'Content-Type' => 'multipart/form-data',
);
$params['file'] = '@' . realpath($imagePath);
//高版本兼容
if (class_exists('\CURLFile')) {
$params['file'] = new \CURLFile(realpath($filePath));
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
logs('Docs Result>>>'.$result);
$callRet = json_decode($result, true);
//print_R($result);
if($callRet['error_code'] == 52000){
$arr = array(
'requestId' => $callRet['data']['requestId'],
);
$this->common->update($arr, array('id'=>$id));
//$this->load->libraliry('Kafka');
$this->kafkaMsg($callRet['data']['requestId']);
$show = array('status'=>0,'message'=>'翻译成功','requestId'=>$callRet['data']['requestId']);
json($show, $callback);
}else{
$show = array('status'=>$callRet['error_code'],'message'=>$callRet['error_msg']);
json($show, $callback);
}
}
//图片翻译接口
private function picTran($data)
{
$file = $data['localFile'];//I('file');
//echo $file;
$from = I('from', 'zh');
$to = I('to','en');
$callback = I('callback');
if(empty($file)){
$show = array('status'=>422,'message'=>'请上传图片文件');
json($show, $callback);
}
$path_parts = pathinfo($file);
$ext = strtolower($path_parts["extension"]);
$extAry = array(
'jpg','jpeg','png'
);
if(!in_array($ext, $extAry)){
$show = array('status'=>423,'message'=>'请上传图片文件');
json($show, $callback);
}
if($from == $to){
$show = array('status'=>424,'message'=>'翻译语言不能与原文件言语一样');
json($show, $callback);
}
$appid = $this->appid;
$salt = rand();
$cuid = 'APICUID';
$mac = 'MAC';
$secKey = $this->seckey;
$imagePath = $file;
//echo $imagePath;
$sign = md5($appid . md5(file_get_contents($imagePath)) . $salt . $cuid . $mac . $secKey);
$url = 'https://fanyi-api.baidu.com/api/trans/sdk/picture?appid=' . $this->appid . '&from=' . $from . '&to=' . $to . '&salt=' . $salt . '&cuid=' . $cuid . '&mac=' . $mac . '&sign=' . $sign;
$header = array(
'Content-Type' => 'multipart/form-data',
);
$sendData = array(
'image' => '@' . realpath($imagePath) . ';type=image/jpeg',
);
if (class_exists('\CURLFile')) {
$sendData['image'] = new \CURLFile(realpath($imagePath));
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,$sendData);
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
logs('PIC Result>>>'.$result);
$callRet = json_decode($result, true);
//print_R($callRet);
if($callRet['error_code'] == 0){
$arr = array(
'requestId' => uniqid(),
'sourceFile' => $data['sourceFile'],
'localFile' => $file,
'type' => 'pic',
'content' => $callRet['data']['sumSrc'],
'create_time' => time()
);
//print_r($arr);
$this->common->set($arr);
$show = array('status'=>0,'message'=>'翻译成功','srcContent'=>$callRet['data']['sumSrc'] ,'tranContent' => $callRet['data']['sumDst']);
json($show, $callback);
}else{
$show = array('status'=>$callRet['error_code'],'message'=>$callRet['error_msg']);
json($show, $callback);
}
}
//消息生产
private function kafkaMsg($requestId)
{
$topicname = "testlin";
logs('kafkaMsgStart>>>' . $requestId . '>>' . $topicname);
$conf = new RdKafka\Conf();
$conf->set('metadata.broker.list', 'localhost:9092');
$producer = new RdKafka\Producer($conf);
$topic = $producer->newTopic($topicname);
$topic->produce(RD_KAFKA_PARTITION_UA, 0, $requestId);
//$producer->poll(0);
$result = $producer->flush(1000);
if (RD_KAFKA_RESP_ERR_NO_ERROR !== $result) {
logs('ERROR>>>' . 'Was unable to flush, messages might be lost!');
}
logs('kafkaMsgEnd>>>' . $requestId);
}
//百度返回后的处理,通过kafka订阅消息
public function dealLocal()
{
$topicname = "testlin";
logs('deal start>>>'.$topicname);
$conf = new RdKafka\Conf();
// Set the group id. This is required when storing offsets on the broker
$conf->set('group.id', 'myConsumerGroup');
$rk = new RdKafka\Consumer($conf);
$rk->addBrokers("localhost:9092");
$topicConf = new RdKafka\TopicConf();
$topicConf->set('auto.commit.interval.ms', 100);
// Set the offset store method to 'file'
$topicConf->set('offset.store.method', 'broker');
// Alternatively, set the offset store method to 'none'
// $topicConf->set('offset.store.method', 'none');
// Set where to start consuming messages when there is no initial offset in
// offset store or the desired offset is out of range.
// 'earliest': start from the beginning
$topicConf->set('auto.offset.reset', 'earliest');
$topic = $rk->newTopic($topicname, $topicConf);
// Start consuming partition 0
$topic->consumeStart(0, RD_KAFKA_OFFSET_STORED);
while (true) {
$message = $topic->consume(0, 120*10000);
switch ($message->err) {
case RD_KAFKA_RESP_ERR_NO_ERROR:
logs('MESSAGE>>>' . $message->payload);
$this->dd($message->payload);
break;
case RD_KAFKA_RESP_ERR__PARTITION_EOF:
logs( "No more messages; will wait for more\n");
break;
case RD_KAFKA_RESP_ERR__TIMED_OUT:
logs( "Timed out\n");
break;
default:
logs( $message->errstr());
//throw new \Exception($message->errstr(), $message->err);
break;
}
}
}
//下载文档保存
private function dd($requestId)
{
$app = '2020bd';
$dataEx = $this->common->getLog( array('requestId' => $requestId) );
logs('原数据ID>>>>'.json_encode($dataEx));
$fileSrcUrl = $dataEx['fileSrcUrl'];
$this->common->update(array('bdfile' => $fileSrcUrl, 'status' => 1, 'update_time' => time()), array('requestId' => $requestId));
//die();
//下载文件
$sPath = '/var/www/html/activity/storage/'.$app.'/';
if(empty($fileSrcUrl)){
logs('文件不存在,结束>>>>>');
die();
}
$fileTemp = explode('?', $fileSrcUrl);
$path_parts = pathinfo($fileTemp[0]);
$ext = strtolower($path_parts["extension"]);
logs('ext:'.$ext);
try{
logs('下载中>>>>'.$fileSrcUrl);
$filename = date('Ymd'). '_' .rand(10000, 99999).'_'.time();
$tmpfile = $filename . '.'.$ext;
$img = file_get_contents($fileSrcUrl);
//logs('下载成功'.var_dump($img));
$path1 = $sPath . $tmpfile;
//logs('下载中>>>>'.$path1);
$res = file_put_contents($path1, $img);
$_SERVER['SERVER_NAME'] = 'w.com';
if(is_https()){
$url = 'https://'.$_SERVER['SERVER_NAME'].'/storage/'.$app.'/'.$tmpfile;
}else{
$url = 'http://'.$_SERVER['SERVER_NAME'].'/storage/'.$app.'/'.$tmpfile;
}
//if($res){
$this->common->update(array('localFile' => $url), array('requestId' => $requestId));
logs('下载成功>>>>'.$url);
/*}else{
logs('下载失败');
}*/
}catch(Exception $e){
logs($e->getMessage());
}
logs($requestId.'校验结束');
echo 'ok';
}
//通过swoole处理,返回给前端
public function dealForUser()
{
$topicname = "getDocs";
logs('swoole start>>>'.$topicname);
//创建WebSocket Server对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
//监听WebSocket连接打开事件
$ws->on('Open', function ($ws, $request) {
$ws->push($request->fd, '');
});
//监听WebSocket消息事件
$ws->on('Message', function ($ws, $frame) {
log("Message: {$frame->data}\n") ;
$requestId = $frame->data;
$file = $this->getDocs($requestId);
if(!empty($file)){
$ws->push($frame->fd, "{$file}");
}
});
//监听WebSocket连接关闭事件
$ws->on('Close', function ($ws, $fd) {
//echo "client-{$fd} is closed\n";
});
$ws->start();
logs('swoole end>>>'.$topicname);
}
//读取文档内容
private function getDocs($requestId)
{
$app = '2020bd';
$dataEx = $this->common->get_one( array('requestId' => $requestId) );
if(!empty($dataEx)){
logs('DATA FIND>>>'.$dataEx['bdfile']) ;
return $dataEx['bdfile'];
}
return '';
logs($requestId.'校验结束');
echo 'ok';
}
前端测试代码
Document Test
文件上传框
显示进度条
上传成功后的返回内容
翻译测试:
<翻译》》》
下载文档》》》
下载
图片翻译内容>>>