Flowable动态多实例任务:从原理到实战,解决流程中参与者不确定性问题
1. 项目概述为什么需要动态多实例在流程引擎的实际应用中我们经常会遇到一种场景一个任务需要由一组人来处理但这组人的数量在流程设计时是未知的只有在流程运行到该节点时才能确定。比如一个报销审批流程需要所有项目组成员会签但项目组成员名单是动态的可能随着项目进展而变化。再比如一个文档需要发送给多个部门的负责人审阅而涉及的部门数量取决于文档的类型。如果你还在为每个可能的参与者数量创建多个相同的任务节点或者写一堆复杂的网关逻辑来判断那说明你还没用上Flowable的“动态多实例”这个利器。动态多实例顾名思义就是多实例任务的参与者集合是动态生成的。它与静态多实例在流程设计时就固定了参与者列表如user1, user2, user3形成鲜明对比。静态多实例适合参与者固定的场景比如固定的评审委员会而动态多实例则解决了业务流程中最大的不确定性之一——人的不确定性。掌握它意味着你的流程模型能更好地贴合现实世界的复杂性和灵活性从“僵硬的图纸”变成“有生命的有机体”。2. 核心概念与运行机制拆解在深入代码之前我们必须先厘清几个核心概念这是理解动态多实例如何工作的基石。2.1 多实例与动态多实例的本质区别很多人容易混淆这两个概念。简单来说多实例Multi-Instance是一种活动通常是UserTask的行为特性。它允许一个活动在运行时创建多个并行的或串行的实例。每个实例都是独立的拥有自己的执行上下文如任务变量。静态多实例在BPMN XML中通过multiInstanceLoopCharacteristics元素定义并使用collection属性指定一个固定的列表值如${assigneeList}但这个列表在流程启动时就必须确定。动态多实例同样使用multiInstanceLoopCharacteristics但其collection属性指向一个流程变量这个变量的值一个集合可以在流程运行到该节点时通过前置监听器、服务任务或其他方式动态计算或修改。关键在于这个集合的内容在流程设计时是未知的。2.2 关键属性解析在BPMN 2.0规范中多实例活动通过multiInstanceLoopCharacteristics元素配置。对于动态多实例以下几个属性至关重要isSequential: 布尔值。false表示并行多实例所有实例同时创建常见于会签true表示串行多实例一个接一个地完成常见于逐级审批。collection:这是实现动态性的核心。它的值是一个表达式如${dynamicUserList}该表达式在运行时被解析必须得到一个java.util.Collection、java.util.Iterable或数组。Flowable会遍历这个集合为每个元素创建一个任务实例。elementVariable: 为集合中的每个元素指定一个变量名。在任务实例中你可以通过这个变量名访问当前迭代的元素。例如elementVariableassignee那么在任务表达式中就可以用${assignee}来指代当前任务的办理人。completionCondition: 完成条件。这是一个可选但强大的属性允许你定义在多实例任务完成前需要满足的条件。例如${nrOfCompletedInstances/nrOfInstances 0.5}表示超过一半的实例完成时整个多实例活动就完成“多数决”。2.3 内置变量与生命周期Flowable为每个多实例活动自动管理一组内置变量在表达式中可以直接使用nrOfInstances: 实例的总数即collection集合的大小。nrOfActiveInstances: 当前活跃未完成的实例数。nrOfCompletedInstances: 已完成的实例数。loopCounter: 当前正在执行的实例的索引从0开始。理解生命周期很重要当流程执行到达动态多实例节点时引擎会计算collection表达式的值得到集合A。根据集合A的大小n创建n个任务实例并行或准备创建第一个实例串行。为每个实例设置elementVariable。每个实例独立运行可以独立完成或驳回。根据completionCondition如果存在或所有实例的完成状态判断整个多实例活动是否完成然后推动流程继续。3. 实战从建模到代码的完整实现理论讲完我们进入实战环节。我将通过一个“项目组月度报告会签”的场景展示从BPMN设计到Java代码实现的完整链路。3.1 BPMN 2.0 XML 模型定义首先我们设计流程。关键是在multiInstanceLoopCharacteristics中将collection指向一个运行时变量。?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://flowable.org/bpmn process iddynamic_multi_instance_process name动态多实例会签流程 isExecutabletrue startEvent idstartEvent1 / sequenceFlow idflow1 sourceRefstartEvent1 targetRefgenDynamicListTask / !-- 服务任务动态生成会签人员列表 -- serviceTask idgenDynamicListTask name生成动态会签列表 flowable:classcom.example.flowable.listener.GenerateDynamicAssigneeListener / sequenceFlow idflow2 sourceRefgenDynamicListTask targetRefdynamicMultiInstanceTask / !-- 动态多实例用户任务 -- userTask iddynamicMultiInstanceTask name项目报告会签 !-- 这里是关键定义多实例循环特性 -- multiInstanceLoopCharacteristics isSequentialfalse !-- 并行会签 -- flowable:collectiondynamicAssigneeCollection !-- 指向流程变量 -- flowable:elementVariablesingleAssignee !-- 每个实例中的元素变量名 -- !-- 可选完成条件此处为全部完成 -- !-- completionCondition${nrOfCompletedInstances nrOfInstances}/completionCondition -- /multiInstanceLoopCharacteristics /userTask sequenceFlow idflow3 sourceRefdynamicMultiInstanceTask targetRefendEvent1 / endEvent idendEvent1 / /process /definitions关键点解析flowable:collectiondynamicAssigneeCollection这告诉Flowable去流程变量中查找名为dynamicAssigneeCollection的变量并将其值作为集合进行遍历。这个变量将在服务任务genDynamicListTask中设置。flowable:elementVariablesingleAssignee在创建的每个会签任务实例中都会有一个名为singleAssignee的局部变量其值就是集合中当前遍历到的元素比如一个用户ID。我们可以在任务分配时使用它flowable:assignee${singleAssignee}。但注意在上面的XML中我们直接在multiInstanceLoopCharacteristics里定义了elementVariable通常也需要在userTask上配置flowable:candidateUsers或flowable:assignee来引用它这里为了清晰先省略后面在Java代码中演示另一种设置方式。3.2 Java服务任务动态生成参与者集合接下来实现那个用于生成动态列表的服务任务。这里我们使用一个实现了JavaDelegate接口的类。package com.example.flowable.listener; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; Component(generateDynamicAssigneeListener) public class GenerateDynamicAssigneeListener implements JavaDelegate { Override public void execute(DelegateExecution execution) { // 模拟从数据库、外部接口或根据业务逻辑动态获取参与者列表 // 例如根据当前项目ID查询该项目下的所有成员 String processInstanceId execution.getProcessInstanceId(); // 假设这是动态查询的结果 ListString dynamicAssignees fetchDynamicAssigneesFromService(processInstanceId); // 将动态生成的列表设置为流程变量变量名必须与BPMN中的collection属性值一致 execution.setVariable(dynamicAssigneeCollection, dynamicAssignees); // 可选记录日志便于调试 execution.setVariable(assigneeListSize, dynamicAssignees.size()); System.out.println(动态生成的会签人员列表: dynamicAssignees); } private ListString fetchDynamicAssigneesFromService(String processInstanceId) { // 这里模拟业务逻辑可能是根据流程变量、表单数据、RPC调用结果来生成列表 // 示例1固定逻辑 // return Arrays.asList(zhangsan, lisi, wangwu, zhaoliu); // 示例2基于流程变量决定 String projectType (String) execution.getVariable(projectType); if (urgent.equals(projectType)) { return Arrays.asList(manager_li, director_wang); // 紧急项目只需经理和总监 } else { return Arrays.asList(zhangsan, lisi, wangwu, zhaoliu, sunqi); // 普通项目全体成员 } // 示例3从数据库查询需要注入Repository // return projectMemberRepository.findUserIdsByProcessInstanceId(processInstanceId); } }注意execution.setVariable设置的变量是流程变量在整个流程实例生命周期内有效除非被覆盖。而elementVariable如singleAssignee是任务局部变量只在其对应的单个任务实例中有效。3.3 流程启动与任务查询现在我们来启动这个流程并观察动态多实例的创建。Autowired private RuntimeService runtimeService; Autowired private TaskService taskService; public void startDynamicMultiInstanceProcess() { // 1. 设置初始流程变量可选用于影响动态列表的生成 MapString, Object variables new HashMap(); variables.put(projectType, normal); // 普通项目 variables.put(projectId, PROJ-2023-001); // 2. 启动流程实例 ProcessInstance processInstance runtimeService.startProcessInstanceByKey( dynamic_multi_instance_process, // 流程定义Key variables ); System.out.println(流程实例启动成功ID: processInstance.getId()); // 3. 稍等片刻让服务任务执行完毕然后查询生成的多实例任务 // 在实际应用中这里可能需要异步等待或使用事件监听器。 // 我们直接查询该流程实例下所有的任务 ListTask tasks taskService.createTaskQuery() .processInstanceId(processInstance.getId()) .list(); System.out.println(当前流程实例下的任务数量: tasks.size()); for (Task task : tasks) { System.out.println(任务ID: task.getId() , 任务名称: getName() , 办理人: getAssignee() , 创建时间: getCreateTime()); // 可以查看任务的本地位变量 // MapString, Object taskLocalVariables taskService.getVariablesLocal(task.getId()); // System.out.println(任务局部变量: taskLocalVariables); } }执行这段代码如果fetchDynamicAssigneesFromService返回5个用户那么你将在控制台看到创建了5个独立的“项目报告会签”任务每个任务的assignee或candidateUser分别对应列表中的一个用户。3.4 另一种常见模式通过任务监听器分配有时我们可能不想在BPMN XML中写死flowable:assignee${singleAssignee}而是希望更灵活地在代码中分配。这时可以在userTask上添加一个任务创建监听器。修改BPMN XML中的userTask部分userTask iddynamicMultiInstanceTask name项目报告会签 extensionElements flowable:taskListener eventcreate classcom.example.flowable.listener.MultiInstanceTaskAssignmentListener/ /extensionElements multiInstanceLoopCharacteristics isSequentialfalse flowable:collectiondynamicAssigneeCollection flowable:elementVariablesingleAssignee /multiInstanceLoopCharacteristics /userTask然后实现监听器Component(multiInstanceTaskAssignmentListener) public class MultiInstanceTaskAssignmentListener implements TaskListener { Override public void notify(DelegateTask delegateTask) { // 从任务局部变量中获取当前迭代的元素即集合中的单个办理人 String assignee (String) delegateTask.getVariableLocal(singleAssignee); // 设置任务的办理人 delegateTask.setAssignee(assignee); // 你还可以在这里做更多事情比如根据assignee设置任务优先级、到期时间等 if (manager_li.equals(assignee)) { delegateTask.setPriority(100); } System.out.println(动态多实例任务创建分配办理人: assignee); } }这种方式将业务逻辑从XML移到了Java代码中提供了更强的控制力。4. 高级应用与避坑指南掌握了基础用法后我们来看看一些更高级的场景和实践中容易踩的坑。4.1 动态修改运行中的多实例集合这是一个经典需求会签过程中突然需要增加或减少一个参与者。Flowable提供了API支持。public void modifyRunningMultiInstance(String processInstanceId, String activityId, ListString newAssigneeList) { // 假设我们要修改的节点ID是 dynamicMultiInstanceTask // 1. 首先需要找到该多实例活动的执行实例代表整个多实例活动而非单个任务 ListExecution executions runtimeService.createExecutionQuery() .processInstanceId(processInstanceId) .activityId(activityId) // 多实例活动的ID .list(); if (executions.isEmpty()) { throw new RuntimeException(未找到运行中的多实例活动: activityId); } // 通常只有一个执行代表这个多实例活动本身 Execution multiInstanceExecution executions.get(0); // 2. 设置新的集合到该执行的变量中注意变量作用域 runtimeService.setVariable(multiInstanceExecution.getId(), dynamicAssigneeCollection, newAssigneeList); // 3. **重要**手动触发多实例行为的重新计算。这通常需要调用内部命令。 // 更常见的做法是通过“事件子流程”或“边界事件”来响应变化或者设计流程时就考虑变更路径。 // 直接运行时修改集合是高级操作可能需要结合 RuntimeService 的 trigger 方法或自定义命令。 // 对于增加参与者可以创建新任务对于减少可以尝试删除未完成的任务实例。 // 此处演示一种思路非完整代码 // - 查询当前该多实例活动下所有未完成的任务。 // - 对比新旧列表找出需要新增的 assignee为每个新增的 assignee 调用 taskService.createTaskQuery()... 并手动创建新任务。 // - 找出需要移除的 assignee找到其对应的未完成任务并删除 (taskService.deleteTask(...))。 // 注意这非常复杂且容易破坏流程一致性。生产环境慎用最好通过流程设计如“加签”、“减签”子流程来实现。 }实操心得动态修改运行中的多实例是“雷区”。除非万不得已应尽量避免。更好的架构设计是将“确定参与者”这个动作作为一个独立的、可重复调用的服务或子流程。当需要变更时走一个“变更审批”子流程审批通过后终止旧的多实例活动然后带着新的参与者列表重新进入一个新实例。这样逻辑更清晰数据一致性也更好保障。4.2 串行动态多实例与循环计数器串行多实例常用于逐级审批。loopCounter在这里非常有用。userTask idserialDynamicTask name串行审批 multiInstanceLoopCharacteristics isSequentialtrue flowable:collection${approvalLevelList} flowable:elementVariablecurrentApprover /multiInstanceLoopCharacteristics extensionElements !-- 利用loopCounter设置不同的任务名称 -- flowable:formProperty idtaskTitle name任务标题 expression第${loopCounter1}级审批 / /extensionElements /userTask在监听器中你可以通过delegateTask.getVariableLocal(loopCounter)获取当前是第几轮循环从而执行不同的业务逻辑。4.3 完成条件的灵活运用completionCondition赋予了多实例活动更智能的完成逻辑。multiInstanceLoopCharacteristics isSequentialfalse flowable:collection${voterList} flowable:elementVariablevoter !-- 超过三分之二同意则通过 -- completionCondition${nrOfCompletedInstances 0 amp;amp; (agreeCount / nrOfInstances) 0.666}/completionCondition /multiInstanceLoopCharacteristics为了实现这个条件你需要在每个投票任务完成时更新一个流程变量如agreeCount。这可以通过在任务完成事件complete的监听器中实现。4.4 常见问题与排查技巧实录问题1动态多实例任务没有创建。排查步骤检查集合变量确保在到达多实例节点前流程变量dynamicAssigneeCollection已被正确设置且不为null。使用runtimeService.getVariable(executionId, “dynamicAssigneeCollection”)验证。检查变量类型确保设置的变量是List、Array等集合类型而不是字符串。Arrays.asList(“a”, “b”)是正确的“a,b,c”是错误的。查看日志开启Flowable的DEBUG级别日志搜索Creating a new task instance相关的日志看引擎是否尝试创建实例。检查表达式确认BPMN中的flowable:collection属性值是否正确指向了流程变量名没有拼写错误。问题2任务创建了但办理人assignee为null。排查步骤检查elementVariable确认elementVariable的名字如singleAssignee在任务分配表达式中被正确引用。例如flowable:assignee${singleAssignee}。检查集合内容确保你放入dynamicAssigneeCollection的集合里的每个元素都是有效的用户ID字符串且不为null或空字符串。使用任务监听器如果分配逻辑复杂改用任务创建监听器TaskListener在notify方法中打印delegateTask.getVariableLocal(“singleAssignee”)进行调试。问题3串行多实例卡住不继续创建下一个实例。排查步骤检查当前任务是否完成确认上一个实例的任务是否已经调用taskService.complete(taskId)。串行多实例只有在当前实例任务完成后才会创建下一个。检查完成条件如果设置了completionCondition检查条件是否被意外满足导致活动提前结束。检查异常查看是否有全局或局部的事件监听器抛出了未捕获的异常阻塞了流程推进。问题4性能问题当动态集合非常大时如上千人流程启动或任务查询变慢。优化建议分页查询在查询任务时务必使用.listPage(start, size)而不是.list()。异步执行考虑将生成超大列表的服务任务设置为异步flowable:async”true”避免阻塞流程事务。业务拆分从业务层面思考是否需要真的为上千人创建独立任务是否可以用“角色组”、“邮件通知”或“公告板”模式替代有时技术方案需要配合业务重构。数据库索引确保ACT_RU_TASK表上的PROC_INST_ID_,ASSIGNEE_等字段有合适索引。一个我踩过的坑曾经在collection表达式中使用了SpEL表达式调用一个返回List的Spring Bean方法像这样${userService.findApprovers(projectId)}。在单元测试中一切正常但在生产环境的某些高并发场景下偶尔会出现集合为null的情况。后来发现是因为Spring Bean的代理和作用域问题。解决方案改为在明确的前置服务任务JavaDelegate中调用服务方法将结果列表显式地设置为流程变量再传递给多实例节点。这样更稳定也更容易调试和记录日志。

