java中动态生成word并转换为pdf

一、背景需求

系统平台需要做一个功能,在某菜单页面输入条件参数,查询生成一个pdf报告,既可在线查看,也可导出。
如图所示:
在这里插入图片描述

二、具体实现

1、引入依赖包,这里技术选用freemark、aspose和selenium,freemark用来生成word和动态数据填充,aspose用于把word转换为pdf,selenium用于web自动化写入echarts的图表。

      <!--freemarker 模板生成工具-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.23</version>
        </dependency>

        <!-- word转换工具-->
        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>21.11.0</version>
        </dependency>

       <!--selenium Web自动化工具-->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.141.59</version>
        </dependency>

2、word模板处理,将word模板另存为xml格式,可以将其放在项目同级目录下。
在这里插入图片描述
在这里插入图片描述

3、aspose凭证,aspose转换word为pdf默认是有水印的,使用凭证去除。

<!--word转换pdf破解去水印-->
<License>
    <Data>
        <Products>
            <Product>Aspose.Total for Java</Product>
            <Product>Aspose.Words for Java</Product>
        </Products>
        <EditionType>Enterprise</EditionType>
        <SubscriptionExpiry>20991231</SubscriptionExpiry>
        <LicenseExpiry>20991231</LicenseExpiry>
        <SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber>
    </Data>
    <Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>

在这里插入图片描述

4、编写工具类。

/**
 * word工具
 * author: xht
 * date: 2023-08-31
 */
public class WordUtil {

    /**
     * word生成方法
     *
     * @param TemplateName     模版名
     * @param root             填充的数据
     * @param GenerateFilename 生成文件名
     */
    public static void wordCreate(String TemplateName, Map<String, Object> root, String GenerateFilename) throws Exception {
        //模板存放目录
        String TemplatePath = getTempPath();

        Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
        //指定模板文件的来源目录
        cfg.setDirectoryForTemplateLoading(new File(TemplatePath));
        cfg.setDefaultEncoding("UTF-8");
        //设置错误的显示方式(日志)
        //在生产系统中:TemplateExceptionHandler.RETHROW_HANDLER 默认值
        //在开发HTML模板期间:TemplateExceptionHandler.HTML_DEBUG_HANDLER
        //在开发非HTML模板期间:TemplateExceptionHandler.DEBUG_HANDLER
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

        //获取模板文件
        Template temp = cfg.getTemplate(TemplateName);
        //合并模板和数据模型
        File file = new File(TemplatePath + "wpFiles");
        //如果文件夹不存在,则创建文件夹
        if (!file.exists()) {
            file.mkdirs();//多级目录
            // file.mkdir();//只创建一级目录
        }
        //Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(TemplatePath + "new-built/" + GenerateFilename)));//输出文件
        FileWriter out = new FileWriter(TemplatePath + "wpFiles" + File.separator + GenerateFilename);
        //Writer out = new OutputStreamWriter(System.out);//输出控制台
        //StringWriter out = new StringWriter();//输出为字符串,可作为接口动态返回

        temp.process(root, out);
        //可不手动调用
        out.flush();
    }

    /**
     * 获取模板路径
     * File.separator:兼容了win和linux的/和\
     * 不同项目的结构不同,复用请修改
     */
    public static String getTempPath() {
        //此路径与启动路径相关,在linux中以jar命令形式启动时,获取的路径为启动命令中输入的路径
        String analysisReportUrl = System.getProperty("user.dir");
        return analysisReportUrl + File.separator + "yb_temp" + File.separator;
    }

