Skip to main content

· 3 min read
wen

背景&目的

项目中使用的第三方服务(Shopify)的 API 隔几个月就会发布新版本。

为了及时对应 API 变化,我们希望在该服务的 API 的 RSS 有更新时,能够及时收到通知。

但是 Lark(飞书)并没有提供 RSS 订阅的功能,所以我们需要通过其他方式实现。

实现方法

通过 Zapier 将 RSS 新文章通知发送到 Lark(飞书)Webhook。

Zapier 是一个在线的自动化工作流程工具,可以将不同的应用程序连接起来,实现自动化工作流程。

实现步骤

0. 前提条件

  • Lark(飞书)中创建一个接受通知的 Group,并设置 Webhook,用于接收通知。

1. Zapier 中创建一个 Zap

分 2 步:

  • 1️⃣ 添加 RSS by Zapier, 填入 Shopify API RSS 地址, 用于获取 RSS 新文章通知。(图 1)

  • 2️⃣ 添加 Code by Zapier, 通过编写代码将 RSS 新文章发送到 Lark(飞书)Webhook。(图 2)

    • 如果是付费用户的话,可以使用 Webhook by Zapier 替代 Code by Zapier,省得写代码。
    • 代码语言支持 JavaScript 和 Python,这里用的是 JavaScript。

图 1 图 1 图 2 图 2

图 2 中的设置如下:

  • 1️⃣ 添加变量 Webhook,用于存放 Lark(飞书)Webhook 地址。
  • 2️⃣ 添加变量 Data,用于存放 RSS 新文章通知。 具体存放什么东西可以自己选择,这里存放的是 TitleLink等。
  • 3️⃣ 代码如下:
// 获取设置的变量
let Webhook = inputData.Webhook.trim();
let Method = "POST";

// 发送给 Webhook 的 JSON 对象 (注意格式要符合 Lark(飞书)Webhook 的要求)
let JSONObject = {
msg_type: "text",
content: { text: inputData.Data },
};

// creates the Method, Headers, and Body of the HTTP POST Request
let Options = {
method: Method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(JSONObject),
};

const Request = await fetch(Webhook, Options); // HTTP POST Request
const Response = await Request.json(); // HTTP POST Response

output = { Response, Request, Webhook, Method };

References

Make a HTTP POST Request to Fire a Webhook with Headers and Parameters via a Zap Code Step

· 2 min read
wen

0. Manim 是什么?

Manim 是一个用于创建数学动画的库,它是由 3Blue1BrownGrant Sanderson @X开发的。

Manim 的目标是提供一个用于创建数学动画的简单、快速且强大的工具。

它是一个用 Python 编写的开源项目,它的代码托管在 GitHub 上。

1. 最简单快速的安装方法: 利用 GitHub Codespaces

1). 打开(或者 fork)我配置好的仓库 wifecooky/manim-devcontainer,

1️⃣ 点击右上角的 Code 按钮, 2️⃣ 选择 Open with Codespaces,等待构建完成后,就可以在浏览器中使用 Manim 了。

img

2). 测试一下

1️⃣ : 打开 example_scenes.py 文件。

2️⃣ : 在 terminal 运行 manim -ql example_scenes.py SquareToCircle

3️⃣ : 等待运行完成,就可以在 media/videos 目录下看到生成的视频了。

img

2. 本地最快速的安装方法: 利用 Docker

0). 安装 Docker

已经安装过的请跳过这一步。

1). 拉取 Manim 镜像

docker pull manimcommunity/manim

2). 使用 Docker 启动本地 JupyterLab

docker run -it -p 8888:8888 manimcommunity/manim jupyter lab --ip=0.0.0.0

3). 打开 JupyterLab 链接并创建一个新的 Notebook

img

img

4). 测试一下

1️⃣ : 在 Notebook 中运行以下代码

%%manim -qm -v WARNING SquareToCircle

