[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-866":3,"consumer-news-interaction-866":40,"consumer-news-related-866":43},{"detail":4,"item":35},{"card":5,"schemaVersion":22,"fields":23,"content":29},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":14,"href":15,"sourceName":12,"meta":16,"metrics":19,"tags":20,"resolved":21},"NEWS_ARTICLE:866","news","NEWS_ARTICLE",866,"资讯","Spring Boot事件监听，这东西到底解决啥问题？","博客园","一、先聊个场景，你就明白这玩意儿干啥的了 点过外卖吧？那咱们就用这个场景来说事。 你掏出手机下了单，付了钱。接下来会发生啥？ 厨房那头开始备菜炒菜（这件事耽误不得，客户饿着呢） 手机收到一条短信：&quot;您的订单已收到，预计30分钟送达&quot; 你的会员账户里多了一堆积分 店长那个收银小喇叭","","\u002Fnews\u002F866",[17,18],"2026","软件开发",{},[18],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":24,"categoryName":18,"summary":13,"description":13,"publishTime":25,"updateTime":26,"sourceUrl":27,"language":28},"佛祖让我来巡山","2026-09-01T17:29","2026-09-02T20:37:50","https:\u002F\u002Fwww.cnblogs.com\u002Fsun-10387834\u002Fp\u002F22794911","中文",{"format":30,"policy":31,"normalized":21,"html":32,"text":33,"wordCount":34,"hasBody":21},"HTML","NEWS_CONTENT_V1","\u003Ch2>一、先聊个场景，你就明白这玩意儿干啥的了\u003C\u002Fh2>\n\u003Cp>点过外卖吧？那咱们就用这个场景来说事。\u003C\u002Fp>\n\u003Cp>你掏出手机下了单，付了钱。接下来会发生啥？\u003C\u002Fp>\n\u003Cul>\n \u003Cli>厨房那头开始备菜炒菜（这件事耽误不得，客户饿着呢）\u003C\u002Fli>\n \u003Cli>手机收到一条短信：\"您的订单已收到，预计30分钟送达\"\u003C\u002Fli>\n \u003Cli>你的会员账户里多了一堆积分\u003C\u002Fli>\n \u003Cli>店长那个收银小喇叭\"叮咚\"响了一声\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>现在你设想一下，如果这家店的流程\u003Cstrong>必须先发完短信 → 再加完积分 → 再响铃 → 最后厨房才能开始炒菜\u003C\u002Fstrong>，你会不会觉得这店脑子有问题？\u003C\u002Fp>\n\u003Cp>万一短信网关卡了三秒，后厨那师傅就拿着锅铲干等着。你作为客户，等餐时间硬生生多了好几秒。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>稍微正常点的店，肯定是后厨该干啥干啥，短信、积分、响铃这几件事各自并行处理，谁也别耽误谁。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>Spring Boot的事件监听机制，解决的就是这类问题——\u003Cstrong>核心业务只管宣布\"出事了\"，至于谁要响应、怎么响应，核心业务一概不关心。\u003C\u002Fstrong> 大家各干各的，谁也不等谁。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2>二、就三个角色，没你想的那么复杂\u003C\u002Fh2>\n\u003Cp>这套东西其实就三个参与者：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>事件\u003C\u002Fstrong>：就是一个装数据的包裹，告诉你\"刚才发生了一件事，这是相关数据\"\u003C\u002Fli>\n \u003Cli>\u003Cstrong>发布者\u003C\u002Fstrong>：就是\"喊一嗓子\"的那个人，通常是你核心业务代码里的某个Service\u003C\u002Fli>\n \u003Cli>\u003Cstrong>监听器\u003C\u002Fstrong>：就是\"听到动静就干活\"的那个人，可能有多个，各干各的活\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>三、别废话了，直接上代码\u003C\u002Fh2>\n\u003Cp>老规矩，用一个\u003Cstrong>真实的订单创建\u003C\u002Fstrong>场景，从最基本的写法开始，一步一步往里加东西。\u003C\u002Fp>\n\u003Ch3>3.1 先加依赖\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;\u002FgroupId&gt;\n    &lt;artifactId&gt;spring-boot-starter-web&lt;\u002FartifactId&gt;\n&lt;\u002Fdependency&gt;\n&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;\u002FgroupId&gt;\n    &lt;artifactId&gt;spring-boot-starter&lt;\u002FartifactId&gt;\n&lt;\u002Fdependency&gt;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>就这俩，别的不用。\u003C\u002Fp>\n\u003Ch3>3.2 第一步：把事件定义出来（就是那个\"包裹\"）\u003C\u002Fh3>\n\u003Cp>订单创建之后，我们需要把订单号、用户ID、金额这几个关键数据打包传出去，让监听器们拿着用。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>package com.example.demo.event;\n\nimport org.springframework.context.ApplicationEvent;\n\n\u002F**\n * 订单创建事件\n * 说白了就是一个装数据的筐，谁监听这个事件，谁就能从这个筐里拿数据\n *\u002F\npublic class OrderCreatedEvent extends ApplicationEvent {\n\n    \u002F**\n     * 订单号\n     *\u002F\n    private final String orderNo;\n\n    \u002F**\n     * 用户ID\n     *\u002F\n    private final Long userId;\n\n    \u002F**\n     * 订单金额（单位：分，避免浮点数精度问题）\n     *\u002F\n    private final Long amount;\n\n    \u002F**\n     * 构造方法\n     * @param source 谁发的这个事件，通常传this就行\n     *\u002F\n    public OrderCreatedEvent(Object source, String orderNo, Long userId, Long amount) {\n        super(source);  \u002F\u002F 父类要求，传进去就完了\n        this.orderNo = orderNo;\n        this.userId = userId;\n        this.amount = amount;\n    }\n\n    \u002F\u002F -------- Getter 让监听器能拿到数据 --------\n    public String getOrderNo() {\n        return orderNo;\n    }\n\n    public Long getUserId() {\n        return userId;\n    }\n\n    public Long getAmount() {\n        return amount;\n    }\n\n    @Override\n    public String toString() {\n        return \"OrderCreatedEvent{\" +\n                \"orderNo='\" + orderNo + '\\'' +\n                \", userId=\" + userId +\n                \", amount=\" + amount +\n                '}';\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>说一嘴：那个\u003Ccode>source\u003C\u002Fcode>是父类\u003Ccode>ApplicationEvent\u003C\u002Fcode>的构造参数，大多数情况下你传\u003Ccode>this\u003C\u002Fcode>就行了。真正干活用到的是我们自己加的那三个字段。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>3.3 第二步：写监听器——\"收到包裹后我要干啥\"\u003C\u002Fh3>\n\u003Cp>监听器的工作就是\"当某某事件发生了，我该做什么\"。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>package com.example.demo.listener;\n\nimport com.example.demo.event.OrderCreatedEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\n\n\u002F**\n * 订单事件监听器\n *\u002F\n@Component\npublic class OrderEventListener {\n\n    private static final Logger log = LoggerFactory.getLogger(OrderEventListener.class);\n\n    \u002F**\n     * 发短信\n     * Spring看到@EventListener注解的方法，并且参数是OrderCreatedEvent，\n     * 就会在事件发布时自动调用这个方法\n     *\u002F\n    @EventListener\n    public void handleSendSms(OrderCreatedEvent event) {\n        log.info(\"【短信】给用户 {} 发短信，订单号：{}\", event.getUserId(), event.getOrderNo());\n        \n        \u002F\u002F 假装调了一下短信接口，sleep模拟网络IO\n        try {\n            Thread.sleep(300);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        }\n        \n        log.info(\"【短信】发送成功\");\n    }\n\n    \u002F**\n     * 加积分\n     *\u002F\n    @EventListener\n    public void handleAddPoints(OrderCreatedEvent event) {\n        log.info(\"【积分】用户 {} 获得 {} 积分\", event.getUserId(), event.getAmount() \u002F 100);\n        \u002F\u002F 实际项目里这里会调积分服务的接口\n    }\n\n    \u002F**\n     * 写日志\n     *\u002F\n    @EventListener\n    public void handleWriteLog(OrderCreatedEvent event) {\n        log.info(\"【日志】记录订单创建：{}\", event.getOrderNo());\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>这里有个细节：一个类里可以写多个\u003Ccode>@EventListener\u003C\u002Fcode>方法，每个方法监听同一个事件，各干各的事，互不影响。你也可以把它们拆到不同的类里，看你的组织习惯。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>3.4 第三步：发布事件——\"喊一嗓子\"\u003C\u002Fh3>\n\u003Cp>在订单Service里，把\u003Ccode>ApplicationEventPublisher\u003C\u002Fcode>注入进来，然后在合适的位置把事件发出去。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>package com.example.demo.service;\n\nimport com.example.demo.event.OrderCreatedEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.ApplicationEventPublisher;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.UUID;\n\n@Service\npublic class OrderService {\n\n    private static final Logger log = LoggerFactory.getLogger(OrderService.class);\n\n    @Autowired\n    private ApplicationEventPublisher eventPublisher;  \u002F\u002F 就靠这个发布事件\n\n    @Transactional\n    public void createOrder(Long userId, Long amount) {\n        \u002F\u002F ---------- 核心：保存订单 ----------\n        String orderNo = \"ORD\" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6);\n        log.info(\"【核心】订单创建：{}，用户：{}，金额：{}分\", orderNo, userId, amount);\n\n        \u002F\u002F 实际项目里这里会调用 orderMapper.insert(order)\n        \u002F\u002F 如果插入失败，事务回滚，后续代码不会执行\n\n        \u002F\u002F ---------- 发布事件：通知别人\"订单好了\" ----------\n        \u002F\u002F 注意：此时事务还没提交，监听器们正在同步执行\n        log.info(\"【核心】发布事件...\");\n        OrderCreatedEvent event = new OrderCreatedEvent(this, orderNo, userId, amount);\n        eventPublisher.publishEvent(event);\n        log.info(\"【核心】事件发布完毕\");\n\n        \u002F\u002F 方法结束，事务提交\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.5 第四步：搞个Controller触发一下\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>package com.example.demo.controller;\n\nimport com.example.demo.service.OrderService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@RestController\npublic class OrderController {\n\n    @Autowired\n    private OrderService orderService;\n\n    @PostMapping(\"\u002Forder\u002Fcreate\")\n    public Map&lt;String, Object&gt; createOrder(\n            @RequestParam(defaultValue = \"1001\") Long userId,\n            @RequestParam(defaultValue = \"10000\") Long amount) {\n\n        long start = System.currentTimeMillis();\n        orderService.createOrder(userId, amount);\n        long cost = System.currentTimeMillis() - start;\n\n        Map&lt;String, Object&gt; result = new HashMap&lt;&gt;();\n        result.put(\"code\", 200);\n        result.put(\"message\", \"订单创建成功\");\n        result.put(\"costTime\", cost + \"ms\");\n        return result;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.6 跑一下看看\u003C\u002Fh3>\n\u003Cp>启动项目，POST请求一下 \u003Ccode>\u002Forder\u002Fcreate\u003C\u002Fcode>，控制台大概输出这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>【核心】订单创建：ORD1693567890123abcde，用户：1001，金额：10000分\n【核心】发布事件...\n【短信】给用户 1001 发短信，订单号：ORD1693567890123abcde\n【短信】发送成功\n【积分】用户 1001 获得 100 积分\n【日志】记录订单创建：ORD1693567890123abcde\n【核心】事件发布完毕\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>注意看顺序\u003C\u002Fstrong>：所有监听器执行完了，才打印\"事件发布完毕\"。说明默认是\u003Cstrong>同步\u003C\u002Fstrong>的，一个一个排队执行。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>还有一个坑\u003C\u002Fstrong>：要是任何一个监听器报错了，整个事务会回滚，订单创建失败。这点后面会说怎么处理。\u003C\u002Fp>\n\u003Ch2>四、你肯定会问：跟直接调@Async有啥区别？\u003C\u002Fh2>\n\u003Cp>这问题问到点子上了。很多人第一反应就是\"我用\u003Ccode>@Async\u003C\u002Fcode>注解也能异步执行啊，搞这么复杂干嘛？\"\u003C\u002Fp>\n\u003Ch3>4.1 先看直接用@Async的方式\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>@Service\npublic class OrderServiceV1 {\n\n    @Autowired\n    private SmsService smsService;\n    @Autowired\n    private PointsService pointsService;\n\n    @Transactional\n    public void createOrder(Long userId, Long amount) {\n        String orderNo = saveOrder(userId, amount);\n        \n        \u002F\u002F 显式调用，明确告诉程序要做什么\n        smsService.sendSmsAsync(orderNo, userId);\n        pointsService.addPointsAsync(userId, amount);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这么写有啥毛病？最大的毛病就是——如果产品经理说\"再加个App推送\"，你得改\u003Ccode>OrderServiceV1\u003C\u002Fcode>这个类的代码。改一次两次还好，改个十次八次这个类就变成个上千行的怪物了。\u003C\u002Fp>\n\u003Ch3>4.2 再看事件的方式\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>@Service\npublic class OrderServiceV2 {\n\n    @Autowired\n    private ApplicationEventPublisher publisher;\n\n    @Transactional\n    public void createOrder(Long userId, Long amount) {\n        String orderNo = saveOrder(userId, amount);\n        \n        \u002F\u002F 只发事件，谁处理、怎么处理，一概不管\n        publisher.publishEvent(new OrderCreatedEvent(this, orderNo, userId, amount));\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>区别在哪？\u003C\u002Fstrong> 主方法里只有一个\u003Ccode>publishEvent\u003C\u002Fcode>，新增功能完全不用动这个类，新建一个监听器就完事了。\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>@Async直接调\u003C\u002Fth>\n   \u003Cth>事件监听\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>主方法知道谁在干活吗？\u003C\u002Ftd>\n   \u003Ctd>知道，挨个调了一遍\u003C\u002Ftd>\n   \u003Ctd>不知道，只管发事件\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>新增一个后续动作\u003C\u002Ftd>\n   \u003Ctd>改主方法代码\u003C\u002Ftd>\n   \u003Ctd>新建监听器就行\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>代码耦合度\u003C\u002Ftd>\n   \u003Ctd>越来越高\u003C\u002Ftd>\n   \u003Ctd>主类始终保持干净\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Cstrong>说白了就是：你点了一个外卖，你是直接打电话给厨师、骑手、店长一个一个通知，还是在外卖App上点一下\"下单\"按钮完事？事件监听就是后者。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ch2>五、进阶：事务提交后再执行，这个很实用\u003C\u002Fh2>\n\u003Cp>上面那个版本有个隐藏问题，你可能已经注意到了：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>@Transactional\npublic void createOrder(Long userId, Long amount) {\n    orderMapper.insert(order);      \u002F\u002F ① 插入数据，但事务还没提交\n    publisher.publishEvent(event);  \u002F\u002F ② 发布事件，监听器开始干活\n    \u002F\u002F ③ 方法结束，事务提交\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>监听器在步骤②执行的时候，数据库事务\u003Cstrong>还没提交\u003C\u002Fstrong>。如果监听器里需要查询刚插入的订单数据，查不到！因为别的线程根本看不到未提交的数据。\u003C\u002Fp>\n\u003Cp>这个在真实项目里经常遇到，解决方案就是Spring提供的\u003Ccode>@TransactionalEventListener\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch3>5.1 四个事务阶段\u003C\u002Fh3>\n\u003Cp>Spring提供了四个\"钩子\"，让你精确控制监听器在事务的哪个阶段执行：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>阶段\u003C\u002Fth>\n   \u003Cth>执行时机\u003C\u002Fth>\n   \u003Cth>啥时候用？\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>BEFORE_COMMIT\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>事务提交前\u003C\u002Ftd>\n   \u003Ctd>需要和主业务在同一事务里，失败了要一起回滚\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>AFTER_COMMIT\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>事务提交后\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>最常用\u003C\u002Fstrong>，确保数据落盘了再做别的\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>AFTER_ROLLBACK\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>事务回滚后\u003C\u002Ftd>\n   \u003Ctd>记录失败原因、做补偿\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Ccode>AFTER_COMPLETION\u003C\u002Fcode>\u003C\u002Ftd>\n   \u003Ctd>事务完成后（不管成功还是失败）\u003C\u002Ftd>\n   \u003Ctd>清理临时资源\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3>5.2 用代码说话\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>package com.example.demo.listener;\n\nimport com.example.demo.event.OrderCreatedEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.event.TransactionPhase;\nimport org.springframework.transaction.event.TransactionalEventListener;\n\n@Component\npublic class OrderTransactionListener {\n\n    private static final Logger log = LoggerFactory.getLogger(OrderTransactionListener.class);\n\n    \u002F**\n     * 事务提交后再执行——这个最常用\n     * 只有主方法的事务成功提交了，这个方法才会被触发\n     * 如果主方法回滚了，这个方法不会执行\n     *\u002F\n    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)\n    public void handleAfterCommit(OrderCreatedEvent event) {\n        log.info(\"【事务提交后】数据已经落盘了，订单号：{}，放心查吧\", event.getOrderNo());\n        \u002F\u002F 这里适合干这些事：发MQ消息、调第三方接口、同步ES……\n    }\n\n    \u002F**\n     * 事务回滚后执行\n     *\u002F\n    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)\n    public void handleAfterRollback(OrderCreatedEvent event) {\n        log.info(\"【事务回滚后】订单创建失败了，订单号：{}\", event.getOrderNo());\n        \u002F\u002F 记录失败日志、触发补偿流程\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>跑一下，看输出顺序：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cpre>\u003Ccode>【核心】订单创建：ORD1693567890123\n【核心】发布事件...\n【核心】事件发布完毕\n（事务提交中...）\n【事务提交后】数据已经落盘了，订单号：ORD1693567890123，放心查吧\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意：\u003Ccode>AFTER_COMMIT\u003C\u002Fcode>的监听器是\u003Cstrong>在方法结束、事务提交之后\u003C\u002Fstrong>才执行的。这时候数据已经妥妥地写在数据库里了，监听器里怎么查都安全。\u003C\u002Fp>\n\u003Ch3>5.3 生产级写法长这样\u003C\u002Fh3>\n\u003Cp>真实项目里通常是\"事务提交后 + 异步执行\"的组合：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>@Async\n@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)\npublic void handleSendSms(OrderCreatedEvent event) {\n    try {\n        \u002F\u002F 调用短信网关\n        smsClient.send(event.getUserId(), \"订单\" + event.getOrderNo() + \"已创建\");\n    } catch (Exception e) {\n        \u002F\u002F 注意：千万不能把异常抛出去，否则会影响其他监听器\n        \u002F\u002F 记录日志 + 报警，让人工介入\n        log.error(\"短信发送失败，订单号：{}\", event.getOrderNo(), e);\n        \u002F\u002F 可以存到失败表，定时任务重试\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意别忘了在启动类上加\u003Ccode>@EnableAsync\u003C\u002Fcode>，不然\u003Ccode>@Async\u003C\u002Fcode>不生效。\u003C\u002Fp>\n\u003Ch2>六、几个容易踩的坑\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>坑1：监听器抛异常，主业务回滚\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>默认情况下，监听器和主业务在同一个事务里。监听器报错，主业务也跟着回滚。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F ❌ 这么写有风险\n@EventListener\npublic void handle(OrderCreatedEvent event) {\n    smsService.send(event.getUserId());  \u002F\u002F 万一发短信报错，整个订单都没了\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>\u002F\u002F ✅ 稳妥的做法\n@EventListener\npublic void handle(OrderCreatedEvent event) {\n    try {\n        smsService.send(event.getUserId());\n    } catch (Exception e) {\n        log.error(\"短信发送失败，不影响主流程\", e);\n        \u002F\u002F 记下来，后面人工或定时任务补偿\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>坑2：监听器太慢，拖垮了整个接口响应时间\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>默认同步执行，如果监听器里有个耗时操作，接口响应时间就会被拖长。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F ❌ 同步执行，接口被卡住\n@EventListener\npublic void handle(OrderCreatedEvent event) {\n    Thread.sleep(5000);  \u002F\u002F 用户要等5秒才能看到\"下单成功\"\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode>\u002F\u002F ✅ 加个@Async就解决了\n@Async\n@EventListener\npublic void handle(OrderCreatedEvent event) {\n    Thread.sleep(5000);  \u002F\u002F 接口瞬间返回，监听器在后台慢慢跑\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>坑3：多个监听器的执行顺序不确定\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>如果你依赖某个监听器先执行，另一个后执行，需要加\u003Ccode>@Order\u003C\u002Fcode>明确顺序：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>@Component\n@Order(1)\npublic class FirstListener {\n    @EventListener\n    public void handle(OrderCreatedEvent event) {\n        \u002F\u002F 先执行\n    }\n}\n\n@Component\n@Order(2)\npublic class SecondListener {\n    @EventListener\n    public void handle(OrderCreatedEvent event) {\n        \u002F\u002F 后执行\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>七、项目结构长这样\u003C\u002Fh2>\n\u003Cpre>\u003Ccode>src\u002Fmain\u002Fjava\u002Fcom\u002Fexample\u002Fdemo\u002F\n├── DemoApplication.java                  # 启动类，记得加@EnableAsync\n├── controller\u002F\n│   └── OrderController.java              # 入口\n├── service\u002F\n│   └── OrderService.java                 # 核心业务，发布事件\n├── event\u002F\n│   └── OrderCreatedEvent.java            # 事件定义\n└── listener\u002F\n    ├── OrderEventListener.java           # 普通监听器\n    └── OrderTransactionListener.java     # 事务监听器\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>八、总结一下\u003C\u002Fh2>\n\u003Cp>回到最开头的那个外卖场景。用事件监听的方式去组织代码，核心业务只管\"下单\"这一件事，短信、积分、日志都是旁边各自独立的监听器，谁出问题也不影响核心流程。新增一个\"给店长发通知\"的功能，也只需要加一个新的监听器，不用动核心代码。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>记住两句话就够了：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cblockquote>\n \u003Col>\n  \u003Cli>核心业务只管喊一嗓子，谁响应、怎么响应，跟它没关系。\u003C\u002Fli>\n  \u003Cli>跟数据一致性相关的监听器，用\u003Ccode>@TransactionalEventListener(phase = AFTER_COMMIT)\u003C\u002Fcode>确保事务提交后再执行，同时用\u003Ccode>@Async\u003C\u002Fcode>异步处理，别让旁路逻辑拖慢主流程。\u003C\u002Fli>\n \u003C\u002Fol>\n\u003C\u002Fblockquote>\n\u003Cp>代码这东西，你自己敲一遍比看十遍都管用。建议把上面的代码复制到你自己的项目里跑一下，改改参数，看看日志输出顺序，感受一下事件机制的运作方式。遇到问题欢迎留言交流。\u003C\u002Fp>","一、先聊个场景，你就明白这玩意儿干啥的了 点过外卖吧？那咱们就用这个场景来说事。 你掏出手机下了单，付了钱。接下来会发生啥？ 厨房那头开始备菜炒菜（这件事耽误不得，客户饿着呢） 手机收到一条短信：\"您的订单已收到，预计30分钟送达\" 你的会员账户里多了一堆积分 店长那个收银小喇叭\"叮咚\"响了一声 现在你设想一下，如果这家店的流程必须先发完短信 → 再加完积分 → 再响铃 → 最后厨房才能开始炒菜，你会不会觉得这店脑子有问题？ 万一短信网关卡了三秒，后厨那师傅就拿着锅铲干等着。你作为客户，等餐时间硬生生多了好几秒。 稍微正常点的店，肯定是后厨该干啥干啥，短信、积分、响铃这几件事各自并行处理，谁也别耽误谁。 Spring Boot的事件监听机制，解决的就是这类问题——核心业务只管宣布\"出事了\"，至于谁要响应、怎么响应，核心业务一概不关心。 大家各干各的，谁也不等谁。 二、就三个角色，没你想的那么复杂 这套东西其实就三个参与者： 事件：就是一个装数据的包裹，告诉你\"刚才发生了一件事，这是相关数据\" 发布者：就是\"喊一嗓子\"的那个人，通常是你核心业务代码里的某个Service 监听器：就是\"听到动静就干活\"的那个人，可能有多个，各干各的活 三、别废话了，直接上代码 老规矩，用一个真实的订单创建场景，从最基本的写法开始，一步一步往里加东西。 3.1 先加依赖 \u003Cdependency> \u003CgroupId>org.springframework.boot\u003C\u002FgroupId> \u003CartifactId>spring-boot-starter-web\u003C\u002FartifactId> \u003C\u002Fdependency> \u003Cdependency> \u003CgroupId>org.springframework.boot\u003C\u002FgroupId> \u003CartifactId>spring-boot-starter\u003C\u002FartifactId> \u003C\u002Fdependency> 就这俩，别的不用。 3.2 第一步：把事件定义出来（就是那个\"包裹\"） 订单创建之后，我们需要把订单号、用户ID、金额这几个关键数据打包传出去，让监听器们拿着用。 package com.example.demo.event; import org.springframework.context.ApplicationEvent; \u002F** * 订单创建事件 * 说白了就是一个装数据的筐，谁监听这个事件，谁就能从这个筐里拿数据 *\u002F public class OrderCreatedEvent extends ApplicationEvent { \u002F** * 订单号 *\u002F private final String orderNo; \u002F** * 用户ID *\u002F private final Long userId; \u002F** * 订单金额（单位：分，避免浮点数精度问题） *\u002F private final Long amount; \u002F** * 构造方法 * @param source 谁发的这个事件，通常传this就行 *\u002F public OrderCreatedEvent(Object source, String orderNo, Long userId, Long amount) { super(source); \u002F\u002F 父类要求，传进去就完了 this.orderNo = orderNo; this.userId = userId; this.amount = amount; } \u002F\u002F -------- Getter 让监听器能拿到数据 -------- public String getOrderNo() { return orderNo; } public Long getUserId() { return userId; } public Long getAmount() { return amount; } @Override public String toString() { return \"OrderCreatedEvent{\" + \"orderNo='\" + orderNo + '\\'' + \", userId=\" + userId + \", amount=\" + amount + '}'; } } 说一嘴：那个source是父类ApplicationEvent的构造参数，大多数情况下你传this就行了。真正干活用到的是我们自己加的那三个字段。 3.3 第二步：写监听器——\"收到包裹后我要干啥\" 监听器的工作就是\"当某某事件发生了，我该做什么\"。 package com.example.demo.listener; import com.example.demo.event.OrderCreatedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; \u002F** * 订单事件监听器 *\u002F @Component public class OrderEventListener { private static final Logger log = LoggerFactory.getLogger(OrderEventListener.class); \u002F** * 发短信 * Spring看到@EventListener注解的方法，并且参数是OrderCreatedEvent， * 就会在事件发布时自动调用这个方法 *\u002F @EventListener public void handleSendSms(OrderCreatedEvent event) { log.info(\"【短信】给用户 {} 发短信，订单号：{}\", event.getUserId(), event.getOrderNo()); \u002F\u002F 假装调了一下短信接口，sleep模拟网络IO try { Thread.sleep(300); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } log.info(\"【短信】发送成功\"); } \u002F** * 加积分 *\u002F @EventListener public void handleAddPoints(OrderCreatedEvent event) { log.info(\"【积分】用户 {} 获得 {} 积分\", event.getUserId(), event.getAmount() \u002F 100); \u002F\u002F 实际项目里这里会调积分服务的接口 } \u002F** * 写日志 *\u002F @EventListener public void handleWriteLog(OrderCreatedEvent event) { log.info(\"【日志】记录订单创建：{}\", event.getOrderNo()); } } 这里有个细节：一个类里可以写多个@EventListener方法，每个方法监听同一个事件，各干各的事，互不影响。你也可以把它们拆到不同的类里，看你的组织习惯。 3.4 第三步：发布事件——\"喊一嗓子\" 在订单Service里，把ApplicationEventPublisher注入进来，然后在合适的位置把事件发出去。 package com.example.demo.service; import com.example.demo.event.OrderCreatedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @Service public class OrderService { private static final Logger log = LoggerFactory.getLogger(OrderService.class); @Autowired private ApplicationEventPublisher eventPublisher; \u002F\u002F 就靠这个发布事件 @Transactional public void createOrder(Long userId, Long amount) { \u002F\u002F ---------- 核心：保存订单 ---------- String orderNo = \"ORD\" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6); log.info(\"【核心】订单创建：{}，用户：{}，金额：{}分\", orderNo, userId, amount); \u002F\u002F 实际项目里这里会调用 orderMapper.insert(order) \u002F\u002F 如果插入失败，事务回滚，后续代码不会执行 \u002F\u002F ---------- 发布事件：通知别人\"订单好了\" ---------- \u002F\u002F 注意：此时事务还没提交，监听器们正在同步执行 log.info(\"【核心】发布事件...\"); OrderCreatedEvent event = new OrderCreatedEvent(this, orderNo, userId, amount); eventPublisher.publishEvent(event); log.info(\"【核心】事件发布完毕\"); \u002F\u002F 方法结束，事务提交 } } 3.5 第四步：搞个Controller触发一下 package com.example.demo.controller; import com.example.demo.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController public class OrderController { @Autowired private OrderService orderService; @PostMapping(\"\u002Forder\u002Fcreate\") public Map\u003CString, Object> createOrder( @RequestParam(defaultValue = \"1001\") Long userId, @RequestParam(defaultValue = \"10000\") Long amount) { long start = System.currentTimeMillis(); orderService.createOrder(userId, amount); long cost = System.currentTimeMillis() - start; Map\u003CString, Object> result = new HashMap\u003C>(); result.put(\"code\", 200); result.put(\"message\", \"订单创建成功\"); result.put(\"costTime\", cost + \"ms\"); return result; } } 3.6 跑一下看看 启动项目，POST请求一下 \u002Forder\u002Fcreate，控制台大概输出这样： 【核心】订单创建：ORD1693567890123abcde，用户：1001，金额：10000分 【核心】发布事件... 【短信】给用户 1001 发短信，订单号：ORD1693567890123abcde 【短信】发送成功 【积分】用户 1001 获得 100 积分 【日志】记录订单创建：ORD1693567890123abcde 【核心】事件发布完毕 注意看顺序：所有监听器执行完了，才打印\"事件发布完毕\"。说明默认是同步的，一个一个排队执行。 还有一个坑：要是任何一个监听器报错了，整个事务会回滚，订单创建失败。这点后面会说怎么处理。 四、你肯定会问：跟直接调@Async有啥区别？ 这问题问到点子上了。很多人第一反应就是\"我用@Async注解也能异步执行啊，搞这么复杂干嘛？\" 4.1 先看直接用@Async的方式 @Service public class OrderServiceV1 { @Autowired private SmsService smsService; @Autowired private PointsService pointsService; @Transactional public void createOrder(Long userId, Long amount) { String orderNo = saveOrder(userId, amount); \u002F\u002F 显式调用，明确告诉程序要做什么 smsService.sendSmsAsync(orderNo, userId); pointsService.addPointsAsync(userId, amount); } } 这么写有啥毛病？最大的毛病就是——如果产品经理说\"再加个App推送\"，你得改OrderServiceV1这个类的代码。改一次两次还好，改个十次八次这个类就变成个上千行的怪物了。 4.2 再看事件的方式 @Service public class OrderServiceV2 { @Autowired private ApplicationEventPublisher publisher; @Transactional public void createOrder(Long userId, Long amount) { String orderNo = saveOrder(userId, amount); \u002F\u002F 只发事件，谁处理、怎么处理，一概不管 publisher.publishEvent(new OrderCreatedEvent(this, orderNo, userId, amount)); } } 区别在哪？ 主方法里只有一个publishEvent，新增功能完全不用动这个类，新建一个监听器就完事了。 @Async直接调 事件监听 主方法知道谁在干活吗？ 知道，挨个调了一遍 不知道，只管发事件 新增一个后续动作 改主方法代码 新建监听器就行 代码耦合度 越来越高 主类始终保持干净 说白了就是：你点了一个外卖，你是直接打电话给厨师、骑手、店长一个一个通知，还是在外卖App上点一下\"下单\"按钮完事？事件监听就是后者。 五、进阶：事务提交后再执行，这个很实用 上面那个版本有个隐藏问题，你可能已经注意到了： @Transactional public void createOrder(Long userId, Long amount) { orderMapper.insert(order); \u002F\u002F ① 插入数据，但事务还没提交 publisher.publishEvent(event); \u002F\u002F ② 发布事件，监听器开始干活 \u002F\u002F ③ 方法结束，事务提交 } 监听器在步骤②执行的时候，数据库事务还没提交。如果监听器里需要查询刚插入的订单数据，查不到！因为别的线程根本看不到未提交的数据。 这个在真实项目里经常遇到，解决方案就是Spring提供的@TransactionalEventListener。 5.1 四个事务阶段 Spring提供了四个\"钩子\"，让你精确控制监听器在事务的哪个阶段执行： 阶段 执行时机 啥时候用？ BEFORE_COMMIT 事务提交前 需要和主业务在同一事务里，失败了要一起回滚 AFTER_COMMIT 事务提交后 最常用，确保数据落盘了再做别的 AFTER_ROLLBACK 事务回滚后 记录失败原因、做补偿 AFTER_COMPLETION 事务完成后（不管成功还是失败） 清理临时资源 5.2 用代码说话 package com.example.demo.listener; import com.example.demo.event.OrderCreatedEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; @Component public class OrderTransactionListener { private static final Logger log = LoggerFactory.getLogger(OrderTransactionListener.class); \u002F** * 事务提交后再执行——这个最常用 * 只有主方法的事务成功提交了，这个方法才会被触发 * 如果主方法回滚了，这个方法不会执行 *\u002F @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleAfterCommit(OrderCreatedEvent event) { log.info(\"【事务提交后】数据已经落盘了，订单号：{}，放心查吧\", event.getOrderNo()); \u002F\u002F 这里适合干这些事：发MQ消息、调第三方接口、同步ES…… } \u002F** * 事务回滚后执行 *\u002F @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void handleAfterRollback(OrderCreatedEvent event) { log.info(\"【事务回滚后】订单创建失败了，订单号：{}\", event.getOrderNo()); \u002F\u002F 记录失败日志、触发补偿流程 } } 跑一下，看输出顺序： 【核心】订单创建：ORD1693567890123 【核心】发布事件... 【核心】事件发布完毕 （事务提交中...） 【事务提交后】数据已经落盘了，订单号：ORD1693567890123，放心查吧 注意：AFTER_COMMIT的监听器是在方法结束、事务提交之后才执行的。这时候数据已经妥妥地写在数据库里了，监听器里怎么查都安全。 5.3 生产级写法长这样 真实项目里通常是\"事务提交后 + 异步执行\"的组合： @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleSendSms(OrderCreatedEvent event) { try { \u002F\u002F 调用短信网关 smsClient.send(event.getUserId(), \"订单\" + event.getOrderNo() + \"已创建\"); } catch (Exception e) { \u002F\u002F 注意：千万不能把异常抛出去，否则会影响其他监听器 \u002F\u002F 记录日志 + 报警，让人工介入 log.error(\"短信发送失败，订单号：{}\", event.getOrderNo(), e); \u002F\u002F 可以存到失败表，定时任务重试 } } 注意别忘了在启动类上加@EnableAsync，不然@Async不生效。 六、几个容易踩的坑 坑1：监听器抛异常，主业务回滚 默认情况下，监听器和主业务在同一个事务里。监听器报错，主业务也跟着回滚。 \u002F\u002F ❌ 这么写有风险 @EventListener public void handle(OrderCreatedEvent event) { smsService.send(event.getUserId()); \u002F\u002F 万一发短信报错，整个订单都没了 } \u002F\u002F ✅ 稳妥的做法 @EventListener public void handle(OrderCreatedEvent event) { try { smsService.send(event.getUserId()); } catch (Exception e) { log.error(\"短信发送失败，不影响主流程\", e); \u002F\u002F 记下来，后面人工或定时任务补偿 } } 坑2：监听器太慢，拖垮了整个接口响应时间 默认同步执行，如果监听器里有个耗时操作，接口响应时间就会被拖长。 \u002F\u002F ❌ 同步执行，接口被卡住 @EventListener public void handle(OrderCreatedEvent event) { Thread.sleep(5000); \u002F\u002F 用户要等5秒才能看到\"下单成功\" } \u002F\u002F ✅ 加个@Async就解决了 @Async @EventListener public void handle(OrderCreatedEvent event) { Thread.sleep(5000); \u002F\u002F 接口瞬间返回，监听器在后台慢慢跑 } 坑3：多个监听器的执行顺序不确定 如果你依赖某个监听器先执行，另一个后执行，需要加@Order明确顺序： @Component @Order(1) public class FirstListener { @EventListener public void handle(OrderCreatedEvent event) { \u002F\u002F 先执行 } } @Component @Order(2) public class SecondListener { @EventListener public void handle(OrderCreatedEvent event) { \u002F\u002F 后执行 } } 七、项目结构长这样 src\u002Fmain\u002Fjava\u002Fcom\u002Fexample\u002Fdemo\u002F ├── DemoApplication.java # 启动类，记得加@EnableAsync ├── controller\u002F │ └── OrderController.java # 入口 ├── service\u002F │ └── OrderService.java # 核心业务，发布事件 ├── event\u002F │ └── OrderCreatedEvent.java # 事件定义 └── listener\u002F ├── OrderEventListener.java # 普通监听器 └── OrderTransactionListener.java # 事务监听器 八、总结一下 回到最开头的那个外卖场景。用事件监听的方式去组织代码，核心业务只管\"下单\"这一件事，短信、积分、日志都是旁边各自独立的监听器，谁出问题也不影响核心流程。新增一个\"给店长发通知\"的功能，也只需要加一个新的监听器，不用动核心代码。 记住两句话就够了： 核心业务只管喊一嗓子，谁响应、怎么响应，跟它没关系。 跟数据一致性相关的监听器，用@TransactionalEventListener(phase = AFTER_COMMIT)确保事务提交后再执行，同时用@Async异步处理，别让旁路逻辑拖慢主流程。 代码这东西，你自己敲一遍比看十遍都管用。建议把上面的代码复制到你自己的项目里跑一下，改改参数，看看日志输出顺序，感受一下事件机制的运作方式。遇到问题欢迎留言交流。",9578,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":15,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":39},"2026 · 软件开发","#2563eb","16 \u002F 10",[18],{"targetType":8,"targetId":9,"likedByMe":41,"likeCount":42,"commentCount":42,"contentLikeCount":42,"contentCommentCount":42,"sourceLikeCount":42,"sourceCommentCount":42},false,0,[44,50,57,63,70,77,84,90],{"id":45,"kind":7,"title":46,"summary":47,"image":14,"href":48,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":49},"NEWS_ARTICLE:832","用 runtime-async 写一个支持 async\u002Fawait 的轻量脚本引擎","起因：一个好奇 事情的开头很简单。 给 .NET 做过动态脚本的人大概都碰到过同一堵墙：表达式树也好、Reflection.Emit 也好，都很难支持 async\u002Fawait。原因不在语法，而在 await 的实现方式——C# 编译器要为每个 async 方法生成一个状态机结构体，把方法体切成若干片","\u002Fnews\u002F832",[18],{"id":51,"kind":7,"title":52,"summary":53,"image":54,"href":55,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":56},"NEWS_ARTICLE:855","被罚了500后，整个人都变老实了","我们组有个同事，以前是那种闲不住的人。现在也闲不住 只不过是不敢动了。 去年部门空降了一位新总监，阿里P8，第一次全员会PPT上就八个大字：拥抱变化，永不言弃。 新领导上来搞流程改革，每个模块指定Owner，出事追责到人，A级事故罚款500起步，B级300 依次类推，发版上线改配置全走审批。说实话之","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F273387\u002F202608\u002F273387-20260818222617092-925481998.png","\u002Fnews\u002F855",[18],{"id":58,"kind":7,"title":59,"summary":60,"image":14,"href":61,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":62},"NEWS_ARTICLE:862","go中make声明切片, 修改切片,append给切片扩容,合并切片,复制切片","make 声明切片 make([]类型, 长度， 容量) kage main import (&quot;fmt&quot;) \u002F\u002F 入口函数 func main() { \u002F\u002F make 声明切片，长度是4， 容量是10 var sliceArr1 = make([]int, 4, 10) fmt.","\u002Fnews\u002F862",[18],{"id":64,"kind":7,"title":65,"summary":66,"image":67,"href":68,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":69},"NEWS_ARTICLE:881","RSA 密码传输加密通用接入方案","@目录前言一、介绍二、RSA 在本方案中的角色三、密文协议格式四、密钥生成与 Apollo 配置4.1 编译工具类4.2 执行生成密钥4.3 执行输出示例4.4 Apollo 配置示例五、后端接入方式AOP 注解示例六、后端解密流程七、前端接入方式7.1 获取公钥和 nonce7.2 使用 RSA-","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1867541\u002F202608\u002F1867541-20260831165110853-162420906.png","\u002Fnews\u002F881",[18],{"id":71,"kind":7,"title":72,"summary":73,"image":74,"href":75,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":76},"NEWS_ARTICLE:888","[开源] LogCrate：免解压、支持筛选和 AI 分析的 PC 端桌面日志工具","日常排查客户软件运行问题时，经常需要反复下载日志、解压缩，再从一堆文件里慢慢翻找，过程比较麻烦；而且很多日志工具也不支持针对具体字段进行筛选。 对比了一些市面上的日志分析工具后，发现能够同时兼容归档阅读、目录监控、到达通知、结构化筛选和 AI 分析的工具并不多，于是就自己动手做了一款。 PS：自己做","https:\u002F\u002Fiili.io\u002FCy4ztaa.png","\u002Fnews\u002F888",[18],{"id":78,"kind":7,"title":79,"summary":80,"image":81,"href":82,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":83},"NEWS_ARTICLE:900","java 多线程开发系列之七：玩转多线程（线程的协作）","线程的协作多种多样，这次主要说说wait和notify两种Object的方法。这也是java早期原生的重要的协作机制之一。先说下这两个英文单词：wait [weɪt] v.等待;等候;(尤指长期地)希望，盼望，期待notify [ˈnəʊtɪfaɪ] vt.通知;(正式)通报;也就是一个等待，一个通","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F704073\u002F202608\u002F704073-20260831103138251-1707185366.png","\u002Fnews\u002F900",[18],{"id":85,"kind":7,"title":86,"summary":87,"image":14,"href":88,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":89},"NEWS_ARTICLE:902","DeepSeek Harness 插件","准备环境 dsh 从源码运行，见 dsh 说明。或见之前分享的 DeepSeek Harness 开始。 开发插件 依照 dsh 文档，动手做一遍： 第一个插件: https:\u002F\u002Fdeepseek-harness.github.io\u002Fdeepseek-harness\u002Fdevelop\u002Fbasic\u002F 第","\u002Fnews\u002F902",[18],{"id":91,"kind":7,"title":92,"summary":93,"image":94,"href":95,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":96},"NEWS_ARTICLE:917","[python] pywinauto使用指北","当业务系统没有API、命令行接口或可直接集成的数据通道时，桌面自动化往往是打通业务流程的最后一公里。pywinauto库通过Win32 API与Microsoft UI Automation（UIA）访问Windows窗口及控件，使Python脚本能够驱动桌面应用。本文聚焦于pywinauto的基础","https:\u002F\u002Fgitlab.com\u002Fluohenyueji\u002Farticle_picture_warehouse\u002F-\u002Fraw\u002Fmain\u002FCSDN\u002F%5Bpython%5D%20pywinauto%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8C%97\u002Fimgs\u002F1.jpg","\u002Fnews\u002F917",[18]]