前言

前面有写博文,利用phantomjs实现网页快照,分别适配windows,linux,mac,好不容易搞好了,结果把项目镜像放到docker运行容器的时候,居然失效了。docker内部不也是linux内核么,居然会出问题。找了半天,发现是docker中很多库不兼容可能,不能运行phantomjs命令。想了很多办法,比如更换类库,但是已更换会出现连锁反应,其他一个一个都要换,明显不行。很久才解决,在这里记录一下,帮助以后需要的朋友。

开始

由于docker容器中的类库不兼容直接运行phantomjs命令,所以我们选择用docker创建一个phantomjs容器,在项目容器中调用这个phantomjs容器来运行phantomjs相关功能。

1.首先要创建phantomjs容器,我这里运用的是docker-compose启动容器,所以写在了docker-compose.yml文件中,下文列出,有关使用docker-compose创建容器的可以移步【docker】——docker-compose部署springboot镜像项目

docker run -d -p 4444:4444 --shm-size=256m -e TZ=Asia/Shanghai selenium/standalone-chrome

2.phantomjs容器创建好之后使用docker ps命令是这样的

dockerdesktop快照_快照缩略图

对应4444端口。

那么我们上面所说的在自己的项目中调用 phantomjs驱动,可以直接写phantomjs容器映射的ip:4444,但是这样显然不合适,因为这个docker容器部署在哪里是会变的,所以我们可以使用docker run --link命令来是两个容器之间可以相互通信,我们就可以写成phantomjs:4444的格式了,这样无论docker容器部署在哪里,只要我们在启用项目容器的同时创建一个phantomjs容器就可以了。

下面附上我的docker-compose文件,既可以同时创建所需要的容器,也可以用link命令将其联系起来

version: "3"  
services: 
  phantomjs:
    image: selenium/standalone-chrome
    container_name: phantomjs
    privileged: true
    ports:
      - "4444:4444"
    volumes:
      - /opt/phantomjs/config:/opt/config/
    shm_size: 256m
  store:
    image: registry.xxx.aliyuncs.com/xxx
    container_name: store
    restart: always
    privileged: true
    ports:
      - "9527:80"
    volumes:
      - /opt/store/config:/opt/config/
    links:
      - phantomjs:phantomjs

这样我们就可以在需要的项目中获取到phantomjs驱动啦!

3.项目代码

下面的代码都是在前面的博文【PhantomJs】——利用phantomjs实现网页快照的两种方式的基础上添加的,如有需要可自便

在之前的博文中有写过判断操作系统的工具类,这里我们新增一个判断是否是docker环境的方法:

public static boolean isDocker() {
        if (isInDocker != null) {
            return isInDocker;
        }
        try {
            final File dockerinit = new File("/.dockerinit");
            if (dockerinit.exists()) {
                return isInDocker = true;
            }
        } catch (Exception ignore) {

        }

        try {
            final File dockerenv = new File("/.dockerenv");
            if (dockerenv.exists()) {
                return isInDocker = true;
            }
        } catch (Exception ignore) {

        }
        return isInDocker = false;
    }

之后在phantomjsUtils中新增docker环境是获取网页快照缩略图的方法

dockerdesktop快照_dockerdesktop快照_02

private static String getBase64ForDocker(String url, int w, int h) throws MalformedURLException {
        RemoteWebDriver driver = new RemoteWebDriver(
                new URL("http://phantomjs:4444/wd/hub"),
                DesiredCapabilities.chrome());
        driver.get(url);
        driver.manage().window().setSize(new Dimension(1900,900));
        //设置页面加载超时
        driver.manage().timeouts().pageLoadTimeout(20, TimeUnit.SECONDS);
        //设置查询组件等待时间
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        File file = driver.getScreenshotAs(OutputType.FILE);
        driver.quit();
        return ImageUtils.thumbnailImage(file, w, h, scale);
    }

快照缩略图工具类

原本的博文中并未对快照进行缩略,这里顺便附上图片缩略的工具类

public class ImageUtils {

    public static final Boolean DEFAULT_FORCE = false;
    private static final String DOT = ".";

    /**
     * <p>Title: thumbnailImage</p>
     * <p>Description: 依据图片路径生成缩略图 </p>
     * @param imgFile    图片
     * @param w            缩略图宽
     * @param h            缩略图高
     * @param force        是否强制依照宽高生成缩略图(假设为false,则生成最佳比例缩略图)
     */
    public static String thumbnailImage(File imgFile, int w, int h, boolean force){
        if(imgFile.exists()){
            try {
                // ImageIO 支持的图片类型 : [BMP, bmp, jpg, JPG, wbmp, jpeg, png, PNG, JPEG, WBMP, GIF, gif]
                String types = Arrays.toString(ImageIO.getReaderFormatNames());
                String suffix = null;
                // 获取图片后缀
                if(imgFile.getName().contains(DOT)) {
                    suffix = imgFile.getName().substring(imgFile.getName().lastIndexOf(".") + 1);
                }// 类型和图片后缀所有小写,然后推断后缀是否合法
                if(suffix == null || !types.toLowerCase().contains(suffix.toLowerCase())){
                    Logger.error("Sorry, the image suffix is illegal. the standard image suffix is {}." + types);
                    return null;
                }
                Logger.debug("target image's size, width:{}, height:{}.",w,h);
                Image img = ImageIO.read(imgFile);
                if(!force){
                    // 依据原图与要求的缩略图比例,找到最合适的缩略图比例
                    int width = img.getWidth(null);
                    int height = img.getHeight(null);
                    if((width*1.0)/w < (height*1.0)/h){
                        if(width > w){
                            h = Integer.parseInt(new java.text.DecimalFormat("0").format(height * w/(width*1.0)));
                            Logger.debug("change image's height, width:{}, height:{}.",w,h);
                        }
                    } else {
                        if(height > h){
                            w = Integer.parseInt(new java.text.DecimalFormat("0").format(width * h/(height*1.0)));
                            Logger.debug("change image's width, width:{}, height:{}.",w,h);
                        }
                    }
                }
                BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
                Graphics g = bi.getGraphics();
                g.drawImage(img, 0, 0, w, h, Color.LIGHT_GRAY, null);
                g.dispose();
                // 将图片保存在原文件夹并加上前缀
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ImageIO.write(bi, "jpg", baos);
                byte[] bytes = baos.toByteArray();
                String base64 =  org.apache.commons.codec.binary.Base64.encodeBase64String(bytes).trim();
                base64 = base64.replaceAll("\n", "").replaceAll("\r", "");
                return "data:image/png;base64," + base64;
            } catch (IOException e) {
                Logger.error("generate thumbnail image failed.",e);
            }
        }else{
            Logger.warn("the image is not exist.");
        }
        return null;
    }
}