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();
  }
};