pom配置

<dependency>
    <groupId>io.github.fanyong920</groupId>
    <artifactId>jvppeteer</artifactId>
    <version>1.1.3</version>
</dependency>

后端获取截图文件实例

实现原理,后台代码修改浏览器的文件默认保存位置,前端将截图的文件保存在默认位置,保存后后端读取指定位置的目录获取保存的截图文件

package com.thsware.framework.web.rest.util;

import com.ruiyun.jvppeteer.core.Puppeteer;
import com.ruiyun.jvppeteer.core.browser.Browser;
import com.ruiyun.jvppeteer.core.browser.BrowserFetcher;
import com.ruiyun.jvppeteer.core.page.JSHandle;
import com.ruiyun.jvppeteer.core.page.Page;
import com.ruiyun.jvppeteer.options.LaunchOptions;
import com.ruiyun.jvppeteer.options.LaunchOptionsBuilder;
import com.ruiyun.jvppeteer.options.Viewport;
import com.ruiyun.jvppeteer.options.WaitForSelectorOptions;
import com.thsware.framework.service.DocumentObjectService;
import com.thsware.framework.service.ModelSnapshotService;
import com.thsware.framework.service.dto.DocumentObjectDTO;
import com.thsware.framework.service.dto.ModelSnapshotDTO;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @program: file-manage
 * @description: Puppeteer 工具类
 * windows 下能自动下载chromiume浏览器
 * centos 自动下载后需安装以下依赖
 * yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y
 * yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y
 **/
@Component
public class PuppeteerUtils {
    private static final Logger log = LoggerFactory.getLogger(PuppeteerUtils.class);

    private static DocumentObjectService documentObjectService;

    private static ModelSnapshotService modelSnapshotService;

    private static long executeDone = 0;

    private static long executeTotal = 0;

    //线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(4);

    private static String previewUrl;

    private static String idPlaceholder;

    private static String serverPort;

    private static Browser browser;

    private static String AUTO_SCREEN_SHOT_PATH = "filebase-id-auto-screen-shot\\";

    private static String SYSTEM_ARCHITECTURE = "x86_64";

    private static final String SYSTEM_ARCHITECTURE_X86_64 = "x86_64";

    private static String fileSavePath;// 文件存储路径

