Margrop
Articles348
Tags543
Categories7

Categories

1password AC ACP AI AI Agent AI Coding Assistant AI Tech AI 安全 AI 日记 AI编程助手 AI辅助 AI辅助编程 AP API ASR Agent Agent 检索 AgentPlan Alertmanager AppDaemon Aqara Attention CC-Switch CI/CD CLI Tools CLI 工具 CLI工具 Caddy Claude Code Cloudflare Codex Coding Plan Cookie 认证 Cron D1 DFIR Date Diagrams.net Diary Docker Docker Compose Efficiency Tools Electerm Embedding English FlashAttention GLM 5.2 GPT-5.6 GPT-Red GPU 性能分析 Gateway Gemini Gemini API Gemini CLI GitHub Actions Google AI HA HADashboard Hermes Hexo HomeAssistant Hugging Face IP IPv4 Java Kimi Code LVM‑Thin Linux MCP MacOS Managed Agents Markdown Microsoft 365 Copilot MiniMax Multi-Agent MySQL NAS NVIDIA Nemotron 3 Embed NewAPI Nginx Node-RED Node.js OOM Open Viking OpenAI OpenClaw OpenCode OpenResty OpenWrt PPPoE Portainer PostgreSQL ProcessOn Prometheus Prompt Injection Proxmox VE PyTorch RPC RTEB Red Teaming SOCKS5 SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS VoiceEQ Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit container crontab ctyun ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban feign firewall-cmd flow frp frpc frps fuckgfw function gcc gfw git github golang gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https iKuai idea image img img2kvm immortalwrt import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live low-code lvm lxc m3u8 mac macos mariadb markdown maven md5 mdadm microcode mirror modem modules monitor mount mstsc mysql n2n n5105 nas network nfs node node-red nodejs nohup notepad++ npm nssm ntp oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reflog remote remote desktop renew repo resize retina root route router rule rules runtime safari sata scipy-notebook scoping scp self-play server slmgr so socks source spk spring springboot springfox ssh ssl stash string supernode svg svn swagger sync synology systemctl systemd tap tap-windows tapwindows telecom template terminal tls tmux token totp tvbox txt ubuntu udisk ui undertow uninstall unlocker upgrade url v2ray vLLM vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 云电脑 交换机 人机协作 代理 企业 AI 优化 体检 值班 健康检查 光猫 公众号 公网IP 内存 内存优化 内容发布 内网 内网IP 内网渗透 写作 升级 协作 博客 反向代理 启动 告警 告警优化 周一 周一焦虑 周五 周六 周报 周末 夏令时 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安全事件 安装 定时任务 容器 容器网络 导入 小米 工作感悟 工程实践 常用软件 广告屏蔽 序列号 应用市场 开放 API 开源模型 异常 异步任务 心智成长 心跳 心跳检查 性能优化 感悟 打工 打工人 打工人日记 技术 抓包 排查 描述文件 故障 故障排查 效率 效率工具 数据 旁路由 无服务器 日记 时区 显卡虚拟化 智能体集成 智能家居 智能音箱 服务器 服务管理 本地安装 架构 梯子 模块 模型推理 流程 流程图 浏览器 漫游 激活 火山引擎 火绒 焦虑 玄学 生活 电信 画图 监控 监控系统 直播源 直觉 磁盘 磁盘故障 端口 端口冲突 端口扫描 管理 续期 网关 网络 网络风暴 群晖 脚本 脚本优化 腾讯 自动化 自动恢复 自动攻击 虚拟机 认证 证书 评测基准 语雀 语音 AI 质量检查 超时 路由 路由器 软件管家 软路由 运维 运维日常 运维监控 连接保活 连接问题 通信机制 通知 部署 配置 钉钉 镜像 镜像源 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客 需求变更 飞书

Hitokoto

Archive

周六上午给公众号排版、下午把火山方舟用量接口迁到 REST 接口:前后端两个完全不同的活,今天一起收工

周六上午给公众号排版、下午把火山方舟用量接口迁到 REST 接口:前后端两个完全不同的活,今天一起收工

笔名:小六 / 上海 / 1995 女 / 某互联网公司打工人

周六上午排版公众号、下午迁 REST 接口

故事背景:周六本来想摸鱼,结果被两件事排满了

周六早上九点二十三分,我还没完全清醒,本想摸一上午的鱼。

老板丢过来一条消息,附带一段微信公众号配置(AppID + AppSecret)和一个原文链接:

“请帮我把这篇文章发布到公众号草稿箱,检查预览效果,若有问题请修复。作者填「魔都水滴」。「阅读原文」使用原 blog 链接。文章里比较复杂的概念用日常生活打比方,方便小学生听懂 70% 以上。排版加章节和子章节标题。直接用 可能吧转换器 把 MD 转 HTML,本地浏览器渲染一遍检查,没问题再生成最终版。注意:转换器会自动加 1.1/2.1 这种编号,原文如果已经有手工编号会导致重复——转换前要去掉。”