相关新闻

从LangChain入门AI Agent:手把手实现ReAct智能体与核心原理剖析

从LangChain入门AI Agent:手把手实现ReAct智能体与核心原理剖析

1. 项目缘起:为什么从LangChain开始你的AI Agent之旅?最近和不少刚入行的朋友聊天,发现大家对“AI Agent”这个概念既兴奋又迷茫。兴奋的是,它听起来像是能自动完成复杂任务的“数字员工”;迷茫的是,一搜教…

2026/8/7 5:00:52 阅读更多 →
RT-Thread定时器深度解析:从硬件到软件,从原理到实战避坑

RT-Thread定时器深度解析:从硬件到软件,从原理到实战避坑

1. 从一次“定时不准”的调试说起最近在调试一个基于RT-Thread的传感器数据采集项目,遇到了一个挺典型的问题:我设置了一个100毫秒的软件定时器,期望它能稳定地唤醒一个线程去读取传感器数据。但在实际运行中,通过日志打印时间戳发…

2026/8/7 5:00:52 阅读更多 →
Conda虚拟环境完全指南:从安装到项目部署的Python环境管理

Conda虚拟环境完全指南:从安装到项目部署的Python环境管理

1. 为什么我们需要一个独立的“工作间”:Conda虚拟环境的核心价值如果你刚开始接触Python,或者已经写了一些脚本,可能会遇到一个让人头疼的问题:昨天还能跑通的代码,今天更新了一个库之后,突然就报错了。又…

