辛宝的玄酒清谈!
2041 words
10 minutes
使用 Nitro + Serverless 函数实现动态头图 Banner

image.png

关联内部链接:

  • [[速通-unjs 家族的 Web 框架 Nitro]]
  • [[速通-腾讯云云函数 scf]]
  • [[编写一个小工具方便快速为文章添加 Banner]]
  • [[速通-动态 SVG 图片生成方案 Satori]]

背景#

现在比较习惯在文章顶部增加一张包含当前标题的图片,在不同平台会默认第一张图片有用,比如认为这是文章 Banner,Cover, Hero Image 等说法。

之前在 [[编写一个小工具方便快速为文章添加 Banner]] 文章中就提到了我写了一个网站,可以在页面上操作生成 banner。

那有没有一种办法直接写一个服务,当我访问 xx.com/cover?title=xxx 时候自动返回一张图片,这样的话,我就不需要打开页面去创建了。后续我只需要查看日志就知道有多少人访问过啦!

而且不同于浏览器,在 node.js 里渲染图片要更复杂一点,不可以像浏览器那样把计算、渲染的能力丢给浏览器了,最直接的就是 html+css 生成截图不可以了,因为 node 环境里没有 css 渲染的能力,得靠 puppeteer 那一套,得使用 canvas/svg 等方案来画。

Canvas 的跨平台兼容就显现出来了。但 Canvas 没接触过不太会,得现学。后来发现 svg 也非常简单,而且返回的是字符串,体积更小。

鱼和熊掌不可兼得啊。

需求设计#

首先这是一个 node 服务,我们携带参数请求接口,返回的是一张图片。这就需要我们部署后端。

相通的请求参数应该出现一致的结果,主要是背景图,不要一刷新页面里的图片变了,产生不必要的误会。

后续可以增加不同的模板,展望可以做成服务对外提供。

参数我们希望:

  • width 宽度
  • height 可选,自动高度
  • 比例 4:3 16:9 21:9 2:1 1:1 等
  • title 文字内容
  • source 角标,比如 @ijust.cc
  • 指定色相 0-360
  • 默认开启渐变 linear-gradient

这样基本就和网页版 https://ijust.cc/tools/canvas 一致了。

技术设计#

交付托管#

node 服务托管,显然是可以使用 serverless,我们可以选择国内的云厂商服务,也可以使用 cloudflare/vercel 提供的服务。考虑服务为了正常需要迁移,可以选择 serverless 框架,之前提到过的 [[速通-unjs 家族的 Web 框架 Nitro]] 就可以拍上用场了。Nitro 可以很容易构建出不同的产物。

云厂商这里我们使用腾讯云 Serverless 函数,后面补充 Cloudflare Workers。

主题色持久化#

相通的内容产生相通的结果,设计一个方案,根据 title 内容算出一个数字,表达主色调。问了 GPT 给了几个方案,比如每个字符串去算 charCodeAt ,得到的数字进行处理。

function stringToDegrees(inputString) {
    let hash = 0;
    for (let i = 0; i < inputString.length; i++) {
        let char = inputString.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash |= 0; // 将哈希值转换为32位整数
    }
    return Math.abs(hash % 360);
}
stringToDegrees('中文') // 194

使用 Satori 制作动态 SVG#

画布因为考虑 Canvas,很多东西就不能复用了,经过实践发现 Vercel 的 Satori最近比较火热,在 [[12-速通 syntax.fm 659 OG Image]] 这一期其实也提到过。写 demo 成功了,以后能经常用得上。

image.svg

这么一张 SVG 图,才 4.5k 大小,要是 jpg 和 png 都不敢想。至少大 10 倍、20 倍。

技术实现#

这里我们准备一个新仓库用来解释这一切,后续也可以作为分享内容。

代码在这里 https://github.com/Otto-J/serverless-dynamic-banner

我先去写代码了,写完代码回来补充。

经过一些时间后,终于写完了,serverless 服务也上线可用了。大量吐槽来了,气人 😡。

踩坑经验#

之前讲到本次技术选择是:nitro+腾讯云 serverless,目标是上线 serverless 服务,最终我们手里有一个网址,一打开是一张图片。

Nitro 文档稀烂#