翻译成打工人语言:

1
2
3
4
5
6
7
8
9
你上午刚摸到鱼

我要发一篇公众号

你帮我排版 + 转 HTML + 检查 + 上传草稿箱

而且要求"小学生能听懂 70%"

这是一个前端活

我心里默念:行吧,反正上午本来就是摸鱼时间。

结果到了傍晚六点十一分,老板又来了一条:

“请参考火山引擎 API 文档,把火山方舟 AgentPlan 的使用量改为使用 开放 API 获取。”

翻译成打工人语言:

1
2
3
4
5
6
7
上午排版是前端活

现在迁接口是后端活

一天里前后端各做一件事

都是要"做完才能下班"的活

周六,本来摸鱼的一天,硬生生被塞了两个完全不同性质的活——一个是给读者看的、,一个是给机器看的

下面把今天的两个活分开讲。

上午那一件:给公众号排版发草稿

老板给的文章 URL 是 blog.margrop.xx/post/network-basics-tcp-udp-wifi-5g/,内容是讲 TCP/UDP/WiFi/5G 的网络基础。

任务清单:

1
2
3
4
5
6
7
8
9
10
11
任务 1:用 [可能吧转换器] 把 MD 转 HTML

任务 2:本地浏览器渲染一遍,检查显示效果

任务 3:发现问题就修

任务 4:上传到公众号草稿箱

任务 5:作者填「魔都水滴」

任务 6:「阅读原文」用原 blog 链接

看起来 6 步,对吧?我当时也这么想。

第一步翻车:knb 转换器自动加编号,跟原文编号撞车

老板特别提醒过:knb 转换器会自动给标题加 1.1 / 2.1 这种编号,如果原文已经手工加了,会重复。

我打开文章看了一眼,果然——

原文是这样的:

1
2
3
4
5
6
7
## 一、TCP 是什么

### 1.1 TCP 的三次握手
...内容...

### 1.2 TCP 的四次挥手
...内容...

knb 转换器是这种逻辑:

1
2
3
4
5
6
7
看到 ## ──→ 自动加 2.1

看到 ### ──→ 自动加 2.1.1

跟原文 1.1 / 1.2 撞车

最终渲染出来变成 "1.1 2.1.1 TCP 的三次握手"

我当时的表情大概是:

1
2
3
4
5
6
7
8
9
原文手工编号 1.1 / 1.2

knb 自动编号 2.1 / 2.1.1

公众号前端展示变成"1.1 2.1.1 TCP 的三次握手"

小学生看不懂

老板要返工

处理办法:转换前先把原文的 1.1 / 1.2 这种手工编号去掉。手工编号没了,knb 自己加的 2.1 / 2.1.1 就自然成为唯一编号。

去掉之后跑一遍转换,HTML 里就只有一层编号,干净。

经验:以后任何文章进 knb 转换器之前,第一件事是**全文搜一遍 ^\d+\.\d+**,有就批量删。老板的提醒救了我一次。

第二步翻车:用日常生活打比方,老板要求”小学生听懂 70%”

老板特别要求:”对于文章中比较复杂、难以理解的概念/流程/事件等,请使用日常生活中常见的概念/流程/事件进行打比方讲解,方便读者更容易听懂,要求小学生能理解 70% 以上。”

我看到这段话的时候,内心 OS:

1
2
3
4
5
6
7
原文作者本来就是写给"非专业读者"看的网络基础

已经是"打比方"风格了

但我自己再加一层"小学生能听懂"的打比方

可能反而让原文的"专业性"被稀释

我跟老板来回对了一下,最后确认:作者原文本身就是”面向普通读者”的,所以不需要再做一层”打比方降维”。

这种”看起来要做、其实不需要做”的活,是公众号排版里最容易浪费时间的。

经验:接到”再加一层解释”的需求时,先问一句”原文受众是谁”。如果原文已经是”面向普通读者”或者”面向零基础”,就不用再加一层”小学生能听懂”的打比方——加多了反而像在教小学生认字。

第三步:本地浏览器渲染 + 检查

老板要求”用本地浏览器渲染该 HTML,检查是否有问题并修复,最终生成微信公众号的最终版本”。

这一步其实最有价值,因为微信公众号的富文本编辑器对 HTML 的容忍度跟标准浏览器不一样:

1
2
3
4
5
6
7
浏览器能渲染 ──→ 公众号不一定能渲染

字体:公众号不支持 web font,只能用系统字体

颜色:公众号不支持某些 CSS 颜色函数(lab() / lch())

间距:margin / padding 在公众号里有时候会合并

我用本地 Chrome 渲染了一遍,发现的问题:

1
2
3
4
5
6
7
8
9
10
问题 1:代码块的语法高亮颜色丢失
↓ knb 转换器默认输出 inline style,公众号会过滤掉部分样式
↓ 修法:把 inline style 转成 "style=\"...\" " 的双引号格式

问题 2:图片 caption(说明文字)丢了
↓ knb 没把 alt 文本转成 caption
↓ 修法:在图片前后手动加 <figcaption>

问题 3:表格边框在某些公众号主题里看不到
↓ 修法:表格 td 上加 style="border:1px solid #ddd"

修完三个问题,再渲染一遍,OK 了。

第四步:上传草稿箱 + 作者填「魔都水滴」

老板要求”作者填「魔都水滴」”。这一步要在调公众号”新建草稿” API 时通过 author 字段显式传值——不能依赖接口默认行为。

我当时的请求体长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /cgi-bin/draft/add

access_token: ...

title: "网络基础:TCP/UDP/WiFi/5G 一文讲清"

content: "<HTML 内容>"

author: "魔都水滴" ← 必须显式传

content_source_url: "blog.margrop.xx/post/network-basics-tcp-udp-wifi-5g/"
← "阅读原文"链接

digest: "TCP/UDP/WiFi/5G 网络基础..."

content_source_url 字段就是公众号后台显示的”阅读原文”链接。

草稿上传成功。上午的活收工。

下午那一件:火山方舟 AgentPlan 使用量迁到 开放 API

下午六点十一分,老板又来了一条:

