Java 手写 AI Agent:ZenoAgent 实战笔记

Java 手写 AI Agent:ZenoAgent 实战笔记
摘要:作为一个长期使用 Java 的后端开发者,我对 AI Agent 的内部运作机制充满了好奇。为了深入理解 Agent 的工作原理,我决定动手写一个简单的 Agent 系统 —— ZenoAgent。本文记录了我在这个过程中的学习心得与技术实践,包括如何手写 ReAct 循环在分布式环境下实现 Human-in-the-loop尝试复刻类 o1 的流式思考以及探索错误处理机制。希望这些踩坑经验能给同样想探索 AI 的 Java 开发者一些参考。

👀 在线体验:项目已部署上线,欢迎试玩:线上部署地址
(注:受限于服务器资源,线上本地部署了 Qwen3:8B 模型(参见另一篇博文华为云服务器本地部署大模型实战),虽不如商业模型聪明,但足以演示 Agent 的核心能力)

在这里插入图片描述

💡 写在前面:我的学习初衷

市面上已经有了像 LangChain 和 AutoGen 这样非常优秀的成熟框架,它们功能强大且生态丰富。但作为一名对 AI 技术充满热情的 Java 开发者,我总觉得光是调用 API 或使用现成的 SDK,很难真正触达 Agent 技术的核心。

我发起 ZenoAgent 这个项目的初衷非常简单:我想通过亲手造一遍轮子,来弄清楚 Agent 到底是怎么跑起来的。

在这个过程中,我主要关注以下几个学习点:

  1. 原理探索:不依赖复杂的封装,亲手实现 ReAct 循环,理解 LLM 是如何规划和执行任务的。
  2. 全栈打通:尝试从后端推理到前端可视化,完整走一遍 AI 应用的开发流程。
  3. 工程实践:看看在 Java 生态下,如何处理流式响应、并发控制等实际问题。

ZenoAgent 是我学习过程中的一份“大作业”,虽然它还很稚嫩,但这趟探索之旅让我收获颇丰。


🏗️ 核心架构概览

ZenoAgent 采用经典的 DDD(领域驱动设计) 分层架构,确保了代码的高内聚低耦合。

Data & External

Backend (Spring Boot)

Infrastructure

Core Engine

HTTP/SSE

Call

Retrieve

Load Context

User

API Layer (Controller)

Application Service

ReAct Engine

Agent State Machine

Thinking Engine

Action Executor

Observation Engine

MCP Tool Client

RAG Enhancer

Memory System

Redis
Short-term Memory
Dist. Lock

Mysql
long-term Memory

PostgreSQL
pgvector

LLM Provider
(OpenAI/DeepSeek)

External Tools

  • 后端:Java 17, Spring Boot 3, LangChain4j (LLM 交互核心), Redisson (分布式协调)
  • 数据存储:PostgreSQL + pgvector (向量数据库), Redis (缓存与消息队列)
  • 前端:Vue 3, TypeScript, Tailwind CSS, SSE (Server-Sent Events)

🔥 硬核亮点解析

1. 上下文构造与思维链控制 (Context Construction & CoT)

Agent 聪明的关键在于如何构建 Prompt。ZenoAgent 的 ThinkingEngine 采用了 Hybrid Context Assembly(混合上下文组装) 的策略。

🧩 混合上下文组装原理 (Hybrid Strategy)

我们不再简单地将所有信息拼接到一个巨大的 User Prompt 中,而是充分利用 LLM 的 Native Messages 结构,将上下文拆解为三个部分:

  1. System Message: 定义角色、工具使用规范和输出格式(JSON Schema)。
  2. History Messages (Native): 直接插入 List<ChatMessage>,保留 User/Assistant 的原生角色信息。这让模型能完美理解对话流转,且有利于 KV Cache 加速。
  3. Current Context (Text): 将“当前目标”、“可用工具”、“最近的 Action 执行结果”拼接到最新的一条 UserMessage 中。
// ThinkingEngine.java 核心重构List<ChatMessage> messages =newArrayList<>();// 1. System Prompt (规则与约束) messages.add(newSystemMessage(sysPrompt));// 2. Native History (利用 KV Cache) messages.addAll(context.getMessages());// 3. Current Context (动态任务背景)String currentStepPrompt =""" ## 当前目标 %s ## 可用工具 %s ## 最近执行结果 %s """.formatted(goal, tools, actionHistory); messages.add(newUserMessage(currentStepPrompt));

这种设计既保留了 Agent 执行过程的灵活性(通过 Text 描述复杂的 Action/Result),又利用了 LLM 原生对话结构的优势(语义清晰、Token 复用)。

🧠 如何控制大模型"先想后做"?

我们通过 Prompt Engineering 强制 LLM 遵循 Thought-Action-Observation 范式。

  • 强制输出 <thinking> 标签:在 System Prompt 中明确要求 LLM 先输出 XML 格式的思考过程,再输出 JSON 格式的动作指令。
  • 流式解析状态机:后端维护一个轻量级的状态机,实时解析流式响应。

"三段式"输出协议(踩坑经验)

⚠️ 踩坑记录:最初我只要求模型输出 <thinking><actions> 两部分。结果发现,许多模型(尤其是非顶流模型)在输出完 </thinking> 后,会习惯性地加一句“好的,根据以上分析,我现在的计划是…”,然后才输出 JSON。这会导致 JSON 解析器直接报错。

最终解决方案:引入一个强制的“截断标记” <THINKING_DONE>

1. <thinking>...</thinking> 2. <THINKING_DONE> <-- 强制刹车信号 3. <actions>{...}</actions> 

后端检测到 <THINKING_DONE> 后,会触发状态流转,并忽略该标记之前的所有非 JSON 文本,确保 <actions> 的纯净。

Yes

Stream Delta

Yes

Extract JSON

Start

Detect

State: THINKING

Frontend UI

Detect

State: PARSING_JSON

Action Executor

这种机制不仅让 Agent 的决策更理性(Chain of Thought),还让用户能实时看到 AI 的"心路历程",体验感拉满。

🛡️ 防御式编程:四重保障解决“大模型不听话”

为了让 LLM 稳定输出符合 Java 强类型要求的 JSON,我采取了一套四重保障机制

    • 剔除 ```json 和 ` ````包裹。
    • 自动补全截断的 JSON:利用堆栈检测括号平衡,自动补齐末尾缺失的 }

自愈式重试循环 (Self-Correction Loop)这是最关键的一道防线。当解析失败时,不要直接抛出异常,而是将错误堆栈作为 Prompt 反馈给模型。

try{ finalActions =parseThinkingResult(fullText);}catch(LLMParseException e){// 将错误信息回传给模型,进行"反思修正" retryHint ="上次输出解析失败:"+ e.getMessage()+",请严格修正格式。";// 进入下一轮循环 retry...}

实测表明,这种机制能将复杂任务的执行成功率从 60% 提升至 95% 以上。

鲁棒性解析 (Robust Parsing)后端解析器不能有“洁癖”。我的 cleanJsonResponse 方法会自动清洗常见的 LLM 格式错误:

// PromptGuidedThinkingEngine.javaif(depth >0&& start >=0){ log.warn("检测到 JSON 截断,尝试自动补全...");// 自动补齐剩余的 '}'}

原生 Response Format (Native API)对于支持 JSON Mode 的模型,在调用时直接注入约束:

// 强制模型以 JSON Object 模式输出ResponseFormat.builder().type(ResponseFormatType.JSON).build();

Prompt 强约束 (Prompt Engineering)DECISION_FRAMEWORK_PROMPT 中,不仅定义 Schema,更要预判错误。例如,明确禁止 Markdown 代码块:

privatestaticfinalString JSON_OUTPUT_FORMAT_PROMPT =""" ## 输出格式强制约束(必须100%遵守,任何违规都会导致输出无效) 1. 输出内容必须是**纯合法JSON对象**,无任何非JSON文本(如<thinking>标签、注释、说明文字等); 2. JSON对象包含2个必填顶级字段: - thinking:字符串类型,填写你的逻辑推演过程(需清晰说明“为什么选择该动作类型”“参数如何确定”等); - actions:数组类型,仅包含1个动作指令对象(单轮仅输出1个动作); 3. actions数组中的每个动作对象必须包含以下基础字段: - actionType:字符串类型,仅允许取值【TOOL_CALL/RAG_RETRIEVE/LLM_GENERATE/DIRECT_RESPONSE】,严禁使用其他值; - actionName:字符串类型,填写动作名称(如search_weather/retrieve_knowledge/generate_content/reply_user); - reasoning:字符串类型,填写选择该动作的简短理由(区别于thinking的详细推演); 4. 不同actionType需额外包含对应必填参数字段(缺失会判定为无效): ..... """;

2. ReAct 模式设计与工程实现

ZenoAgent 的核心并未采用现成的 Agent 框架,而是手写了一个基于状态机的 ReAct 循环引擎。在设计过程中,我重点思考了Action 的原子化交互的低延迟

2.1 Action 的抽象设计

为了让 LLM 的输出更可控,我将 Agent 的所有能力抽象为 4 种原子 Action:

  • TOOL_CALL:调用外部工具(通过 MCP 协议)。
  • RAG_RETRIEVE:检索知识库(弥补模型长短期记忆)。
  • LLM_GENERATE:让子模型处理纯文本生成任务(如润色、翻译)。
  • DIRECT_RESPONSE:直接向用户输出结果。

这种设计让 ReAct 循环的边界非常清晰:Thinking -> Action List -> Execution -> Observation

2.2 为什么设计 DIRECT_RESPONSE?(性能优化的关键)

在早期的 ReAct 实践中,我发现一个严重的性能浪费:
对于简单的闲聊(如“你好”)或知识性问答,标准 ReAct 模式太重了。

  • 传统模式:用户问“你好” -> LLM 思考 -> 决定不调用工具 -> 输出 Final Answer。这通常需要消耗完整的推理步骤,甚至有的框架会强制要求先调用一个 talk 工具,增加了一次毫无意义的 RTT(网络往返)。
  • ZenoAgent 模式:我将“回复用户”设计为一种 Action (DIRECT_RESPONSE)。

优化收益
当模型判断无需使用工具时,它可以直接在决策阶段生成 DIRECT_RESPONSE 动作。这意味着“思考”和“回复”在一次 LLM 调用中同时完成了,对于简单场景,延迟降低了 50% 以上,且大幅节省了 Token。

2.3 并发执行机制 (Parallel Execution)

传统的 ReAct 模式是严格串行的(Think -> Act -> Observe -> Think),这会导致严重的性能瓶颈。

ZenoAgent 对此进行了改良:允许 LLM 在一次 Thinking 过程中输出多个 Action
例如,当用户问“对比北京和上海的天气”时,Agent 会同时生成两个查询动作,后端利用 Java 的 CompletableFuture 并行分发请求。

// ActionExecutor.java 核心逻辑publicList<ActionResult>executeParallel(List<AgentAction> actions){List<CompletableFuture<ActionResult>> futures = actions.stream().map(action ->CompletableFuture.supplyAsync(()->execute(action), executor)).toList();// 并行等待所有结果return futures.stream().map(CompletableFuture::join).toList();}

这种设计将多步串行任务的耗时压缩到了最慢的那一个 Action 的耗时,响应速度提升显著。

2.4 实战踩坑:isComplete 的语义陷阱

在实现 DIRECT_RESPONSE 时,我引入了一个 isComplete 字段来控制循环是否结束。看似简单,却踩了一个大坑。

问题复现
当用户说“查天气”,Agent 需要反问“请问是哪个城市?”。
此时,Agent 认为任务并未完成,于是将 isComplete 设为 false
结果:后端 ReAct 引擎收到 false 后,认为 Agent 还要继续干活,于是不等待用户回复,直接进入下一轮循环。Agent 发现没拿到城市名,又问一遍,导致死循环。

深度反思
大模型对 Complete(完成)的理解是“任务终结”。但在交互式场景中,我们需要的是“控制权移交”。

解决方案
我重构了 Prompt 中对 isComplete 的语义定义,从“任务状态”转变为“流控制状态”:

  • isComplete=true移交控制权
    • 场景 1:任务彻底完成。
    • 场景 2:需要提问并等待用户回复(虽然任务没完,但我得停下来等你)。
  • isComplete=false保持控制权
    • 场景:发送“正在查询”、“已调用工具”等过程通知,不等待用户回复,流程自动继续。

修正后的 Prompt 片段

// PromptGuidedThinkingEngine.java3. 需要向用户输出内容时使用 DIRECT_RESPONSE,并通过 isComplete 字段控制流程: - isComplete=true:**终止本轮**。用于:1. 任务彻底完成;2.**需要提问并等待用户回复**。此时意味着 Agent 暂停工作移交控制权给用户; - isComplete=false:**过程通知**。用于:任务仍在进行中,仅向用户发送进度提示,**不等待用户回复**,流程自动继续。 

通过这种语义重构,配合 Few-Shot 示例(明确展示“反问用户”时设为 true),Agent 终于学会了在适当的时候“闭嘴等待”。


3. 分布式环境下的 “Human-in-the-loop”

在企业级应用中,敏感操作(如删除文件、转账)必须经过人工确认。但在 ** 集群部署**场景下,这面临一个巨大挑战:

问题:Agent 的推理线程(持有 SSE 连接)可能在 Pod A 上运行,而用户点击“确认”的 HTTP 请求可能打到了 Pod B。如何让 Pod A 知道用户在 Pod B 做了确认?

解决方案:基于 Redis 阻塞队列的分布式信号量

ZenoAgent 利用 Redisson 实现了跨实例的线程挂起与唤醒。

分布式确认时序图

Backend (Pod B)RedisBackend (Pod A)FrontendUserBackend (Pod B)RedisBackend (Pod A)FrontendUserAgent runs thinking loop...Thread BLOCKED on Redis QueueReceived by different Pod!Thread ResumesDecide to call 'delete_file'Create BlockingQueue (key=exec_123)SSE: confirmation_request (id=exec_123)Click "Approve"POST /confirm (id=exec_123, action=APPROVE)Queue.push("APPROVED")Unblock: "APPROVED"Execute ToolSSE: tool_result

代码实现参考:

// ToolConfirmationManager.javapublicToolExecutionDecisionwaitForDecision(String executionId){RBlockingQueue<ToolExecutionDecision> queue = redissonClient.getBlockingQueue(KEY_PREFIX + executionId);// 阻塞等待用户决策,支持超时自动拒绝return queue.poll(60,TimeUnit.SECONDS);}

4. 流式思考:从 Prompt 模拟到原生 API 适配

为了让用户感知到 AI 的“思考过程”,ZenoAgent 实现了基于 Prompt 注入和原生支持的思考引擎,支持根据配置自动注入,分别适配普通模型和推理模型。

方案一:Prompt 引导的模拟思考 (通用方案)

对于普通模型(如 GPT-4, Qwen-2.5),我们通过 System Prompt 强制要求模型在输出最终 JSON 动作前,先在 thinking 字段中输出思维链。

核心原理
将“思考”作为 JSON 结构的一部分。虽然这违反了“纯数据”的原则,但能确保模型在生成 Action 之前必须先进行逻辑推演(CoT)。

实现挑战
在流式响应中,JSON 是逐字生成的。我们需要在后端实时解析这个不完整的 JSON 字符串,提取出 thinking 字段的增量内容推送到前端。

代码参考
PromptGuidedThinkingEngine.java 使用了状态机来解析流式 JSON:

// 伪代码:从流式 JSON 中提取 thinking 字段if(token.contains("\"thinking\"")){ isThinking =true;}if(isThinking &&!token.contains("\"actions\"")){ sseEmitter.send(token);// 实时推送思考过程}
方案二:原生 Reasoning 字段适配 (未来趋势)

随着 DeepSeek-R1 和 OpenAI o1 的发布,模型开始原生支持“推理”。这种模式下,思考过程不再占用 content 字段,而是通过独立的 reasoning 通道传输。

核心原理
模型在输出 Response 之前,会先在一个独立的 Channel 中输出思维链(CoT)。这个过程对 Prompt 是透明的,属于模型原本的能力。客户端需要监听特定的 API 字段(如 reasoning_content)来获取这部分数据。

代码参考
OpenAIReasoningThinkingEngine.java 实现了对原生推理字段的监听。

实战遇到的协议不匹配问题
除了模拟思考,我也尝试接入模型原生的思考能力(如 DeepSeek-R1),但在对接 Ollama 时遇到了协议标准不统一的深坑。

问题复现
我在 OpenAIReasoningThinkingEngine.java 中尝试解析流式响应,发现虽然模型在“思考”,但后端却拿不到内容。

排查过程

  1. 社区现状:查阅 Ollama Issue #13372 发现,由于 OpenAI 官方 Spec 尚未标准化该字段,各家实现出现了分歧。

源码比对:LangChain4j 框架底层期望的字段却是 reasoning_content

// LangChain4j Delta.java@JsonPropertyprivatefinalString reasoningContent;// 字段名不匹配!

抓包分析:Ollama 的 API 返回字段是 reasoning

{"choices":[{"delta":{"role":"assistant","reasoning":"好的,用户想要..."// Ollama 使用 reasoning}}]}

后果与反思
这意味着,如果我们直接用 LangChain4j 调用 Ollama 部署的 R1 模型,模型在耗费算力进行“昂贵”的思考(首 Token 延迟高),但客户端却直接丢弃了这部分数据。这不仅是体验缺失,更是算力的无端浪费。目前的解决方案只能是通过自定义 StreamingResponseHandler 手动适配字段。

🚧 实战踩坑:流式标签泄露与 Lookahead Buffer

在实现方案一(Prompt 模拟)时,我遇到了一个非常棘手的问题:前端偶尔会闪现出 </thinking 这样残缺的标签。

问题根源:LLM 的 Token 是逐字吐出的。当模型输出 </thinking> 时,可能先吐出 </,此时后端检测不到完整标签,误以为是普通文本就直接发给了前端;紧接着模型吐出 thinking>,后端这才反应过来是标签,拦截了后半段,但前半段 </ 已经泄露了。

解决方案:引入 Lookahead Buffer(前缀缓冲) 机制。

我们在后端增加了一个判断逻辑:在流未结束时,如果缓冲区末尾匹配 </thinking> 的任意前缀(如 <</</t),先扣留这部分内容不发。只有当下一个 Token 进来,证明它拼不成完整标签时,才把缓冲区的内容释放出去。

// ThinkingEngine.javaif(thinkingEndIdx ==-1&&!isComplete){// 检查末尾是否是 </thinking> 的一部分int bufferLen =calculatePartialTagMatchLength(currentContent);if(bufferLen >0){// 扣留这部分内容,暂不发送 logicalEnd -= bufferLen;}}
进阶优化:事件驱动的国际化 (i18n)

为了解决这个问题,我重构了事件系统,不再发送自然语言,而是发送结构化状态事件(如 agent:status:thinking_process)。前端接收到事件 ID 后,再根据当前用户的语言设置(zh-CN/en-US)渲染对应的文案。这不仅解耦了前后端,也让国际化变得异常简单。


5. RAG 知识库:从摄入到召回的全链路优化

ZenoAgent 的 RAG 模块(RAGEnhancer)不仅仅是简单的向量相似度搜索,而是构建了一套完整的知识流转体系。

📂 文档摄入流程 (Document Ingestion)

我们使用 Apache Tika 进行全格式解析,并配合 Recursive Character Splitter 进行语义分块。

Ingestion Pipeline

Tika Parser

Recursive Splitter

MetaData Injection

Embedding Model

pgvector

User Upload

Document Service

Raw Text

Text Segments

Segments + Meta

Vectors

PostgreSQL

🔍 动态召回策略 (Dynamic Retrieval)
  • 元数据隔离:基于 knowledgeId 进行物理/逻辑隔离,确保多用户数据安全。
  • 动态表名路由:根据 Embedding 模型的维度(如 768 vs 1536),动态路由到不同的向量表,支持多模型共存。
  • 结构化上下文注入
// RAGEnhancer.java 核心逻辑 embeddingStore.search(EmbeddingSearchRequest.builder().queryEmbedding(queryVector).filter(metadataKey("knowledgeId").isEqualTo(currentKnowledgeId))// 关键:租户隔离.maxResults(5).minScore(0.75)// 过滤低质量结果.build());

7. 自愈式错误处理 (Self-Correction)

ZenoAgent 不仅能执行工具,还能从错误中学习。当工具调用失败(如参数错误、网络超时)时,Agent 不会直接崩溃,而是进入自愈循环

  • 错误捕获ActionExecutor 捕获异常,生成包含错误信息的 ActionResult
  • 上下文反馈:错误信息被记录到 AgentContext 中。
  • Prompt 注入:在下一轮 Thinking 阶段,Prompt 会包含:“上一步操作失败,错误信息:Invalid argument…”。
  • 智能修正:LLM 看到错误后,会尝试修正参数或选择替代工具,重新发起调用。
这种机制让 ZenoAgent 具备了极强的鲁棒性,能够自动应对 API 变更或临时性网络故障。

8. 动态工具发现与热加载

基于 MCP (Model Context Protocol),ZenoAgent 实现了工具的动态管理。

  • 热加载 (Hot Reload)McpConfigLoader 监听配置文件变更。一旦修改 mcp.json,系统会自动重新连接 MCP Server 并刷新工具列表,无需重启服务。
  • 分组管理:支持对工具进行分组(Group),通过配置即可灵活启用/禁用特定业务线的工具集。

📸 系统预览

为了让大家更直观地感受 ZenoAgent,这里放几张系统截图:
欢迎页:

在这里插入图片描述


智能对话

在这里插入图片描述

Agent 配置 (MCP)

在这里插入图片描述


知识库

在这里插入图片描述

🚀 总结与展望

ZenoAgent 是我边学习边实践的产物。通过 Spring Boot 的工程化能力,我们可以构建出比 Python 原型更稳定、更易于扩展的 Agent 系统。

目前项目已开源,欢迎感兴趣的朋友 Star ⭐️ 和 PR!

让我们一起探索 AI Agent的无限可能!

Read more

【MySQL】三大范式

【MySQL】三大范式

下面我们来聊聊表的设计,如何设计一张比较合理,冗余性低且IO次数比较少,效率高的表。 我们需要先认识一下范式 什么是范式? 范式是⼀组规则。在设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式。 范式有哪些? 关系数据库有六种范式:第⼀范式(1NF)、第⼆范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,⼜称完美范式),越高的范式数据库冗余越小。然而,普遍认为范式越高虽然对数据关系有更好的约束性,但也可能导致数据库IO更繁忙,因此在实际应用中,数据库设计通常只需满足第三范式即可,如果在想提高效率,再去增加某个字段的冗余性 为啥越高的范式数据库冗余越小,IO效率越忙呢?继续看 第一范式 第一范式即:数据库表的每⼀列都是不可分割的原子数据项,而不能是集合,数组,对象等非原子数据 在关系型数据库的设计中,满足第⼀范式是对关系模式的基本要求。

By Ne0inhk
Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构

Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模研发协同、涉及数十个跨职能团队共同维护大型 HAP/HSP 项目的背景下,如何确保每一行代码的变更都“有迹可循”、在端侧实现自动化的版本语义化(Semantic Versioning)管理,已成为衡量工程化成熟度的“地基”。在鸿蒙设备这类强调分布式协同与持续集成(CI)交付的环境下,如果代码提交记录(Commit Messages)依然采用随意的口语化描述,由于由于缺乏机器可读性,极易由于由于无法自动生成变更日志(Changelog)导致跨版本维护时的回溯成本激增。 我们需要一种能够强制执行规范检查、支持 RFC 标准且具备解析语义结构的提交治理框架。 conventional 为 Flutter

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 应用架构设计中,状态管理(State Management)是业务的核心。如果你选择了经典的 Redux 模式,你会发现它天生是“同步”的:Action 发出,Reducer 改变 State。但在真实项目中,我们需要处理网络请求、数据库读写、文件 IO 等延时操作。如何在纯净的 Redux 链条中插入这些破坏性的“副作用”? redux_thunk 提供了一个简单而精妙的方案。它通过扩展 Redux 的中间件机制,允许你 Dispatch(派发)一个 函数 而不仅仅是对象。这为鸿蒙应用处理复杂的业务流提供了极大灵活性。 一、异步 Action

By Ne0inhk
Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案

Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案 前言 在鸿蒙(OpenHarmony)生态的极繁数字化政务底座、大型分布式供应链管理系统以及对架构严密性与交互流畅度有“双重严苛审计要求”的各类企业级应用开发中,“架构的解耦深度与 UI 的响应广度”是衡量软件成熟度的两把关键标尺。面对包含上百个全局服务(Service)与数千个高频局部刷新节点(Widget)的复杂资产体系。如果全量使用 Riverpod 的 Consumer 监听,可能会在大型列表中产生不必要的树扫描开销;而如果仅使用 Signals,又会因为缺乏完善的依赖注入(DI)机制。导致业务逻辑流的组织变得松散且难以维护。 我们需要一种“顶级架构对齐、局部响应闭环”的融合艺术。 riverpod_signals 是一套专注于将

By Ne0inhk