导出文档为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;
        }
    }
}

返回给前端导出信息,前端拿取下载链接下载文件