大家好,欢迎来到IT知识分享网。
纯java项目后端进行HTML转图片
公司有个需求是在小程序将订单信息按一定样式整理后转成图片。客户点击按钮下载后可以将图片保存,并可以直接在微信群里分享。
由于时间紧迫的关系,这里前后端并行尝试方案,前端通过canvas方案手动绘制,后端则由我这边进行摸索。
原本想把各种尝试过的方案都记录下来,但是完成后现在一查,原来已经有人尝试过了,这里就直接他的上图。
图片来自https://blog.csdn.net/xuechangchun007/article/details/120936431
图中说明过的html2img及cssbox都尝试过,但是样式或多或少都有问题,因为这些存Java实现的基本都是通过g2d模拟绘制来生成图片,其样式和直接在浏览器上看到的肯定会存在差异。
另外我还尝试过github上的开源项目openhtmltopdf,结果都差不多。
需要没有差异的话,就必须使用类似selenium这种无头浏览器,模拟用户查看网站并截图的方案。
基于wkhtmltox
虽然我最先考虑过的是selenium,但是由同事推荐,最终我选择了基于wkhtmltox这个方案,这里记录下。
wkhtmltopdf核心点是使用webkit浏览器进行,基本上也是模拟用户在浏览器查看图片并截图保存的方案。
- window按照只需要https://wkhtmltopdf.org/downloads.html下载对应的window包
然后使用命令调用即可,如下载在D:/1/,那么进入到D:/1/wkhtmltox/bin/目录,打开命令行执行
// 格式
// wkhtmltoimage程序地址 来源地址 保存地址
.\wkhtmltoimage.exe http://baidu.com 1.png
即可将将百度截图为图片保存到当前目录:
对于一个html文件也是一样,若将Html文件移入到bin目录,那么
.\wkhtmltoimage.exe 1.html 1.png
- 部署到linux需要根据服务器的系统版本下载对应的包并在服务器上安装即可
方法可参考https://www.cnblogs.com/agang-php/p/14966285.html
编码部分
根据需求,我的方案是
- 创建对应样式的html模板
- 通过freemarker填充参数,获取html字符串。
- 保存字符串为html文件到临时文件目录
- 调用wkhtmltoimage生成图片到本地临时目录
- 读取图片为byte数组
- 删除临时文件
其中html字符串转图片部分代码可以参考如下
import java.io.File; import java.io.FileWriter; import java.nio.file.Files; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import lombok.extern.slf4j.Slf4j; /** * HTML转图片的默认实现,采用wkhtmltox实现 * <p> * wkhtmltox基于webkit浏览器将HTML转为图片,需要一个程序来执行,对于window来说是一个无需安装的exe * 对于linux来说需要进行安装 * */ @Slf4j @Service public class Html2ImageBizImpl implements Html2ImageBiz { /** * wkhtmltox的使用命令,对window来讲是exe的地址,对于linux是安装的命令地址(如/usr/local/bin/wkhtmltoimg) */ private String wkcmd; /** * 用于存放临时文件的文件夹,由于wkhtmltox为系统插件独立进程,只能作为文件操作后再按流进行读取 */ private String tmpFilePath; @Override public byte[] stringToPng(String htmlString) { if (EmptyUtil.isEmpty(wkcmd)) { throw new UnsupportedOperationException("未配置wkhtmltox的使用命令参数,功能不可用"); } if (EmptyUtil.isEmpty(tmpFilePath)) { throw new UnsupportedOperationException("未配置HTML转图片的临时文件目录,功能不可用"); } Assert.hasLength(htmlString, "参数错误,HTML文件没有内容"); String htmlFileName = null; String pngFileName = null; try { // 存HTML文件 htmlFileName = saveHtml2File(htmlString); // 转为PNG,获取其文件名 pngFileName = html2Png(htmlFileName); // 将PNG读取为bytes return readFile2ByteArray(pngFileName); } finally { // 删除临时文件 if (htmlFileName != null) { removeTmpFile(htmlFileName); } if (pngFileName != null) { removeTmpFile(pngFileName); } } } /** * 将HTML内容写入文件 * * @param htmlString HTML文件内容 */ private String saveHtml2File(String htmlString) { String fileName = getRandomFileName(".html"); try (FileWriter fileWriter = new FileWriter(new File(fileName))) { fileWriter.write(htmlString); } catch (Exception e) { throw new RuntimeException(e); } log.info("保存HTML成功: {}", fileName); return fileName; } /** * 使用wkhtmltox将HTML转为PNG(都在硬盘上操作 * * @param htmlFileName HTML文件路径 */ private String html2Png(String htmlFileName) { String pngFileName = getRandomFileName(".png"); StringBuilder cmd = new StringBuilder(); cmd.append(wkcmd); cmd.append(" "); cmd.append(htmlFileName); cmd.append(" "); cmd.append(pngFileName); try { Process proc = Runtime.getRuntime().exec(cmd.toString()); proc.getInputStream(); proc.waitFor(); } catch (Exception e) { throw new RuntimeException(e); } log.info("转PNG成功: {}, {}", htmlFileName, pngFileName); return pngFileName; } /** * 读取文件二进制数组 * * @param fileName */ private byte[] readFile2ByteArray(String fileName) { File file = new File(fileName); try { return Files.readAllBytes(file.toPath()); } catch (Exception e) { throw new RuntimeException(e); } } /** * 删除临时文件 * * @param fileName 文件名 */ private void removeTmpFile(String fileName) { Assert.hasLength(fileName, "临时文件参数为空"); File file = new File(fileName); if (file.isDirectory()) { throw new IllegalArgumentException("不能删除文件夹"); } try { Files.delete(file.toPath()); log.info("删除临时文件成功:{}", fileName); } catch (Exception e) { log.warn("删除临时文件失败", e); } } /** * 构建随机的文件地址(采用IdGenerator) * * @param suffix 如.html */ private String getRandomFileName(String suffix) { return tmpFilePath + "/" + "h2p" + IdGenerator.nextId() + suffix; } }
这里的wkcmd和tmpFilePath是两个通过可配置的参数,如通过spring的@Value注入或者直接写死。
另外由于部分代码类如IdGenerator,用到了公司项目的包,这里删去引用。直接保存的话,需要替换为近似的方法。
END;
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/28440.html