最近接到一个把 ppt 转成 pdf 的需求,和客户端约定 ppt 最大需要50M,用户上传ppt 文件到服务器,服务器经过一系列的处理搞定转化工作,最后客户端在将成品的pdf文件下载下来,写个博客记录下。 首先是技术选型:服务端有个 libreoffice 插件,可以完成 ppt 转 pdf 的功能,和客户端简单的测试了下,发现效果不错,来两张转化的效果图:
可以看到效果还是相当不错的,源文件是我从网上随便 down 的,具体链接可以参考我下面的附录;
接下来是架构实现:客户端上传到服务端,文件上传嘛!服务端上传文件到客户端,表单上传搞定。框架现有的 upload 类轮子,我这吭哧吭哧改了一顿代码之后,自测通过,pass!(这里给自己留了个大坑,留待下一个模块慢慢说)
服务端处理,考虑到 php 处理一个服务是 php-fpm 进程搞定的,直接上手简直爆炸,把一个最大可能50M,最小也得好几M的文件放在 php-fpm 里面处理,一来可能导致当前的 php-fpm 进程阻塞,二来严重阻碍其他 request 请求的顺利执行,三来我们都晓得计算开启 php-fpm 进程数量很重要一个参考因素是 php-fpm 进程占服务器内存20-30m来算的;所以直接使用php-fpm进程搞定就被我们给否了,但是这个服务又不需要太多的并发流量; 考虑再三,使用redis list 实现一个队列,manageProcess 快速实现一个多进程任务,抢占式的 pop redis 队列,上传时创建全局 uuid 并使用 redis 记录处理的进度; 按照事先的接口设计实现代码,服务器设置下跨域,本地一个 form 表单上传完美搞定。
第二天,与客户端联调,本想在前端小姐姐面前秀一波,没想到第一步就卡了,upload 请求收不到,什么情况,查下nginx,发现请求来了,但是我没有正确反馈,wtf??? 细问下了解到原来客户端用的是nodejs,根本没有 form 表单一说(呼应开头的那个坑),好吧找到问题,那就现场撸代码,php 有个神奇的 php://input 自动获得http body 数据流,我直接强行读 php://input 然后写文件不就搞定了,perfect 解决! 还有其他字体的坑,不是本文核心就不一一列举了,贴下核心代码:
while (true) {
$this->flushTimestamp($pid);
$this->nowUuid = null;
$fileInfo = $redis->rpop(self::prefix);
if(!$fileInfo) {
sleep(1);
continue;
}
$output = '';
$uuid = $fileInfo['uuid'];
$this->nowUuid = $uuid;
$savePath = $fileInfo['savePath'];
$ext = $fileInfo['ext'];
$filename = $savePath . $uuid . ".$ext";
$res = [
'status' => 2,
'uuid' => $uuid,
'msg' => 'wait file sync'
];
$redis->set(self::prefix.'_'.$uuid,$res);
if(!file_exists($filename)) {
// 等待 NAS 服务同步数据
$this->flushTimestamp($pid);
sleep(3);
if(!file_exists($filename)) {
$res = [
'status' => -1,
'uuid' => $uuid,
'msg' => 'file not found,please try again!'
];
$redis->set(self::prefix.'_'.$uuid,$res);
$fileInfo['__REASON__'] = 'file not exist';
Fend_Log::write($fileInfo,$this->config['logName']);
continue;
}
}
if(md5_file($filename) != $fileInfo['filemd5']) {
$res = [
'status' => -2,
'uuid' => $uuid,
'msg' => 'file damage!'
];
$redis->set(self::prefix.'_'.$uuid,$res);
$fileInfo['__REASON__'] = 'file damage in nas!';
Fend_Log::write($fileInfo,$this->config['logName']);
continue;
}
$res = [
'status' => 2,
'uuid' => $uuid,
'msg' => 'process transforming...'
];
$redis->set(self::prefix.'_'.$uuid,$res);
exec("/usr/bin/libreoffice --invisible --convert-to pdf --outdir $savePath ".$filename,$output);
$res = [
'status' => 1,
'execOut' => $output,
'msg' => 'transform complete',
'path' => $savePath,
'name' => $uuid. ".$ext",
'uuid' => $uuid,
'ext' => $ext,
'fileName' => $filename
];
$redis->set(self::prefix.'_'.$uuid,$res);
}