导出文档为word pdf是一个很刚需的功能。导出的文档如果图片或者内容很大时,可能会比较耗时,所以可以异步去处理。
整体流程:
发起导出->检查权限,发送mq消息->导出服务接收消息开始处理->处理完成,结果写入redis ->客户端轮训获取导出结果
需要通用 html页面加载文档内容,使用webdriver 渲染页面,图片加载完成时,获取渲染完成的html页面
添加依赖
<dependency>
<!-- jsoup HTML parser library @ http://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.1</version>
</dependency>
<!--
word导出工具类,用于将html保存成word,支持图片操作,
需要破解版本,需要将破解的jar导入到本地maven仓库中,导出命令,需要将license.xml放到项目resources目录.
方案一 18.6版本:
https://blog.csdn.net/shadowkiss/article/details/80868472
mvn install:install-file -DgroupId=com.aspose -DartifactId=aspose-words -Dclassifier=jdk16 -Dversion=18.6 -Dpackaging=jar -Dfile=/Users/longer/Downloads/AsposeWord/lib/aspose-words-18.6-jdk16-crack.jar
mvn install:install-file -DgroupId=com.aspose -DartifactId=aspose-words -Dversion=18.9 -Dpackaging=jar -Dfile=aspose-words-18.6-jdk16.jar -Dclassifier=jdk16
方案二:18.9版本参考:https://download.csdn.net/download/ahgaoyong/10718416
mvn install:install-file -DgroupId=com.aspose -DartifactId=aspose-words -Dversion=18.9 -Dpackaging=jar -Dfile=/Users/longer/Downloads/AsposeWord/lib/aspose-words-18.9-jdk16.jar -Dclassifier=jdk16
-->
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>18.6</version>
<classifier>jdk16</classifier>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.43.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>3.141.59</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<version>3.141.59</version>
</dependency>
<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-core -->
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.3</version>
</dependency>
导出逻辑代码
@Slf4j
@Service
public class FsFileExportServiceImpl implements FsFileExportService {
@Autowired
private JedisCluster jedisCluster;
@Autowired
private OssFeignClient ossFeignClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
private Environment environment;
@Override
public void processFileExport(String message) {
log.info("-->process message:{}",message);
Date now = new Date();
FileExportMessage fileExportMessage = JSON.parseObject(message, FileExportMessage.class);
String key = Constants.EXPORT_TEMP_FILE_REDIS_KEY_PREFIX + fileExportMessage.getExportRequestId();
log.info("--->redis key:{}",key);
String tempStr = jedisCluster.get(key);
log.info("-->tempStr:{}",tempStr);
if ((System.currentTimeMillis() - fileExportMessage.getRequestTime()) > Constants.EXPORT_TEMP_FILE_FAST_FAIL_MILLS) {
log.info("快速失败:当前时间{}发出请求时间{}", System.currentTimeMillis(), fileExportMessage.getRequestTime());
//超时 快速失败
//查询redis
if (!StringUtils.isEmpty(tempStr)) {
//改变状态
jedisCluster.del(key);
}
return ;
}
log.info("--->start process{}"+tempStr);
//标记开始处理状态
ExportTempFile temp = JSON.parseObject(tempStr, ExportTempFile.class);
temp.setStatus(ExportStatus.PROCESSING.getKey());
temp.setStartProcessTime(now);
temp.setExportRequestId(fileExportMessage.getExportRequestId());
String res = jedisCluster.setex(key, Constants.EXPORT_TEMP_FILE_KEEP_SECONDS, JSON.toJSONString(temp));
log.info("--> setex res:{}"+res);
//调用导出接口
String html = getHtml(fileExportMessage.getFid(), fileExportMessage.getExportRequestId());
if(html==null){
temp.setStatus(ExportStatus.PROCESS_FAIL.getKey());
temp.setOverProcessTime(new Date());
jedisCluster.setex(key, Constants.EXPORT_TEMP_FILE_KEEP_SECONDS, JSON.toJSONString(temp));
log.info("get html is null");
return ;
}
// log.info("html {}", html);
byte[] fileBytes = null;
try {
fileBytes = getExportFileByteArray(temp.getExportFileType(), html);
} catch (Exception e) {
log.error("文件上传到云存储异常", e);
e.printStackTrace();
}
Map<String, String> map = Maps.newHashMap();
map.put("objectId", temp.getExportRequestId() + ExportFileType.getSuffix(temp.getExportFileType()));
RtData rtData = ossFeignClient.getFileExportUploadSafetyChain(map);
log.info("获取上传临时链:{}", JSON.toJSONString(rtData));
if (rtData.getCode() == RtCode.SUCCESS.getCode()) {
SafetyChainDto safetyChainDto = JSON.parseObject(JSON.toJSONString(rtData.getData()), SafetyChainDto.class);
//替换为服务内网地址
safetyChainDto.setSafetyChain(safetyChainDto.getSafetyChain().replace("https://xx.storage.cn/","http://xx:8080"));
log.info("->upload safetyChain:{}" + safetyChainDto.getSafetyChain());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//配置超时时间
HttpEntity requestBody = new HttpEntity(fileBytes, headers);
log.info("--->start upload");
restTemplate.put(safetyChainDto.getSafetyChain(), requestBody);
log.info("--->upload success");
//获取下载临时链
Date downloadStartDate = new Date();
log.info("开始获取下载临时链");
RtData downRtData = ossFeignClient.getFileExportDownloadSafetyChain(temp.getExportRequestId() + ExportFileType.getSuffix(temp.getExportFileType()));
log.info("获取下载临时链:{}", JSON.toJSONString(downRtData));
if (downRtData.getCode() == RtCode.SUCCESS.getCode()) {
SafetyChainDto downloadSafetyChainDto = JSON.parseObject(JSON.toJSONString(downRtData.getData()), SafetyChainDto.class);
temp.setStatus(ExportStatus.PROCESS_OVER.getKey());
temp.setOverProcessTime(downloadStartDate);
temp.setContainer(Constants.DOWNLOAD_CONTAINER_NAME);
temp.setDownloadSafetyChain(downloadSafetyChainDto.getSafetyChain());
temp.setSafetyChainEndTime(new Date((downloadStartDate.getTime()+3600000)));
String updateStatus = jedisCluster.setex(key, Constants.EXPORT_TEMP_FILE_KEEP_SECONDS, JSON.toJSONString(temp));
log.info("--> process over,set redis :{}",updateStatus);
return ;
} else {
log.error("获取下载临时链异常:{}" + JSON.toJSONString(downRtData));
jedisCluster.del(key);
return ;
}
} else {
log.error("获取上传临时链异常:{}" + JSON.toJSONString(rtData));
}
return ;
}
@Override
public String getHtml(String fid, String exportRequestId) {
WebDriver webDriver = WebDriverUtil.getWebDriverInstance();
log.info("是否获取到webDriver:{}",webDriver!=null);
try {
String url = environment.getProperty("export.web.simple.url").replace("$fid",fid).replace("$exportRequestId",exportRequestId);
log.info("url{}", url);
webDriver.get(url);
log.info("get url");
WebDriverWait wait = new WebDriverWait(webDriver, 30, 300);
log.info("wait");
wait.until(ExpectedConditions.jsReturnsValue("return imageComplete()"));
WebElement webElement = webDriver.findElement(By.xpath("/html"));//获取页面全部信息
String htmlText = webElement.getAttribute("outerHTML");
// 内容比较大(包含图片的base64),建议不要打印
// log.info("htmlText = " + htmlText);
return htmlText;
} catch (Exception e) {
log.error("获取html异常", e);
e.printStackTrace();
}
return null;
}
@Override
public byte[] getExportFileByteArray(Integer exportFileType, String html) throws Exception {
if (TextUtils.isEmpty(html)) {
return null;
}
Document document = new Document();
DocumentBuilder builder = new DocumentBuilder(document);
builder.insertHtml(html);
if (ExportFileType.WORD.getKey().equals(exportFileType)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream, SaveFormat.DOCX);
return outputStream.toByteArray();
} else if (ExportFileType.PDF.getKey().equals(exportFileType)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream, SaveFormat.PDF);
//获取临时链
return outputStream.toByteArray();
}
return null;
}
}
webdriver初始化,定时任务定时退出、更新webdriver
@Slf4j
public class WebDriverUtil {
private static List<WebDriverBean> list = new ArrayList<>();
private static List<WebDriverBean> webDriverList = Collections.synchronizedList(list);
private static Integer webdriverMaxNumber = 5;
/**
* 6小时替换一次webdriver
*/
private static Long webdriverMaxMillSeconds = 6 * 3600000L;
/**
* 缓冲时间60s
*/
private static Long webdriverBufferMillSeconds = 60000L;
public static WebDriver getWebDriverInstance() {
int i = new Random().nextInt(webdriverMaxNumber);
if(webDriverList.get(i)==null){
return updateOne(i);
}
//获取可用的列表
WebDriverBean webDriverBean = webDriverList.get(i);
if (webDriverBean.webDriver == null || (System.currentTimeMillis() - webDriverBean.getCreateTime()) > webdriverMaxMillSeconds) {
return updateOne(i);
}
return webDriverBean.getWebDriver();
}
public static void quitAll() {
for (WebDriverBean webDriverBean : webDriverList) {
WebDriver webDriver = webDriverBean.webDriver;
if (webDriver != null) {
webDriver.close();
webDriver.quit();
webDriver = null;
}
}
}
public synchronized static void init() {
int needAddSize = webdriverMaxNumber - webDriverList.size();
for (int i = 0; i < needAddSize; i++) {
ChromeOptions options = new ChromeOptions();
// 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败
options.addArguments("--headless");
options.addArguments("--no-sandbox");
WebDriverBean webDriverBean = new WebDriverBean(new ChromeDriver(options), System.currentTimeMillis());
webDriverList.add(webDriverBean);
}
}
public synchronized static void updateAll() {
for (int i = 0; i < webDriverList.size(); i++) {
WebDriverBean webDriverBean = webDriverList.get(i);
//定时任务田间缓冲时间 超过6小时+1分钟更新
if (webDriverBean.webDriver == null || (System.currentTimeMillis() - webDriverBean.getCreateTime()) > (webdriverMaxMillSeconds+webdriverBufferMillSeconds)) {
updateOne(i);
}
}
}
public synchronized static WebDriver updateOne(int index) {
if (webDriverList.get(index) != null) {
WebDriverBean webDriverBean = webDriverList.get(index);
if (webDriverBean.webDriver == null || (System.currentTimeMillis() - webDriverBean.getCreateTime()) > webdriverMaxMillSeconds) {
if (webDriverBean.webDriver != null) {
webDriverBean.webDriver.close();
webDriverBean.webDriver.quit();
}
ChromeOptions options = new ChromeOptions();
// 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败
options.addArguments("--headless");
options.addArguments("--no-sandbox");
webDriverBean = new WebDriverBean(new ChromeDriver(options), System.currentTimeMillis());
webDriverList.set(index, webDriverBean);
}
return webDriverList.get(index).getWebDriver();
}else{
ChromeOptions options = new ChromeOptions();
// 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败
options.addArguments("--headless");
options.addArguments("--no-sandbox");
WebDriverBean webDriverBean = new WebDriverBean(new ChromeDriver(options), System.currentTimeMillis());
webDriverList.add(webDriverBean);
return webDriverBean.getWebDriver();
}
}
static class WebDriverBean {
private WebDriver webDriver;
private long createTime;
public WebDriverBean(WebDriver webDriver, long createTime) {
this.webDriver = webDriver;
this.createTime = createTime;
}
public WebDriver getWebDriver() {
return webDriver;
}
public void setWebDriver(WebDriver webDriver) {
this.webDriver = webDriver;
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
}
}
返回给前端导出信息,前端拿取下载链接下载文件