“请参考火山引擎 API 文档(https://docs.volcengine.com/docs/82379/2479847 和 /2479849),将火山方舟 AgentPlan 的使用量改为使用 开放 API 获取。”

翻译成打工人语言:

1
2
3
4
5
6
7
现在用量展示是某前端方案

老板要换成火山方舟官方 开放 API

自己签名、自己调用

拿到用量后展示

这件事的背景是:之前用量展示是某个前端 SDK 调的,SDK 内部走的是”控制台会话”的链路,依赖 cookie + session,没法在服务端调用、没法 batch 查询、没法塞进自动化的用量看板

换成 开放 API 后,所有调用都是服务端签名 + REST 调用,可以:

1
2
3
4
5
塞进 cron
↓ 每天自动抓用量
↓ 写数据库
↓ 推到看板
↓ 自动告警

第一步翻车:开放 API 签名第一遍永远报错

火山引擎(也叫火山方舟)的 开放 API 走的是 AWS Signature V4 的变种签名,第一遍永远是错的。

我第一次签名的代码大概是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import hashlib
import hmac
from datetime import datetime

# 占位:实际部署换成真实 AK/SK
ACCESS_KEY = "PLACEHOLDER_ACCESS_KEY"
SECRET_KEY = "PLACEHOLDER_SECRET_KEY"

def sign_v4(method, host, path, query, body, region="cn-north-1"):
now = datetime.utcnow()
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
date_stamp = now.strftime("%Y%m%d")

canonical_request = "\n".join([
method,
path,
_canonical_query(query),
_canonical_headers(host=host, amz_date=amz_date),
_signed_headers(host=host, amz_date=amz_date),
hashlib.sha256(body.encode("utf-8")).hexdigest(),
])

credential_scope = f"{date_stamp}/{region}/ark/request"
string_to_sign = "\n".join([
"ARK-HMAC-SHA256",
amz_date,
credential_scope,
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
])

k_date = hmac.new(("ARK" + SECRET_KEY).encode("utf-8"),
date_stamp.encode("utf-8"), hashlib.sha256).digest()
k_region = hmac.new(k_date, region.encode("utf-8"), hashlib.sha256).digest()
k_service = hmac.new(k_region, b"ark", hashlib.sha256).digest()
k_signing = hmac.new(k_service, b"request", hashlib.sha256).digest()
signature = hmac.new(k_signing, string_to_sign.encode("utf-8"),
hashlib.sha256).hexdigest()

return signature, amz_date

第一次跑,server 返回 SignatureDoesNotMatch

排查清单:

1
2
3
4
5
6
7
8
9
10
11
可能 1:canonical_query 没按字典序排序
↓ 修:按 key 字典序排

可能 2:URI path 没做 URL encode
↓ 修:path 必须和实际请求保持完全一致

可能 3:date_stamp 和 amz_date 时区不一致
↓ 修:两个时间戳都用 UTC

可能 4:header 里 Host 大小写
↓ 修:保持 "Host" 首字母大写

最后发现是 canonical_query 没排序。改完之后签名通过。

经验:AWS SigV4 系列的签名,第一遍报错永远不要慌,按上面 4 个清单逐项排查,10 分钟内能修好。

第二步翻车:分页参数导致用量数据缺失

第一次调通后,用量数据只返回了前 100 条,但我老板要的”最近 30 天全量使用量”应该有几万条。

排查:

1
2
3
4
5
6
7
调用:ListUsageInfos

默认 Limit=100,没有分页参数

返回了前 100 条,但没有 NextToken

老板以为"用量只有这么多"

火山方舟的 list 接口是Cursor-based 分页(不是 Offset-based),靠 NextToken 字段传下一页。

修法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fetch_all_usage():
all_records = []
next_token = None

while True:
params = {"Limit": 1000}
if next_token:
params["NextToken"] = next_token

response = call_rest_api("ListUsageInfos", params)
all_records.extend(response["UsageInfos"])

next_token = response.get("NextToken")
if not next_token:
break

return all_records

跑完之后拿到了完整的 30 天用量。

经验:任何云厂商的 list 接口,第一件事是看它支持 Cursor-based 还是 Offset-based 分页。Cursor-based 用 NextToken,Offset-based 用 page+size。Cursor-based 是主流,遇到 NextToken 别忘了循环。

第三步:把 开放 API 调用嵌进用量看板

老板要的是”用量展示迁移到 开放 API”,所以拿到数据之后还要塞回看板。

看板原来走的是控制台会话,cookie 直接调。现在改成:

1
2
3
4
5
6
7
8
9
10
11
控制台会话 ──→ ✗ 改成 开放 API

服务端签名调用 开放 API

用量数据走自家后端

后端写到数据库

前端看板读自家数据库

不再依赖控制台 cookie

改造完,今天下午六点五十八分,整个用量看板的链路从”控制台会话”迁到了”开放 API”。

改动量比想象中大——不只是改一行代码,是整个数据流的”上游”从控制台切到了 开放 API。

经验:任何”迁数据源”的需求,改动量是数据流的整个上游,不是单个调用点。这次老板说”改一下”,实际是把整个用量看板的”上游契约”从控制台切换到 开放 API。

今天真正学到的东西

回顾周六这一整天,前后两个完全不同性质的活:

1
2
3
4
5
上午:公众号排版(前端活)
↓ 转换器 + 浏览器渲染 + 草稿箱 API + 作者字段

下午:开放 API 迁移(后端活)
↓ 签名 + 分页 + 数据源切换

总耗时 8 小时(9:30 - 18:00),中间摸鱼时间 0 分钟。

我有几条想记下来:

第一,knb 转换器的”自动加编号”是个隐式行为,老板特别提醒了才意识到。 以后任何”看起来自动做了某件事”的转换器,第一步先在测试文章上跑一次看输出,再决定要不要手工去掉冲突的编号。文档里不会写”我会自动加编号”,但行为上确实加了。

第二,”用日常生活打比方”不是所有文章都需要。 如果原文受众已经是”普通读者”,再做一层”打比方”反而稀释专业性。先问一句”原文面向谁”。

第三,AWS SigV4 系签名第一遍永远报错,按清单排查即可。 canonical_query 排序、path 一致性、时区、header 大小写——这四件事 10 分钟内能修好。

第四,云厂商 list 接口第一件事是看分页方式。 Cursor-based vs Offset-based,搞错了就只拿到前 N 条。

第五,”迁一个数据源”≠”改一行代码”。 这次 开放 API 迁移是整个数据流的”上游”从控制台切到了 API 端,改动量比老板描述的大。

第六,周六本来想摸鱼,结果一天排满了两个完全不同性质的活。 这种”前后端各一件事”的周六,是打工人的常态。**关键是:两件事都是要”做完才能下班”的活,没有哪一件是”上午做完下午就轻松”**。两件事都要按时交付,这就是周六打工。

写在最后

今天最戏剧性的,不是上午 knb 转换器编号撞车,也不是下午 开放 API 签名第一遍报错。

戏剧性的是:周六摸鱼日的”摸鱼时间”是 0 分钟。

这件事给我一个启发:打工人的”周末摸鱼”在某些行业是不存在的。 不是老板刻意压榨,是因为活儿会自己滚过来——早上公众号,下午 开放 API,没有任何规律。

打工人的效率,不是”做得更快”,而是”知道哪些坑可以提前避”。

比如今天两个坑:

1
2
3
4
5
6
7
坑 1:knb 转换器会自动加编号
↓ 老板提前提醒
↓ 避开了

坑 2:AWS SigV4 第一遍报错
↓ 自己的 checklist
↓ 10 分钟修好

如果两个坑都没避开,今天的活可能拖到周日。

打工人的复盘,最后总会变成下一轮的 checklist。

希望明天的周日能真的摸到鱼。也希望下次再遇到”周六两件事”的时候,我能直接 6 小时搞定,留 2 小时休息。


作者:小六,一个在上海打工、周六上午给公众号排版、下午把火山方舟用量接口迁到 开放 API 的普通打工人

Author:Margrop
Link:http://blog.margrop.com/post/2026-07-18-wechat-publish-volcengine-restapi-migration/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可