使用 Serverless + Puppeteer + ReactDomServer 生成图片

1,011 阅读2分钟

背景

去年我司有个业务需求,员工到了生日 、转正、入职周年或者入职时,企业微信自动为其生成一张图片发送给HR,HR转到公司大群,表达公司对员工的关怀

技术方案 v1.0 - 使用 html2canvas 生成图片

image.png

流程

  1. 员工生日触发生成图片,后端调用企业微信 api 向员工推送一条消息,之后打开消息跳转到前端预览图片
  2. 点击生成图片,使用html2canvas生成
  3. 生成之后保存到本地,这样就可以拿到图片转发了

问题

  1. html2canvas 的兼容性问题
  2. 用户手动触发生成图片,相当于是同步的,需要用户等待,这样用户明显能感受到等待时间,体验不是很好

优化调研

调研过程这里就不细说了,最终的方案是使用 puppeteer 和阿里云的函数计算来生成图片 也就是大家说了解的 serverless,serverless 是什么?可自行 google,网上有很多资料

这里附上相关文档

阿里云函数计算的文档:help.aliyun.com/product/509…

puppeteer文档:pptr.dev/

技术方案 v2.0 - 使用 serverless + puppeteer + nextjs 生成图片

image.png

流程

  1. 当员工生日时,系统触发生成图片服务
  2. 后端生成员工生日相关信息,向 serverless 请求生成图片
  3. serverless 接受请求后调用 puppeteer 生成图片
  4. puppeteer goto nextjs 渲染图片,puppeteer 调用 screenshot api 拍照
  5. 返回图片给 serverless,生成图片成功,返回给后端
  6. 后端调用企业微信发送图片 api 发送到图片接收人
// example.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });
  await browser.close();
})();

优点

  1. 无兼容性问题
  2. 异步生成图片,用户无感知

缺点

  1. 调用链路较长
  2. puppeteer goto 比较耗时(解析域名到渲染完毕)

优化调研

因为 goto 比较耗时,所以有没有办法去掉 nextjs? 有的,使用ReactDOMServer

技术方案 v3.0 - 使用 serverless + puppeteer + ReactDomServer 生成图片

image.png

流程

总体流程其实跟2.0没什么区别,唯一区别就是使用 ReactDomServer 替换了 nextjs,这样就可以去掉 goto 了,当启动 puppeteer 生成图片时候,本地渲染 + 生成图片

// example.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  const component = <Card />
  const content = ReactDOMServer.renderToStaticMarkup(component);
  const html = renderHtmlTemplateString(content);
  await page.setContent(html);

  await page.screenshot({ path: 'example.png' });
  await browser.close();
})();

总结

使用 serverless + puppeteer + ReactDomServer 总体来说还是不错的,完全避免了 html2canvas 兼容性以及同步生成问题