node服务生成图片
# 实现思路
node 服务生成图片?核心在于这个图片是怎么来的,这里我的思路是,在 node 上启动一个浏览器,然后浏览器里面绘制我想要的内容,最后用浏览器的截图功能,生成图片
- 引入
puppeteer
包,这个包用来启动一个浏览器实例,在需要导出图片的地方,启动实例,然后绘制浏览器,然后截图即可
// 启动浏览实例 并生成图片
const htmlToImagePromise = async (htmlContent) => {
const browser = await puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--disable-background-networking',
'--disable-default-apps',
'--disable-extensions',
'--disable-sync',
'--disable-translate',
'--hide-scrollbars',
'--metrics-recording-only',
'--mute-audio',
'--no-first-run',
'--safebrowsing-disable-auto-update',
],
headless: true,
});
// 开启一个标签页
page = await browser.newPage();
// 设置页面内容
await page.setContent(htmlContent);
// 设置视窗大小,以防内容超出视窗范围
await page.setViewport({ width, height });
// 截图并保存
await page.screenshot({ path: fileName });
// 销毁浏览器实例
await browser.close();
return fileName;
};
# 优化
很快我就发现了问题,为啥每一张图片的生成过程要 1 分多钟?后来我发现是启动浏览器实例这个操作,太慢了,所以我决定开始优化
优化思路很简单,启用全局变量,每次服务重启的时候都启动一个浏览器实例,并且让这个全局变量保存浏览器实例,这样所有的生成图片操作公用一个浏览实例,不需要频繁的启动和销毁浏览器实例了
需要注意的是这里用对象属性来指向浏览器实例,如果用变量指向的话,调度任务启动的时候浏览器实例还没有生成,所以调度任务重获取不到浏览器实例
而用对象属性指向的话,由于这块地址是在堆上的,调度任务执行的时候,堆上地址肯定能指向了浏览器实例,这样总能获取到这个实例,可以确保对象的引用不会在任务执行期间丢失,尤其是在异步操作中。
当然,也有更好的办法,那就是启动完成浏览器实例之后,再开始调度任务的注册,但是由于我的调度任务是在别的 js 文件中的,如果在这个文件里面重新去生成浏览器实例的话,不太好,这里我想保证单一职责原则,所以没有采用这种方式
// 定义一个 浏览器实例
let BROWSER_PROMISE = {
instance: null,
};
// 写一个获取浏览器实例的方法
const getBrowser = async () => {
if (!BROWSER_PROMISE.instance) {
logMethodCall(`no-browser`, 'done');
BROWSER_PROMISE.instance = await puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--disable-background-networking',
'--disable-default-apps',
'--disable-extensions',
'--disable-sync',
'--disable-translate',
'--hide-scrollbars',
'--metrics-recording-only',
'--mute-audio',
'--no-first-run',
'--safebrowsing-disable-auto-update',
],
headless: true,
});
} else {
logMethodCall(`has-browser`, 'done');
return BROWSER_PROMISE.instance;
}
};
// 启动浏览实例 并生成图片
const htmlToImagePromise = async (htmlContent) => {
let browser;
let page;
try {
// 查找是否有现成的浏览器实例,没有就重新生成一个
browser = await getBrowser();
if (!browser) {
throw new Error('Browser instance is not initialized');
}
page = await browser.newPage();
// 设置页面内容
await page.setContent(htmlContent);
// 设置视窗大小,以防内容超出视窗范围
await page.setViewport({ width, height });
// 截图并保存
await page.screenshot({ path: fileName });
return fileName;
} catch (error) {
console.error('htmlToImage操作失败:', error);
return null;
} finally {
// 关闭页面而不是浏览器
await page.close();
}
};