观察者模式:加一种通知渠道就要回来改老代码?这回真不用了
作者鱼宵 「Java 后端实战精通营」实战驱动系列・第 6 篇完整可运行工程已开源https://gitee.com/j67mk2/design-patterns一、真实场景老板说加个微信通知你又要动那个老方法第 1 篇给外卖订单烂代码做体检时我标过 6 处坏味道第 4 处就是它// 第 1 篇坏味道 4发通知的 if 连锁if(order.needSms){发短信}if(order.needEmail){发邮件}if(order.needAppPush){App推送}当时我在结尾预告过第 6 课用观察者模式把这条 if 连锁拆掉。这篇就来兑现。先重温一下这个痛点有多真实。老板说加个微信通知吧你在通知那一节又补一个if (order.needWechat)第二周要给数据平台埋个点你又打开同一个方法再补一段。问题出在哪订单服务认识每一个通知渠道——发短信、发推送、埋点每加一个动作下单这个核心方法就要回来动一次。你改埋点的时候手抖碰坏短信分支线上就炸。观察者模式干的事一句话让订单服务只管喊一声订单成了谁关心这件事、谁自己来登记订单服务一个都不认识。代码全在design-patterns仓库的lesson-06工程里clone 下来跑一遍加微信通知、订单服务零改动这个结论你自己就能验证。二、核心原理公众号你天天关注背后就是这个模式2.1 观察者模式四要素观察者模式Observer Pattern定义对象间一对多的依赖——一个对象状态变了所有依赖它的对象自动收到通知。发布者不知道订阅者是谁订阅者自己来登记。类比就是微信公众号你点关注订阅公众号发文章时自动推给你——你不用天天跑去问更新了吗。公众号也不认识你是谁它只管发。对照本课的外卖订单四个角色是这样分工的角色本课对应类干什么主题 / 事件总线 SubjectEventBus维护订阅者名单提供订阅和广播观察者 ObserverOrderEventListener 接口 三个实现登记过的通知通道事件来了各自处理事件对象 EventOrderEvent只装数据订单号、金额不装行为发布者 PublisherOrderService订单落库后喊一嗓子不认识任何监听器关键解耦点OrderService里没有任何要不要发短信的 if只有一句eventBus.publish(...)。短信、邮件、推送是三个互相不认识的独立类自己向总线登记。2.2 为什么这就叫开闭了回到第 1 篇讲的开闭原则对扩展开放、对修改关闭。以前加微信通知改OrderService.processOrder()加一个 if。动老代码 冒着碰坏短信分支的风险。现在加微信通知新写一个WechatListener类在 Main 里subscribe一下。订单服务一行不碰。扩展走的是加新代码动订单服务的次数归零——加渠道 加监听器 登记发布端零改动。2.3 两个高频区别面试常考观察者 vs 发布-订阅观察者模式发布-订阅模式通信方式主题直接调用观察者的方法中间隔一个 Broker / MQ两边互不知道进程同一个 JVM 内同步直连跨进程、跨服务通常异步本课对应手写 EventBusRocketMQ / Kafka概念层一句话观察者是同一个屋子里我喊一嗓子在场的都听见发布-订阅是我把信扔进邮筒邮筒负责送给各个收件人。手写 EventBus 是观察者RocketMQ 是发布-订阅。JDK 自带的 Observable 呢JDK 早期给过一个java.util.Observable类和Observer接口就是观察者模式但早就废弃了——破坏封装、没有事件对象、线程模型混乱。知道它存在就行新项目别用。三、关键代码8 个类看懂 EventBus 就全懂了本课工程在lesson-06/src/main/java/com/dp/lesson06/先贴最小的两个——事件对象和观察者接口packagecom.dp.lesson06;/** * 下单成功事件事件对象只装数据不装行为。 * 事件要瘦别把整个 Order 连数据库连接一起塞过去耦合又回来了。 */publicclassOrderEvent{/** 订单号 */privatefinalStringorderId;/** 实付金额元 */privatefinaldoubleamount;publicOrderEvent(StringorderId,doubleamount){this.orderIdorderId;this.amountamount;}publicStringgetOrderId(){returnorderId;}publicdoublegetAmount(){returnamount;}}packagecom.dp.lesson06;/** * 订单事件监听器观察者接口每个关心下单成功的模块实现它。 */publicinterfaceOrderEventListener{/** 事件来了自己处理自己的事处理完就走互不干扰 */voidonEvent(OrderEventevent);/** 监听器名字演示打印用让大家看到谁收到了事件 */Stringname();}然后是主角——事件总线整个模式的骨架就 40 行packagecom.dp.lesson06;importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;/** * 简易事件总线主题角色就干三件事——订阅、取关、广播。 */publicclassEventBus{/** 订阅者名单所有关心下单成功的模块都登记在这里 */privatefinalListOrderEventListenerlistenersnewCopyOnWriteArrayList();/** 关注把监听器加进名单 */publicvoidsubscribe(OrderEventListenerlistener){listeners.add(listener);System.out.println( [EventBus] 新订阅者listener.name());}/** 取关把监听器移出名单 */publicvoidunsubscribe(OrderEventListenerlistener){listeners.remove(listener);System.out.println( [EventBus] 取消订阅listener.name());}/** 发布事件遍历所有订阅者挨个回调。订单服务只调这一行。 */publicvoidpublish(OrderEventevent){System.out.println( [EventBus] 发布事件订单 event.getOrderId()依次通知 listeners.size() 个订阅者);for(OrderEventListenerlistener:listeners){// 同步挨个调用故意不开线程保证输出顺序固定可复现listener.onEvent(event);}}}这段代码讲了什么subscribe存名单publish遍历名单挨个调onEvent。注意publish里没有任何 if——登记过的都通知没登记的自然不在名单里。为什么用CopyOnWriteArrayList而不是普通ArrayList遍历通知时如果有别的线程在注册/取关普通 List 会炸ConcurrentModificationException这个类就是防这个的。三个通知通道是平级实现几乎同款合起来贴给你packagecom.dp.lesson06;/** 短信监听器订单成功后发短信观察者的具体实现之一 */publicclassSmsListenerimplementsOrderEventListener{OverridepublicvoidonEvent(OrderEventevent){System.out.println(String.format( [短信] 给用户发短信订单 %s 已受理实付 %.2f 元,event.getOrderId(),event.getAmount()));}OverridepublicStringname(){return短信通道;}}packagecom.dp.lesson06;/** 邮件监听器和短信监听器平级互不知道对方存在 */publicclassEmailListenerimplementsOrderEventListener{OverridepublicvoidonEvent(OrderEventevent){System.out.println(String.format( [邮件] 给用户发邮件订单 %s 的详情已生成实付 %.2f 元,event.getOrderId(),event.getAmount()));}OverridepublicStringname(){return邮件通道;}}packagecom.dp.lesson06;/** App 推送监听器订单成功后推通知 */publicclassAppPushListenerimplementsOrderEventListener{OverridepublicvoidonEvent(OrderEventevent){System.out.println(String.format( [App推送] 推送通知订单 %s 有新进展,event.getOrderId()));}OverridepublicStringname(){returnApp推送通道;}}最后是重构后的订单服务——跟第 1 篇的坏代码对比着看packagecom.dp.lesson06;/** * 订单服务发布者落库后发布一个事件不认识任何通知渠道。 */publicclassOrderService{/** 依赖的是总线这个抽象不是任何具体通知渠道 */privatefinalEventBuseventBus;publicOrderService(EventBuseventBus){this.eventBuseventBus;}publicvoidcreateOrder(StringorderId,doubleamount){// 1. 校验和第 1 课一样if(orderIdnull||orderId.isEmpty()){thrownewIllegalArgumentException(订单号不能为空);}// 2. 落库模拟只打印System.out.println(String.format([订单服务] 订单 %s 已落库金额 %.2f 元,orderId,amount));// 3. 发布事件没有任何发短信发邮件的 if关心的模块自己来听eventBus.publish(newOrderEvent(orderId,amount));System.out.println([订单服务] 订单 orderId 创建完成);}}对比第 1 篇坏代码原来下单后面跟着if (needSms) / if (needEmail) / if (needAppPush)三个 if现在只剩一行publish。Main 入口很简单——先订阅三个通道再下两单packagecom.dp.lesson06;publicclassMain{publicstaticvoidmain(String[]args){System.out.println( 第 6 课观察者模式——下单成功自动通知 );// 1. 总线是公众号三个通道是粉丝先关注好EventBusbusnewEventBus();bus.subscribe(newSmsListener());bus.subscribe(newEmailListener());bus.subscribe(newAppPushListener());System.out.println();// 2. 订单服务只依赖总线不认识任何通知渠道OrderServiceorderServicenewOrderService(bus);// 3. 下两单每单发布一次事件三个监听器各自响应System.out.println(-- 创建订单 A3001 --);orderService.createOrder(A3001,130.00);System.out.println();System.out.println(-- 创建订单 A3002 --);orderService.createOrder(A3002,70.40);System.out.println();System.out.println( 结束加微信通知新增一个监听器并 subscribe订单服务零改动 );}}四、完整演示 实测输出clone 下来后在lesson-06目录执行Windows PowerShellcd C:\你的clone路径\design-patterns-journey\lesson-06$env:JAVA_HOMEC:\jdk\jdk-17.0.12mvn clean install-DskipTests实测输出[INFO] BUILD SUCCESS [INFO] Total time: 1.214 s编译通过后运行中文乱码就先执行[Console]::OutputEncoding[System.Text.Encoding]::GetEncoding(936)C:\jdk\jdk-17.0.12\bin\java.exe-cp.\target\classescom.dp.lesson06.Main实测输出 第 6 课观察者模式——下单成功自动通知 [EventBus] 新订阅者短信通道 [EventBus] 新订阅者邮件通道 [EventBus] 新订阅者App推送通道 -- 创建订单 A3001 -- [订单服务] 订单 A3001 已落库金额 130.00 元 [EventBus] 发布事件订单 A3001依次通知 3 个订阅者 [短信] 给用户发短信订单 A3001 已受理实付 130.00 元 [邮件] 给用户发邮件订单 A3001 的详情已生成实付 130.00 元 [App推送] 推送通知订单 A3001 有新进展 [订单服务] 订单 A3001 创建完成 -- 创建订单 A3002 -- [订单服务] 订单 A3002 已落库金额 70.40 元 [EventBus] 发布事件订单 A3002依次通知 3 个订阅者 [短信] 给用户发短信订单 A3002 已受理实付 70.40 元 [邮件] 给用户发邮件订单 A3002 的详情已生成实付 70.40 元 [App推送] 推送通知订单 A3002 有新进展 [订单服务] 订单 A3002 创建完成 结束加微信通知新增一个监听器并 subscribe订单服务零改动 输出解读开头三行新订阅者就是三个监听器向总线登记。每个订单里订单服务只打印了已落库和创建完成中间那段通知是 EventBus 广播出来的——它全程不知道短信、邮件、推送的存在。五、挑战题改参数会怎样答案在源码里⭐ 在 Main 里把短信监听器存成变量SmsListener sms new SmsListener();先bus.subscribe(sms)下完一单后再bus.unsubscribe(sms)接着下一单——短信那行还打不打控制台会多出一行什么打印⭐⭐ 在EmailListener.onEvent第一行加一句throw new RuntimeException(邮件服务炸了)重跑——短信和 App 推送还打不打主线程序会以什么结束想明白了再给EventBus.publish的 for 循环里每个监听器调用包一层 try-catch修复后再跑输出变成什么样⭐⭐ 把EventBus.publish里的同步调用改成裸线程new Thread(() - listener.onEvent(event)).start()再跑一遍——三路通知的到达顺序还固定吗为什么生产不建议这么裸开线程三题答案都在lesson-06源码里clone 下来改一行就能验证。跑出来的结果和你想的不一样评论区贴出来一起查。六、生产进阶手写 EventBus 讲原理Spring 事件总线才是饭碗我们手写这个 EventBus 是为了看清骨架。生产代码里没人这么写——Spring 早就把这套东西做成现成的了// 发布端业务服务注入 ApplicationEventPublisherComponentpublicclassOrderService{AutowiredprivateApplicationEventPublisherpublisher;publicvoidcreateOrder(StringorderId,doubleamount){// 落库...publisher.publishEvent(newOrderCreatedEvent(orderId,amount));}}// 订阅端方法参数类型 关心的事件类型ComponentpublicclassSmsListener{EventListenerAsync// 异步不阻塞下单主流程TransactionalEventListener(phaseTransactionPhase.AFTER_COMMIT)publicvoidonSms(OrderCreatedEventevent){// 发短信}}三个加分点面试答出来就是真用过和背过的区别为什么要AFTER_COMMIT下单是个带事务的操作。事务还没提交就发事件监听器去查库可能查不到这条订单更糟的是用户下单后又退款了短信已经发出去了。AFTER_COMMIT保证数据库落定了再通知。异常隔离手写 EventBus 同步遍历第 2 个监听器炸了第 3 个直接收不到挑战题 2 让你亲手踩。Spring 可以配ErrorHandler把每个监听器隔离开。源码层面就一句话Spring 的SimpleApplicationEventMulticaster.multicastEvent()就是遍历所有监听器挨个调用——和你手写的for (l : listeners) l.onEvent(e)骨架完全一样有线程池就扔异步。还有一个容易被忽略的坑监听器不注销会内存泄漏。事件总线是长生命周期的单例它持有监听器的强引用页面、请求级别的短命监听器用完不unsubscribeGC 永远回收不掉它。生产里要么用 Spring 管监听器生命周期和容器一致要么用完主动取关。选型一句话单机内解耦用 Spring 事件总线跨服务、要削峰、要持久化上 RocketMQ / Kafka。七、面试回答模板Q1观察者模式怎么实现发布者不知道订阅者是谁一句话结论主题维护一份订阅者名单发布者只往主题里扔事件主题遍历名单挨个回调——发布者从头到尾没见过任何订阅者。展开四角色是主题、观察者、事件对象、发布者事件对象要瘦只传必要数据。本课lesson-06的 EventBus 就是手写骨架。Q2观察者模式和发布-订阅是一回事吗一句话结论不是——观察者同进程同步直连发布-订阅中间隔 Broker 跨进程异步。展开手写 EventBus 是观察者RocketMQ/Kafka 是发布-订阅前者解耦模块协作后者解耦服务通信。Q3EventListener和TransactionalEventListener有什么区别一句话结论前者事件一发布就触发后者可以指定在事务的哪个阶段触发AFTER_COMMIT最常用。展开事务没提交就发事件监听器查库查不到、退款了短信已发AFTER_COMMIT就是防这个。Q4为什么教学手写 EventBus生产却用 Spring 事件总线一句话结论手写是为了看清注册 遍历广播的骨架生产要的事务阶段控制、异常隔离、异步执行器手写版都没有。展开Spring 把这三样全给了所以生产直接用注解单机解耦用事件总线跨服务上 MQ。八、总结表坑原因解法加一种通知渠道要回来改订单服务发布端认识每个渠道耦合死观察者加监听器 订阅发布端零改动一个监听器抛异常后面全收不到同步遍历没做异常隔离监听器内部自己 catch或配 Spring ErrorHandler遍历时注册/取关炸 ConcurrentModificationException普通 List 一边遍历一边改订阅表用 CopyOnWriteArrayList事件对象塞太胖耦合又带回来连数据库连接一起传等于没解耦事件只放订单号、金额这种必要数据事务没提交就发事件监听器查不到订单或退款了短信已发TransactionalEventListener(AFTER_COMMIT)监听器用完不注销长生命周期总线持有强引用GC 回收不掉主动 unsubscribe或交给 Spring 容器管生命周期九、关于这个系列本文是「Java 后端实战精通营」设计模式系列第 6 篇。第 1 篇给外卖订单烂代码体检时预告的那一刀今天落了地通知 if 连锁被观察者模式拆掉订单服务从此不认识短信邮件推送。完整可运行工程https://gitee.com/j67mk2/design-patterns本课源码在lesson-06/主类com.dp.lesson06.Main下一篇预告《责任链模式——一个请求过五道关卡谁都不攥着流程不放》。把请求沿着一条链逐级传递、每一环决定自己处理还是传给下一环写成模式手写一个审批链组长 → 经理 → 总监金额超阈值就往上抛再落到 Spring MVC 的HandlerInterceptor和 MyBatis 插件机制——这是面试你怎么处理层层递进的请求的标准答案。互动提问你项目里有没有加一种触发动作就要回来改老主流程的场景下单发通知、操作日志、数据埋点评论区聊聊我帮你看看能不能用事件总线拆开。本文部分内容由 AI 辅助生成

