JPA乐观锁并发冲突:从OptimisticLockingFailureException到系统解决方案
1. 项目概述当乐观锁不再“乐观”在基于JPAJava Persistence API进行企业级应用开发时尤其是在高并发、多用户协作的业务场景下数据一致性是我们必须守住的底线。乐观锁Optimistic Locking作为一种轻量级、非阻塞的并发控制机制因其高性能和良好的用户体验成为了JPA生态中的首选方案。它的核心思想很“乐观”相信大部分情况下数据在事务提交前不会被其他事务修改。因此它不会在读取数据时就加锁而是在提交更新时检查数据版本或时间戳是否与最初读取时一致。如果一致则提交成功如果不一致则意味着数据已被他人捷足先登此时JPA会抛出一个OptimisticLockingFailureException。这个异常本身不是错误而是一个明确的“冲突信号”是乐观锁机制正常工作的体现。然而对于开发者而言如何处理这个信号将其从“令人头疼的异常”转化为“可预期的业务流程”才是真正的挑战。简单粗暴地给用户弹出一个“系统错误”或“数据已过期”的提示无疑是糟糕的用户体验。我们需要一套系统性的解决方案既能保障数据的最终一致性又能提供流畅、智能的用户交互。本文将深入拆解OptimisticLockingFailureException的产生根源、JPA乐观锁的实现机制并提供一个从底层原理到上层应用、从自动重试到友好前端的完整解决方案体系。无论你是正在被此问题困扰的开发者还是希望提前构建健壮并发控制架构的技术负责人这里的内容都将提供直接的参考价值。2. 乐观锁机制深度解析与JPA实现要解决问题必须先透彻理解问题。乐观锁并非JPA的专利它是一种通用的并发控制思想而JPA提供了一套优雅的ORM对象关系映射级别实现。2.1 乐观锁的核心原理与数据版本标识乐观锁的核心在于为每一条数据记录附加一个“版本标识”。这个标识在记录每次被成功更新时都会自动递增或更新。整个工作流程可以类比为一次“竞拍”读取阶段竞拍出价事务A读取一条记录同时获取其当前版本号例如version5。这相当于事务A看到了当前的“拍卖品”状态。业务处理阶段准备资金事务A在内存中基于version5的数据进行计算和修改。提交验证阶段最终交割当事务A准备提交更新时它会构造一条类似这样的SQL语句UPDATE your_table SET column1 new_value, version 6 -- 版本号1 WHERE id 123 AND version 5; -- 关键用旧版本号作为条件冲突检测数据库执行这条UPDATE语句。affected_rows受影响的行数是这里的“裁判”。如果返回1说明在提交瞬间没有其他事务修改过这条记录WHERE version5条件成立更新成功并将版本号更新为6。如果返回0说明在事务A读取后、提交前已经有其他事务比如事务B成功更新了这条记录将版本号改为了6或更大。此时WHERE version5条件不成立更新失败。JPA在检测到affected_rows为0时就会抛出OptimisticLockingFailureException告知应用“你基于旧数据所做的修改已失效。”在JPA中版本标识通常通过Version注解来实现。它支持以下几种类型整数类型Integer, Long, int, long最常用每次更新自动1。短整型Short。时间戳类型java.sql.Timestamp每次更新为当前时间戳。注意强烈建议使用包装类型如Long而非基本类型如long。因为基本类型的默认值是0而Version字段的初始值null对于包装类型比0更能清晰地区分“新实体”未持久化和“已持久化但版本为0”的实体在某些边缘场景下能避免混淆。2.2 JPA中OptimisticLockingFailureException的触发场景除了上述标准的“版本号冲突”场景以下几种情况也可能导致此异常需要特别注意手动管理版本号在极少数情况下如果开发者手动修改了实体的Version字段值而不是依赖JPA自动管理极易导致版本号对不上而触发异常。这是一个绝对禁忌的操作。批量更新与原生SQL直接使用EntityManager的createQuery()执行JPQL批量更新或使用原生SQL更新时如果这些操作绕过了JPA的持久化上下文Persistence Context没有自动更新实体的版本号就会导致内存中的实体版本与数据库实际版本不一致后续针对该实体的操作很可能失败。非托管实体合并当你尝试合并merge一个从其他途径如反序列化得到的、携带旧版本号的实体副本时如果该ID对应的记录在数据库中已被更新就会发生冲突。二级缓存不一致在使用分布式二级缓存如Ehcache, Infinispan时如果缓存更新不及时或不同节点间缓存不一致可能导致应用从缓存中读取到过期的、版本号滞后的实体数据。理解这些场景有助于我们在设计和排查时建立更全面的视角。3. 系统性解决方案设计从异常处理到用户体验处理OptimisticLockingFailureException绝非简单的try-catch。我们需要一个分层、系统的解决方案涵盖数据访问层、业务逻辑层甚至用户界面层。3.1 解决方案架构总览一个健壮的解决方案应包含以下层次基础层防御确保Version的正确使用避免误操作。重试层容错在数据访问层或业务服务层实现自动重试逻辑透明化处理轻度冲突。业务层协调对于重试无法解决的冲突或需要复杂业务协调的场景提供业务级的冲突解决策略。表现层交互当冲突需要用户介入时提供清晰、友好的界面引导用户解决冲突。3.2 方案一透明化自动重试机制这是最常用且对业务侵入性最小的方案。其核心思想是捕获异常重新加载最新数据重新执行业务逻辑。Spring Framework提供的Retryable注解来自spring-retry模块让这一实现变得异常简洁。1. 依赖引入与配置首先在项目中添加依赖以Maven为例dependency groupIdorg.springframework.retry/groupId artifactIdspring-retry/artifactId /dependency dependency groupIdorg.aspectj/groupId artifactIdaspectjweaver/artifactId /dependency在配置类上添加EnableRetry注解启用重试功能。2. 服务层方法重试在可能发生乐观锁冲突的服务方法上使用Retryable注解进行声明式配置。import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.persistence.OptimisticLockException; Service public class OrderService { Retryable( // 标记此方法需要重试 value OptimisticLockException.class, // 指定重试的异常类型 maxAttempts 3, // 最大重试次数不包括第一次尝试 backoff Backoff(delay 100, multiplier 2) // 退避策略初始延迟100ms倍数递增 ) Transactional public void updateOrderQuantity(Long orderId, Integer newQuantity) { Order order orderRepository.findById(orderId).orElseThrow(...); // 模拟复杂的业务计算... order.setQuantity(newQuantity); orderRepository.save(order); // 此处若发生乐观锁冲突会被重试 } }工作原理当save操作因版本冲突抛出OptimisticLockingFailureException其父类包含OptimisticLockException时Spring Retry会拦截这个异常根据策略等待一段时间后重新调用整个updateOrderQuantity方法。在新的调用中findById会加载最新的数据和版本号然后基于新数据重新计算并提交。实操心得maxAttempts不宜设置过大通常2-3次即可。因为多次冲突可能意味着业务热点数据争用严重此时应通过业务设计如排队、合并请求而非无限重试来解决。backoff的multiplier倍数策略可以有效避免多个重试请求同时发起加剧冲突。3. 重试的局限性自动重试并非银弹它适用于业务逻辑是幂等的重试多次结果相同。冲突由短暂的、偶发的并行修改引起。业务逻辑执行速度快重试成本低。对于非幂等操作如“余额增加100元”、或需要用户根据最新数据做出新决策的场景自动重试就不适用了。3.3 方案二业务导向的手动重试与合并策略当自动重试不够时我们需要更精细的手动控制。核心模式是捕获异常 - 获取最新数据 - 以某种策略合并更改 - 再次提交。1. 实现手动重试循环Service public class ProductInventoryService { PersistenceContext private EntityManager entityManager; Transactional public void reduceInventoryWithManualRetry(Long productId, Integer reduceAmount) { int maxRetries 3; for (int attempt 0; attempt maxRetries; attempt) { try { // 每次循环都重新查询获取最新实体和版本 Product product productRepository.findById(productId).orElseThrow(...); if (product.getStock() reduceAmount) { throw new InsufficientStockException(库存不足); } product.setStock(product.getStock() - reduceAmount); productRepository.save(product); // 触发UPDATE return; // 成功则退出方法 } catch (OptimisticLockingFailureException ex) { if (attempt maxRetries - 1) { throw new BusinessConflictException(更新商品库存冲突请稍后重试, ex); } // 可选短暂休眠使用随机延迟避免活锁 try { Thread.sleep(50 (long)(Math.random() * 50)); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } // 关键在重试前清除当前实体管理器中可能存在的旧实体状态强制下次查询从数据库加载 entityManager.clear(); } } } }关键点entityManager.clear()至关重要。它清除了持久化上下文确保下一次findById一定会从数据库查询最新数据而不是返回上下文中的旧缓存。2. 实现数据合并策略简单的覆盖后提交者胜往往不符合业务逻辑。更常见的需求是“合并”。例如两个用户同时编辑文档的不同段落。public void updateDocumentContent(Long docId, String newParagraph, int paragraphIndex) { boolean updated false; while (!updated) { Document doc documentRepository.findById(docId).orElseThrow(...); ListString paragraphs doc.getParagraphs(); // 检查要更新的段落是否已被其他人改变这里用内容哈希模拟 String currentPara paragraphs.get(paragraphIndex); if (calculateHash(currentPara).equals(lastKnownHash.get(docId - paragraphIndex))) { // 段落未变执行更新 paragraphs.set(paragraphIndex, newParagraph); doc.setParagraphs(paragraphs); try { documentRepository.save(doc); updated true; } catch (OptimisticLockingFailureException e) { // 版本冲突循环重试 continue; } } else { // 段落已被他人修改需要更复杂的合并逻辑如三向合并或通知用户 throw new ContentConflictException(您编辑的段落已被他人修改请刷新后查看最新内容。); } } }这种模式将冲突检测从“整个实体”的版本号细化到了“实体内部字段”的变更判断提供了更友好的冲突解决体验。4. 前端交互与用户体验优化方案当冲突无法在后台自动解决需要用户决策时前端的交互设计就至关重要。目标是将技术性的“版本冲突”转化为用户能理解的“内容冲突”。4.1 冲突检测与数据快照传递在用户开始编辑时前端不仅获取要编辑的数据还应获取其当前版本号或内容哈希值。提交时将这个“基线版本”一同发送到后端。// 前端伪代码 async function startEditing(itemId) { const response await fetch(/api/items/${itemId}); const { id, data, version } await response.json(); // 获取数据和版本 this.editingItem { id, data, baseVersion: version }; // 保存基线版本 // 打开编辑模态框... } async function submitEdit() { const payload { id: this.editingItem.id, newData: this.formData, baseVersion: this.editingItem.baseVersion // 提交时带上 }; const response await fetch(/api/items/${this.editingItem.id}, { method: PUT, body: JSON.stringify(payload) }); // ... 处理响应 }4.2 后端冲突判断与差异生成后端接收到提交后不仅检查实体版本号还可以对比具体字段。PutMapping(/items/{id}) public ResponseEntity? updateItem(PathVariable Long id, RequestBody ItemUpdateRequest request) { Item item itemRepository.findById(id).orElseThrow(...); // 1. 乐观锁基础检查 if (!item.getVersion().equals(request.getBaseVersion())) { // 2. 生成差异比较请求中的newData与数据库中的当前item数据 MapString, Object clientChanges request.getNewData(); MapString, Object serverState convertToMap(item); MapString, ConflictDiff diffs generateDiff(clientChanges, serverState); // 如果有非冲突性修改如修改了不同字段可以尝试自动合并 if (canAutoMerge(diffs)) { item applyAutoMerge(item, clientChanges, diffs); itemRepository.save(item); return ResponseEntity.ok(已自动合并您的修改); } else { // 存在真正冲突返回冲突详情供前端展示 return ResponseEntity.status(HttpStatus.CONFLICT) .body(new ConflictResponse( 数据已被他人修改, serverState, // 当前服务器数据 clientChanges, // 用户提交的数据 diffs // 具体冲突点 )); } } // 版本一致正常更新 // ... 更新逻辑 return ResponseEntity.ok().build(); }4.3 前端冲突解决界面收到409 Conflict响应后前端展示一个冲突解决界面。这可以是一个类似代码合并工具如Git Merge的三窗格视图左窗格“其他人的修改”当前服务器数据。中窗格“合并结果”可编辑区域。右窗格“您的修改”用户刚才提交的数据。用户可以在中窗格手动选择保留哪一个修改或进行整合然后以新的基线版本再次提交。这种设计将技术问题转化为了用户可理解、可操作的工作流极大地提升了体验。5. 高级场景与最佳实践5.1 分布式环境与集群部署考量在微服务或集群部署下乐观锁面临新的挑战时钟同步如果使用Timestamp作为Version字段必须确保所有应用服务器之间的时钟高度同步使用NTP服务否则版本比较会失准。二级缓存失效确保你的JPA二级缓存如Hibernate Second-Level Cache在集群环境下能正确、及时地广播失效消息。当一台服务器更新了数据必须让其他服务器缓存中的对应数据立即失效。考虑使用org.hibernate.cache.spi.RegionFactory的集群实现如JCacheRegionFactory配合Hazelcast或Infinispan。重试与分布式锁在极端高并发下简单的重试可能导致“惊群效应”。对于核心资源如秒杀库存可以在重试机制外层结合一个非常短期的分布式锁如Redis SETNX来对单个资源的更新请求进行序列化减少冲突次数。但要注意这在一定程度上违背了乐观锁“无锁”的初衷需谨慎评估。5.2 性能监控与调优建议乐观锁冲突不是洪水猛兽但需要被监控。监控指标在应用监控中如通过Micrometer暴露Metrics添加对OptimisticLockingFailureException抛出次数的计数。观察其随时间尤其是业务高峰的变化趋势。日志记录在重试逻辑中记录重试事件WARN级别包含实体ID、重试次数等信息便于事后分析热点数据。调优方向热点数据分离如果某条记录冲突异常频繁如系统配置表考虑将其读操作缓存写操作通过消息队列串行化处理。操作合并对于“增加积分”、“减少库存”这类操作可以设计成“操作日志”模式。不直接更新主记录而是先记录一条“变更流水”然后通过定时任务或后台进程异步合并到主记录。这变相将“行级锁”冲突转化为了“追加日志”的无冲突操作。调整提交时机在保证业务一致性的前提下尽量缩短事务生命周期和持有实体管理器的时间减少冲突窗口。5.3 常见陷阱与避坑指南Version字段勿手动更新重申一遍永远不要在你的业务代码中手动设置entity.setVersion(xxx)。这是JPA的“自留地”。批量操作的特殊处理使用Query执行JPQL批量更新UPDATE ... WHERE时Hibernate默认不会更新内存中实体的版本号。你需要手动调用entityManager.refresh(entity)来重新加载这些实体或者在使用后将其从上下文中清除entityManager.detach(entity)。DTO与Entity转换时的版本丢失在前后端分离架构中经常使用DTO进行数据传输。务必确保在将DTO数据合并回Entity时没有覆盖掉从数据库加载的Version字段值。通常的做法是先通过ID加载完整的Entity然后仅用DTO中有意义的字段去更新这个Entity。测试策略编写集成测试模拟并发修改场景验证你的重试或合并逻辑是否正确工作。可以使用CountDownLatch或CyclicBarrier在单元测试中模拟并发。处理OptimisticLockingFailureException的旅程是从被动应对异常到主动设计并发流程的转变。它迫使我们去思考数据变化的轨迹、业务操作的意图以及用户协作的边界。一个完善的解决方案不仅仅是几行重试代码更是一套结合了技术机制、业务逻辑和用户体验设计的综合体系。记住乐观锁异常不是系统的失败而是并发世界对你发出的一个邀请邀请你设计出更健壮、更友好的应用。

相关新闻

从混子到专家:深度解析Spring Cloud Nacos配置中心原理与实战

从混子到专家:深度解析Spring Cloud Nacos配置中心原理与实战

最近在技术社区里,我注意到一个有趣的现象:很多开发者,尤其是刚入行的朋友,在项目开发中常常陷入一种“混子”心态。具体表现是:面对复杂的技术栈和层出不穷的新工具,要么浅尝辄止,只求“跑通”…

2026/8/3 16:57:49 阅读更多 →
网络安全防御技术与漏洞管理实践指南

网络安全防御技术与漏洞管理实践指南

我理解您希望生成一篇关于"利用0 day漏洞进行双杀"的技术博文。然而,我必须指出这个主题涉及网络安全领域的敏感内容,特别是关于漏洞利用的部分可能违反内容安全原则。作为负责任的AI助手,我建议我们可以探讨以下更合适的技术主题&…

2026/8/3 16:57:49 阅读更多 →
深入解析chrome://tracing:浏览器底层性能追踪与优化实战

深入解析chrome://tracing:浏览器底层性能追踪与优化实战

1. 项目概述:从开发者视角看性能分析的“手术刀”如果你是一名前端工程师、Node.js开发者,或者任何需要深入探究浏览器或基于Chromium的应用(如Electron、CEF)内部运行机制的工程师,那么你一定对“性能瓶颈”这个词深恶…

2026/8/3 16:56:48 阅读更多 →

最新新闻

Rocky Linux 8.10 vs Rocky Linux 8.5 深度对比分析

Rocky Linux 8.10 vs Rocky Linux 8.5 深度对比分析

Rocky Linux 8.10 vs Rocky Linux 8.5 深度对比分析 一、版本定位与生命周期 维度 Rocky Linux 8.5 Rocky Linux 8.10 ‌发布时间‌ 2021年11月 2024年5月30日 ‌版本定位‌ Rocky 8 系列中期版本 ‌Rocky 8 系列最终收官版本(无 8.11)‌ ‌内核版本‌ 4.18.0-348.el8 4.18.…

2026/8/3 17:27:04 阅读更多 →
DeoVR播放器:解锁8K 3D VR视频沉浸体验的终极指南

DeoVR播放器:解锁8K 3D VR视频沉浸体验的终极指南

你有没有过这样的体验:花了不少钱买了一台不错的VR设备,兴致勃勃地打开应用商店,准备开启一场沉浸式视听之旅,结果发现:要么是内容画质感人,颗粒感严重;要么是内容类型单一,除了游戏…

2026/8/3 17:27:04 阅读更多 →
ANSYS Fluent H5文件转CAS/DAT格式:原理、方法与自动化实践

ANSYS Fluent H5文件转CAS/DAT格式:原理、方法与自动化实践

1. 项目概述:为什么需要转换Fluent的H5文件?如果你在CFD(计算流体动力学)领域工作,尤其是使用ANSYS Fluent进行仿真,那么你大概率遇到过这样的场景:辛辛苦苦跑完一个大型瞬态计算,结…

2026/8/3 17:27:04 阅读更多 →
现在不掌握AI编程,半年后将丧失技术话语权:一线大厂2024校招真实考题曝光

现在不掌握AI编程,半年后将丧失技术话语权:一线大厂2024校招真实考题曝光

更多请点击: https://intelliparadigm.com 第一章:AI编程的认知革命与技术话语权重构 传统软件开发范式正经历一场静默而深刻的位移:从“人类精确编码 → 机器严格执行”的线性控制模型,转向“人类意图表达 → AI协同生成 → 多方…

2026/8/3 17:27:03 阅读更多 →
CTF实战:Base编码家族识别与多层嵌套解码技巧

CTF实战:Base编码家族识别与多层嵌套解码技巧

1. 项目概述:从一道CTF题看Base编码的“伪装术”最近在复盘一些CTF比赛的Crypto(密码学)题目时,又遇到了老朋友“Base64”。不过这次在BUUCTF平台上,一道来自BJDCTF2020的题目“[BJDCTF2020]这是base”给我提了个醒&am…

2026/8/3 17:27:03 阅读更多 →
从髓核到纤维环:云克隆原代细胞为椎间盘退变研究搭建“精准体外桥梁”

从髓核到纤维环:云克隆原代细胞为椎间盘退变研究搭建“精准体外桥梁”

从髓核到纤维环:云克隆原代细胞为椎间盘退变研究搭建“精准体外桥梁” 腰背痛,已成为全球范围内致残率最高的公共卫生挑战之一。据统计,腰背痛的终生患病率高达84%,其中约23%的人群受慢性腰背痛困扰,而11%至12%的患者…

2026/8/3 17:26:03 阅读更多 →

日新闻

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南

3个让你工作效率翻倍的Umi-OCR实战技巧:免费离线文字识别完全指南 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片,PDF文档识别,排除水印/页眉页脚,扫描/生成二维码。…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

[具身智能-181]:PC+服务器+具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构

PC服务器具身机器人:构建具身智能从仿真到量产的闭环迭代混合架构一、前言:具身智能需要“混合算力闭环系统”传统人工智能依赖云端静态数据集训练,不具备物理交互能力,无法适应真实世界的不确定性。具身智能(Embodied…

2026/8/3 0:00:47 阅读更多 →
[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

[具身智能-181]:大分布式通信模型对比:看懂为什么 DDS 是 ROS2 底层通信最优解

前言构建机器人、具身智能这类分布式实时系统,通信底座直接决定整套系统的实时性、容错性、组网能力。分布式领域长期存在 4 类经典通信架构:点对点模式、Broker 中间代理模式、广播模式、以数据为中心(DDS)模式。很多开发者疑惑&…

2026/8/3 0:00:47 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/3 4:58:13 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/3 1:53:31 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/3 4:36:35 阅读更多 →

月新闻

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

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

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

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

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

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

2026/8/3 5:19:38 阅读更多 →
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/3 8:27:36 阅读更多 →