Situation:
在进行企业微信推送的时候, 遇到了markdown格式消息推送长度超限的问题, 且业务上不允许丢失通知样式, 需要找到一种既能保持样式, 又能突破消息长度限制的推送方案
Task
1 . 方案制定
最先想到的是将Markdown内的详情内容转为图片形式, 基础信息部分保留原Markdown推送, 但是在我们的场景下遇到两个问题:
· 图片存储有网络域隔离, 推送出来的链接无法在外部网络环境访问
· 即使是使用图片, 由于使用了文字样式, 稍长的基础消息也有可能触发长度限制
因此这个方案最终没有采纳, 转而考虑整体转图片, 使用图片消息推送的方案
图片消息推送方案中, 我们遇到的问题是无法很好的处理原Markdown消息中的URL部分, 业务强依赖消息中的超链接来提升效率
最终, 选择了分步骤推送的方式来解决纯图片消息无法携带链接的问题
先推送一个包含全文的图片, 再推送一次概览Markdown提供链接
2. 技术实现
首先 直接将原来的Markdown进行图片渲染是行不通的, 没找到合适的渲染引擎, 最后选择了先转Html再通过浏览器渲染截图的方案
Action
需要两个包
1 . github.com/yuin/goldmark
goldmark是go的一个Markdown转Html库, 其实现了Markdown标准,(CommonMark) 易于拓展
2. github.com/chromedp/chromedp
chromedp是一个自动化浏览器测试的库, 基于Chrome DevTools Protocol(CDP)开发, 通过CDP可以直接调用Chromium
具体步骤见如下代码
// 先定义Markdown文本
md := `
> markdown document.
- Item 1
- Item 2
- [Link](https://baidu.com)
`
// 再定义基础的Html模板
htmlTemplate := `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
<!-- 在这里可以设置自定义的HTML样式, 比如字体颜色之类的 -->
</style>
</head>
<body>%s</body>
</html>
`
// 先把Markdown转HTML
var buf bytes.Buffer
if err := goldmark.Convert([]byte(md), &buf); err != nil {
log.Fatal(err)
}
htmlContent := buf.String()
// 模板替换
fullHTML := fmt.Sprintf(htmlTemplate, htmlContent)
// 要将转换完的HTML内容做一次url转换
// 这和后面使用<strong>Chromium</strong>进行截图有关, 直接在地址栏使用
// ”data:text/html,“+HTML代码
// 的方式就可以渲染页面, 不需要存文件转一次了
// 如果不转url的话内容是现实不出来的
encodedHTML := url.PathEscape(fullHTML)
// 设置你<strong>Chromium</strong>的路径, 如果你装了Chrome那应该不用手动设置, Env里有
// 我这里用的是Edge, 所以要设置下
// 建议还是手动设置下, 因为最后都要跑到生产机器上去的, <strong>Chromium</strong>地址不确定
pathOpts := chromedp.ExecPath("/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge")
// 设置无头模式(headless mode)
// 这个是<strong>Chromium</strong>提供的一个模式, 可以在不显示GUI的情况下进行自动化操作
headlessOpts := chromedp.Flag("headless", true)
// 先设置执行选项
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), pathOpts, headlessOpts)
defer cancel()
// 连接<strong>Chromium</strong>
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
// 这里开始截图
var screenshot []byte
err := chromedp.Run(ctx,
// html的内容就在这设置
chromedp.Navigate("data:text/html,"+encodedHTML),
// 等待Body加载完毕
chromedp.WaitVisible("body", chromedp.ByQuery),
// 这里使用FullScreenshot API进行截图
//如果你只需要部分区域, 可以使用CaptureScreenshot加设置Viewport大小的方式限制范围, 100是截图质量
chromedp.FullScreenshot(&screenshot, 100),
)
if err != nil {
log.Fatal(err)
}
// 保存图片
if err := os.WriteFile("output.png", screenshot, 0644); err != nil {
log.Fatal(err)
}
Rusult
通过转换图片的方式, 我们解决了业务方的需求
从我们来看, 虽然引入Chromium的方式可能不太优雅, 但是这个方案可能是当前比较好的复杂样式markdown转图片的解决方案了