content-forge/personal-context/我研究了OpenClaw的8个_反常识_设计,终于明白这个Agent为什么能火爆全球.md

351 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

 
![图片](https://mmbiz.qpic.cn/sz_mmbiz_png/jRAcOqhkFFeyr02IRIwgtEcB7yAY0PnfKdnm155Vxw4BkqQE8VUbBGo17xMlqbw2VR9sLQlVDLNnO5ibv3WhRFCxkzj35l11Xjr2OI9UQePQ/640?wx_fmt=png&from=appmsg&watermark=1&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=0)
做Agent开发这两年踩过的坑不少。[做了两年AI Agent我发现99%的AI Agent项目都死在了Message Flow设计上](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247484194&idx=1&sn=5db2099f5930874735b8e44ae12964de&scene=21#wechat_redirect)
LangChain的抽象层确实强大但复杂度也上来了。AutoGPT的循环控制一直是个难题。自己搭框架维护成本摆在那。
OpenClaw走红后就第一时间部署在海外vps上每天用下来确实不全是吹牛逼是有真东西。
![图片](https://mmbiz.qpic.cn/sz_mmbiz_png/jRAcOqhkFFfNeHPKXVxZRJHb3YIzoZAyC0yGIqricUbxB5gMT40j11G3NYaBzqDI1rPK9NdmyGTbS2G0PI2PZURwOtricicqyosoB15sTiaC6Pw/640?wx_fmt=png&from=appmsg&watermark=1&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=1)
于是开始研究OpenClaw的源码和技术架构感觉自己像回到了刚毕业写代码那会儿——很多设计第一眼看过去觉得简直是“乱来”。
文件系统当数据库、无锁并发、插件化Channel...第一眼看上去有点"反常识"。
但转念一想结合我自己做AtomStorm这几个月的教训[全球首个Skills Vibe AgentsAtomStorm技术揭秘我是怎么用Context Engineering让Agent不"变傻"的](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247485151&idx=1&sn=13206f54618166c276507e91e322ccab&scene=21#wechat_redirect),即使我不完全认同它的每一个选择,我必须承认:
这些设计背后的工程直觉,太精准了,恰到好处。
今天就拆解这8个设计聊聊它们为什么能跑通。
* * *
## Part 1文件系统当数据库——最"蠢"的方案反而最聪明
第一次看到OpenClaw用文件系统做持久化我的第一反应是"这不是开倒车吗?"
2024年了谁还用文件系统存数据Redis、PostgreSQL、MongoDB...一堆成熟方案摆在那,为什么要回到"石器时代"
但深入研究后,我发现这个设计简直是天才。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
### 传统方案的隐藏成本
我们先看传统Agent框架怎么做持久化
```
用户消息 → 序列化 → 写入数据库 → ORM映射 → 缓存层 → 读取 → 反序列化 → 喂给LLM
```
这条链路看起来"专业",但每一步都在增加复杂度:
- • 数据库连接池管理
- • ORM的N+1查询问题
- • 缓存一致性维护
- • 序列化/反序列化开销
- • 数据库迁移脚本
更要命的是这些数据对人类完全不可读。你想看Agent到底记住了什么对不起先写SQL查询再解析JSON。
### OpenClaw的文件系统方案
OpenClaw直接把记忆存成Markdown文件
```
agent_memory/├── SOUL.md          # Agent的核心身份├── USER.md          # 用户画像├── MEMORY.md        # 短期记忆└── memory/    ├── 2024-01-15-project-discussion.md    └── 2024-01-16-code-review.md
```
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
这个设计的天才之处在于:
**1\. 人类可读**
打开`SOUL.md`你能直接看到Agent的完整人格设定。不需要写SQL不需要解析JSON直接用任何文本编辑器就能看。
**2\. Git友好**
所有记忆都是纯文本可以直接用Git管理。你能看到Agent的"记忆演化史",甚至可以回滚到某个历史状态。
**3\. 零依赖**
不需要安装数据库,不需要配置连接池,不需要写迁移脚本。复制文件夹就是完整备份。
**4\. OS级别的一致性保证**
文件系统的锁机制是操作系统级别的,比你自己写的分布式锁靠谱得多。
### 这个方案的适用边界
当然文件系统不是银弹。如果你的Agent需要
- • 每秒处理上万次查询
- • 复杂的关联查询
- • 分布式部署
那还是老老实实用数据库。
但对于大多数Agent应用场景——个人助理、项目管理、知识库——文件系统完全够用而且更简单。
**简单就是最大的优势。**
* * *
## Part 2Lane-Based并发——无锁设计的工程美学
如果说文件系统是"反常识"那Lane并发模型就是"反直觉"。
传统的并发控制,都是围绕"锁"展开的。多个线程要访问共享资源?加锁。担心死锁?加超时。担心性能?加读写锁。
结果就是代码里到处都是`lock.acquire()`和`lock.release()`,稍不注意就死锁,调试起来简直是噩梦。
OpenClaw的Lane模型直接把"锁"这个概念消灭了。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
### 传统线程池的问题
我们先看传统方案:
```
# 传统线程池伪代码global_queue = Queue()global_lock = Lock()def worker():    while True:        task = global_queue.get()        with global_lock:  # 每个任务都要抢锁            process(task)
```
这个模型的问题在于:
- • 所有任务共享一个队列,必须加锁
- • 锁的粒度难以控制(太粗影响性能,太细容易死锁)
- • 主会话和子会话混在一起,互相干扰
我在AtomStorm早期就踩过这个坑。用户在主会话里聊天同时触发了一个深度研究的子任务。结果子任务的高负载直接把主会话拖垮了用户等了30秒才看到回复。
### OpenClaw的Lane隔离方案
OpenClaw的做法是**物理隔离,各自调度**。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
Main Lane处理主会话Sub Lane处理子任务。两个Lane完全独立各自维护自己的任务队列互不干扰。
这个设计的精妙之处在于:
**1\. 消除了特殊情况**
不需要判断"这个任务是主会话还是子会话",不需要加锁,不需要处理死锁。数据结构本身就保证了隔离。
**2\. 资源保护**
主会话永远有保底的并发资源。哪怕Sub Lane上跑着10个深度研究任务用户在主会话里发消息依然能秒回。
**3\. 可配置的弹性**
Main Lane和Sub Lane的并发数可以独立调整。你的Agent主要做对话把Main调大。主要跑后台任务把Sub调大。怎么调把配置文本直接发给它就可以了参考下面
```
Main=4, Sub=8   → 偏对话场景,~50 req/minMain=8, Sub=16  → 均衡场景,~100 req/minMain=16, Sub=32 → 偏后台任务,~200 req/min
```
Linus Torvalds说过一句话"好的设计通过数据结构消除特殊情况而非通过if/else补丁。"
Lane模型就是这句话的完美实践。
* * *
## Part 3协议归一化——消灭if/else地狱
做过多平台接入的人,一定对这种代码深恶痛绝:
```
if (platform === 'feishu') {const text = msg.content.text;const userId = msg.sender.user_id;sendFeishuReply(userId, response);} elseif (platform === 'slack') {const text = msg.text;const userId = msg.user;sendSlackReply(userId, response);} elseif (platform === 'discord') {const text = msg.content;const userId = msg.author.id;sendDiscordReply(userId, response);}// 每加一个平台,这坨代码就膨胀一次...
```
每个平台的消息格式不同字段名不同API不同。你的核心业务逻辑被平台差异"污染"了改一个功能要改N个分支。
OpenClaw的Channel系统用一层薄薄的适配层把这个问题彻底解决了。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
每个Channel插件只做一件事把平台特定格式转成统一格式。
```
// OpenClaw的做法伪代码// Feishu Channel插件convertToUnifiedFormat(rawMsg) {  return {    type: 'text',    content: rawMsg.content.text,    sender: { id: rawMsg.sender.user_id }  }}// Gateway核心代码 —— 永远不需要改const message = channel.convertToUnifiedFormat(rawMessage);gateway.handleMessage(message);
```
新增一个平台写一个Channel插件`npm install`装上,完事。核心代码一行不动。
这个设计还有一个很妙的细节:**能力声明**。
每个Channel会主动告诉Gateway自己支持什么能力——文本、图片、文件、语音。Gateway根据能力自动降级。比如Agent想发一张图但当前Channel只支持文本Gateway会自动把图片转成描述文字。
不需要你写一行判断逻辑。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
* * *
## Part 4另外5个让我放弃抵抗的设计
前面三个是我觉得最"反常识"的接下来快速过一下另外5个同样精彩的设计。
### 4.1 多Provider模型系统——模型切换像换衣服
OpenClaw支持Anthropic、OpenAI、BigModel等多个Provider切换模型只需要改一个字符串
```
# 从Claude切到GPT改一行配置model: "anthropic/claude-4.5-sonnet"# ↓model: "openai/gpt-5.2"
```
背后是一套三级解析机制:直接引用 → 别名查找 → Provider查找。运行时热切换不需要重启。
这意味着你可以在不同任务上用不同模型——复杂推理用Claude简单对话用GPT代码生成用DeepSeek——而核心代码完全不用改。
### 4.2 自描述的Skill系统——代码即文档
传统插件系统有个老大难问题:文档和代码分离,文档永远是过时的。
OpenClaw的Skill系统要求每个Skill自带一个`SKILL.md`放在代码同目录下。这个文件不是给人看的装饰品——Agent会读它来理解这个Skill能做什么、怎么调用。
```
skills/├── deep_research/│   ├── SKILL.md        ← Agent读这个来决定是否调用│   └── src/index.ts    ← 实现代码├── ppt_creation/│   ├── SKILL.md│   └── src/index.ts
```
文档和代码在同一个目录改了代码必须同步改文档否则Agent就会调用出错。这种"强制绑定"比任何代码审查规范都管用。
而且Skill在沙箱环境里执行一个Skill崩了不会影响其他Skill。
### 4.3 配置热更新——改完1秒生效
传统方案改配置要重启服务OpenClaw用`fs.watch`监听配置文件变更,检测到变化后自动热重载。
```
修改配置 → fs.watch检测 → 停止旧Channel → 重新加载 → 启动新Channel                └──────── < 1秒完成 ────────┘
```
配置文件用Zod做类型校验写错了直接报错不会带着错误配置启动。
### 4.4 可控的Agent执行循环——防止失控
Agent最怕的就是陷入无限循环。OpenClaw用`maxTurns`做硬限制:
```
Turn 1: 调用工具A → 成功Turn 2: 调用工具B → 成功Turn 3: 调用工具C → 失败,重试...Turn 10: 达到maxTurns → 强制停止,返回当前结果
```
同时支持流式响应用户能实时看到Agent在做什么不用干等。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
### 4.5 Cron/Heartbeat——Agent终于有了"主动性"
传统Agent都是被动的——你问它才答。OpenClaw给Agent加了定时任务能力
- • **Cron**精确时间触发比如每天10点生成日报
- • **Heartbeat**:条件触发,每小时检查一次,只在需要时执行
而且支持三种唤醒模式:立即执行、下次心跳执行、手动触发。重启后执行状态不丢失。
这让Agent从"被动应答机器"变成了"主动工作伙伴"。
* * *
## Part 5这些设计背后的工程哲学
研究完这8个设计我发现OpenClaw的架构决策背后遵循了软件工程的优秀设计哲学作者是无疑是资深程序员。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
### Good Taste消除特殊情况
Linus Torvalds有句名言"好的设计通过数据结构消除特殊情况而非通过if/else补丁。"
OpenClaw的Lane模型、Channel系统、Provider抽象都是这个哲学的体现。不是用代码去"处理"差异,而是用数据结构让差异"消失"。
### 简洁执念:复杂性是万恶之源
能用文件系统就不用数据库,能用配置就不写代码,能用插件就不改核心。
每个模块都保持在可理解的复杂度内——函数不超过50行嵌套不超过3层文件不超过1000行。
### Never Break Userspace向后兼容是铁律
配置格式演进时新版本兼容旧格式。API扩展时不破坏现有接口。插件更新时支持热更新。
这让OpenClaw可以持续演进而不会让用户的代码突然跑不起来。
### 数据结构优先:设计决定实现
先设计Session Key的格式`platform:user:chat`再设计路由逻辑。先设计统一消息格式再设计Channel适配。
好的数据结构让算法自然简洁,糟糕的数据结构让代码充满补丁。
* * *
## Part 6OpenClaw适合你吗
看到这里你可能会问OpenClaw这么好是不是所有场景都适合
答案是:不是。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
OpenClaw的设计哲学是**为80%的常见场景提供最优雅的解决方案而不是为100%的场景提供平庸的方案。**
如果你的需求刚好落在那80%里OpenClaw会让你觉得"这就是我想要的"。如果落在那20%里,也别硬上,选择更合适的工具。
* * *
## 写在最后从OpenClaw到AtomStorm
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
研究完OpenClaw我对Agent架构的理解又深入了一层。
以前我觉得Agent开发的核心是Prompt Engineering——怎么写好System Prompt怎么设计Few-shot。
现在我的认知,**架构设计才是决定Agent能力上限的关键**。
再好的Prompt也救不了一个Context爆炸的Agent。再强的模型也发挥不出在一个充满if/else的系统里的能力。
这也是为什么我在做AtomStorm时花了大量时间在Context Engineering和架构设计上。
AtomStorm对比OpenClaw加入了
- • **Context Engineering**分层注入、工具意图预判、Just-in-Time RAG
- • **Skills Vibe Agents**:渐进式披露、按需加载、二级分发
- • **流式渲染**实时生成PPT和架构图状态分离
- • **MCP深度集成**20+个MCP工具智能路由
本文的配图均由AtomStorm生成。
如果你对Agent这个方向感兴趣欢迎来试试AtomStorm**studio.atomstorm.ai**目前开放少量内测名额计划本月正式上线海外私信我或者加群获取。为保证群质量其他两个技术交流的群超过200人会有50的红包门槛介意勿加。
![图片](data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='1px' height='1px' viewBox='0 0 1 1' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3E%3C/title%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' fill-opacity='0'%3E%3Cg transform='translate(-249.000000, -126.000000)' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
* * *
**我是栗子KK还在路上的AI产品Founder。studio.atomstorm.ai**
  山远路险,鞋里有沙。
这篇文章如果对你有帮助,哪怕是解决了一个小疑惑,也麻烦点个"在看"或者转发给同样对OpenClaw感兴趣的朋友。
创作不易,感谢阅读 🙏\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
往期精彩推荐:
[AI编程正式进入"团战时代"Claude Code Agent Teams我等了两年的功能终于来了](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247485253&idx=1&sn=0722908710926fbfa1b59b0507f103fa&scene=21#wechat_redirect)
[全球首个Skills Vibe AgentsAtomStorm技术揭秘我是怎么用Context Engineering让Agent不"变傻"的](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247485151&idx=1&sn=13206f54618166c276507e91e322ccab&scene=21#wechat_redirect)
[4个月烧了2万刀Token全球首款Skills Vibe Agent终于开启邀请内测我也终于敢说Sam Altman预言的超级个体可能真的来了](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247485129&idx=1&sn=efea9accb62a7502b464fa872b1cacdd&scene=21#wechat_redirect)
[做了两年AI Agent我发现99%的AI Agent项目都死在了Message Flow设计上](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247484194&idx=1&sn=5db2099f5930874735b8e44ae12964de&scene=21#wechat_redirect)
[精准爆破拆解Claude Skills完整技术架构](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247484919&idx=1&sn=e2d548fad5501c6dd2e763aa732938e9&scene=21#wechat_redirect)
[ClaudeCode工程师亲述为什么你的AI Agent总是"智障"?问题可能出在工具设计上](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247484568&idx=1&sn=01e06b28c3c8d126d35a8390cfadeb00&scene=21#wechat_redirect)
[Claude 多智能体架构深度拆解90.2%性能提升背后的工程真相](https://mp.weixin.qq.com/s?__biz=Mzg4MzAzNTA5Ng==&mid=2247484579&idx=1&sn=b0985523386d3550fe8ef2ae4554cab6&scene=21#wechat_redirect)