2026/8/7 5:00:52 阅读更多 →

最新新闻

CSS伪类选择器全解析:从:hover到:nth-child的实战应用

CSS伪类选择器全解析:从:hover到:nth-child的实战应用

1. 项目概述:为什么伪类选择器是CSS的“灵魂捕手”?刚接触CSS的时候,我们都是从最基础的标签选择器、类选择器、ID选择器开始的。它们就像给网页元素贴上一个固定的标签,然后统一施加样式。但很快你就会发现,现实中的交…

2026/8/7 5:44:21 阅读更多 →
从OpenClaw到虾总9527:AI智能体本地化部署与深度定制实战

从OpenClaw到虾总9527:AI智能体本地化部署与深度定制实战

1. 从“小龙虾”到“虾总9527”:一个AI智能体项目的命名与重生最近在折腾本地AI智能体部署的朋友,估计没少被“OpenClaw”这个名字刷屏。这玩意儿刚出来的时候,大家图个新鲜,都叫它“小龙虾”,形象又好记。但用着用着&…

2026/8/7 5:44:21 阅读更多 →
VSCode Git图形化实战:从原理到高效开发工作流

VSCode Git图形化实战:从原理到高效开发工作流

1. 从命令行到图形化:为什么我们需要在VSCode里用Git?如果你和我一样,是从命令行开始接触Git的,那你一定对git add .、git commit -m "fix"、git push origin main这套组合拳烂熟于心。命令行高效、强大,能让…