相关新闻

TBC精密星历下载失败?五个IGS备用节点配置与脚本批量下载指南

TBC精密星历下载失败?五个IGS备用节点配置与脚本批量下载指南

简介:这份文档面向从事高精度GPS定位测量、地震学、气象学与地球科学研究的科研人员及工程技术人员,整理了IGS精密星历的备用下载地址,用于在主下载通道不稳定或数据需求紧急时保障星历文件的持续获取。内容涵盖IGS Final Orbits、Rapid Orbi…

2026/10/16 22:04:23 阅读更多 →
数据仓库与数据挖掘考试核心考点:从星型建模到Apriori手算

数据仓库与数据挖掘考试核心考点:从星型建模到Apriori手算

简介:一份面向数据仓库与数据挖掘课程期末考试整理的试题资源,适合高校计算机、信息管理等相关专业的学生复习备考,也可供授课教师作为测验参考。压缩包内仅包含1个doc文档,大小约130KB,内容为完整的试题卷面。试卷涵盖…

2026/10/16 22:03:22 阅读更多 →
GeoServer 2.25.3 WAR包部署避坑指南:Tomcat 10+适配与纯净化实战

GeoServer 2.25.3 WAR包部署避坑指南:Tomcat 10+适配与纯净化实战

简介:本资源为 GeoServer 2.25.3 官方 WAR 包部署版本,面向 GIS 开发者、空间数据服务部署工程师及 Web 地图平台搭建初学者,用于快速构建符合 OGC 标准的 WMS/WFS/WCS 等地理信息服务。压缩包共16个文件,主体为1个可直接部署的 g…

