邮件通知合并实现复盘:窗口、分组与幂等设计
最近打开项目的issue列表看到一条标题简洁到不能再简洁的feature request“option to combine notifications in 1 email”。提需求的用户没写长篇大论就一句话能不能把一段时间内产生的多条通知合并到一封邮件里发不要一有动静就给我发一封邮箱直接被刷屏了。这条issue看起来只是加一个开关的事实际动手之后才发现它牵扯出的问题比标题长得多合并窗口怎么定、按什么维度合并、紧急告警要不要绕过合并、多实例部署下怎么保证不重复发送、邮件模板怎么组织几十条摘要……这篇文章就是对这个需求从提出到上线的一次完整复盘希望给正在做通知系统、邮件告警、消息推送的同学一些可参考的经验。1. 需求背景用户为什么需要“合并通知邮件”1.1 一条issue背后的真实痛点先说下我们产品的背景。这是一个面向开发团队的项目协作与监控平台服务端会针对项目动态、CI构建结果、服务异常等事件给用户发送邮件通知。早期逻辑很简单事件产生后异步调用邮件服务给订阅用户发一封邮件。功能确实能用但随着接入项目增多、监控项变多问题开始暴露。最典型的使用场景是这样的周五晚上某个服务开始不稳定触发了告警五分钟内同一类错误事件产生了三四十条用户就收到三四十封邮件。手机锁屏上全是一模一样的主题打开邮箱翻半天才发现最早那条才是真正有用的。这时用户跑到仓库里提issue说想要合并选项表述是“option to combine notifications in 1 email”背后的真实需求是降低邮件噪音让关键信息在一封邮件里就能看全。这其实是通知系统发展到一定阶段必然会遇到的问题。单条邮件在事件量少时体验很好事件量一大单发模式的边际成本会急剧上升用户端的“邮件疲劳”也会加深。所以这条feature request不是个例而是产品演进过程中用户用脚投票出来的结果。1.2 通知轰炸的代价不只是“烦”很多人觉得合并邮件只是为了“少收几封邮件”体验好一点而已。实际从系统运营角度看通知轰炸的代价要具体得多邮件服务成本。邮件发送服务大多是按量计费的同一个事件重复发几十封费用直接翻倍。如果业务量大这部分开销很可观。关键通知的触达率下降。用户被大量低质量通知淹没后会对所有邮件免疫真正重要的告警也可能被划走不看甚至直接屏蔽发件人。投诉与退订风险。部分用户被轰炸烦了会点击“投诉垃圾邮件”这会影响发件域名的信誉导致后续正常邮件也进垃圾箱。这是最麻烦的连锁反应因为修复域名信誉远比少发几封邮件难。所以把“合并通知”当成一个正经功能来做而不是一个顺手加的开关是有实际价值支撑的。它同时改善用户体验、降低成本、保护发件信誉。1.3 同类产品怎么处理这类问题在做方案之前我习惯先看看成熟产品是怎么处理的。GitHub的通知管理是一个典型参考它允许用户对仓库的watch级别进行精细控制可以只看参与讨论、看发布、忽略全部邮件端也支持按线程聚合PagerDuty这类告警平台则会把同一事件在某个时间窗口内的多次触发合并成一次incident对应地只发一轮通知Grafana的notification policy也支持group_wait、group_interval之类的参数本质上是把相同标签的告警归到同一组后再发。这些产品给的启发是一致的合并通知不能只做一个“是否合并”的开关至少要解决三个问题——合并的触发条件是什么合并到什么粒度哪些通知不能被合并。这三个问题没有标准答案要结合自己产品的场景定。GitHub聚合的是“线程”同一讨论串PagerDuty聚合的是“事件”同一告警源我们做的是通用通知服务所以要设计一个更通用的聚合维度。2. 方案设计合并通知的核心决策与取舍2.1 合并窗口怎么定时间驱动还是数量驱动第一个要定的是合并窗口。我们的设计里有两个可配置参数时间窗口combine.window通知进入缓冲后最长等待多久必须发出。比如设15分钟那么最多延迟15分钟用户不会觉得通知“丢失”了。数量阈值combine.max-batch缓冲区里某类事件达到多少条时提前触发发送不必等满时间窗口。这是为了防止极端情况下缓冲区堆积过大也是为了让量大的场景下延迟更短。这两个参数用“谁先到谁触发”的策略简单说就是时间窗口兜底数量阈值加速。时间窗口用时长来控制最大延迟数量阈值用事件量来控制吞吐。具体参数怎么给默认值我们拍脑袋定过一版后来根据实际使用调整了。默认时间窗口是15分钟因为对大部分协作场景来说15分钟的延迟几乎无感默认数量阈值是50条主要是为了避免一封邮件里塞几百条事件正文长到没人会读完。这两个值必须做成可配置的不同团队对延迟和噪音的容忍度完全不同没有万能值。2.2 合并维度与分组规则第二个问题是按什么维度合并。当时我们内部讨论过两个方向按接收人合并还是按“项目接收人”合并。最简单的方案是按接收人合并一个用户在窗口内收到的所有通知打包成一封。优点是邮件少缺点是主题会很混乱比如一封邮件里既有构建成功、又有服务告警、还有评论回复信息太杂。我们最终采用的是“按接收人按项目分组”的二级结构。简单说邮件还是按接收人聚合一个用户一封但邮件内部按项目分组展示每个项目下面再按类型列出事件明细。这样邮件数量少的优势保留了邮件内容的可读性也保住了。如果项目很多、差异很大也可以用group-by参数切换成只按类型分组但生产环境我们默认还是项目分组。当然这一步也会带来一个需要权衡的问题缓存key的粒度变细了缓冲区里的对象会变多内存损耗会上升。对通知服务来说单条事件本身很小内存开销基本可以忽略不需要过度设计。2.3 紧急事件的“绕过通道”合并通知本质上是拿“及时性”换“整洁性”。有些场景不能接受延迟比如服务宕机、安全告警、账单扣费失败。这类事件如果也被塞进15分钟的合并窗口用户可能错过了最佳处理时机那功能就变成事故了。所以方案里必须有一个绕过机制。我们给事件设计了级别severity字段critical级别的通知默认不进入合并缓冲直接走即时发送通道high级别是否走合并由配置决定normal和low级别一律走合并。这个机制在实现上成本很低就是在事件入口加一个分支判断但它的价值很大直接决定了这个功能能不能在线上环境安全打开。我们还在配置里加了一个开关叫critical-immediate默认true。如果某些团队想把所有通知都合并可以把它关掉但我们不会推荐这样做。2.4 配置项的最终形态功能最终面向用户的配置项如下表所示。配置不是越多越好每多一个配置用户就多一份认知负担所以能收敛的尽量收敛。配置项默认值说明combine.enabledfalse是否开启合并通知默认关闭保持向后兼容combine.window15m合并时间窗口超过后强制发送combine.max-batch50单组事件数量阈值达到后提前发送combine.wait-for-batchfalse窗口内只有1条事件时是否也延迟合并true则延迟false则立即发combine.critical-immediatetruecritical级别事件是否绕过合并即时发送combine.group-byproject邮件内分组维度project或typewait-for-batch这个配置是后来加上的因为有个内部团队反馈说晚上事件少的时候一封邮件里就一条通知还要等15分钟才发体验反而变差。加了之后他们设成false单条事件立即发多条才聚合灵活很多。3. 实现细节从事件到摘要邮件的完整链路3.1 整体模块划分功能落地时我们没把逻辑塞进已有的邮件发送模块里而是独立出了一个“通知聚合器”NotificationAggregator放在通知入口和邮件发送器之间。整个链路是事件接入接口 - 聚合器缓冲与聚合 - Flush触发 - 摘要渲染 - 邮件发送器 - SMTP这样做的原因很直接聚合器是可插拔的如果某天不做合并了把它摘掉事件就直接走原逻辑对主链路影响最小。写代码时模块边界越清晰后续维护越省心。这个聚合器需要解决四件事接收事件、按key缓存、按条件触发flush、渲染发送。下面分别说。3.2 聚合器核心实现聚合器核心结构用的是ConcurrentHashMapkey是聚合维度接收人ID 项目ID 类型等value是一个PendingGroup对象里面存事件列表和首次事件进入时间。这样实现简单直接单机内性能完全够用。以下是核心逻辑我用Java表示逻辑和语言无关换成Go、Python同理public class NotificationAggregator { private final ConcurrentHashMapAggregateKey, PendingGroup buffer new ConcurrentHashMap(); private final ScheduledExecutorService scheduler Executors.newScheduledThreadPool(1); private final Duration window; private final int maxBatch; private final ListFlushListener listeners new CopyOnWriteArrayList(); public void onEvent(NotificationEvent event) { // 紧急事件直接发不进入聚合 if (event.getSeverity() Severity.CRITICAL criticalImmediate) { sendImmediately(event); return; } AggregateKey key AggregateKey.from(event, groupBy); PendingGroup group buffer.computeIfAbsent(key, k - new PendingGroup(key, Instant.now())); group.add(event); // 数量阈值触发 if (group.size() maxBatch) { flush(key); } } Scheduled(fixedDelayString ${notify.combine.window}) public void flushExpired() { Instant deadline Instant.now().minus(window); buffer.forEach((key, group) - { if (group.getFirstEventTime().isBefore(deadline)) { flush(key); } }); } private void flush(AggregateKey key) { PendingGroup group buffer.remove(key); if (group null || group.isEmpty()) { return; } listeners.forEach(l - l.onFlush(group)); } }这里有一个关键点flush的时候不是直接发邮件而是通过FlushListener把数据交给下游下游做一个统一的摘要渲染和发送。这样聚合逻辑和发送逻辑解耦也方便测试。3.3 邮件摘要模板设计合并邮件和普通单发邮件最大的区别在于邮件正文组织。单发邮件主体就是事件内容摘要邮件的主体是一个事件列表必须让用户能在30秒内扫完关键信息。我们用Freemarker模板渲染摘要邮件。核心是两层循环外层遍历项目分组内层遍历事件。每条事件默认展示一个标题行超长内容会被截断。如果某组事件超过10条只展示前10条末尾加一行提示“还有N条事件未展示请到控制台查看完整列表”避免邮件过长。邮件主题也有讲究。单发邮件的主题是“[项目A] 构建失败”摘要邮件的主题是“[通知摘要] 您有12条未读事件3个项目”。之前我们试过把事件主题都拼进邮件标题结果长到被邮件服务商截断改成只写数量后干净很多。这个细节要特别注意主题太长不仅会被截断还可能触发服务商的垃圾邮件规则。3.4 分布式环境下的幂等处理我们的通知服务是多个实例部署的事件会通过消息队列随机分发到任意实例。如果聚合器各自维护内存buffer同一个用户的事件可能被分散到两台实例上各自为政合并效果就会打折扣。线上环境如果要求不高可以采用“按用户哈希路由”的思路事件进入消息队列时根据接收人ID哈希把同一个人的消息路由到固定的实例上。这样单个实例内的buffer就是全局视图实现成本最低。我们内部量大所以直接采用了Redis定时扫描的方案做全局聚合两个方案谈不上谁绝对好关键是符合自己的规模。在这种方案下还引出了幂等性flush是一个“取出并删除”的操作多实例并发flush同一个key可能重复发送邮件。我们用的是Redis的SET NX EX锁来保证同一时刻只有一个实例在flush某个key锁的过期时间设为30秒正常情况下flush在这个时间内肯定完成。4. 踩坑实录上线前后遇到的问题与排查4.1 时区问题导致摘要时间错乱第一个吐槽来自内部测试摘要邮件里的时间比实际时间慢了8小时。排查后发现事件时间在存储时是UTC渲染模板时直接用了服务器本地时区而我们的服务器时区正好有偏移用户在外区看到的时间自然就是错的。这个问题的根源在于时间处理不统一。修复方案是在渲染层把时间统一转成接收者的个人时区用户设置里有时区字段取不到就用企业默认时区。这里我分享一个经验任何通知类功能内部存储一律用UTC展示时才做时区转换不能在存储时就把本地时区写进去。4.2 多实例重复发送还有一个问题在压测阶段暴露出来同一批事件偶尔会收到两封内容几乎一样的邮件。原因是消息队列做了重试投递某条事件在消费端处理超时后重新入队又被另一个实例消费了一次聚合器就收到了两份相同事件。解决办法有两步。第一步消费端做去重事件本身带一个全局唯一的eventId处理前查一下是否已处理这里我们用了Redis的幂等集合。第二步聚合器内部对同key事件合并时会根据eventId去重。两层去重下来重复邮件的概率基本归零。我们其实没有让数据库去做唯一约束因为通知识别是弱一致场景偶尔重复可容忍但容忍不等于不处理。4.3 邮件体过大被服务商拒绝摘要邮件的邮件体大小也要重视。刚开始maxBatch设得比较大有用户在一个窗口内收到几十条带完整错误堆栈的事件邮件大小超过2MB被邮件服务商退了回来。教训是摘要邮件的正文必须在组装前就限制大小而不是组装完成后再判断。我们的策略是事件进flusher前先按条数截断单条事件内容按长度截断超过的部分提供控制台链接让用户点击查看。还要注意正文里的HTML标签不能因为截断而破损否则邮件在部分客户端里会显示错乱。最好在截断时只保留完整段落不按字节硬切。4.4 和既有“立即发送”逻辑的兼容这个功能推出时有一部分老用户已经习惯了即时通知如果默认开启合并他们一定会来反馈“为什么邮件变慢了”。所以我们在配置层面必须保证向后兼容combine.enabled默认false未开启的用户走老逻辑行为完全不变。用户升级到新版本后我们不是在发送链路里硬塞合并逻辑而是在通知设置页面加了一个“通知频率”选项实时、摘要15分钟、每日摘要。这样从产品层面给了用户明确的预期也把“合并通知”从一个隐藏配置提升成了用户可感知的能力。这一改动比单纯加配置项带来的接受度高很多。实际运营中我建议分两步走第一版先做后台配置灰度一部分内部团队用第二版再把功能暴露到用户设置页配上引导说明。直接全面上线的话对习惯了实时邮件的用户冲击比较大。4.5 排查速查表上线后我们整理了一张排查表遇到问题先对照着看能省很多时间。现象可能原因排查方向合并邮件一直没收到combine.enabled未开或窗口未到查聚合器日志确认flusher是否触发只收了几封数量不对maxBatch阈值过早触发看配置调大阈值邮件内容重复消息队列重试导致重复事件查消费去重逻辑和eventId摘要时间显示错误时区未转换检查渲染层时区处理邮件被退信/进垃圾箱邮件体过大或主题过长检查邮件大小、主题长度和域名信誉紧急告警也被合并了critical-immediate配置被关闭确认系统级别配置建议保持默认true5. 上线效果与下一步规划5.1 内测数据邮件量下降明显功能灰度两周我们统计了内部和种子用户的数据。开启合并通知的项目邮件发送总量下降了约68%同一个用户每天收到的通知邮件中位数从9封降到了3封。打开率提升了约15个百分点说明邮件少的时候用户更愿意细看。邮件服务商的账单也明显下降虽然这部分不是核心目的但确实是实打实的收益。有个细节值得说合并之后退订和垃圾投诉的点击率也下降了。虽然样本不大但符合预期——邮件噪音减少后用户不会那么烦躁地去找退订入口。5.2 用户反馈与迭代收集到的用户反馈里正面反馈占多数集中在“早上打开邮箱终于不是十几封了”这类声音。也有几条有建设性的负面反馈有人希望不同的项目用不同的合并窗口有人希望摘要邮件里可以直接点按钮处理事件比如确认告警、标记已读还有人希望按小时维度做“今日摘要”而不是15分钟窗口。这些反馈我们排进了后续迭代。第一轮迭代做了“通知频率”三级选项实时、摘要、每日用户可以在设置页自由切换第二轮计划做摘要邮件内嵌操作按钮比如“确认告警”“跳转工单”把邮件从信息展示升级成处理入口。这一步比单纯合并邮件价值更大也是业内比较认可的方向。5.3 后续可以扩展的方向这个功能做完以后我回头看觉得合并通知本质上是一个“通知降噪”体系的一部分后续还值得做的方向至少有三个第一是智能优先级。结合用户行为判断哪些事件用户通常会点开哪些事件看了标题就够了对后者自动降噪。第二是跨渠道整合邮件、站内信、IM机器人共用同一套聚合策略用户在IM里收到的也是合并后的摘要而不是每个渠道各发各的。第三是通知回执闭环记录用户是否打开摘要、是否点击了里面的链接用这些数据反向调整合并策略。这三个方向工程量都不小但方向是对的。通知系统做久了就会发现用户要的从来不是“更多通知”而是“更少但更有用的通知”。合并邮件只是这个目标的第一块拼图。写到这里我在这个功能上踩过的坑、做过的取舍基本都交代完了。如果让我只说一条最值得分享的经验那就是接到类似“option to combine notifications in 1 email”这种看起来很小的feature request不要急着在邮件发送前面加一个开关就完事先花时间把合并窗口、合并维度、紧急绕过、幂等控制这几个问题想清楚。这些决策直接决定了功能是让用户觉得“清爽了”还是变成“邮件来得更慢但还是一样乱”。希望这篇复盘对正在做通知系统的你有帮助。

相关新闻

从座舱测试到HIL与机器人测试:自动化测试体系设计与转岗经验

从座舱测试到HIL与机器人测试:自动化测试体系设计与转岗经验

26 届应届生,做了半年智能座舱测试后,我为什么转去做 HIL 和机器人控制测试?先说一下背景。我是 26 届应届生,学校普通,专业也偏小众,实习加正式工作加起来在座舱测试方向待了大概半年。这半年里我碰过中控…

2026/8/29 12:18:47 阅读更多 →
YOLO人脸检测与表情识别双任务系统实战指南

YOLO人脸检测与表情识别双任务系统实战指南

简介:人脸检测与表情识别是计算机视觉中典型的多任务协同问题,其核心在于共享特征提取与解耦预测头的设计原理。基于YOLO架构的轻量级单阶段模型,凭借高推理速度与良好精度平衡,成为边缘端部署的主流选择;通过主干频域…

2026/8/29 12:18:47 阅读更多 →
AI自动化测试实战:从智能定位到弹窗处理

AI自动化测试实战:从智能定位到弹窗处理

传统自动化测试有一个很难回避的事实:脚本写出来不难,难的是让它第二天还能继续跑。元素定位偶发失败、版本升级导致DOM变更、页面弹窗抢焦点、测试数据写死,任何一个环节出问题,都会让一批用例在夜里悄悄变红。这个问题困扰了测试…

2026/8/29 12:18:47 阅读更多 →

最新新闻

PowerToys 文本提取器教程:如何把屏幕上的文字一键变成可复制文本

PowerToys 文本提取器教程:如何把屏幕上的文字一键变成可复制文本

PowerToys 文本提取器教程:如何把屏幕上的文字一键变成可复制文本 【免费下载链接】PowerToys Microsoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows 项目地址: https://gitcode.com/GitHub_Trending/p…

2026/8/29 12:55:00 阅读更多 →
三块屏不再混乱:免费搞定多窗口排列的FancyZones快速指南

三块屏不再混乱:免费搞定多窗口排列的FancyZones快速指南

三块屏不再混乱:免费搞定多窗口排列的FancyZones快速指南 【免费下载链接】PowerToys Microsoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows 项目地址: https://gitcode.com/GitHub_Trending/po/PowerT…

2026/8/29 12:55:00 阅读更多 →
Browser-Use AI浏览器自动化入门指南:从0到跑通你的第一个任务

Browser-Use AI浏览器自动化入门指南:从0到跑通你的第一个任务

Browser-Use AI浏览器自动化入门指南:从0到跑通你的第一个任务 【免费下载链接】browser-use 🌐 Make websites accessible for AI agents. Automate tasks online with ease. 项目地址: https://gitcode.com/GitHub_Trending/br/browser-use 早上…

2026/8/29 12:55:00 阅读更多 →
2026最值得关注的开源SEO工具OpenSEO:完整功能解析

2026最值得关注的开源SEO工具OpenSEO:完整功能解析

2026最值得关注的开源SEO工具OpenSEO:完整功能解析 【免费下载链接】open-seo Open source alternative to Semrush and Ahrefs 项目地址: https://gitcode.com/GitHub_Trending/op/open-seo OpenSEO 是一款开源的 SEO 工具,被称为 Semrush 和 Ah…

2026/8/29 12:55:00 阅读更多 →
Vue3加载条(LoadingBar)

Vue3加载条(LoadingBar)

效果如下图: 在线预览 APIs LoadingBar 参数说明类型默认值containerClass加载条容器的类名stringundefinedcontainerStyle加载条容器的样式CSSProperties{}loadingBarSize加载条大小,单位 pxnumber2colorLoading加载中颜色stringundefinedcolorFinis…

2026/8/29 12:55:00 阅读更多 →
FLAC格式播放不了怎么办?flac转mp3的简单方法实测记录

FLAC格式播放不了怎么办?flac转mp3的简单方法实测记录

使用背景与需求分析 相信不少朋友和我一样,收藏了很多无损音乐,尤其是FLAC格式的。音质确实好,但麻烦也不少:手机内存占得飞快,想发给朋友听听,结果对方设备不支持播放,或者车载音响根本不认这…

2026/8/29 12:54:00 阅读更多 →

日新闻

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

一、设置环境变量etc目录下的profile.d文件目录 /etc/profile.d1、编写 vi test.sh文件内容# jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile 命令使…

2026/8/29 0:00:24 阅读更多 →
【JavaScript】内存管理-垃圾回收机制-内存泄露

【JavaScript】内存管理-垃圾回收机制-内存泄露

内存管理 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。 而 JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。 整…

2026/8/29 0:00:24 阅读更多 →
Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP 的目标是把 MCP(Model Context Protocol)能力延伸到真实嵌入式硬件实验室:AI Agent 通过一个标准化的 MCP Server,就能查看目标板状态、控制上电断电、复位开发板、读取串口日志,甚至执行镜像刷写。对于经…

2026/8/29 0:00:24 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/28 11:23:26 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/28 23:05:07 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/28 19:47:53 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/29 4:34:53 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/28 17:43:04 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/29 2:05:18 阅读更多 →