背景
去年我司有个业务需求,员工到了生日 、转正、入职周年或者入职时,企业微信自动为其生成一张图片发送给HR,HR转到公司大群,表达公司对员工的关怀
技术方案 v1.0 - 使用 html2canvas 生成图片
流程
- 员工生日触发生成图片,后端调用企业微信 api 向员工推送一条消息,之后打开消息跳转到前端预览图片
- 点击生成图片,使用html2canvas生成
- 生成之后保存到本地,这样就可以拿到图片转发了
问题
- html2canvas 的兼容性问题
- 用户手动触发生成图片,相当于是同步的,需要用户等待,这样用户明显能感受到等待时间,体验不是很好
优化调研
调研过程这里就不细说了,最终的方案是使用 puppeteer 和阿里云的函数计算来生成图片 也就是大家说了解的 serverless,serverless 是什么?可自行 google,网上有很多资料
这里附上相关文档
阿里云函数计算的文档:help.aliyun.com/product/509…
puppeteer文档:pptr.dev/
技术方案 v2.0 - 使用 serverless + puppeteer + nextjs 生成图片
流程
- 当员工生日时,系统触发生成图片服务
- 后端生成员工生日相关信息,向 serverless 请求生成图片
- serverless 接受请求后调用 puppeteer 生成图片
- puppeteer goto nextjs 渲染图片,puppeteer 调用 screenshot api 拍照
- 返回图片给 serverless,生成图片成功,返回给后端
- 后端调用企业微信发送图片 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();
})();
优点
- 无兼容性问题
- 异步生成图片,用户无感知
缺点
- 调用链路较长
- puppeteer goto 比较耗时(解析域名到渲染完毕)
优化调研
因为 goto 比较耗时,所以有没有办法去掉 nextjs? 有的,使用ReactDOMServer
技术方案 v3.0 - 使用 serverless + puppeteer + ReactDomServer 生成图片
流程
总体流程其实跟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 兼容性以及同步生成问题