2026/8/7 5:44:21 阅读更多 →
液压传动实战指南:从核心原理到系统调试的工程实践

液压传动实战指南:从核心原理到系统调试的工程实践

1. 项目概述:一份来自“硬核”课堂的实战笔记如果你对机械、自动化或者重型装备感兴趣,那么“液压传动”这四个字对你来说一定不陌生。它不像电路板那样精致,也不像代码那样抽象,它充满了力量感——用油液作为介质,传递…

2026/8/7 5:44:21 阅读更多 →
软考证书挂靠全流程解析:风险、操作与合法替代方案

软考证书挂靠全流程解析:风险、操作与合法替代方案

1. 项目概述:软考A计划的核心价值与风险边界 “软考A计划”这个提法,在行业内通常不是一个官方称谓,更像是一个圈内人为了特定目的——比如高效通过考试并实现“挂靠”——而私下总结的一套策略组合。我接触过不少咨询这类问题的朋友&#xf…

2026/8/7 5:44:21 阅读更多 →
计算机进制转换:从原理到编程实践

计算机进制转换:从原理到编程实践

1. 进制转换的基本概念与日常应用 计算机科学中最基础也最容易被忽视的技能之一就是进制转换。很多人觉得这不过是数学课上的一个小知识点,但实际上它贯穿了整个数字世界。从我们每天使用的手机APP到银行转账系统,底层都在进行着各种进制的转换运算。 …