2026/10/16 22:03:22 阅读更多 →

最新新闻

Nanosaur2-670M的DiT架构深度解析:SPRINT稀疏块、2D RoPE、QK-norm与670M参数设计哲学

Nanosaur2-670M的DiT架构深度解析:SPRINT稀疏块、2D RoPE、QK-norm与670M参数设计哲学

大模型人工智能媒体生成计算机视觉基础模型本地部署 【免费下载链接】Nanosaur2-670M 项目地址: https://ai.gitcode.com/hf_mirrors/well9472/Nanosaur2-670M 点击查看 免费下载 Nanosaur2-670M 是一个仅花 600 美元算力训练出的 670M 参数插画文生图 DiT&#xf…

2026/10/16 22:48:15 阅读更多 →
SpringBoot+Vue纹理生成图片管理系统:从算法到前后端实现

SpringBoot+Vue纹理生成图片管理系统:从算法到前后端实现

做毕业设计选源码这事,我见过太多同学卡在同一个地方:要么系统太"玩具",只有登录注册加一张表,答辩老师看一眼就问"你的工作量在哪";要么功能堆得太散,前后端各写各的,问起…

2026/10/16 22:47:14 阅读更多 →
Vue项目微信公众号图片防盗链、跨域与canvas问题详解

Vue项目微信公众号图片防盗链、跨域与canvas问题详解

