在最近的一个项目中,遇到一个非常棘手的性能问题,场景是这样的:有PC端和手机端两个应用,用户在PC端上传的附件,如word,Excel,pdf等,当用户出差或不在电脑边上时,上传的附件在手机端能够打开预览。然后问题就来了,当在PC端上传的附件比较大,在手机端想要预览就比较慢,有时候甚至打不开,怎么解决这个性能问题呢?
一、多线程分段下载
分析:在预览的时候,将原文件切分成多分,使用多线程分段下载,这种方案可以快速的下载一个大文件到本地,然后直接打开预览,但是用户不同意将附件下载到手机上,因为手机内存有限,几个大附件就把内存占满了。
二、生成静态HTML文件
分析:在PC端上传附件的时候,启用异步线程,借助aspose组件,将附件转成成静态HTML文件,并放在单独的一个Tomcat下面,手机端预览附件的时候,直接访问这个Tomcat中的静态HTML,这样大大提升了预览速度,还不会占用手机内存,上传及预览的逻辑如下
转静态资源时,需要aspose组件的支持,因此需要在pom.xml中引入aspoxe相关组件
<dependency>
<groupId>aspose</groupId>
<artifactId>aspose.pdf</artifactId>
<version>17.2.0</version>
</dependency>
<dependency>
<groupId>aspose</groupId>
<artifactId>aspose-cells</artifactId>
<version>17.3.0</version>
</dependency>
<dependency>
<groupId>aspose</groupId>
<artifactId>aspose-slides</artifactId>
<version>17.2-jdk16</version>
</dependency>
<dependency>
<groupId>aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>17.3.0-jdk16</version>
</dependency>
转静态HTML文件核心逻辑如下
package com.mairuan.common.utils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aspose.cells.Workbook;
import com.aspose.slides.Presentation;
import com.aspose.words.Document;
import com.aspose.words.SaveFormat;
import com.hrsj.mcc.base.system.service.IFileOperateService;
public class TransferToHtmlThread extends Thread{
private static final Logger logger = LoggerFactory.getLogger(TransferToHtmlThread.class);
private String sourceFileRoot;
private String htmlPathRoot;
private List<Map<String, Object>> fileList;
private HttpServletRequest req;
private String tranFileType;
private String fontPath;
public TransferToHtmlThread(String sourceFileRoot, String htmlPathRoot, List<Map<String, Object>> fileList,
HttpServletRequest req, String tranFileType,String fontPath) {
this.sourceFileRoot = sourceFileRoot;
this.htmlPathRoot = htmlPathRoot + File.separator + "filedownload" + File.separator;
this.fileList = fileList;
this.req = req;
this.tranFileType = tranFileType;
this.fontPath = fontPath;
}
@Override
public void run() {
String sourceFilePath = "";
String htmlPathPath = "";
String filePath = "";
File file = null;
List<Map<String,String>> files = null;
InputStream is = null;
String fileName = null;
String fileType = null;
try {
files = new ArrayList<Map<String,String>>();
for(Map<String, Object> fileInfo: fileList) {
filePath = String.valueOf(fileInfo.get("file_path")); // 存储在DB的文件路径
fileName = String.valueOf(fileInfo.get("saved_name")); // 文件名称
fileType = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); // 获取文件后缀
List<String> tranTypeList = Arrays.asList(this.tranFileType.split(","));
if (!tranTypeList.contains(fileType)) { // 不属于xlsx,xls,docx,doc,pptx,ppt,pdf类型文件不转HTML
continue;
}
sourceFilePath = sourceFileRoot + File.separator + filePath; // 原文件路径
htmlPathPath = htmlPathRoot + filePath + ".html"; // 生成的HTML文件路径
is = getClass().getResourceAsStream("/Aspose.Total.Java.lic"); // License认证文件,如果没有此认证,最多只能转3页
//readContent(is);
if (StringUtils.startsWith(fileType, "doc")) {
com.aspose.words.License wordLicense = new com.aspose.words.License();
wordLicense.setLicense(is);
Document doc = new Document(sourceFilePath);
doc.save(htmlPathPath, SaveFormat.HTML);
}else if (StringUtils.startsWith(fileType, "xls")) {
com.aspose.cells.License excelLicense = new com.aspose.cells.License();
excelLicense.setLicense(is);
Workbook book = new Workbook(sourceFilePath);
book.save(htmlPathPath, com.aspose.cells.SaveFormat.HTML);
}else if (StringUtils.startsWith(fileType, "ppt")) {
com.aspose.slides.License pptLicense = new com.aspose.slides.License();
pptLicense.setLicense(is);
Presentation doc = new Presentation(sourceFilePath);
doc.save(htmlPathPath, com.aspose.slides.SaveFormat.Html);
}else if (StringUtils.startsWith(fileType, "pdf")) {
com.aspose.pdf.License pdfLicense = new com.aspose.pdf.License();
pdfLicense.setLicense(is);
com.aspose.pdf.Document.addLocalFontPath(fontPath);
com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(sourceFilePath);
pdfDoc.save(htmlPathPath, com.aspose.pdf.SaveFormat.Html);
}
htmlPathPath = htmlPathPath.replace(htmlPathRoot, ""); // 去掉配置文件指定路径
Map<String,String> map = new HashMap<String, String>();
map.put("id", String.valueOf(fileInfo.get("id")));
map.put("tranPath", htmlPathPath);
files.add(map);
}
if (CollectionUtils.isNotEmpty(files)) {
IFileOperateService fileOperateService = (IFileOperateService) SpringUtils.getBean("fileOperateService");
fileOperateService.updateTransFilePath(files);//保存转换后的静态资源文件路径到DB
}
}catch (Exception e) {
logger.error("Transfer File to Html fail,The message is:", e);
}finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
}
}
}
}
启异步线程将附件转成HTMl,核心逻辑如下
String tranFileType ="txt,xlsx,xls,docx,doc,pptx,ppt,zip,rar,img,jpg,pdf";
String filePath =/usr/mairuan/upload/";//PC端上传附件的存放路径
String tranFilePath ="/usr/mairuan/filepath/tran";//转换后静态资源文件的存放路径
List<Map<String,Object>> fileList = fileOperateService.uploadFile(multiFile, filePath);
String fontPath = req.getSession().getServletContext().getRealPath("/fonts/");//PDF转出来在服务端预览时可能会出现乱码,需要一些字体支持
new Thread(new TransferToHtmlThread(filePath,tranFilePath,fileList,req,tranFileType,fontPath)).start();
部署一个空的Tomcat,在Tomcat的server.xml中配置静态资源文件存放根路径,手机端预览附件时,直接请求这个Tomcat提供的服务即可
<Context docBase="/usr/mairuan/filepath/tran" path="/mairuan.mobile" reloadable="false"/>