2026/8/7 5:43:20 阅读更多 →

日新闻

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南

为什么scrcpy成为Android投屏的终极解决方案:完整实战指南 【免费下载链接】scrcpy Display and control your Android device 项目地址: https://gitcode.com/GitHub_Trending/sc/scrcpy 想要将Android手机屏幕完美投射到电脑上,享受大屏操作的自…

2026/8/7 0:00:19 阅读更多 →
如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南

如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南

如何在5分钟内掌握Tom Select:打造现代化表单选择器的终极指南 【免费下载链接】tom-select Tom Select is a lightweight (~16kb gzipped) hybrid of a textbox and select box. Forked from selectize.js to provide a framework agnostic autocomplete widget wi…

2026/8/7 0:00:19 阅读更多 →
5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件

5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件

5分钟快速上手:NSZ压缩工具终极指南,轻松管理Switch游戏文件 【免费下载链接】nsz NSZ - Homebrew compatible NSP/XCI compressor/decompressor 项目地址: https://gitcode.com/gh_mirrors/ns/nsz 你是否在为Nintendo Switch游戏文件占用大量存储…

2026/8/7 0:00:19 阅读更多 →

周新闻

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

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

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

2026/8/6 22:02:27 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

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

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

2026/8/6 22:02:27 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

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

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

2026/8/6 22:02:27 阅读更多 →

月新闻

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

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

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

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

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

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

2026/8/6 22:02:28 阅读更多 →
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/5 23:46:51 阅读更多 →