如果你的 Vue 项目里接过微信公众号的图片&#xff0c;我猜你大概率碰到过这种场景&#xff1a;后端接口把图片地址甩过来&#xff0c;浏览器一加载&#xff0c;<img>直接裂开&#xff0c;控制台冒出一串 403&#xff0c;或者是某个 HTTPS 混合内容错误。更麻烦的是&…

2026/10/16 22:47:14 阅读更多 →
AI智能体是什么?从核心原理到应用形态的完整拆解

AI智能体是什么?从核心原理到应用形态的完整拆解

1. 先搞清楚&#xff1a;智能体到底是什么1.1 从一个场景说起我经常举一个例子&#xff1a;你让普通AI助手“帮我把上个月所有项目相关的邮件找出来&#xff0c;按重要性排序&#xff0c;再写一份摘要发给团队”&#xff0c;它大概率只能给你一段泛泛的建议&#xff0c;告诉你“…

2026/10/16 22:47:14 阅读更多 →
Phoenix 映射 HBase 表:表映射与视图映射的选型与实战

Phoenix 映射 HBase 表:表映射与视图映射的选型与实战

1. 为什么要在 Phoenix 里映射 HBase 表&#xff0c;而不是直接建表很多人第一次接触 Phoenix&#xff0c;脑子里冒出来的第一个问题就是&#xff1a;我 HBase 里已经有一堆表了&#xff0c;难道还要用 Phoenix 再建一遍&#xff1f;答案是不用。Phoenix 最实用的一类场景&…