我选择 nitro 有两个原因:

  • 不用我学习新语法,比如 next.js 的那些概念,轻松上手,比 express/koa 相比不是玩具,大量内置了 ts、路由、中间件、常用方法,更快更容易写出真实业务代码
  • Nitro 是 nuxt 的组成部分,学会了 nitro 一鱼两吃,写后端服务、写全栈服务都可以用得上,经验直接复用

问题来了,如果我想获取路由的信息,设置返回的信息等技术细节,我应该怎么学会呢?问 GPT,GPT 不知道这么新的东西。只能看文档和教程。

Nitro 文档就几页,没中文,功能介绍跟 ppt 似的,也没有 playground,看的眼睛都花了,也没找到 api 索引。生气,guide+api 索引+playground+项目实战不应该标配嘛!

image.png

不过我是高手,好在我知道 Nitro( [[速通-unjs 家族的 Web 框架 Nitro]]) 是 unjs/h3 ([[unjs家族里的 http 框架 h3]]) 的封装,h3 提供低级 api,nitro 提供高级封装开箱即用。所以我应该去看 h3 的文档,聪明如我。

github 搜索 unjs h3,打开 https://github.com/unjs/h3 结果…

image.png

算了,告诉你吧,api 索引在这里,在 h3 长长文档的最下方

image.png

如何设定返回值时候,指定 content-type 为 svg?自己试。结论是使用 send 方法。

对了,如果你是 Nuxt 用户,那么你需要按照这个路线学习 Nuxt 文档 - Nitro 文档 - h3 文档。

腾讯 Serverless 函数体验贼差#

之前文档也提到过 ([[速通-腾讯云云函数 scf]]),作为国内大厂,值得信赖,而且做了也不是一年两年了。

结果体验贼差,我以为的云函数上线过程:

  • 授权 github 信息,读取项目
  • 选择项目源码,自动或者手动选择基本信息,帮我做好构建
  • 拿到交付产物,上传到云端
  • 给我一个地址,我一点击就访问通了
  • 告诉我你可以自定义网址,为了安全和推广其他服务,建议你开通网关鉴权、监控、日志之类的
  • 用吧,额度不够可以买套餐

挺合理的,服务好用户,也能带一带其他商业服务。

实际上不是,每一步都有大量的错误,大量的延伸技术概念,大量的问号帮助文档。生怕我觉得前端上线个服务挺简单,没有技术难度。

无力吐槽,算了,不写了,好在走过来了。

image.png

image.png

image.png

后面单独开个文档介绍吧。

DNSPOD 扭扭捏捏不融入腾讯云#

DNSPOD 被腾讯云收购了,现在属于腾讯云。即便我域名、备案、解析都在腾讯云和 DNSPOD,我开通 DNS 解析,还是要重复开页面、登录、扫码、编写。

麻烦死了。

自己愚蠢文件放错了位置#

这个看我源码就知道,为了目录好看,我包了一层,结果脑抽忘了,一些文件放外层了,怎么都拿不到,嘿嘿,后面不同项目开不同的窗口,乱了就不好了。

satori 不支持 css 和图片的特性#

因为 svg 技术限制,satori 不支持 z-index,不支持 transfrom,必须全程使用 flex 布局,字体必须指定,不支持 webp/avif 等图片格式,不支持 hsl 颜色格式。

怎么说呢,好在东西不复杂,样式改改也就能用了。

我 banner 不是有一个小人头像么,svg 是要通过 base64 内嵌进去的,体积会变大。为了减少图片体积,我尝试转 svg,转 avif/webp 等格式,都不是很满意。

最后用的 png 颜色减少,感觉 24 色就足够了,轻松让 png 缩小了五六倍,肉眼看不出差异,挺好的。

总结展望#

东西做完了,感受如何,昨天都气疯了,今天算写完了。

下次还来,希望轻车熟路,能快一点。

做国内的云服务虽然有价格战,但还是有位置的,业务还是要专注、做好。服务使用流畅应该是标配的。

使用 Nitro + Serverless 函数实现动态头图 Banner
https://ijust.cc/posts/use-nitro-make-a-serverless-dynamic-banner/
Author
辛宝 Otto
Published at
2023-10-30