    /**
     * word转换pdf图片方法
     */
    public static void wordToPdf(String wordName, String pdfName) {
        // 验证License 若不验证则转化出的pdf文档会有水印产生
        if (!getLicense()) {
            return;
        }

        //模板存放目录
        String TemplatePath = getTempPath();

        FileOutputStream os = null;
        try {
            long old = System.currentTimeMillis();
            // 新建一个空白pdf文档
            File file = new File(TemplatePath + "wpFiles" + File.separator + pdfName);
            os = new FileOutputStream(file);
            // Address是将要被转化的word文档
            Document doc = new Document(TemplatePath + "wpFiles" + File.separator + wordName);
            // 全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF,
            doc.save(os, SaveFormat.PDF);
            // EPUB, XPS, SWF 相互转换
            long now = System.currentTimeMillis();
            // 转化用时
            System.out.println("pdf转换成功,共耗时:" + ((now - old) / 1000.0) + "秒");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.flush();
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取aspose水印凭证
     */
    public static boolean getLicense() {
        boolean result = false;
        try {
            InputStream is = Test.class.getClassLoader().getResourceAsStream(File.separator + "license.xml");
            License aposeLic = new License();
            aposeLic.setLicense(is);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

5、echarts图表生成策略
yml配置文件:

#selenium+ChromeDriver驱动加载位置,执行html文件位置
selenium:
  #请下载当前谷歌浏览器对应版本的驱动 https://googlechromelabs.github.io/chrome-for-testing/
  chromedriver:
    url: C:\chromedriver\chromedriver.exe
  #模板地址需要部署在服务器做web地址映射
  template:
    url: http://localhost:8848/Platform_nj/EchartsTemplate/echartsTemplate.html

模板文件html(注意jq和echarts的script文件引入):

<html>
<meta charset="UTF-8">
<meta http-equiv="content-type" content="text/html; charset=utf-8">

<head>
    <script src="scripts/jquery-1.8.2.min.js"></script>
    <script src="scripts/echarts-5.2.2/echarts.js"></script>
</head>

<body>
    <div id="rhEcharts" style="height:400px;width:550px"></div>
</body>
<script>
    var rhEcharts = null;
    $(function () {
        // 基于准备好的dom,初始化echarts实例
        rhEcharts = echarts.init(document.getElementById('rhEcharts'));
        // showImg()
    });
    function showImg(data) {
        //var option = {
        //    backgroundColor: '#ffffff',
        //    xAxis: {
        //        type: 'category',
        //        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
        //    },
        //    yAxis: {
        //       type: 'value'
        //    },
        //    series: [{
        //       data: [820, 932, 901, 934, 1290, 1330, 1320],
        //        type: 'line'
        //    }]
        //};
        // option && rhEcharts.setOption(option);
        // const picInfo = returnEchartImg();
        rhEcharts.setOption(data);
    }

    function returnEchartImg() {
        var url = rhEcharts.getDataURL({ backgroundColor: '#ffffff' });
        //清空绘画内容,清空后实例可用
        rhEcharts.clear();
        return url;
    }
</script>

</html>

echarts图表生成类:

/**
 * 后台加载Echarts并获取echarts图片(Base64)
 */
@Component
public class EchartsToPicUtil {

    private static final Logger logger = LoggerFactory.getLogger(EchartsToPicUtil.class);

    /**
     * chromeDriver.exe驱动位置
     */
    private static String CHROME_DRIVER_URL;

    /**
     * 模板位置
     */
    private static String TEMPLATE_URL;


    @Value("${selenium.chromedriver.url}")
    public void setChromeDriverUrl(String chromeDriverUrl) {
        CHROME_DRIVER_URL = chromeDriverUrl;
    }

    @Value("${selenium.template.url}")
    public void setTemplateUrl(String templateUrl) {
        TEMPLATE_URL = templateUrl;
    }

    /**
     * ChromeDriver驱动对象
     */
    private static WebDriver driver;

    @PostConstruct
    public void EchartsToPicUtil() {
        //创建driver对象
        EchartsToPicUtil.driver = getWebDriver();
        //获取html模板页面
        driver.get(TEMPLATE_URL);

        // 多线程模式,初始化对象池
//         ChromeDriverPool.preload();

        //shutdown时执行钩子,退出WebDriver防止内存泄露
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            if (logger.isDebugEnabled()) {
                logger.debug("开始执行shutdown退出操作");
            }
            if (driver != null) {
                driver.close();
                driver.quit();
                if (logger.isDebugEnabled()) {
                    logger.debug("退出执行成功");
                }
            }
            // 多线程模式,销毁对象池中的所有对象
            // ChromeDriverPool.destroy();
        }));
    }

    //采用单例模式
    public static WebDriver getWebDriverInstance() {
        return driver;
    }

    /**
     * 初始化ChromeDriver对象
     */
    public static void initWebDriver() {
        driver = getWebDriver();
    }

    /**
     * 获取图片base64
     * 使用多线程时,该方法必须加锁,否则会导致图片生成失败等问题
     */
    public static synchronized String getImgBase64(String option) {
        // WebDriver driver = null;
        try {
            // 多线程模式,执行CAS循环检查,尝试从对象池中获取对象
            // while ((driver = ChromeDriverPool.borrowChrome()) == null){
            //
            // }
            if (driver == null || driver.toString().contains("(null)")) {
                throw new NullPointerException("WebDriver已关闭,请初始化WebDriver");
            }

            if (logger.isDebugEnabled()) {
                logger.debug("当前系统环境为:" + System.getProperties().getProperty("os.name"));
            }

            if (logger.isDebugEnabled()) {
                logger.debug("当前driver获取到的html资源url:" + driver.getCurrentUrl());
            }
            //执行html,开始画图
            JavascriptExecutor js = (JavascriptExecutor) driver;
            js.executeScript("showImg(" + option + ")");

            String imgBase64 = js.executeScript("return returnEchartImg()").toString().replace("data:image/png;base64,", "");
            if (logger.isDebugEnabled()) {
                logger.debug("当前获取到的图片base64:" + imgBase64);
            }
            return imgBase64;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 多线程模式,归还对象到对象池中
            // ChromeDriverPool.returnChrome(driver);
        }
        return null;
    }


    /**
     * 初始化WebDriver
     * 设置私有化,防止外部获取webDriver对象
     *
     * @return
     */
    public static WebDriver getWebDriver() {
        // 设置ChromeDriver的路径加载驱动
        System.setProperty("webdriver.chrome.driver", CHROME_DRIVER_URL);
        //设置 chrome 的无头模式
        ChromeOptions chromeOptions = new ChromeOptions();
        //无头模式
        chromeOptions.setHeadless(true);
        //地址出现data:,
//        chromeOptions.addArguments("--user-data-dir=C:/Users/Administrator/AppData/Local/Google/Chrome/User Data/Default");
        //Chrome正在受到显示自动软件的控制  不提示语
        chromeOptions.addArguments("disable-infobars");
        //禁用gpu加速,没有gpu运行时会报错,但是并不会影响chrome正常运行
        chromeOptions.addArguments("--disable-gpu");
        //linux版本需要禁用沙盒,linux 下让Chrome在root权限下跑
        chromeOptions.addArguments("--no-sandbox");
        //启动一个 chrome 实例
        return new ChromeDriver(chromeOptions);
    }

    /**
     * 单根比率柱状图
     * 示例
     * @param title
     * @param nameStr
     * @param dataStr
     * @return
     */
    public static String getBarSimpleRatioEchartsOption(String title, String nameStr, String dataStr) {

        return String.format("{title:{text:'%s',left:'center'}," +
                        "animation:false," +
                        "xAxis:{type:'category',data:[%s],axisLabel:{show:true,fontSize:13,rotate:55 }}," + //,formatter:function(value){return value.split("").join("");}
                        "yAxis:{type:'value',axisLabel:{show:true,interval: 0,color: '#000',fontSize:8,formatter: '{value}%%'}}," +
                        "series:[{data:[%s],type:'bar',label:{show:true,position:'top',formatter:'{c}%%',fontSize:10}}]}",
                title, nameStr, dataStr);
    }
}    

6、写入数据填充模板。

    /**
     * 安全情况分析报告word生成
     * 这个方法在我自己项目中写在了服务层,这里只是举个模板参数的示例,根据具体情况调整
     */
    @Override
    public String createWord(ReqAcdAnalysis reqAcdAnalysis) throws Exception {
        //数据集
        Map<String, Object> dataMap = new HashMap<>();
        //自适应时间
        dataMap.put("varTime", "2023-01-01");
        //生成报告名称
        String fileName = "交通安全情况分析";
        //word生成
        WordUtil.wordCreate("AcdAnalysisTemp.xml", dataMap, fileName + ".doc");
        //生成pdf
        WordUtil.wordToPdf(fileName + ".doc", fileName + ".pdf");

        //图表测试
        String barLineSimpleEchartsOption = EchartsToPicUtil.getBarSimpleRatioEchartsOption("测试图", "['考勤']","[100]");
        dataMap.put("testPic", EchartsToPicUtil.getImgBase64(barLineSimpleEchartsOption));

        return fileName;
    }

对应的xml模板中的参数:
在这里插入图片描述
图片则替换对应的base64:
在这里插入图片描述

7、word生成接口和在线预览接口

/**
     * 安全情况分析报告word生成接口
     */
    @ApiOperation(value = "安全情况分析报告word生成接口")
    @PostMapping("/createWord")
    public Response createWord(@RequestBody ReqAcdAnalysis reqAcdAnalysis, HttpServletResponse response) throws Exception {
        //参数验证
        if (reqAcdAnalysis.getEtime() == null || reqAcdAnalysis.getStime() == null) {
            return Response.error("请完整输入时间");
        }
        //word生成服务
        String fileName = acdAnalysisService.createWord(reqAcdAnalysis);

        //将word和pdf上传至mongodb(存储方式随意)
        File wordFile = new File(WordUtil.getTempPath() + "wpFiles" + File.separator + fileName + ".doc");
        File pdfFile = new File(WordUtil.getTempPath() + "wpFiles" + File.separator + fileName + ".pdf");
        Map map = new HashMap<>();
        map.put("wordUpload", upload(wordFile));
        map.put("pdfUpload", upload(pdfFile));
        return Response.success(map);
    }
     /**
     * 预览安全情况分析报告pdf
     * (在前端调用的时候写在word生成接口的回调里面,保证先后顺序)
     */
    @GetMapping("/previewPDF")
    public void previewPDF(@RequestParam String fileName, HttpServletResponse response) throws Exception {
        try {
            if (fileName == null) {
                throw new Exception("没有找到文件");
            }
            File file = new File(WordUtil.getTempPath() + "wpFiles" + File.separator + fileName);
            FileInputStream fileInputStream = new FileInputStream(file);
            response.setHeader("Content-Type", "application/pdf");
            OutputStream outputStream = response.getOutputStream();
            IOUtils.write(IOUtils.toByteArray(fileInputStream), outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

三、注意事项

如果部署到linux上大概率会出现字体对应不上然后乱码的情况,需要将开发环境中(例如windows)的字体上传到linux中并且加载。
在这里插入图片描述
解决方案:

##查看linux目前的所有字体
fc-list
##查看Linux目前的所有中文字体
fc-list :lang=zh
##将上面字体全部拷贝到linux下的字体目录
mkdir /usr/share/fonts/win
cp /local/src/fonts/* /usr/share/fonts/win
##执行安装字体命令
cd /usr/share/fonts
sudo mkfontscale
sudo mkfontdir 
sudo fc-cache -fv
##执行命令让字体生效
source /etc/profile
##如果安装失败,可以考虑修改字体权限
chmod 755 *.ttf

参考链接:https://developer.aliyun.com/article/1331552

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值