    public PuppeteerUtils(DocumentObjectService documentObjectService, ModelSnapshotService modelSnapshotService, @Value("${preview.url}") String previewUrl, @Value("${preview.id-placeholder}") String idPlaceholder, @Value("${server.port}") String serverPort, @Value("${upload.basePath}") String fileSavePath) {
        PuppeteerUtils.documentObjectService = documentObjectService;
        PuppeteerUtils.modelSnapshotService = modelSnapshotService;
        PuppeteerUtils.previewUrl = previewUrl;
        PuppeteerUtils.idPlaceholder = idPlaceholder;
        PuppeteerUtils.serverPort = serverPort;
        PuppeteerUtils.fileSavePath = fileSavePath;
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.startsWith("win")) {
//            AUTO_SCREEN_SHOT_PATH =  "C:\\" + AUTO_SCREEN_SHOT_PATH;
            AUTO_SCREEN_SHOT_PATH =  AUTO_SCREEN_SHOT_PATH;
        } else if (osName.startsWith("mac")) {
            AUTO_SCREEN_SHOT_PATH =  "\\" + AUTO_SCREEN_SHOT_PATH;
        } else {
            // unix or linux
            AUTO_SCREEN_SHOT_PATH =  AUTO_SCREEN_SHOT_PATH;
            // 判断是否未ARM64架构,如果时ARM64架构,则结果为aarch64,否则为x86_64
            SYSTEM_ARCHITECTURE = execCmd("uname -a | awk -F \" \" '{print $(NF-1)}'");
        }
        log.info("当前puppeteer运行环境-OS:{}, 架构:{}", osName, SYSTEM_ARCHITECTURE);
    }

    /**
     * 初始化Puppeteer
     */
    @PostConstruct
    public static Browser initBrowser() {
        try{
            ArrayList<String> argList = new ArrayList<>();
            //自动下载,第一次下载后不会再下载
            BrowserFetcher.downloadIfNotExist(null);
            LaunchOptions options = new LaunchOptionsBuilder().withArgs(argList).withHeadless(false).build();
            // --headless 无需界面显示
            argList.add("--headless");
            argList.add("--no-sandbox");
            argList.add("--disable-extensions");
            argList.add("--disable-setuid-sandbox");
            // 设置浏览器占用内存
            argList.add("--shm-size=3gb");
            browser = Puppeteer.launch(options);
            return browser;
        }catch (Exception ex){
            log.error("初始化puppeteer失败,错误原因:", ex);
            return null;
        }
    }

    public static String execCmd(String cmd){
        try{
            //在特定路径下执行cmd命令
            Process process = Runtime.getRuntime().exec(cmd);
            //获得输入流
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf8"));
            //读取返回信息
            String reply = null;
            StringBuilder result = new StringBuilder();
            while ((reply = in.readLine()) != null) {
                result.append(reply).append("\n");
            }
            return result.toString();
        }catch (UnsupportedEncodingException e) {
            log.error("execCmd时UnsupportedEncodingException,错误原因:{}", e);
        } catch (IOException e) {
            log.error("execCmd时IOException,错误原因:{}", e);
        }
        return "";
    }


    /**
     * 对外只提供异步调用,控制chrome页签数量,仅支持X86架构服务器运行
     * @param fileBaseInfoId
     */
    public static void asyncAutoScreenShot(String fileBaseInfoId){
//        if(!SYSTEM_ARCHITECTURE_X86_64.equals(SYSTEM_ARCHITECTURE)){
//            // 如果当前架构为ARM架构,则无法本机调用puppeteer,转到专门的X86服务器上执行
//            try{
//                asyncAutoScreenShot(fileBaseInfoId);
//            }catch (Exception ex){
//                log.error("远程调用reConvertPreviewImgByFileBaseInfoId时出错,错误fileBaseInfoId,错误原因:{}", fileBaseInfoId, ex);
//            }
//            return;
//        }
        executeTotal ++;
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                PuppeteerUtils.autoScreenShot(fileBaseInfoId);
            }
        });
    }

    /**
     * 按原始文件id自动获取对应族文件的六面截图
     * @param fileBaseInfoId 原始文件id, 非族库预览id
     */
    private static String autoScreenShot(String fileBaseInfoId){
        log.info("开始自动截图,fileBaseInfoId:{},当前第{}个任务,总共{}个任务", fileBaseInfoId, executeDone, executeTotal);
        executeDone++;
        String imgPath = fileSavePath + "\\" + AUTO_SCREEN_SHOT_PATH + fileBaseInfoId +"\\";

        String osName = System.getProperty("os.name").toLowerCase();
        // win是\\ linux是/
        if (osName.startsWith("win")) {
            imgPath = imgPath.replace("/", "\\");
        } else if (osName.startsWith("mac")) {
            imgPath = imgPath.replace("/", "\\");
        } else {
            imgPath = imgPath.replace("\\", "/");
        }

//        if(browser.isConnected()){
//            browser.close();
//            browser = initBrowser();
//        }
        try {
            Page page = browser.newPage();
            // 此处需要通过页面加载模型,通过fileBaseInfoId表的id加载。
            String url = previewUrl.replace(idPlaceholder, String.valueOf(fileBaseInfoId));
            url = Base64Utils.encodeToString(url.getBytes(StandardCharsets.UTF_8));
            Viewport viewport = new Viewport(1024, 768 ,1, false, false, false);
            page.setViewport(viewport);
            Map<String, Object> downloadParams = new HashMap<>();
            // 允许自动下载文件
            downloadParams.put("behavior", "allow");
            downloadParams.put("downloadPath", imgPath);
            page.client().send("Page.setDownloadBehavior", downloadParams, false);
            page.goTo("http://localhost:" + serverPort + "/forge-viewer/index.html?previewUrl=" + url);
            WaitForSelectorOptions waitOptions = new WaitForSelectorOptions();
            // 2分钟超时
            waitOptions.setTimeout(120000);
            try {
                JSHandle jsHandle = page.waitForFunction("isFinish == true", waitOptions);
                if(jsHandle == null || jsHandle.jsonValue() == null){
                    log.info("自动截图失败,fileBaseInfoId:{}", fileBaseInfoId);
                    imgPath = "";
                }else{
                    log.info("自动截图成功,fileBaseInfoId:{}, 结果:{}", fileBaseInfoId, jsHandle.jsonValue());
                }
            }   catch ( Exception ex){
                log.error("自动截图过程中出错,fileBaseInfoId:{}, 错误原因:{}", fileBaseInfoId, ex);
                imgPath = "";
            }
            page.close();
        }catch (Exception ex){
            log.error("自动截屏时出错,fileBaseInfoId:{},错误原因:", fileBaseInfoId, ex);
        } finally {
        }
        // 如果图片路径不为空,则处理成功,保存记录
        if(!StringUtils.isEmpty(imgPath)){
            File dir = new File(imgPath);
            Collection<File> fileCollection = FileUtils.listFiles(dir,  new String[]{"png"}, true);

            // 获取轻量化物理模型信息
            DocumentObjectDTO data = documentObjectService.findOne(fileBaseInfoId).orElse(null);

            for(File file : fileCollection) {
                String filePath = file.getPath();
                filePath = filePath.replace(fileSavePath,"");
                // win是\\ linux是/
                if (osName.startsWith("win")) {
                    filePath = filePath.replace(fileSavePath.replace("/", "\\"),"");
                } else if (osName.startsWith("mac")) {
                    filePath = filePath.replace(fileSavePath.replace("/", "\\"),"");
                } else {
                    filePath = filePath.replace(fileSavePath,"");
                }
                String fileName = file.getName();
                fileName = fileName.replace(" ","-");
                String orientation = fileName.substring(0,fileName.lastIndexOf("."));

                ModelSnapshotDTO modelSnapshotDTO = new ModelSnapshotDTO();
                modelSnapshotDTO.setModelObjectId(fileBaseInfoId);
                modelSnapshotDTO.setSnapName(data.getFileName().substring(0,data.getFileName().lastIndexOf(".")) + "-" + fileName);
                modelSnapshotDTO.setSnapSavePath(filePath);
                modelSnapshotDTO.setOrientation(orientation);
                modelSnapshotDTO.setCreatedDate(Instant.now());
                modelSnapshotService.save(modelSnapshotDTO);
            }

        }
        return  imgPath;
    }

    public static void autoScreenShot2(String fileBaseInfoId){
        log.info("开始自动截图,fileBaseInfoId:{},当前第{}个任务,总共{}个任务", fileBaseInfoId, executeDone, executeTotal);
        executeDone++;
        String imgPath = "E:\\uploadFiles" + "\\" + AUTO_SCREEN_SHOT_PATH + fileBaseInfoId +"\\";
        try {
            Page page = browser.newPage();
            // 此处需要通过页面加载模型,通过fileBaseInfoId表的id加载。
            String url = previewUrl.replace(idPlaceholder, String.valueOf(fileBaseInfoId));
            url = Base64Utils.encodeToString(url.getBytes(StandardCharsets.UTF_8));
            Viewport viewport = new Viewport(1024, 768 ,1, false, false, false);
            page.setViewport(viewport);
            Map<String, Object> downloadParams = new HashMap<>();
            // 允许自动下载文件
            downloadParams.put("behavior", "allow");
            downloadParams.put("downloadPath", imgPath);
            page.client().send("Page.setDownloadBehavior", downloadParams, false);
            page.goTo("http://localhost:" + serverPort + "/forge-viewer/index.html?previewUrl=" + url);
            WaitForSelectorOptions waitOptions = new WaitForSelectorOptions();
            // 2分钟超时
            waitOptions.setTimeout(120000);
            try {
                JSHandle jsHandle = page.waitForFunction("isFinish == true", waitOptions);
                if(jsHandle == null || jsHandle.jsonValue() == null){
                    log.info("自动截图失败,fileBaseInfoId:{}", fileBaseInfoId);
                    imgPath = "";
                }else{
                    log.info("自动截图成功,fileBaseInfoId:{}, 结果:{}", fileBaseInfoId, jsHandle.jsonValue());
                }
            }   catch ( Exception ex){
                log.error("自动截图过程中出错,fileBaseInfoId:{}, 错误原因:{}", fileBaseInfoId, ex);
                imgPath = "";
            }
            page.close();
        }catch (Exception ex){
            log.error("自动截屏时出错,fileBaseInfoId:{},错误原因:", fileBaseInfoId, ex);
        } finally {
        }
        // 如果图片路近不为空,则处理成功,保存记录
        if(!StringUtils.isEmpty(imgPath)){
            File dir = new File(imgPath);
            Collection<File> fileCollection = FileUtils.listFiles(dir,  new String[]{"png"}, true);

            // 获取轻量化物理模型信息
            DocumentObjectDTO data = documentObjectService.findOne(fileBaseInfoId).orElse(null);

            for(File file : fileCollection) {
                String filePath = file.getPath();
                filePath = filePath.replace(fileSavePath,"");
                String fileName = file.getName();
                fileName = fileName.replace(" ","-");
                String orientation = fileName.substring(0,fileName.lastIndexOf("."));

                ModelSnapshotDTO modelSnapshotDTO = new ModelSnapshotDTO();
                modelSnapshotDTO.setModelObjectId(fileBaseInfoId);
                modelSnapshotDTO.setSnapName(data.getFileName().substring(0,data.getFileName().lastIndexOf(".")) + "-" + fileName);
                modelSnapshotDTO.setSnapSavePath(filePath);
                modelSnapshotDTO.setOrientation(orientation);
                modelSnapshotDTO.setCreatedDate(Instant.now());
                modelSnapshotService.save(modelSnapshotDTO);
            }

        }
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no"/>
    <title>云族库自动化截图</title>
    <link rel="stylesheet" type="text/css" href="./lib/style.min.css">
    <script src="./lib/viewer3D.min.js"></script>
</head>
<body style="margin:0px">
    <div id="viewer-local"></div>
<script>
    //ENABLE_DEBUG = false;
    // 指定截图的面
    // var cubeFaces = ['front', 'back', 'top', 'bottom', 'left', 'right'];
    var cubeFaces = ['front top right', 'right top back', 'back top left', 'left top front'];
    var nowCubeFace = '';
    var config = {
        extensions: [
            "Autodesk.Viewing.ZoomWindow"
        ],
        disabledExtensions: {
            measure: false,
            section: false,
        },
        memory: {
            limit: 32 * 1024    //32 GB
        }
    };
    var isFinish = false;
    var element = document.getElementById('viewer-local');
    var viewer = new Autodesk.Viewing.Private.GuiViewer3D(element, config);
    //var viewer = new Autodesk.Viewing.Viewer3D(element, config);
    var model;
    var options = {
        env: 'Local',
        offline: 'true',
        useADP: false
    };
    var loadOptions = {
        // 'applyScaling' : 'm',
        // 提高模型材质细节,更好看,但影响性能
        'isAEC' : true,
    };

    Autodesk.Viewing.Initializer (options, function () {

        var startedCode = viewer.start();
        if (startedCode > 0) {
            console.error('Failed to create a Viewer: WebGL not supported.');
            return;
        }
        // 加载模型
        let previewUrl = getQueryString("previewUrl");
        previewUrl = atob(previewUrl);
        viewer.loadModel(previewUrl, loadOptions, onLoadSuccess, onLoadError);
        // 添加事件
        // viewer.addEventListener(Autodesk.Viewing.RENDER_PRESENTED_EVENT, (event) => this.crop(viewer, 820, 650));

    });

    function onLoadSuccess(event) {

        viewer.setEnvMapBackground(false);
        model = event;
        console.log('success');
            // viewer.fitToView([], model, false);
        viewer.loadExtension('Autodesk.ViewCubeUi').then( res => {
            viewcuiext = viewer.getExtension('Autodesk.ViewCubeUi');
            //front/back], [top/bottom], [left/right
            setTimeout( () => {
                autoScreenShot();
            }, 5000);
        });
    }

    function onLoadError(event) {
        console.log('fail');
    }


    async function autoScreenShot(){
        for( let i = 0; i < cubeFaces.length; i ++){
            nowCubeFace = cubeFaces[i];
            viewcuiext.setViewCube(nowCubeFace);
            await crop(viewer);
        }
        // 标记截图完成 延迟标记,避免chrome还未下载好
        setTimeout( () => {
            isFinish = true;
        }, 5000);
    }

    // 获取模型快照
    function crop(viewer) {
        return new Promise(resolve => setTimeout( () => {
            let vw = viewer.container.clientWidth;
            let vh = viewer.container.clientHeight;

            viewer.getScreenShot(vw, vh, blob => {
                var save_link = document.createElement('a');
                save_link.href = blob;
                save_link.download =nowCubeFace + '.png';
                save_link.click();
                save_link.remove();
                resolve(true);
            });
        }, 1500));
    }

    // 获取url参数
    function getQueryString(name) {
        let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
        let r = window.location.search.substr(1).match(reg);
        if (r != null) {
            return decodeURIComponent(r[2]);
        };
        return null;
    }
</script>
</body>
</html>
最后修改:2024 年 03 月 04 日
如果觉得我的文章对你有用,请随意赞赏