💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥
🏆本篇文章阅读大约耗时5分钟。
⛳️motto:不积跬步、无以千里
📋📋📋本文目录如下:🎁🎁🎁
目录
前言
小伙伴们大家好,最近开发过程中遇到一个比较折磨的小问题,正如文章的标题所述,是在java项目中将 html 文件转换为图片中遇到的,具体的异常和排查后文会提及,也可以直接跳到最后查看解决方案(不一定能百分百解决各位的问题,但是可以尝试下)
功能概要
需要返回给前端一个图片的下载链接,图片的要求是按照特殊格式排版的,并且图中会有很多自定义变量,基本每张图片的变量填充都不一样
原始方案:使用 html 搭建出基本的图片结构,再将 html 转换为 图片
1. 使用 Java2DRenderer 工具实现 html 转换为图片
2. 使用 Themeleaf 工具实现 html 中待填充的变量灵活替换
问题乍现:按照开发流程在本地开发完成之后,测试没什么问题,接着发布到测试环境,出现了以下问题(测试环境服务器属于 Centos linux)
1.服务器上生成的图片大小与本地生成的图片相差很多,本地 50kb的,服务器生成的只有 7kb左右
2.服务器生成的图片质量较差,并且字体明显不对,大概如下(左边本地,右边服务器)
问题排查
1.排除代码问题,代码都是同一套
2.环境问题:服务器环境和本地差异很大
2.1 代码中尝试指定渲染图片时使用指定的字体,经测试不生效
2.2 服务器安装指定字体,服务器新增字体后,经测试还是没解决
2.3 html 源码中指定使用字体,经测试不生效
2.4 代码中生成图片的工具,切换为别的sdk后本地测试没问题,服务器上测试问题依然存在
寻找方案
1.市面上各种 ai 查询解决方案和替代方案,一开始都徒劳无果
2.各大博客寻找相似问题未果,可能这种问题遇到的人比较少
3.摆烂,就这样了
解决方案
多亏别人提醒,回想之前的方案都是将html使用代码转换成一张图片,这里面会经过服务器配置去渲染,问题出现在服务器渲染的时候,那么何不直接换种方式,已经有html了,那直接通过代码调用服务器的应用程序打开html文件,然后直接截图一张不就跳过上面那种问题了,实测一下,问题解决
大概就是使用 Headless Chrome 将 HTML 打开,然后代码控制直接截图
实现步骤
1.服务器安装浏览器和想要使用的字体
1.1服务器更新安装工具
sudo yum update (更新成功就不用执行这条下载命令了sudo yum install epel-release -y)
1.2 安装 Chromium 浏览器
sudo yum install chromium -y
1.3 安装指定字体
目标使用用 Arial 字体,需要将该字体的文件上传到服务器(可自行查询服务器安装字体教程),也可以看一下之前服务器的字体都是什么,除自行安装的 Arial 就是 DejaVu 字体了,图片上生成的字符使用的应该就是这种字体了,看着很别扭
2. 将获取图片方法 封装为一个工具类
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.spi.ServiceException;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
/**
* @author benbenhuang
* @date 2025年10月16日 22:03
*/
@Slf4j
@Component
public class HtmlUtil {
/**
* 使用 Headless Chrome 将 HTML 渲染为 PNG 并上传
*/
public void convertHtmlToImageAndUpload(String htmlContent, String fileName) {
Path htmlPath = null;
Path outputPath = null;
try {
//生成临时 HTML 文件
htmlPath = Files.createTempFile("page_", ".html");
Files.write(htmlPath, htmlContent.getBytes(StandardCharsets.UTF_8));
//生成临时输出图片路径
outputPath = Files.createTempFile("screenshot_", ".png");
//组装 Chrome 命令
String chromePath = detectChromeBinary();
ProcessBuilder pb = new ProcessBuilder(
chromePath,
"--headless",
"--disable-gpu",
"--no-sandbox",
"--hide-scrollbars",
"--window-size=350,550",
"--screenshot=" + outputPath.toAbsolutePath(),
htmlPath.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
Process process = pb.start();
//打印 Chrome 输出日志(方便排错)
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
reader.lines().forEach(line -> log.debug("[chrome] {}", line));
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Chrome render failed, exit code = " + exitCode);
}
//转换为 Base64
byte[] imageBytes = Files.readAllBytes(outputPath);
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
// //上传到 OSS
// ossclient.uploadPic(base64Image, fileName);
} catch (Exception e) {
throw new ServiceException("Render HTML to image failed");
} finally {
// 清理临时文件
try {
if (htmlPath != null) Files.deleteIfExists(htmlPath);
if (outputPath != null) Files.deleteIfExists(outputPath);
} catch (IOException ignored) {}
}
}
/**
* 检测 Chrome 可执行路径
*/
private String detectChromeBinary() {
String[] candidates = {
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", // macOS 主要路径
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", // macOS Canary版本
"/Applications/Chromium.app/Contents/MacOS/Chromium", // macOS Chromium路径
"/usr/bin/google-chrome", // Linux 路径
"/usr/bin/chromium-browser", // Linux 路径
"/usr/local/bin/chrome", // 可能的自定义路径
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" // Windows 路径
};
for (String path : candidates) {
if (Files.exists(Paths.get(path))) {
return path;
}
}
return "google-chrome";
}
}
3. 这一个工具类别的就没了,看下入参,一个是 html 的代码字符串(已经处理过变量填充问题),一个是 fileName,因为本地是需要将图片上传到oss的,当然,也可以调整为输出到本地,,只需要调整一下最后的图片上传操作即可
检测可执行路径方法就是找到该浏览器到的可执行路径,里面添加了不同系统该软件默认安装后的可执行路径
整体就是通过浏览器打开临时html文件,截图保存即可
经测试生成的图片没有问题了,图片大小和本地大差不差,字体也是目标字体
章末
文章到这里就结束了~
往期推荐 > > >