,  ![图片](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 Agents,AtomStorm技术揭秘:我是怎么用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 2:Lane-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 6:OpenClaw适合你吗? 看到这里,你可能会问: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 Agents,AtomStorm技术揭秘:我是怎么用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)