背景
- 最近的公司有一个需求,需要在线预览office(xls ppt doc)文件, 之前使用了微软的office在线预览服务,https://view.officeapps.live.com/op/view.aspx?src=文档地址(需要是文件的真实路径,如果使用文件流的话,url的后缀必须是文件的类型,如:https://view.officeapps.live.com/op/view.aspx?src=文档地址.doc), 这样就可以在线预览,但是该网站有文件大小限制(xls小于5m, ppt doc 小于10m),因此需要自己实现一种预览服务或者使用第三方的预览服务,第三方预览服务使用方式类似上述微软,需要收费,个人实现方式也有很多,这里只说我实现的方式。
实现在线预览服务
- 用户上传office文件,
- 搭建可以转换office软件的服务, 或者直接在项目服务器上直接搭建转换服务
- 使用soffice命令转换office文件为pdf文件
- 更改header头,输出pdf文件内容
实现步骤
- 安装openoffice或liboffice, 有的系统已经预装该软件,使用soffice --help测试是否安装,如果没有安装,使用以下命令安装
centos: yum install libreoffice ubuntu: apt-get install libreoffice - 编写php脚本, 这样使用tp3.2搭建的转换服务
public function viewFile() { define('APPLICATION_PATH', dirname(dirname(__DIR__))); define('FILE_UPLOAD_PATH', APPLICATION_PATH . '/Runtime/Uploadfile/'); define('FILE_CONVERT_PATH', APPLICATION_PATH . '/Runtime/Convertfile/'); $fileUrl = I('file_url'); if (empty($fileUrl)) { return $this->ajaxReturn(array('status' => 'failed', 'message' => 'parameters is missing!')); } $urlInfo = pathinfo($fileUrl); $httpUrlInfo = parse_url($fileUrl); switch ($urlInfo['extension']) { case 'doc': $result = $this->getConvertViewFile($fileUrl, $httpUrlInfo, $urlInfo); if ($result['status'] == 'failed') { return $this->ajaxReturn($result); } else { header('Content-type:application/pdf'); echo $result['message']; } exit(); case 'pdf': $result = $this->getViewFile($fileUrl, $httpUrlInfo); if ($result['status'] == 'failed') { return $this->ajaxReturn($result); } else { header('Content-type:application/pdf'); echo $result['message']; } exit(); case 'txt': $result = $this->getViewFile($fileUrl, $httpUrlInfo); if ($result['status'] == 'failed') { return $this->ajaxReturn($result); } else { header('Content-type:text/plain'); echo $result['message']; } exit(); default: return $this->ajaxReturn(array('status' => 'failed', 'message' => '不合法的文件格式!')); } return $this->ajaxReturn(array('status' => 'failed', 'message' => 'url不合法!')); } private function getConvertViewFile($fileUrl, $httpUrlInfo, $urlInfo) { $result = $this->getViewFile($fileUrl, $httpUrlInfo); if ($result['status'] == 'failed') { return $result; } $fileKey = md5($result['message']); $convertFile = FILE_CONVERT_PATH . $fileKey . '.pdf'; // 获取缓存文件 if (file_exists($convertFile)) { $fileData = file_get_contents(FILE_CONVERT_PATH . $fileKey . '.pdf'); return array('status' => 'success', 'message' => $fileData); } else { // 保存文件 $day = date('Y-m-d', time()); $path = FILE_UPLOAD_PATH . $day . '/'; is_dir($path) OR mkdir($path, 0777, true); $originFileUrl = $path . $fileKey . '.' . $urlInfo['extension']; if (!file_exists($originFileUrl)) { $end = file_put_contents($originFileUrl, $result['message']); if ($end === false) { return array('status' => 'failed', 'message' => '文件保存失败!'); } } // 转换文件 $isSuccess = $this->convertFile2Pdf($originFileUrl); if (!$isSuccess) { return array('status' => 'failed', 'message' => '文件转换失败!'); } // 再次获取文件 if (file_exists($convertFile)) { $fileData = file_get_contents($convertFile); return array('status' => 'success', 'message' => $fileData); } else { return array('status' => 'failed', 'message' => '文件处理失败!'); } } } private function convertFile2Pdf($originFileUrl) { try { $shellPath = APPLICATION_PATH . '/Runtime/shell/'; $shellName = 'new_convert.sh'; $convertFile = FILE_CONVERT_PATH; $execute = $shellPath . $shellName . " {$originFileUrl} {$convertFile} "; $execute = 'bash ' . $execute . ">> " . $shellPath . "newconvert_log 2>&1"; // 阻塞转换文件 system($execute, $status); if ($status === 0) { return true; } else { return false; } } catch (\Exception $e) { return false; } } private function getViewFile($fileUrl, $httpUrlInfo) { try { if (empty($httpUrlInfo['scheme'])) { $url = 'http://' . $fileUrl; $fileData = file_get_contents($url); if (empty($fileData)) { $url = 'https://' . $fileUrl; $fileData = file_get_contents($url); } } else { $fileData = file_get_contents($fileUrl); } if (empty($fileData)) { return array('status' => 'failed', 'message' => '文件找不到!'); } else { return array('status' => 'success', 'message' => $fileData); } } catch (\Exception $e) { return array('status' => 'failed', 'message' => $e->getMessage()); } }
3.bash转换脚本
#!/bin/bash
startTime=$(date "+%Y-%m-%d %H:%M:%S")
echo "---------------------------------startTime: ${startTime}------------------------------------------------"
echo "sourceFile: ${1}"
echo "outFile: ${2}"
echo "----------------------------------------------start--------------------------------------------------"
soffice --headless --invisible --convert-to pdf:writer_pdf_Export ${1} --outdir ${2} "-env:UserInstallation=file:///tmp/LibreOffice_Conversion_${USER}"
echo "----------------------------------------------end----------------------------------------------------"
endTime=$(date "+%Y-%m-%d %H:%M:%S")
echo "---------------------------------endTime: ${endTime}------------------------------------------------"
- 访问转换服务
转换服务域名/home/index/viewFile?file_url=你的文件网址
遇到的问题
一。运行bash脚本报错如下
[Java framework] Error in function createSettingsDocument (elements.cxx).
javaldx failed!
解决办法: 查看/etc/passwd中的apache用户, passwd的apache默认是
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
可以发现apache的用户目录是/usr/share/httpd,使用一下命令更改/usr/share/httpd权限
chown -R apache:apache /usr/share/httpd
这样就可以解决上述问题
二。转换文件时中文乱码的问题,一般中文会变成方框
一般在linux服务器上运行soffice命令时,会在用户目录下面的隐藏目录.config中产生libreoffice配置文件,例如你登录的用户是user, 用户目录是/home/user, 这个路径为/home/user/.config/libreoffice,root用户是/root/.config/libreoffice,解决上述问题,解决办法有两种:
//将C:\Windows\Fonts下的宋体,即simsun.ttc(我为了防止意外,将Fonts目录下所以文件复制过去了,你随意)复制 linux机器的/usr/share/fonts中, 更改文件权限为,
sudo chmod 644 simsun.ttc
//更新字体缓存:
sudo fc-cache -fv
上述方法这样会破坏系统的默认字体设置,特别是Ubuntu,由于宋体的优先级高于文泉驿,系统会优先抓取宋体,默认漂亮的光滑矢量字体会变成点矩阵的宋体,因为上述更改是将所有用户的字体设置进行了更改
第二种解决办法:
刚才我们在上面说了。在执行soffice文件,会在用户目录下产生libreoffice缓存配置文件, 我们只需要将上面的window下的文字文件复制到用户的libreoffice文件中即可,步骤如下
1. cat /etc/passwd 查看apache用户目录为/usr/share/httpd, 但是apache是不可登录用户, 默认如下
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
2. 暂时修改为可登陆用户,如下
apache:x:48:48:Apache:/usr/share/httpd:/bin/bash
3. 切换apache用户
su apache
4. 在apache随便一个文件下执行soffice命令,下面的源文件,你自己去创建或者上传到服务器一个即可
soffice --convert-to pdf xx.doc(源文件) --outdir 输出文件目录
5. 这样在/usr/share/httpd下就会有个.config文件,里面会有libreoffice目录, 进入/usr/share/httpd/.config/libreoffice/4/user, 在该目录下创建fonts目录, 将C:\Windows\Fonts下的文件复制到该目录中
6. 更改文件权限
chown -R apache:apache /usr/share/httpd
7. 退出apache用户,然后将其改为不可登录用户
exit
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
上述方法只会对apache用户的字体进行更改,不会影响其他用户
二。执行bash脚本时,命令无响应,无返回
原因: 这是因为soffice命令在一个用户时只能启动一个进程,
解决办法:1. 先查看服务器进程,将正在转换soffice命令杀死,如下
先使用kill -9 进程号, 将上述程序全部杀死,然后在执行soffice命令时添加"-env:UserInstallation=file:///tmp/LibreOffice_Conversion_${USER}",每次执行创建临时用户即可, 在上面的步骤脚本时已经添加了该参数
解决办法2. 上面是创建临时用户,还可以使用创建临时目录,脚本如下:
#!/bin/sh
dir=`mktemp -d`
if [ -d $dir ]; then
HOME=$dir
export HOME
libreoffice $@
rm -Rf $dir
fi
php脚本:
exec("/usr/bin/libreoffice_run_many --headless --invisible --convert-to pdf ./general.pptx 2>&1", $output, $return);
print_r($output);