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>