今天遇到一个需求,是前端给后台一个网址,后台需要返回这个网址的快照回去,刚接手的时候一脸懵,了解了一下,是项目中有个播放视频或者直播的区域,需要一张图片来作为封面图,但是如果专门去存的话不方便,也不灵活。
既然有需求,那就只能找方法了,网上各种翻阅,方法不多,也不算少,但是很杂,很多工具及代码都有不足之处,有的比较慢,有的是会出现可视化的工具框,比如IFrame,显然不好,最后确定了用phantomjs,也应该是用的最多的。
由于phantomjs是一个工具,所以对于不同的操作系统是不通用的,所以要下载各个系统的工具包来做适配,在phantomjs官网镜像下载phantomjs工具包。
我这里选择了2.1.1的版本。
下载好之后解压并对名字做修改先暂时放在F盘
这里项目用的是springboot项目,其他框架下面内容请自行调整
由于phantomjs工具包比较大,四个加起来也有将近200M,放在项目中显然是不合适的,所以我们选择在yml文件中配置路径,将工具包放在服务器本地路径中
phantomJs:
windows: F:/
mac: /opt/
linux: /opt/
这里就是配置了windows,mac,linux环境下phantomjs工具包所需要放置的路径。
接口放回结果还是通过的ResultBean类,这里简化只有code和data两个属性:
public class ResultBean {
private Integer code;
private T data;
public Integer getCode() {return code;}
public void setCode(Integer code) {this.code = code;}
public T getData() {return data;}
public void setData(T data) {this.data = data;}
public ResultBean() {}
public ResultBean(Integer code, T data) {
this.code = code;
this.data = data;
}
@Override
public String toString() {
return "ResultBean{" +"code='" + code + '\'' +", data=" + data +'}';
}
}
这里写一个工具类来区分不同的操作系统,来进行不同系统phantomjs的选择
public class PlatformUtils {
private static final Logger logger = LoggerFactory.getLogger(PlatformUtils.class);
private static final String OS_NAME = System.getProperty("os.name").toLowerCase();
private static final String OS_ARCH = System.getProperty("os.arch").toLowerCase();
private static final String OSARCH = "64";
public static boolean isWindows() {
return OS_NAME.contains("windows");
}
public static boolean isMac() {
return OS_NAME.contains("mac");
}
public static boolean isLinux() {
return OS_NAME.contains("linux");
}
public static boolean is64OsArch() {
return OS_ARCH.contains(OSARCH);
}
}
下面我们开始这两种方式的演示与实现:
org.seleniumhq.selenium
selenium-java
2.45.0
com.codeborne
phantomjsdriver
1.2.1
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* @description: 快照工具类
* @author: WangZhiJun
* @create: 2019-10-25 14:36
**/
public class SnapShotUtils {
private static Logger logger = LoggerFactory.getLogger(SnapShotUtils.class);
private static final String EQUAL = "=";
public static String getDriverPath(String path) {
if (PlatformUtils.isWindows()) {
logger.info("当前系统:Windows");
return path + "phantomjs/windows/bin/phantomjs.exe";
}else {
if (PlatformUtils.isLinux()) {
logger.info("当前系统:Linux");
if (PlatformUtils.is64OsArch()) {
logger.info("当前系统:64位");
return path + "phantomjs/linux-x86_64/bin/phantomjs";
} else {
return path + "phantomjs/linux-i686/bin/phantomjs";
}
} else {
logger.info("当前系统:Mac");
return path + "phantomjs/macosx/bin/phantomjs";
}
}
}
public static String getBase64(String url, String path) {
//设置必要参数
DesiredCapabilities dcaps = new DesiredCapabilities();
//ssl证书支持
dcaps.setCapability("acceptSslCerts", true);
//截屏支持
dcaps.setCapability("takesScreenshot", true);
//css搜索支持
dcaps.setCapability("cssSelectorsEnabled", true);
//js支持
dcaps.setJavascriptEnabled(true);
//驱动支持(第二参数表明的是你的phantomjs引擎所在的路径)
dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
getDriverPath(path));
//创建无界面浏览器对象
PhantomJSDriver driver = new PhantomJSDriver(dcaps);
//设置隐性等待(作用于全局)
driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
//打开页面
driver.get(url);
String base64 = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BASE64);
logger.info("成功获取URL快照,快照大小:"+imageSize(base64));
return base64;
}
/**
* 通过图片base64流判断图片等于多少字节
* image 图片流
*/
private static Integer imageSize(String image) {
// 1.需要计算文件流大小,首先把头部的data:image/png;base64,(注意有逗号)去掉。
String str = image.substring(22);
//2.找到等号,把等号也去掉
int equalIndex = str.indexOf("=");
if (str.indexOf(EQUAL) > 0) {
str = str.substring(0, equalIndex);
}
//3.原来的字符流大小,单位为字节
int strLength = str.length();
//4.计算后得到的文件流大小,单位为字节
return (strLength - (strLength / 8) * 2);
}
public static void main(String[] args) {
System.out.println(getBase64("https://www.baidu.com","F:/"));
}
}
net.coobird
thumbnailator
0.4.8
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* @description: 快照工具类
* @author: WangZhiJun
* @create: 2019-10-25 14:36
**/
public class PhantomUtils {
private static Logger logger = LoggerFactory.getLogger(PhantomUtils.class);
private static final Integer BITNUM = 64;
private static final String BLANK = " ";
private static final String EQUAL = "=";
/**
* 功能描述 关闭命令
*
* @param process 1
* @param bufferedReader 1
* @author CosmosRay
* @date 2019/4/13
*/
public static void close(Process process, BufferedReader bufferedReader) throws IOException {
if (bufferedReader != null) {
bufferedReader.close();
}
if (process != null) {
process.destroy();
}
}
/**
* 功能描述 通过URL获取该URL快照base64
* @param url 连接
* @return java.lang.String
* @author CosmosRay
* @date 2019/4/13
*/
public static String getBase64(String url, String path) throws IOException {
if(StringUtils.isBlank(url)){
return null;
}
String[] pathArray;
if (PlatformUtils.isWindows()) {
pathArray = getWindowsPath(path);
} else {
if (PlatformUtils.isLinux()) {
if (PlatformUtils.is64OsArch()) {
pathArray = getLinuxPath(64, path);
} else {
pathArray = getLinuxPath(32, path);
}
} else {
pathArray = getMacPath(path);
}
}
return printUrlScreen(url, pathArray);
}
/**
* 功能描述 windows系统服务器路径地址
*
* @return java.lang.String[]
* @author CosmosRay
* @date 2019/4/13
*/
private static String[] getWindowsPath(String path) {
String[] pathArray = new String[3];
//图片临时保存路径
pathArray[0] = path + "phantomjs/windows";
//phantomjs主程序路径
pathArray[1] = path + "phantomjs/windows/bin/phantomjs";
//phantomjs参数js路径
pathArray[2] = path + "phantomjs/windows/examples/rasterize.js";
return pathArray;
}
/**
* 功能描述
*
* @return java.lang.String[]
* @author CosmosRay
* @date 2019/4/13
*/
private static String[] getLinuxPath(int osArch, String path) {
String[] pathArray = new String[3];
if (osArch == BITNUM) {
//图片临时保存路径
pathArray[0] = path + "phantomjs/linux-x86_64";
//phantomjs主程序路径
pathArray[1] = path + "phantomjs/linux-x86_64/bin/phantomjs";
//phantomjs参数js路径
pathArray[2] = path + "phantomjs/linux-x86_64/examples/rasterize.js";
} else {
//图片临时保存路径
pathArray[0] = path + "phantomjs/linux-i686";
//phantomjs主程序路径
pathArray[1] = path + "phantomjs/linux-i686/bin/phantomjs";
//phantomjs参数js路径
pathArray[2] = path + "phantomjs/linux-i686/examples/rasterize.js";
}
return pathArray;
}
/**
* 功能描述 MAC系统
* @return java.lang.String[]
* @author CosmosRay
* @date 2019/4/13
*/
private static String[] getMacPath(String path) {
String[] pathArray = new String[3];
//图片临时保存路径
pathArray[0] = path + "phantomjs/macosx";
//phantomjs主程序路径
pathArray[1] = path + "phantomjs/macosx/bin/phantomjs";
//phantomjs参数js路径
pathArray[2] = path + "phantomjs/macosx/examples/rasterize.js";
return pathArray;
}
/**
* 通过图片base64流判断图片等于多少字节
* image 图片流
*/
private static Integer imageSize(String image) {
// 1.需要计算文件流大小,首先把头部的data:image/png;base64,(注意有逗号)去掉。
String str = image.substring(22);
//2.找到等号,把等号也去掉
int equalIndex = str.indexOf("=");
if (str.indexOf(EQUAL) > 0) {
str = str.substring(0, equalIndex);
}
//3.原来的字符流大小,单位为字节
int strLength = str.length();
//4.计算后得到的文件流大小,单位为字节
return (strLength - (strLength / 8) * 2);
}
/**
* 功能描述 执行cmd命令
*
* @param imagePath 图片名称绝对路径
* @param url URL地址
* @return java.lang.String
* @author CosmosRay
* @date 2019/4/13
*/
private String cmd(String imagePath, String url, String[] pathArray) {
return pathArray[1] + BLANK + pathArray[2] + BLANK + url + BLANK + imagePath;
}
/**
* 功能描述
*
* @param url 连接URL
* @return void 无
* @author CosmosRay
* @date 2019/4/13
*/
private static String printUrlScreen(String url, String[] pathArray) throws IOException {
if(StringUtils.isBlank(url)){
return null;
}
//图片路径
String imagePath = pathArray[0] + "/menu.png";
//Java中使用Runtime和Process类运行外部程序
PhantomUtils phantomTools2 = new PhantomUtils();
Process process = Runtime.getRuntime().exec(phantomTools2.cmd(imagePath, url, pathArray));
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String rs;
while ((rs = reader.readLine()) != null) {
close(process, reader);
}
File file;
BufferedInputStream in;
byte[] ret = null;
file = new File(imagePath);
if (file.exists()) {
in = new BufferedInputStream(new FileInputStream(file));
Thumbnails.Builder extends InputStream> builder = Thumbnails.of(in).size(256, 256);
BufferedImage bufferedImage = builder.asBufferedImage();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", baos);
ret = baos.toByteArray();
}
String base64 = null;
if (ret != null) {
//转换成base64串
String pngBase64 = Base64.encodeBase64String(ret).trim();
//删除 \r\n
pngBase64 = pngBase64.replaceAll("\n", "").replaceAll("\r", "");
base64 = "data:image/png;base64," + pngBase64;
logger.info("成功获取URL快照,快照大小:"+imageSize(base64));
}
return base64;
}
public static void main(String[] args) throws IOException {
System.out.println(getBase64("https://baidu.com", "F:/"));
}
}
@RestController
@RequestMapping()
public class ScreenshotController {
@Autowired
private ScreenshotService screenshotService;
@GetMapping("/snap")
public ResultBean getScreenshot(@RequestParam(value = "url") String url){
ResultBean resultBean = new ResultBean();
try {
resultBean.setCode(1);
resultBean.setData(screenshotService.getScreenshot(url));
} catch (Exception e) {
e.printStackTrace();
}
return resultBean;
}
}
public interface ScreenshotService {
String getScreenshot(String url) throws Exception;
}
@Service
public class ScreenshotServiceImpl implements ScreenshotService {
@Value("${phantomJs.mac}")
private String phantomJsMac;
@Value("${phantomJs.windows}")
private String phantomJsWin;
@Value("${phantomJs.linux}")
private String phantomJsLinux;
@Override
public String getScreenshot(String url) throws IOException {
String diverPath;
if (PlatformUtils.isMac()) {
diverPath = phantomJsMac;
} else if (PlatformUtils.isWindows()) {
diverPath = phantomJsWin;
} else {
diverPath = phantomJsLinux;
}
//第一种
//return SnapShotUtils.getBase64(url, diverPath);
//第二种
return PhantomUtils.getBase64(url, diverPath);
}
}