前言
记录一个需求实现,要求在服务端对项目专题页面首屏截图并在此基础上增加元素生成分享海报。
整理了已下三种实现思路,并证实可用。
方案一 (适用于简单且固定页面)
思路
Thumbnails 、Canvas等图像处理工具按页面构图。直接开画。
方案二 (适用于复杂且少量模板页面)
思路
- 第一步 使用 模板语言 生成对应专题的静态页面
- 第二步 静态html 转换成图片
- jtidy html 转换为标准 html
- xhtmlrenderer 标准 html 转换为图片。(模板语言生成的html需要补全为标准html才能转换图片)
案列
- pom.xml
<!-- 模板语言 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.28</version> </dependency> <!-- jtidy --> <!-- https://mvnrepository.com/artifact/net.sf.jtidy/jtidy --> <dependency> <groupId>net.sf.jtidy</groupId> <artifactId>jtidy</artifactId> <version>r938</version> </dependency> <!-- xhtmlrenderer --> <!-- https://mvnrepository.com/artifact/org.docx4j/xhtmlrenderer --> <dependency> <groupId>org.docx4j</groupId> <artifactId>xhtmlrenderer</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>core-renderer</artifactId> <version>R8</version> </dependency>
- 模板
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head lang="en"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </meta> <title>测试</title> </head> <body> ${name},你好。${message} <#assign qq="QQ1012421396"> QQ:${qq} <#assign info={"address":"广东省深圳市", "mobile":"17665208888"}> 地址:${info.address} 电话:${info.mobile} <div style="background:#000; color:#FFF;width: 1000px;height: 1000px"> 背景为黑色 </div> <#if success==true> 你已通过实名认证 <#else > 你未通过实名认证 </#if> <#list goodsList as goods> ${goods_index+1} 商品名称:${goods.name} 价格:${goods.price} </#list> </body> </html>
- Java
public static void main(String[] args) throws IOException, TemplateException { //1.创建配置对象 Configuration configuration = new Configuration(Configuration.getVersion()); //2.设置模板文件所在的路径 configuration.setDirectoryForTemplateLoading(new File("G:\\workCode\\freemarker-demo\\src\\main\\resources")); //3.设置模板文件使用的字符集。一般就是 utf-8 configuration.setDefaultEncoding("utf-8"); //4.加载一个模板,创建一个模板对象 Template template = configuration.getTemplate("test.ftl"); //5.创建一个模板使用的数据集,可以是 pojo 也可以是 map。一般是 Map HashMap map = new HashMap(); map.put("name", "测试"); map.put("message", "测试数据"); map.put("success", true); Map goods1 = new HashMap(); goods1.put("name", "苹果"); goods1.put("price", 5.8); Map goods2 = new HashMap(); goods2.put("name", "香蕉"); goods2.put("price", 2.5); List goodsList = new ArrayList(); goodsList.add(goods1); goodsList.add(goods2); map.put("goodsList", goodsList); // 文件地址 String url1 = "G:\\workCode\\freemarker-demo\\demo.html"; String url2 = "G:\\workCode\\freemarker-demo\\demo1.html"; String url3 = "G:\\workCode\\freemarker-demo\\demo1.png"; //6.创建一个 Writer 对象,一般创建一 FileWriter 对象,指定生成的文件名 FileWriter fileWriter = new FileWriter(new File(url1)); //7.调用模板对象的 process 方法输出文件 template.process(map, fileWriter); //8.关闭流 fileWriter.close(); // 标准化html htmlCovertTohtml(url1, url2); convertToImage(url2,url3,3000, 1000); } // 转换成图片 public static void convertToImage(String inputFilename,StringoutputFilename, Integer widthImage,Integer heightImage) throws IOException { System.out.println("Calling convertToImage inputFilename=" + inputFilename + " outputFilename=" + outputFilename); final File f = new File(inputFilename); final Java2DRenderer renderer = new Java2DRenderer(f, widthImage, heightImage); final BufferedImage img = renderer.getImage(); final FSImageWriter imageWriter = new FSImageWriter(); imageWriter.write(img, outputFilename); System.out.println("Done with rendering"); } // 转换成标准HTML public static void htmlCovertTohtml(String sourceFilename,String targetFilename) { Tidy tidy = new Tidy(); tidy.setInputEncoding("UTF-8"); tidy.setOutputEncoding("UTF-8"); // 每行的最多字符,如果为0,不自动换行 tidy.setWraplen(0); // 是否保持属性中的空白字符 tidy.setLiteralAttribs(true); // 需要转换的文件,当然你也可以转换URL的内容 FileInputStream in; FileOutputStream out; try { in = new FileInputStream(sourceFilename); out = new FileOutputStream(targetFilename); // 输出的文件 tidy.parse(in, out); // 转换完成关闭输入输出流 out.close(); in.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
注意 :
1. 转换图片失败请确认 Java2DRenderer 的引入是否支持。
2. 如果出现中文乱码,请确认当前系统是否包含使用字体包,或者是否支持当前字体。
方案三 (适用于全场景页面)
思路
Headless Chrome 服务器调起浏览器访问相同页面进行截屏
使用准备
- Chrome浏览器 版本要和驱动一致或者相近
- ChromeDriver驱动 下载地址:http://chromedriver.storage.googleapis.com/index.html
Linux安装步骤
一、安装chrome
1. 下载安装脚本,在下载目录中,执行以下命令,将安装脚本下载到本地
wget https://intoli.com/install-google-chrome.sh
2.然后授予可执行权限
chmod 755 ./install-google-chrome.sh
3.执行脚本
./install-google-chrome.sh
安装脚本会自动下载、安装chrome(合适的版本),并且目前两个系统中,所缺少的依赖,都会被安装。
4.测试安装结果执行命令
google-chrome-stable --no-sandbox --headless --disable-gpu --screenshot https://www.baidu.com/
如果在当前文件夹中出现screenshot.png 则安装成功
5.查看linux 安装的谷歌浏览器版本
#google-chrome --version
Google Chrome 83.0.4103.106
二、安装chromedriver
1. 在https://npm.taobao.org/mirrors/chromedriver中找到对应版本的chromedriver
2.下载文件包
wget https://npm.taobao.org/mirrors/chromedriver/75.0.3770.90/chromedriver_linux64.zip
3.解压文件包
unzip chromedriver_linux64.zip
解压后,在/opt/google/ 会多出一个文件chromedriver
4.修改chromedriver 执行权限
chmod +x chromedriver
5.连接文件
ln -s /opt/google/chromedriver /usr/bin/chromedriver
安装完成
chrome path = /opt/google/chrome/chrome
chrome driver path = 解压后路径
案列
-
pom.xml
<!-- selenium 包含了对Chrome操作工具 --> <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>3.141.59</version> </dependency>
-
Java
public class ScanWebImg { // 谷歌浏览器地址 private static String DRIVER_PATH = "D:\\systemSoftware\\chromedriver.exe"; // 谷歌驱动地址 private static String CHROM_PATH = "C:\\ProgramFiles\\Google\\Chrome\\Application\\chrome.exe"; public static void screenshot(String screenshotUrl,String filePath) { // 设置驱动地址 System.setProperty("webdriver.chrome.driver",DRIVER_PATH); ChromeOptions options = new ChromeOptions(); // 设置谷歌浏览器exe文件所在地址 options.setBinary(CHROM_PATH); // 这里是要执行的命令,如需修改截图页面的尺寸,修改--window-size的参数即可 // --hide-scrollbars 隐藏滚动条 --no-sandbox linux root账户需要增加此参数 options.addArguments( "--headless", "--disable-gpu", "--hide-scrollbars", "--window-size=414,730", "--ignore-certificate-errors" ); WebDriver driver = new ChromeDriver(options); // 访问页面 driver.get(screenshotUrl); // 页面等待渲染时长,如果你的页面需要动态渲染数据的话一定要留出页面渲染的时间,单位默认是秒 Wait<WebDriver> wait = new WebDriverWait(driver, 30); wait.until(new ExpectedCondition<WebElement>() { @Override public WebElement apply(WebDriver d) { //等待前台页面中 id为“kw”的组件渲染完毕,后截图 //若无需等待渲染,return true即可。 不同页面视情况设置id return d.findElement(By.className("list")); //return d.findElement(By.id("app")); } }); // 获取到截图的文件 File screenshotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); if ((screenshotFile != null) && screenshotFile.exists()) { //截取到的图片存到本地 FileOutputStream out = null; FileInputStream in = null; try { in = new FileInputStream(screenshotFile); out = new FileOutputStream(filePath); byte[] b = new byte[1024]; while (true) { int temp = in.read(b, 0, b.length); // 如果temp = -1的时候,说明读取完毕 if (temp == -1) { break; } out.write(b, 0, temp); } screenshotFile.delete(); } catch (Exception e) { //TODO异常处理 } } System.out.println("========done============="); // 记得要释放资源 driver.close(); } public static void main(String[] args) { String filePath = "D:\\image\\"+"1"+".jpg"; // 要截取的页面 String url = "url"; screenshot(url,filePath); }
后记
最终因为专题模板过多样式差距较大选择第三种方式。