class SquareToCircle(Scene):
def construct(self):
square = Square()
circle = Circle()
circle.set_fill(PINK, opacity=0.5)
self.play(Create(square))
self.play(Transform(square, circle))
self.wait()
tip

%%manim 是 Manim 的 Jupyter magic 命令,用于链接 Binder 来运行 Manim。

-qm 参数表示 quality medium,即生成的视频质量为中等。

-v WARNING 参数表示只显示警告信息。

img

Reference

Manim easy Installation for all operating systems (Windows, Linux, Mac OS)

docker manimcommunity/manim

· One min read
wen

问题

当你在同一台电脑上使用多个 Git 用户的时候,你可能会遇到在 commit push 之后才发现自己没有切换到正确的用户的问题。

为了避免这种情况,我们可以在终端显示当前的 Git 用户信息。

img

解决方案

终端的显示信息(Shell Prompt) ,我推荐 Starship 配置来实现。

安装 Starship 可以参考官方文档, 这里就不再赘述。

配置

安装完后在 Starship 的配置文件 ~/.config/starship.toml 中添加以下配置, 格式可以按照自己的喜好修改 format = 部分 。

~/.config/starship.toml
format = """
...
${custom.git_username}\
...

[custom.git_username]
command = "git config user.name"
when = "[ -d .git ] && echo .git || git rev-parse --git-dir > /dev/null 2>&1"
format = ' [$symbol($output)@git]($style) '

· One min read
wen

Problem

Docusaurus(3.0) 的文档侧边栏默认是按文件的创建日期的升序排列的。

Docuaurus v3 's docs sidebar is sorted by the creation date of the file by default.

It's not very convenient for me to manage and find docs, so I want to reverse the order of the sidebar items.

img

Solution

Modify the docusaurus.config.js file as follows:

docusaurus.config.js
+// Reverse the sidebar items ordering (including nested category items)
+function reverseSidebarItems(items) {
+ // Reverse items in categories
+ const result = items.map((item) => {
+ if (item.type === 'category') {
+ return {...item, items: reverseSidebarItems(item.items)};
+ }
+ return item;
+ });
+ // Reverse items at current level
+ result.reverse();
+ return result;
+}

/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'thewang',
...
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
routeBasePath: 'weekly',
sidebarPath: require.resolve('./sidebars.js'),
showLastUpdateTime: true,
showLastUpdateAuthor: true,
sidebarCollapsed: false,
+ async sidebarItemsGenerator({defaultSidebarItemsGenerator, ...args}) {
+ const sidebarItems = await defaultSidebarItemsGenerator(args);
+ return reverseSidebarItems(sidebarItems);
+ return sidebarItems;
+ },
},
...

References

Customize the sidebar items generator

· 3 min read
wen

Background

img

  • It is a common requirement to get the common elements between two slices, but I found that some people still use double loops to implement it during Code Review. (The time complexity is O(n^2), and the efficiency is relatively low)
  • I have been using Golang recently, so I will share how to get the common elements between two slices in Golang.

❌ Code example implemented with double loops:

func intersection(nums1 []int, nums2 []int) []int {
var result []int

// Double loop O(n^2)
for _, v1 := range nums1 { // O(n) outer loop
for _, v2 := range nums2 { // O(n) inner loop
if v1 == v2 {
result = append(result, v1)
}
}
}
return result
}

Improvement

Change the element lookup in the inner loop in the above example to be implemented using set*,

so that the time complexity of the inner loop part can be reduced to O(1), and the overall time complexity can be reduced to O(n).

Set in Javascript

Set objects are collections of values. A value in the set may only occur once; it is unique in the set's collection.

It could be represented internally as a hash table (with O(1) lookup),

a search tree (with O(log(N)) lookup), or any other data structure, as long as the complexity is better than O(N).

Reference

Implementation example using set

func intersection(nums1 []int, nums2 []int) []int {
var result []int

// Convert nums2 to set, so that the time complexity of looking up elements in nums2 becomes O(1).
// Consider performance optimization, you can compare the number of elements in nums1 and nums2, and convert the slice with more elements into set.
set := make(map[int]struct{}) // There is no set in Golang, use map to implement. struct{} is an empty structure to save memory.
for _, v := range nums2 {
set[v] = struct{}{}
}

// Traverse nums1, if the element in nums1 exists in nums2, add it to result
for _, v := range nums1 {
if _, ok := set[v]; ok {
result = append(result, v)
}
}
}

Implementation using third-party libraries

If you consider performance optimization and various types of slices, we can use the following third-party libraries to implement.

deckarep/golang-set

As the name suggests, it is a set implementation in Golang.

import (
"fmt"
mapset "github.com/deckarep/golang-set/v2"
)

func main() {
set1 := mapset.NewSet[string]()
set1.Add("a")
set1.Add("b")
set1.Add("c")

set2 := mapset.NewSet[string]()
set2.Add("c")
set2.Add("d")
set2.Add("e")

// 交集 intersection
intersectionSet := set1.Intersect(set2)
fmt.Println(intersectionSet) // Set{c}

// Besides intersection, it also supports union, difference, symmetric difference, etc.
// 并集 union
unionSet := set1.Union(set2)
fmt.Println(unionSet) // Set{a, b, c, d, e}

// 差集 difference
diffSet := set1.Difference(set2)
fmt.Println(diffSet) // Set{a, b}

// 对称差集 symmetric difference
symDiffSet := set1.SymmetricDifference(set2)
fmt.Println(symDiffSet) // Set{a, b, d, e}
}

samber/lo

If you need to sort, group, etc. in addition to intersecting slices, you can consider using the samber/lo library.

It is similar to lodash in Javascript.

import (
"github.com/samber/lo"
)

func main() {
// 交集 intersection
lo.Intersection([]int{1, 2, 3}, []int{2, 3, 4}) // return []int{2, 3}

// 并集 union
lo.Union([]int{1, 2, 3}, []int{2, 3, 4}) //return []int{1, 2, 3, 4}

// 差集 difference
lo.Difference([]int{1, 2, 3}, []int{2, 3, 4}) // return []int{1}, []int{4}
}

· 2 min read
wen

Symmetric API testing 是啥?

这个概念应该是源自于 Gopher Academy Blog

作者在维护一个 Golang 的 Twitter API 客户端,为了对 Twitter 的 API 进行测试,所以作者提出了 Symmetric API testing 的概念。

简单地说就是保存 API 的返回结果,然后在测试的时候,用保存的结果来进行测试。

这样就不用编写 mock 和 测试用例了。

至于名字为什么叫 Symmetric, 是相对于传统的需要编写 mock 和 测试用例的方式 Asymmetric 而言的。

其实个人觉得把它叫做 SnapShot Testing 更为合适。

怎么实现?

除了手动保存 API 的返回结果,还可以使用 go-vcr 这个库来实现。

大概的代码如下:

r, err := recorder.New("<filename>")
if err != nil {
return err
}
defer r.Stop()
client.Transport = r
res, err := client.Get("http://api.twitter.com/...")
if err != nil {
return err
}

这里提供了完整的 示例代码

Reference

Symmetric API Testing

Symmetric API Testing という、手間なく堅牢に外部 API Client をテストする手法

go-vcr を使った Symmetric API Testing のメモ

· 3 min read
wen

背景&需求

在 Golang 中,我们经常会遇到需要解析 JSON 数据的场景,比如从 HTTP 请求中获取 JSON 数据,或者从文件中读取 JSON 数据。

通常我们会提前定义好对应的结构体,然后才能将 JSON 数据解析到结构体中。

比如:

type User struct {
Name string `json:"name"`
Age int `json:"age"`
}

func main() {
jsonStr := `{"name": "wen", "age": 18}`
var user User
json.Unmarshal([]byte(jsonStr), &user)
fmt.Println(user)
}

但是有时候我们并不知道 JSON 数据的结构,或者 JSON 数据的结构会经常变化,这时候我们就无法提前定义好对应的结构体。

解决方案

可以使用 map[string]any (Golang1.18 之前的话 map[string]interface{} ) 来解析 JSON 数据,这样就不需要提前定义结构体了。

func main() {
jsonStr := `{"name": "wen", "age": 18}`
var user map[string]any
json.Unmarshal([]byte(jsonStr), &user)
fmt.Println(user)

// 获取具体的值
fmt.Println(user["name"])
fmt.Println(user["age"])
}

扩展

如果觉得 map[string]any 这种方式解析速度比较慢,可以使用 jsonparser 这个库来解析,速度会快很多。

我用 User 结构体来测试了一下,解析速度快了 8-9 倍左右 🚀

其他的比较大的 JSON 数据,解析速度也会快很多,具体可以看下这里的 benchmark

NameIterationsns/op
BenchmarkEncodingJsonInterfaceUser-122540230460.6 ns/op
BenchmarkJsonParserUser-122141329655.91 ns/op
查看测试代码
// Just for emulating field access, so it will not throw "evaluated but not used"
func nothing(_ ...interface{}) {}

// 使用 jsonparser
func BenchmarkJsonParserUser(b *testing.B) {
for i := 0; i < b.N; i++ {
jsonparser.Get(user, "name")
jsonparser.Get(user, "age")
nothing()
}
}

// 使用 map[string]any
func BenchmarkEncodingJsonInterfaceUser(b *testing.B) {
for i := 0; i <details b.N; i++ {
var data interface{}
json.Unmarshal(user, &data)
m := data.(map[string]interface{})

nothing(m["name"].(string), m["age"])
}
}

· 3 min read
wen

0. Background

My blog is hosted on Cloudflare Pages and the source code is on Github.

But Cloudflare Pages does not provide deployment status notification, so I thought of using Github Actions to implement this function.

1. Prerequisites

  • Already created your website's repository on Github
  • Already set up your Github repository on Cloudflare Pages
    • For example, I set it to deploy automatically when the main branch is updated

2. The idea

3. Implementation

[arddluma/cloudflare-pages-slack-notification] 这个 Github Action 已经实现这个了我们所要的功能。

arddluma/cloudflare-pages-slack-notification Github Action has already implemented the function we want.

So we only need to prepare the variables used in this Github Action.

- name: Await CF Pages and send Slack notification
uses: arddluma/cloudflare-pages-slack-notification@v4
with:
# Cloudflare API token
apiToken: ${{ secrets.CF_API_TOKEN }}
# CloudFlare account ID
accountId: ${{ secrets.CF_ACC_ID }}
# CloudFlare Pages project name
project: ${{ secrets.CF_PAGES_PROJECT }}
# Create Slack Incoming webhook and add as variable https://hooks.slack.com/...
slackWebHook: ${{ secrets.SLACK_WEBHOOK }}
# Add this if you want to wait for a deployment triggered by a specfied commit
commitHash: ${{ steps.push-changes.outputs.commit-hash }}

3.1 Get variables

1️⃣ 2️⃣ 3️⃣ are the parameters that Github Actions needs to get the deployment status through the Cloudflare Pages API. 4️⃣ is the webhook URL that Github Actions needs to send messages to Slack.

3.2 Set variables

In your website's Github repository, click Settings -> Secrets and variables -> New repository secret, and then set the variables above.

img

4. Github Actions code

github/workflows/cloudflare-pages.yml
name: Detect Cloudflare pages deployment status and notify to Slack
on:
push:
branches:
- main
paths-ignore:
- .github/**
pull_request:
branches:
- main
types: [closed]
paths-ignore:
- .github/**
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Await CF Pages and send Slack notification
id: cf-pages
uses: arddluma/cloudflare-pages-slack-notification@v4
with:
# Clouodflare API token
apiToken: ${{ secrets.CF_API_TOKEN }}
# CloudFlare account ID
accountId: ${{ secrets.CF_ACC_ID }}
# CloudFlare Pages project name
project: ${{ secrets.CF_PAGES_PROJECT }}
# Create Slack Incoming webhook and add as variable https://hooks.slack.com/...
slackWebHook: ${{ secrets.SLACK_WEBHOOK }}
# Add this if you want to wait for a deployment triggered by a specfied commit
commitHash: ${{ steps.push-changes.outputs.commit-hash }}

The screenshots

  • Github Action Build Progress: img

  • Slack notification: img

Reference

Setup Cloudflare Pages Slack notifications

· 2 min read
wen

问题

Docuaurus 的文档(Docs)默认的 URL 路径名是 /docs,如:https://thewang.net/docs

如果你想把它改成 /api,如:https://thewang.net/api ,该怎么做呢?

解决方法

docusaurus.config.js 中,

  • 找到 docs 的配置项,添加 routeBasePath 属性,值为你想要的 URL 路径名,如:api
  • 找到 navbar 的配置项,
docusaurus.config.js
  presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
+ routeBasePath: 'api',
sidebarPath: require.resolve('./sidebars.js'),
...
docusaurus.config.js
     navbar: {
...
items: [
{ to: '/blog', label: 'Blog', position: 'left' },
// API docs
{
to: '/api',
type: 'docSidebar',
label: 'API',
...
},

补充

那那些已经公开的文档(Docs)的 URL 路径不能访问了該怎么办呢?

可以安装 docusaurus/plugin-client-redirects 插件,来实现重定向解决。

  npm install @docusaurus/[email protected] // 注意和你的 docusaurus 版本对应
docusaurus.config.js
...
+ plugins: [
+ [
+ '@docusaurus/plugin-client-redirects',
+ {
+ createRedirects(existingPath) {
+ if (existingPath.includes('/api')) {
+ // Redirect from /docs to /weekly
+ return [
+ existingPath.replace('/api', '/docs'),
+ ];
+ }
+ return undefined; // Return a falsy value: no redirect created
+ },
+ },
+ ]
+ ],

presets: [
...
PRODUCTION ONLY

This plugin is always inactive in development and only active in production because it works on the build output.

@docusaurus/plugin-client-redirects 只在生产环境下生效。

· 2 min read
wen

背景

以前经常用 plantuml 来生成各种示意图。

自从 Github 支持了 mermaid 语法后,就慢慢的转向了 mermaid。

今天就来记录一下如何使用 mermaid 来绘制 Gitflow 的示意图。

tip

Mermaid 是一个基于 JavaScript 的图表绘制工具,

它使用简单的文本描述来定义图表,然后将文本转换为图表。

Mermaid 支持流程图、序列图、类图、状态图、Git 图、甘特图等多种图表。

官方还提供了一个在线编辑器

1. Gitflow 的 Mermaid 代码

gitGraph LR:
commit id: "1:init"
branch "hotfix/{ticket_number}" order: 1
branch "release/{x}" order: 2
branch develop order: 3
commit id: "2:init"
branch "feature/{ticket_number}" order: 4
checkout "feature/{ticket_number}"
commit id: "3:commit-x"
checkout develop
merge "feature/{ticket_number}" id: "4:merge-feat" type: HIGHLIGHT

# release
checkout "release/{x}"
merge develop id: "5:release-x"
checkout main
merge "release/{x}" id: "6:merge-release" tag: "v.1.0.0" type: HIGHLIGHT

# hotfix
checkout "hotfix/{ticket_number}"
commit id: "7:commit-hotfix"
checkout develop
merge "hotfix/{ticket_number}" id: "8:merge-hotfix" type: HIGHLIGHT

2. Mermaid 代码的渲染结果

似乎 本博客( Docusaurus )的渲染结果 和官方提供的在线编辑器 生成的结果(↓)稍微有点不一样。

不过,大家可以通过修改 Mermaid 的配置 调整显示效果。

img