2026/10/16 22:46:13 阅读更多 →
省级PBF建筑白模制作:从原始数据到矢量瓦片的自动化流程

省级PBF建筑白模制作:从原始数据到矢量瓦片的自动化流程

做省级尺度的时候&#xff0c;最折磨人的从来不是怎么画楼&#xff0c;而是怎么把几十个GB的原始数据&#xff0c;变成浏览器里那一片干净利落的白色体块。PBF格式的数据包大家都懂&#xff0c;里面塞满了道路、水系、铁路、地块、POI等等内容&#xff0c;三分之一都不一定是建…

2026/10/16 22:46:13 阅读更多 →

日新闻

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

个人 AI Agent 平台接入 TaoToken:统一 Key 与 API 通道的配置大纲

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 0:00:05 阅读更多 →
信号与系统频域分析作业解题指南

信号与系统频域分析作业解题指南

1. 作业背景与核心要求解析信号与系统作为电子信息类专业的核心基础课&#xff0c;其作业设计往往直接关联课程的核心知识框架。2026年春季第六次作业的布置&#xff0c;通常对应着课程进展到频域分析的关键阶段。根据常规教学进度推测&#xff0c;本次作业可能涉及以下核心内容…

2026/10/16 0:00:05 阅读更多 →
Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

Django MVT实战:从零构建图书借阅系统,彻底搞懂Model-View-Template分层

1. 从一个"四不像"的练手项目说起如果你已经看完了Django的官方教程前几章&#xff0c;跟着敲完了投票应用&#xff0c;大概会有一种"我好像会了&#xff0c;又好像什么都不会"的割裂感。教程里每个知识点都是拆开的——这一章讲模型&#xff0c;下一章讲视…

2026/10/16 0:00:05 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →