[
  {
    "path": ".editorconfig",
    "content": "﻿# EditorConfig is awesome:http://EditorConfig.org\n# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference\n\n# top-most EditorConfig file\nroot = true\n\n# Don't use tabs for indentation.\n[*]\nindent_style = space\n# (Please don't specify an indent_size here; that has too many unintended consequences.)\n\n# Code files\n[*.{cs,csx,vb,vbx}]\nindent_size = 4\ninsert_final_newline = false\ncharset = utf-8-bom\nend_of_line = crlf\n\n# Xml project files\n[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]\nindent_size = 2\n\n# Xml config files\n[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]\nindent_size = 2\n\n# JSON files\n[*.json]\nindent_size = 2\n\n# Dotnet code style settings:\n[*.{cs,vb}]\n# Sort using and Import directives with System.* appearing first\ndotnet_sort_system_directives_first = true\n\ncsharp_indent_case_contents = true\ncsharp_indent_switch_labels = true\ncsharp_indent_labels = flush_left\n\n#csharp_space_after_cast = true\n#csharp_space_after_keywords_in_control_flow_statements = true\n#csharp_space_between_method_declaration_parameter_list_parentheses = true\n#csharp_space_between_method_call_parameter_list_parentheses = true\n#csharp_space_between_parentheses = control_flow_statements, type_casts\n\n# 单行放置代码\ncsharp_preserve_single_line_statements = true\ncsharp_preserve_single_line_blocks = true\n\n# Avoid \"this.\" and \"Me.\" if not necessary\ndotnet_style_qualification_for_field = false:warning\ndotnet_style_qualification_for_property = false:warning\ndotnet_style_qualification_for_method = false:warning\ndotnet_style_qualification_for_event = false:warning\n\n# Use language keywords instead of framework type names for type references\ndotnet_style_predefined_type_for_locals_parameters_members = false:suggestion\ndotnet_style_predefined_type_for_member_access = false:suggestion\n#dotnet_style_require_accessibility_modifiers = for_non_interface_members:none/always:suggestion\n\n# Suggest more modern language features when available\ndotnet_style_object_initializer = true:suggestion\ndotnet_style_collection_initializer = true:suggestion\ndotnet_style_coalesce_expression = true:suggestion\ndotnet_style_null_propagation = true:suggestion\ndotnet_style_explicit_tuple_names = true:suggestion\ndotnet_style_prefer_inferred_tuple_names = true:suggestion\ndotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion\n\n# CSharp code style settings:\n[*.cs]\n# Prefer \"var\" everywhere\ncsharp_style_var_for_built_in_types = true:warning\ncsharp_style_var_when_type_is_apparent = true:warning\ncsharp_style_var_elsewhere = true:warning\n\n# Prefer method-like constructs to have a block body\ncsharp_style_expression_bodied_methods = when_on_single_line:suggestion\ncsharp_style_expression_bodied_constructors = when_on_single_line:suggestion\ncsharp_style_expression_bodied_operators = when_on_single_line:suggestion\n\n# Prefer property-like constructs to have an expression-body\ncsharp_style_expression_bodied_properties = true:suggestion\ncsharp_style_expression_bodied_indexers = true:suggestion\n#csharp_style_expression_bodied_accessors = true:suggestion\n\n# Suggest more modern language features when available\ncsharp_style_pattern_matching_over_is_with_cast_check = true:suggestion\ncsharp_style_pattern_matching_over_as_with_null_check = true:suggestion\ncsharp_style_inlined_variable_declaration = true:suggestion\n\ncsharp_prefer_simple_default_expression = true:suggestion\ncsharp_style_deconstructed_variable_declaration = true:suggestion\ncsharp_style_pattern_local_over_anonymous_function = true:suggestion\n\ncsharp_style_throw_expression = true:suggestion\ncsharp_style_conditional_delegate_call = true:suggestion\n\n# 单行不需要大括号\ncsharp_prefer_braces = false:suggestion\n\n# Newline settings\ncsharp_new_line_before_open_brace = all\ncsharp_new_line_before_else = true\ncsharp_new_line_before_catch = true\ncsharp_new_line_before_finally = true\ncsharp_new_line_before_members_in_object_initializers = true\ncsharp_new_line_before_members_in_anonymous_types = true\ncsharp_new_line_between_query_expression_clauses = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# NewLife Copilot 协作指令\n\n适用于 NewLife 系列全部 C#/.NET 仓库，本文件可随仓库/指令目录一起拷贝到其他项目直接复用。存在本文件则必须遵循。**简体中文回复。**\n通用 C# 最佳实践（设计模式、SOLID、健壮性等）AI 已知，此处不赘述，**仅列出组织专属规则与反常规约定**。\n\n---\n\n## 1. 专用指令（前置检查，必须执行）\n\n**开始任何任务前，必须先将用户请求与下表触发信号逐行匹配。命中则立即用 `get_file` 读取 `.github/instructions/{指令文件}`，读取成功后遵循其中全部规则。未命中任何行才跳过。**\n\n| 触发信号（用户请求含以下任意关键词即命中） | 指令文件 |\n|---------|---------|\n| XCode/实体生成/Model.xml/数据库 CRUD/`NewLife.XCode` 引用/`*.xcode.xml`/项目名含 `.Data`/`XCode.*` 命名空间/用户提及修改任意 `.xml` 文件 | `xcode.instructions.md` |\n| Cube/魔方/Web开发/`NewLife.Cube` 引用/`NewLife.Cube.*` 命名空间 | `cube.instructions.md` |\n| 性能测试/基准测试/压力测试/压测/BenchmarkDotNet/Benchmark/benchmark/吞吐量评估/性能分析/性能对比/性能报告/速度对比/速度测试/内存分配/perf/性能优化测试/做性能/跑分/测试报告 | `benchmark.instructions.md` |\n| NetServer/NetSession/网络服务器/网络客户端/Socket服务/TCP服务/UDP服务/`NewLife.Net` 引用/`NewLife.Net.*` 命名空间/ISocketClient/ISocketRemote/CreateRemote/StandardCodec/LengthFieldCodec/管道编解码/网络编程/Echo服务/网络会话/长连接/粘包拆包 | `net.instructions.md` |\n| 新建系统/新建项目/新增模块/需求整理/需求文档/需求分析/架构设计/技术方案/功能清单/功能拆分/任务分解/迭代开发/迭代计划/验收/PRD/用户故事/做一个系统/做一个平台/开发流程/全部搞完/批量开发/自治模式/一次性做完/继续处理/接着做 | `development.instructions.md` |\n| 缓存/ICache/MemoryCache/Redis缓存/ICacheProvider/缓存设计/`NewLife.Caching` 命名空间 | `caching.instructions.md` |\n| 序列化/JSON/Binary/JsonHelper/序列化设计/SpanSerializer/CSV导出/`NewLife.Serialization` 命名空间 | `serialization.instructions.md` |\n| 加密/安全/Hash/MD5/SHA/AES/SM4/RSA/JWT/SecurityHelper/TokenProvider/`NewLife.Security` 命名空间 | `security.instructions.md` |\n| 远程调用/ApiHttpClient/ApiClient/ApiServer/负载均衡/LoadBalancer/RPC/HTTP客户端/`NewLife.Remoting` 命名空间 | `remoting.instructions.md` |\n| 配置/Config/IConfigProvider/HttpConfigProvider/CommandParser/配置中心/`NewLife.Configuration` 命名空间 | `configuration.instructions.md` |\n\n**自动匹配指令**（无需触发，按 `applyTo` 路径自动生效）：`caching`、`serialization`、`security`、`remoting`、`configuration` 这 5 个指令文件同时配置了 `applyTo` 模式，编辑对应目录下的文件时 VS Code 会自动加载。\n\n---\n\n## 2. 核心原则\n\n检索优先、风格一致、兼容友好、**主动优化**。\n发现明显缺陷（资源泄漏、空引用、逻辑错误）时主动修复；优化请求时深入分析，不做表面工作。\n改动较小直接做并说明；改动较大（涉及公共 API 或大范围重构）先列方案询问确认。\n\n---\n\n## 3. 兼容性约束（极重要）\n\n- **语言版本**：当前为 **C# 14**（`<LangVersion>latest</LangVersion>`），最大化使用最新语法糖（switch 表达式、集合表达式 `[]`、`?.`/`??`/`??=`、模式匹配、目标类型 `new`、record 等）\n- **框架版本**：新增 API 前，先查看当前项目 `.csproj` 的 `<TargetFrameworks>` 配置，**只需满足已声明版本的兼容性**，无需对所有历史版本降级。若包含 `net45`/`netstandard2.0` 等低版本，再提供条件编译降级实现。\n- **禁止高版本专属 BCL API**（低版本项目）：❌ `ArgumentNullException.ThrowIfNull()` → ✅ `if (x == null) throw new ArgumentNullException(nameof(x));`\n- **条件编译符号**：`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`\n\n---\n\n## 4. 编码规范\n\n### 4.1 类型名（关键差异）\n\n**必须**使用 .NET 正式名：`String`/`Int32`/`Boolean`/`Int64`/`Double`/`Object` 等。\n❌ **禁止**使用 C# 别名：`string`/`int`/`bool`/`long`/`double`/`object`\n\n### 4.2 命名\n\n| 成员类型 | 规则 | 示例 |\n|---------|------|------|\n| 类型/公共成员 | PascalCase | `UserService`、`GetName()` |\n| 参数/局部变量 | camelCase | `userName`、`count` |\n| 私有字段 | `_camelCase` | `_cache`、`_instance` |\n| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` |\n\n### 4.3 代码风格\n\n- **命名空间**：file-scoped namespace\n- **单文件**：每文件一个主要公共类型；较大平台差异使用 `partial`\n- **集合初始化**：优先使用集合表达式 `[]`，如 `List<String> Tags { get; set; } = [];`\n- **Null 条件运算符**：优先使用 `?.`/`??` 简化空值检查；**C# 14 空条件赋值 `??=`**：变量为 null 时才赋值，可显著提升可读性\n\n```csharp\n// ✅ C#14 空条件赋值（??=）：为 null 时才赋值，替代 if (x == null) x = ...\n_cache ??= new MemoryCache();\nlist ??= [];\n\n// ✅ if 内只有单行代码时可不加花括号（单行 if 同行或换行均可）\nif (value == null) return;\nif (key == null) throw new ArgumentNullException(nameof(key));\n\n// ✅ 语句较长时另起一行，仍不加花括号\nif (value == null)\n    throw new ArgumentNullException(nameof(value), \"Value cannot be null\");\n\n// ✅ 多分支单语句：不加花括号\nif (count > 0)\n    DoSomething();\nelse\n    DoOther();\n\n// ✅ for/foreach/while 循环体必须保留花括号（即使单语句）\nforeach (var item in list)\n{\n    Process(item);\n}\n\nfor (var i = 0; i < count; i++)\n{\n    Process(i);\n}\n\n// ✅ using 优先无花括号声明；仅需生命周期（如锁）时用弃元\nusing var stream = File.OpenRead(\"file.txt\");\nusing var _ = _lock.AcquireLock();\n```\n\n### 4.4 Region 与日志\n\n较长类使用 `#region` 分段，顺序：`属性` → `静态` → `构造` → `方法` → `辅助` → **`日志`**。\n含 `ILog Log` 和 `WriteLog` 时：**必须放类末尾**，用名为\"日志\"的 region 包裹，不放入\"辅助\"。\n关键过程可使用 `Tracer?.NewSpan()` 埋点。\n\n### 4.5 文档注释\n\n- `<summary>` **必须同行闭合**：`/// <summary>获取名称</summary>`\n- 每个参数**必须有** `<param>` 标签，无论方法可见性\n- 有返回值**必须有** `<returns>`；复杂方法可增加 `<remarks>`\n- `public`/`protected` 成员必须注释；`[Obsolete]` 必须包含迁移建议\n\n### 4.6 异步与性能\n\n- 异步方法后缀 `Async`，库内部默认 `ConfigureAwait(false)`\n- 热点路径避免反射/复杂 Linq，优先手写循环/`ArrayPool<T>`/`Span`\n- 池化资源明确获取/归还，异常分支不遗失归还\n\n### 4.7 错误处理\n\n- 精准异常类型：`ArgumentNullException`/`InvalidOperationException` 等\n- TryXxx 模式：不用异常作常规分支\n- 类型转换：优先使用 `Utility` 扩展方法，完整列表：`ToInt()`/`ToLong()`/`ToDouble()`/`ToDecimal()`/`ToBoolean()`/`ToDateTime()`/`ToDateTimeOffset()`\n- 对外异常不暴露内部实现/路径\n\n---\n\n## 5. NewLife 内置工具\n\n优先使用项目内置工具而非标准库，**禁止重复造轮子**：\n\n- 字符串构建：`Pool.StringBuilder`（替代 `new StringBuilder()`）\n- 时间戳（毫秒级相对时间）：`Runtime.TickCount64`；**代码计时（精确耗时测量）：`Stopwatch`**\n- 类型转换：`Utility` 扩展方法 — `ToInt()`/`ToLong()`/`ToDouble()`/`ToDecimal()`/`ToBoolean()`/`ToDateTime()`/`ToDateTimeOffset()`\n- 二进制读写：`SpanReader` / `SpanWriter`（替代手动字节偏移操作）\n- 追踪埋点：`Tracer?.NewSpan()`\n\n---\n\n## 6. 防御性注释（禁止删除）\n\n代码中带有说明文字的被注释代码属于**防御性注释**，记录历史踩坑经验。**禁止删除，禁止\"恢复\"执行**。可补充更详细说明。\n\n```csharp\n// 曾经尝试过同步等待，但会导致线程池饥饿和死锁\n// var result = task.Result;\n\n// 不要使用 SendAsync 的无超时重载，否则会造成连接泄漏\n// await client.SendAsync(data);\n```\n\n---\n\n## 7. 工作流\n\n触发检查（第 1 节触发信号表匹配，命中则读取专用指令） → 检索（**优先复用**现有实现） → 评估（公共 API/兼容性/性能） → 方案 → 实施 → 验证 → **AskQuestions 多选确认** → [需调整则循环] → 说明\n\n- **触发检查**：开始工作前必须完成，遗漏专用指令将导致输出不符合要求\n- **实施**：完成主任务；顺带修复明显缺陷；顺带简化重复代码；保留原注释与结构\n- **验证**：代码变更必须编译通过；找到相关测试则运行；仅文档变更可跳过\n- **AskQuestions 多选确认**：使用 `vscode_askQuestions` 工具，多选询问用户是否满意；若需调整则修改后重新编译，循环至满意；确认满意后才进入下一项开发\n\n### 主动优化原则\n\n用户要求**分析/优化代码**时：\n\n| 行动 | 说明 |\n|------|------|\n| **架构梳理** | 重构不清晰的结构，让代码更易懂 |\n| **缺陷修复** | 资源泄漏、空引用、并发问题、逻辑错误 → 直接修复 |\n| **代码简化** | 提取重复代码、合并冗余判断、应用现代语法 |\n| **性能优化** | 缓存重复计算、池化高频对象、避免无用分配 |\n| **注释完善** | 补充缺失的 XML 注释和关键逻辑说明 |\n\n---\n\n## 8. 测试\n\n- 框架 xUnit；类名 `{ClassName}Tests`；方法加 `[DisplayName(\"中文描述意图\")]`\n- 网络端口用 `0`/随机，IO 用临时目录\n- 先搜索 `{ClassName}` 引用定位测试文件，再找 `{ClassName}Tests.cs`；**未找到需说明**，不自动创建测试项目\n\n---\n\n## 9. 文档与发布\n\n### Markdown 文档\n\n**UTF-8 无 BOM**；存放 `Doc/` 目录；文件名优先中文，内容优先简体中文，避免乱码。**已有文件必须先读取再增量修改，禁止覆盖。**\n\n> 代码注释同样要求 UTF-8 无 BOM，优先简体中文。生成或编辑任何文件时须确保编码正确，防止中文乱码。\n\n### NuGet 版本\n\n| 类型 | 格式 | 示例 |\n|------|------|------|\n| 正式版 | `{主}.{子}.{年}.{月日}` | `11.9.2025.0701` |\n| 测试版 | `{主}.{子}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` |\n\n---\n\n## 10. 重要禁止项\n\n以下是 AI 容易犯但在本项目影响严重的错误：\n\n- 将 `String`/`Int32` 改为 `string`/`int`（本项目反 C# 惯例，**必须用正式名**）\n- 删除防御性注释（带说明的注释代码）\n- 删除 for/foreach/while 循环体的花括号（**循环体必须有花括号，即使只有一行**）\n- 将 `<summary>` 拆成多行\n- 擅自删除 `public`/`protected` 成员\n- 擅自新增外部 NuGet 依赖（需说明理由）\n- 仅删除空白行/注释制造\"格式优化\"提交\n- 虚构不存在的 API/文件/类型\n- 伪造测试结果/性能数据\n- 在热点路径添加未缓存反射/复杂 Linq\n- 输出敏感凭据/内部地址\n- 发现问题却视而不见\n- 用户要求优化时仅做注释/测试等表面工作\n- **跳过第 1 节触发检查**（命中关键词却未加载专用指令文件，是最严重的遗漏错误）\n\n---\n\n## 11. 变更说明模板\n\n```markdown\n## 概述\n做了什么 / 为什么\n\n## 影响\n- 公共 API：是/否\n- 性能影响：无/有（说明）\n\n## 兼容性\n降级策略 / 条件编译点\n\n## 风险与后续\n潜在回归 / 是否补测试\n```\n\n---\n\n## 12. Skills 技能文件\n\n`.github/skills/` 目录下的技能文件提供特定领域的详细使用指南和代码示例，用户可在 Copilot Chat 中通过 `#` 引用。\n\n| 技能文件 | 覆盖领域 |\n|---------|---------|\n| `caching.skill.md` | ICache/MemoryCache/Redis 统一缓存接口 |\n| `logging-tracing.skill.md` | ILog/XTrace 日志与 ITracer/DefaultTracer 链路追踪 |\n| `networking.skill.md` | NetServer/NetSession TCP/UDP/WebSocket 网络编程 |\n| `serialization.skill.md` | JSON/Binary/Span/CSV 序列化 |\n| `configuration.skill.md` | Config&lt;T&gt;/IConfigProvider/HttpConfigProvider 配置管理 |\n| `http-client.skill.md` | ApiHttpClient 多节点 HTTP 客户端与负载均衡 |\n| `dependency-injection.skill.md` | ObjectContainer/Host/Plugin/Actor 依赖注入与宿主 |\n| `timer-scheduling.skill.md` | TimerX/Cron 高级定时调度 |\n| `security.skill.md` | Hash/AES/SM4/RSA/JWT/TokenProvider 安全与加密 |\n| `type-conversion.skill.md` | ToInt/ToBoolean/StringHelper/Pool.StringBuilder 类型转换与工具 |\n\n---\n\n## 13. Agents 智能代理\n\n`.github/agents/` 目录下定义了专用 AI 代理角色，用户可在 Copilot Chat 中通过 `@` 调用。\n\n| 代理文件 | 用途 |\n|---------|------|\n| `newlife-expert.agent.md` | NewLife 组件专家：功能查询、组件推荐、编码指导 |\n| `code-review.agent.md` | 代码审查：按 NewLife 规范 8 维度检查代码 |\n| `project-init.agent.md` | 项目初始化：按模板创建新 NewLife 项目结构 |\n\n---\n\n（完）\n"
  },
  {
    "path": ".github/instructions/benchmark.instructions.md",
    "content": "---\napplyTo: \"**/Benchmark/**\"\n---\n\n# 性能测试指令\n\n适用于性能测试、压力测试、基准测试、BenchmarkDotNet 相关任务。\n\n---\n\n## 1. 项目结构\n\n- 基准测试统一放在 `Benchmark/` 项目，按主题分子目录（如 `PacketBenchmarks/`、`CacheBenchmarks/`）\n- 入口 `Program.cs` 使用 `BenchmarkSwitcher` 模式，**不要修改**\n- TFM 使用最新稳定版，`<LangVersion>latest</LangVersion>`\n\n## 2. 代码规范\n\n遵循主指令全部编码规范（类型名用 `String`/`Int32` 等、file-scoped namespace），另有以下补充：\n\n- **命名空间**：`Benchmark.{主题}Benchmarks`\n- **类名**：`{被测类型}Benchmark` 或 `{被测主题}Benchmark`\n- **必须标注** `[MemoryDiagnoser]` 和 `[SimpleJob]`（需调整迭代次数时用 `[SimpleJob(iterationCount: N)]`）\n- **方法描述**：`[Benchmark(Description = \"中文描述\")]`，方便报告阅读\n- **参数化**：用 `[Params]` 或 `[ParamsSource]` 控制数据规模\n- **初始化 / 清理**：分别放 `[GlobalSetup]` 和 `[GlobalCleanup]`\n- **分组**：同类测试用 `#region` 分组\n- **多线程并发**：动态线程数包含 CPU 核心数，推荐模板：\n\n```csharp\npublic static IEnumerable<Int32> ThreadCounts\n{\n    get\n    {\n        var cores = Environment.ProcessorCount;\n        var set = new SortedSet<Int32> { 1, 4, 8, 32 };\n        set.Add(cores);\n        return set;\n    }\n}\n\n[ParamsSource(nameof(ThreadCounts))]\npublic Int32 ThreadCount { get; set; }\n```\n\n## 3. 运行要求\n\n- 必须以 **Release 模式**运行，获取有代表性的峰值数据\n- 运行全部：`dotnet run -c Release`\n- 运行指定类：`dotnet run -c Release -- --filter *ClassName*`\n- ❌ 禁止在 Debug 模式下采集数据写入报告\n\n## 4. 测试维度\n\n- **并发维度**：单线程 + 多线程（多线程含与当前 CPU 核心数相同的并发数）\n- **操作维度**：单一操作 + 批量操作\n\n## 5. 常见错误\n\n- ❌ 在 `[Benchmark]` 方法内做初始化（应放 `[GlobalSetup]`）\n- ❌ 忽略返回值导致 JIT 死码消除（确保返回或赋值给字段）\n- ❌ 手动 `Stopwatch` 计时（BDN 自动处理）\n- ❌ `using` 的 `Dispose` 开销混入测量（仅在测试 Dispose 本身时才包含）\n\n## 6. 报告存放\n\n`Doc/Benchmark/{测试主题}性能测试.md`（UTF-8 无 BOM）\n\n## 7. 报告结构（顺序固定）\n\n1. **性能概览**（放最前：一句话点明被测功能用途 + 简单语言总结核心发现）\n2. 测试环境 → 测试方法 → 测试结果（BDN 原始表格，保留 Mean/Error/StdDev/Allocated）\n3. 核心指标（换算 msg/s、QPS 等业务指标）\n4. **对比分析**\n   - 纵向：同场景不同并发趋势，找出最优并发点\n   - 横向：不同方案同并发差异百分比\n5. 性能瓶颈定位（按重要程度排序）→ 优化建议（含预期收益与内存节省预估）\n\n## 8. 性能瓶颈定位规范\n\n性能瓶颈定位章节是报告的核心价值输出，必须遵循以下规范：\n\n### 8.1 瓶颈点结构（每个瓶颈必须包含）\n\n每个瓶颈点必须包含以下要素，缺一不可：\n\n| 要素 | 说明 | 示例 |\n|------|------|------|\n| **优先级标签** | P0/P1/P2/P3，按影响程度降序 | P0 |\n| **瓶颈名称** | 一句话准确描述瓶颈 | VisitTime 写入触发 MESI 缓存行争用 |\n| **优化收益占比** | 该瓶颈在总体可优化空间中的占比 | ~35% |\n| **现象与数据** | 用 BDN 实测数据量化问题严重程度 | 4T→8T 扩展仅 1.3x，低于预期 2.0x |\n| **根因分析** | 从代码执行路径分析到底层硬件行为 | Get 每次写 VisitTime → 缓存行 Modified → 多核 MESI 失效 |\n| **开销占比估算** | 在单次操作总耗时中的占比 | 占 Get 总耗时 30%~40% |\n| **内存影响** | 每次操作的额外内存分配或 GC 压力 | 48 B/次装箱分配，32 线程累计 3 MB |\n| **优化方向** | 具体可落地的优化方案 | 时间窗口内跳过更新（如 1s 内不重复写） |\n| **预期收益** | 速度提升倍数 + 内存节省比例 | 多线程吞吐 +20-30%，消除缓存行争用 |\n\n### 8.2 瓶颈分级标准\n\n| 级别 | 定义 | 优化收益占比 | 行动 |\n|------|------|------------|------|\n| **P0** | 影响核心吞吐或造成 >30% 性能损失 | ≥25% | 必须优化 |\n| **P1** | 影响多线程扩展性或造成显著内存压力 | 15%~25% | 建议优化 |\n| **P2** | 特定场景下的次要瓶颈 | 5%~15% | 可选优化 |\n| **P3** | 微小开销，仅在极端场景有影响 | <5% | 记录备查 |\n\n### 8.3 瓶颈定位表格模板\n\n性能瓶颈定位章节使用以下统一表格格式：\n\n```markdown\n### 核心瓶颈点总览\n\n| 优先级 | 瓶颈 | 优化收益占比 | 当前开销 | 优化后预估 | 内存节省 |\n|--------|------|------------|---------|-----------|---------|\n| P0 | {瓶颈名称} | ~{X}% | {耗时/分配} | {目标值} | {节省比例} |\n| P1 | {瓶颈名称} | ~{X}% | {耗时/分配} | {目标值} | {节省比例} |\n| ... | ... | ... | ... | ... | ... |\n```\n\n### 8.4 内存优化方向表格模板\n\n紧跟瓶颈总览表之后，补充内存优化方向：\n\n```markdown\n### 关键内存优化方向\n\n| 优先级 | 优化方向 | 当前分配 | 优化后预估 | 节省比例 | 实施方案 |\n|--------|---------|---------|-----------|---------|---------|\n| P0 | {方向} | {X} B/op | {Y} B/op | {Z}% | {方案} |\n| ... | ... | ... | ... | ... | ... |\n```\n\n### 8.5 开销拆解要求\n\n对每个核心操作，必须给出开销来源拆解表：\n\n```markdown\n| 开销来源 | 占比估算 | 耗时估算 | 说明 |\n|---------|---------|---------|------|\n| {来源1} | ~{X}% | ~{N} ns | {原因} |\n| {来源2} | ~{X}% | ~{N} ns | {原因} |\n```\n\n### 8.6 撰写原则\n\n- **数据驱动**：所有结论必须有 BDN 实测数据支撑，禁止无数据臆测\n- **量化优先**：用\"快 X 倍\"、\"省 Y%\"、\"降 Z B/op\"表达，避免\"显著\"、\"明显\"等模糊词\n- **根因到底**：从应用层代码 → 运行时机制 → CPU 微架构逐层分析\n- **可操作**：每个优化建议必须指明具体修改位置和实施方案，而非泛泛建议\n- **排序严格**：P0 在前，P3 在后，同级按收益占比降序\n"
  },
  {
    "path": ".github/instructions/development.instructions.md",
    "content": "---\napplyTo: \"Doc/**\"\n---\n\n# AI 辅助开发流程指令\n\n适用于新建应用系统、新增功能模块、需求整理、架构设计等研发全流程任务。\n\n---\n\n## 1. 流程总览\n\n```\n需求整理 → 需求评审与拆分 → 技术方案设计 → 任务分解 → 迭代开发 → 集成验证 → 验收回顾\n```\n\n**核心原则**：大需求必须拆小，每个迭代交付可验证的最小功能单元。禁止\"一次性全做完\"。\n\n---\n\n## 2. 各阶段规范\n\n### 2.1 需求整理\n\n用户提供原始描述（口语化、列表、草稿均可），AI 整理为以下结构（需求 + 功能清单 + 验收 合为一个文件）：\n\n```markdown\n# {项目/模块名}需求\n\n## 1. 背景与目标\n- 为什么做（痛点/动机）\n- 做到什么程度算成功（可衡量目标）\n\n## 2. 用户角色\n| 角色 | 说明 | 核心诉求 |\n|------|------|---------|\n\n## 3. 功能需求\n### 3.1 {功能模块名}\n- **描述**：一句话说明\n- **用户故事**：作为{角色}，我希望{操作}，以便{价值}\n- **验收条件**（AC）：\n  - [ ] 条件 1\n  - [ ] 条件 2\n- **优先级**：Must / Should / Could / Won't\n\n## 4. 非功能需求\n- 性能 / 安全 / 兼容性（三项必填）\n\n## 5. 边界与约束\n- 不做什么（明确排除项）\n- 已知限制 / 技术债务\n\n## 6. 功能清单与迭代计划\n（需求评审拆分后填写，见 2.2）\n\n## 7. 验收记录\n（开发完成后填写，见 2.7）\n\n## 8. 术语表\n| 术语 | 定义 |\n|------|------|\n```\n\n**规则**：每个功能必须有 AC，无 AC 不可进入开发；优先级用 MoSCoW 四级；非功能需求至少覆盖性能、安全、兼容性。\n\n### 2.2 需求评审与拆分\n\n按**纵向切片**（端到端功能，非技术层）拆分，遵循 INVEST 原则，单个功能单元 ≤ 1-2 天工作量，有依赖须标注。\n\n写入需求文档「6. 功能清单与迭代计划」：\n\n```markdown\n## 6. 功能清单与迭代计划\n\n### 迭代 1：{主题}（Must 级别）\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 预估工作量 |\n|------|--------|---------|---------|----------|\n| F001 | xxx | AC1, AC2 | 无 | 0.5d |\n| F002 | xxx | AC1 | F001 | 1d |\n\n### 迭代 2：{主题}（Should 级别）\n...\n```\n\n### 2.3 技术方案设计\n\n```markdown\n# {项目/模块名}架构\n\n## 1. 架构概览\n## 2. 数据模型\n## 3. 接口设计\n| 接口 | 方法 | 路径/签名 | 入参 | 出参 | 说明 |\n|------|------|----------|------|------|------|\n## 4. 技术选型\n| 领域 | 选型 | 理由 |\n|------|------|------|\n## 5. 关键设计决策\n| 决策点 | 方案 | 备选方案 | 选择理由 |\n|--------|------|---------|---------|\n## 6. 任务分解\n（见 2.4）\n## 7. 风险与缓解\n| 风险 | 影响 | 缓解措施 |\n|------|------|---------|\n```\n\n**规则**：优先使用 NewLife 已有组件（XCode、Remoting、Stardust 等）；数据模型考虑 XCode 实体规范；接口遵循现有 API 风格。\n\n### 2.4 任务分解\n\n单个任务 = 一次 AI 对话可完成的工作量（编码 + 测试 + 自测通过）。写入技术方案「6. 任务分解」：\n\n```markdown\n### 任务 T001：{动词 + 目标}\n- **对应功能**：F001\n- **输入**：前置条件 / 已有代码\n- **产出**：新增/修改哪些文件\n- **验收**：怎样算完成\n```\n\n**批次编排**（用于自治模式，见第 6 节）：按依赖关系编排为批次，每批次 5-8 个任务，同批次内尽量无相互依赖，基础设施任务排在前面，每批次结束设 `[检查点 N]`，标注本批次产出是下批次哪些输入。\n\n### 2.5 迭代开发\n\n流程：`理解任务 → 检索现有实现 → 编码 → 编译通过 → 测试通过 → 提交说明`\n\n- 严格遵守主指令编码规范，每个任务必须编译通过\n- 常规模式：遇歧义暂停确认；自治模式：记录跳过继续（见第 6 节）\n- 有依赖按顺序执行，不跳跃\n\n### 2.6 集成验证\n\n全部编译通过 → 单元测试通过 → 端到端主流程走通 → 异常场景覆盖 → 性能符合预期\n\n### 2.7 验收与回顾\n\n对照需求文档逐条验收，写入「7. 验收记录」：\n\n```markdown\n## 7. 验收记录\n\n### 功能验收\n| 编号 | 功能点 | 验收条件 | 状态 | 备注 |\n|------|--------|---------|------|------|\n\n### 遗留问题\n| 问题 | 影响 | 后续计划 |\n|------|------|---------|\n\n### 经验总结\n- 做得好的 / 待改进的\n```\n\n---\n\n## 3. 文档存放规范\n\n全流程仅产出 **2 个文档**，扁平存放在 `Doc/` 下：\n\n| 文档 | 文件名 | 包含内容 |\n|------|--------|--------|\n| 需求文档 | `Doc/{项目名}需求.md` | 背景目标 + 功能需求 + 功能清单 + 验收记录 + 术语表 |\n| 技术方案 | `Doc/{项目名}架构.md` | 架构 + 数据模型 + 接口 + 技术选型 + 任务分解 + 风险 |\n\nUTF-8 无 BOM；已有文件必须先读取再增量修改，禁止覆盖；各阶段产出追加到对应章节，不新建文件。\n\n---\n\n## 4. AI 协作要点\n\n### 4.1 阶段切换\n\n| 用户说 | 进入阶段 |\n|--------|---------|\n| \"整理需求\"/\"写需求\" | 2.1 需求整理 |\n| \"拆分\"/\"拆解\"/\"排优先级\" | 2.2 需求评审与拆分 |\n| \"技术方案\"/\"架构设计\"/\"怎么实现\" | 2.3 技术方案设计 |\n| \"开始开发\"/\"写代码\"/\"实现 F001\" | 2.5 迭代开发 |\n| \"全部搞完\"/\"批量开发\"/\"自治模式\"/\"一次性做完\"/\"继续处理\"/\"接着做\" | 第 6 节自治批处理 |\n| \"验收\"/\"检查完成情况\" | 2.7 验收与回顾 |\n| 一大段描述未指定阶段 | 默认 2.1 需求整理 |\n\n### 4.2 主动引导\n\n每阶段完成后提示下一步：需求整理完 → 拆分？ → 技术方案？ → 任务分解 → 开发？\n\n### 4.3 大需求防护\n\n功能点 > 5 / 实体 > 3 / 跨 2 层以上 / 描述 > 500 字 → 必须先拆分再开发。\n\n---\n\n## 5. 常见反模式（禁止）\n\n- ❌ 跳过需求直接编码\n- ❌ 一次性输出所有代码（大需求必须拆迭代或使用自治模式）\n- ❌ 需求文档没有验收条件\n- ❌ 功能拆分按技术层而非用户价值\n- ❌ 任务没有完成标准就开始编码\n- ❌ 完成后不做验收对照\n- ❌ 自治模式下遇阻塞问题死等用户（应记录跳过，继续后续）\n- ❌ 自治模式下做需要人工决策的架构变更（应记录待确认，现有方案兜底）\n- ❌ 跨批次不做编译验证\n\n---\n\n## 6. 自治批处理模式\n\n架构师已确认需求和技术方案后，AI 按任务清单自主执行，最小化人工介入。\n\n### 6.1 进入条件（全部满足）\n\n- [ ] 需求文档已完成且架构师已确认\n- [ ] 技术方案已完成且架构师已确认\n- [ ] 任务已分解并编排为批次\n- [ ] 用户明确触发（\"全部搞完\"/\"批量开发\"/\"自治模式\"等）\n\n未满足时提示缺少哪些条件。\n\n### 6.2 计划结构与循环刷新\n\nAI 用 plan 工具创建层次化计划，「前置刷新 + 批次执行」循环：\n\n```\n1. [前置] 读取需求文档与技术方案\n2. [前置] 读取任务清单与进度状态\n3. [前置] 全量编译确认基线\n4. [前置] 识别可并行的批次组\n5. [批次1] 执行 T001-T005（子步骤展开各任务）\n6. [检查点1] 输出批次1报告\n7. [刷新] 重读需求文档与技术方案\n8. [批次2] 执行 T006-T010\n9. [检查点2] 输出批次2报告\n...（循环：刷新 → 批次 → 检查点）\nN-2. [后置] 全量编译与集成验证\nN-1. [后置] 补完被跳过的任务\nN.   [后置] 生成验收报告\n```\n\n**要点**：\n- 主步骤 15-25 个（不超过 30），子步骤展开具体任务仅供参考不单独追踪\n- 刷新步骤穿插在每两个批次之间，`get_file` 重读文档对抗上下文漂移\n- 用 `update_plan_progress` 跟踪主步骤，不为每个子任务调用\n- 无依赖的批次可合并为一个主步骤执行，有依赖的必须顺序执行\n\n### 6.3 执行协议\n\n| 情况 | 处理方式 |\n|------|----------|\n| 任务明确无歧义 | 直接执行：编码 → 编译 → 测试 |\n| 小歧义可合理推断 | 执行并在问题日志记录推断依据 |\n| 重大歧义或多种等价方案 | 标记 `⏸️ 待确认`，跳过 |\n| 前置任务被跳过 | 标记 `⏸️ 依赖阻塞：T0xx`，跳过 |\n| 编译失败短时间无法修复 | 回滚改动，记录并跳过 |\n| 涉及公共 API / 架构变更 | 标记 `⏸️ 需架构师决策`，兜底或跳过 |\n\n### 6.4 检查点报告\n\n每批次完毕后输出：\n\n```markdown\n## 检查点 N 报告\n\n### 完成情况\n| 任务 | 状态 | 说明 |\n|------|------|------|\n| T001 | ✅ 完成 | |\n| T003 | ⏸️ 跳过 | 需确认：xxx |\n\n### 编译状态\n- 全量编译：✅ 通过 / ❌ 失败（错误详情）\n\n### 问题日志\n| 编号 | 类型 | 描述 | 影响任务 | 建议方案 |\n|------|------|------|---------|----------|\n\n### 统计\n- 本批次 N 个，完成 X 个，跳过 Y 个\n- 累计进度：已完成 X / 总计 Z（XX%）\n- 上下文预估：{已处理任务数} / {建议上限}\n```\n\n### 6.5 用户回复与继续\n\n架构师回来后：AI 呈现检查点报告 → 架构师批量回复问题（\"Q001 OK，Q002 选 A\"）→ AI 修正推断 + 执行跳过的任务 + 继续下批次 → 循环至完成。\n\n触发词：\"继续\"/\"继续处理\"/\"回复完了\"/\"接着做\"\n\n### 6.6 质量护栏（自动执行）\n\n编译门禁（失败即修复或回滚）/ 命名与技术方案一致 / 编码规范严格遵守 / 新增代码前搜索现有实现避免重复 / 不擅自引入新 NuGet 包\n\n### 6.7 会话边界处理\n\n每个检查点后、连续完成 15+ 任务后、搜索结果不准确时 → 评估是否需要新会话。\n\n**新会话续接模板**：\n\n```\n我们在做 {项目名} 的自治批处理开发。\n- 需求文档：Doc/{项目名}需求.md\n- 技术方案：Doc/{项目名}架构.md\n- 当前进度：批次 N 已完成，从批次 N+1 的 T0xx 开始继续\n- 待解决问题：{问题编号}\n请读取以上文档，从 T0xx 继续执行，自治模式。\n```\n\n上下文即将耗尽时 AI 主动提醒并生成上述模板。新会话前 4 步仍为前置刷新，已完成批次直接标记完成。\n\n### 6.8 批次大小建议\n\n| 复杂度 | 批次大小 |\n|--------|---------|\n| 简单（CRUD） | 8-10 |\n| 中等（业务逻辑） | 5-7 |\n| 复杂（算法、并发） | 3-5 |\n\n单会话上限：3-4 个批次（约 15-25 个任务）。\n\n---\n\n（完）\n"
  },
  {
    "path": ".github/instructions/net.instructions.md",
    "content": "---\napplyTo: \"**/Net/**\"\n---\n\n# 网络编程指令\n\n适用于基于 `NewLife.Net` 的网络服务器（`NetServer`）和客户端（`ISocketClient`）开发任务。\n\n---\n\n## 1. 架构概览\n\nNewLife 网络框架分为两层：\n\n| 层级 | 服务端 | 客户端 | 说明 |\n|------|--------|--------|------|\n| **应用层** | `NetServer` / `NetServer<TSession>` | — | 管理监听、会话生命周期、管道 |\n| **传输层** | `TcpServer` / `UdpServer` | `TcpSession` / `UdpServer`（客户端模式） | 底层 Socket 收发 |\n| **会话** | `NetSession` / `NetSession<TServer>` | — | 每个连接对应一个会话，业务逻辑入口 |\n| **管道** | `IPipeline` + `IPipelineHandler` | 同左 | 编解码、粘包拆包、消息匹配 |\n\n**关键接口**：\n- `ISocketClient` — 客户端连接接口（Open/Close/Send/Receive）\n- `ISocketRemote` — 远程通信接口（Send/Receive/SendMessageAsync）\n- `INetSession` — 网络会话接口（服务端每个连接的业务处理单元）\n- `INetHandler` — 网络数据处理器接口（Init/Process）\n\n---\n\n## 2. 服务端开发规范\n\n### 2.1 基本模式\n\n推荐使用泛型 `NetServer<TSession>` + 自定义 `NetSession` 子类：\n\n```csharp\n/// <summary>自定义网络服务器</summary>\nclass MyServer : NetServer<MySession> { }\n\n/// <summary>自定义会话，每个客户端连接对应一个实例</summary>\nclass MySession : NetSession<MyServer>\n{\n    /// <summary>客户端连接</summary>\n    protected override void OnConnected()\n    {\n        base.OnConnected();\n        WriteLog(\"客户端已连接 {0}\", Remote);\n    }\n\n    /// <summary>收到客户端数据</summary>\n    protected override void OnReceive(ReceivedEventArgs e)\n    {\n        base.OnReceive(e);\n        // 业务处理\n    }\n\n    /// <summary>客户端断开</summary>\n    protected override void OnDisconnected(String reason)\n    {\n        base.OnDisconnected(reason);\n    }\n}\n```\n\n### 2.2 服务器启动配置\n\n```csharp\nvar server = new MyServer\n{\n    Port = 8080,                              // 监听端口，0 表示随机\n    ProtocolType = NetType.Tcp,               // Tcp/Udp/Unknown（同时监听）\n    // AddressFamily = AddressFamily.InterNetwork, // 仅IPv4，默认同时IPv4+IPv6\n    ServiceProvider = provider,               // 依赖注入\n    Log = XTrace.Log,                         // 应用日志\n    SessionLog = XTrace.Log,                  // 会话日志\n    Tracer = tracer,                          // APM 追踪\n#if DEBUG\n    SocketLog = XTrace.Log,                   // Socket 层日志（仅调试）\n    LogSend = true,\n    LogReceive = true,\n#endif\n};\nserver.Start();\n```\n\n### 2.3 会话生命周期\n\n```\n连接建立 → OnConnected() → OnReceive()... → OnDisconnected(reason) → Dispose()\n```\n\n- **OnConnected**：初始化会话状态、发送欢迎消息\n- **OnReceive**：核心业务处理入口，`e.Packet` 为原始数据，`e.Message` 为管道解码后的消息\n- **OnDisconnected**：清理资源、记录日志，`reason` 包含断开原因\n- 会话内可通过 `ServiceProvider` 获取 Scoped 服务\n\n### 2.4 服务端发送数据\n\n| 方法 | 说明 |\n|------|------|\n| `Send(IPacket)` | 直接发送原始数据，不经过管道 |\n| `Send(String)` | 发送字符串，默认 UTF-8 |\n| `Send(ReadOnlySpan<Byte>)` | 高性能发送 |\n| `SendMessage(Object)` | 通过管道编码后发送，不等待响应 |\n| `SendReply(Object, ReceivedEventArgs)` | 发送响应消息，与请求关联（用于 StandardCodec 等协议） |\n| `SendMessageAsync(Object)` | 通过管道发送并等待响应 |\n\n### 2.5 群发\n\n```csharp\n// 群发数据给所有在线客户端\nawait server.SendAllAsync(data);\n\n// 带过滤条件群发\nawait server.SendAllAsync(data, session => session.ID > 100);\n\n// 群发管道消息\nserver.SendAllMessage(message, session => session[\"VIP\"] is true);\n```\n\n群发要求 `UseSession = true`（默认开启）。\n\n### 2.6 事件模式（简单场景）\n\n不需要自定义会话时，可直接使用事件：\n\n```csharp\nvar server = new NetServer { Port = 8080 };\nserver.Received += (sender, e) =>\n{\n    if (sender is INetSession session)\n        session.Send(e.Packet);  // Echo\n};\nserver.Start();\n```\n\n---\n\n## 3. 客户端开发规范\n\n### 3.1 创建客户端\n\n通过 `NetUri.CreateRemote()` 扩展方法创建：\n\n```csharp\n// TCP 客户端\nvar client = new NetUri(\"tcp://127.0.0.1:8080\").CreateRemote();\n\n// UDP 客户端\nvar client = new NetUri(\"udp://127.0.0.1:8080\").CreateRemote();\n\n// WebSocket 客户端\nvar client = new NetUri(\"ws://127.0.0.1:8080/path\").CreateRemote();\n```\n\n`CreateRemote` 根据协议自动返回 `TcpSession` / `UdpServer` / `WebSocketClient`。\n\n### 3.2 客户端使用\n\n```csharp\nvar uri = new NetUri(\"tcp://127.0.0.1:8080\");\nvar client = uri.CreateRemote();\nclient.Log = XTrace.Log;\nclient.Open();\n\n// 发送原始数据（不经过管道）\nclient.Send(\"Hello\");\n\n// 事件驱动接收\nclient.Received += (sender, e) =>\n{\n    // e.Packet 原始数据，e.Message 管道解码后的消息\n};\n\n// 或同步/异步接收\nusing var pk = client.Receive();\nusing var pk = await client.ReceiveAsync(cancellationToken);\n\nclient.Close(\"完成\");  // 或 client.Dispose()\n```\n\n### 3.3 请求-响应模式（需要管道编解码器）\n\n```csharp\nvar client = new NetUri(\"tcp://127.0.0.1:8080\").CreateRemote();\nclient.Add<StandardCodec>();\nclient.Open();\n\nvar response = await client.SendMessageAsync(payload, cancellationToken);  // 等待响应\nclient.SendMessage(message);  // 不等待响应\n```\n\n### 3.4 SSL/TLS\n\n```csharp\n// 服务端 SSL\nvar server = new NetServer\n{\n    Port = 443,\n    SslProtocol = SslProtocols.Tls12,\n    Certificate = new X509Certificate2(\"server.pfx\", \"password\"),\n};\n\n// 客户端 SSL（自动根据端口判断，或手动指定）\nvar client = new NetUri(\"tcp://host:443\").CreateRemote();\nif (client is TcpSession tcp)\n{\n    tcp.SslProtocol = SslProtocols.Tls12;\n    // tcp.Certificate = cert;  // 客户端证书（如果服务端要求）\n}\n```\n\n---\n\n## 4. 管道与编解码器\n\n### 4.1 管道机制\n\n管道（`IPipeline`）是处理器链，Read/Write 返回值作为下一个处理器的输入，返回 `null` 截断管道：\n\n```\n接收：Socket → [Codec1.Read] → [Codec2.Read] → FireRead → OnReceive\n发送：SendMessage → [Codec2.Write] → [Codec1.Write] → FireWrite → Socket\n```\n\nOpen 正序传播，Close 逆序传播。先添加的在底层（靠近 Socket），后添加的在上层（靠近业务）。\n\n### 4.2 内置编解码器\n\n| 编解码器 | 基类 | 说明 | 典型场景 |\n|---------|------|------|---------|\n| `StandardCodec` | `MessageCodec<IMessage>` | 4字节头部（Flag+Seq+Length），支持请求-响应匹配 | 自定义 RPC 协议 |\n| `LengthFieldCodec` | `MessageCodec<IPacket>` | 长度字段头部，可配置偏移和大小 | MQTT、通用二进制协议 |\n| `JsonCodec` | `Handler` | JSON 文本编解码，不处理粘包 | 文本协议（通常与 StandardCodec 级联） |\n| `SplitDataCodec` | `Handler` | 分隔符拆包（默认 `\\r\\n`） | 文本行协议 |\n| `WebSocketCodec` | `Handler` | WebSocket 帧编解码 | WebSocket 通信 |\n\n### 4.3 添加编解码器\n\n```csharp\n// 服务端添加\nserver.Add<StandardCodec>();\n\n// 客户端添加\nclient.Add<StandardCodec>();\n\n// 多层管道级联（按添加顺序组成链）\nserver.Add<StandardCodec>();  // 底层：粘包拆包 + 请求响应匹配\nserver.Add<JsonCodec>();      // 上层：JSON 编解码\n```\n\n### 4.4 StandardCodec 请求-响应\n\nStandardCodec 使用 `DefaultMessage`，包含 Flag（1字节）、Sequence（1字节）、Length（2字节），\n支持自动序列号分配和请求-响应匹配。\n\n```csharp\n// 服务端 Echo 示例\nserver.Add<StandardCodec>();\nserver.Received += (sender, e) =>\n{\n    if (sender is INetSession session && e.Message is IPacket pk)\n        session.SendReply(pk, e);  // 使用 SendReply 关联请求上下文\n};\n\n// 客户端请求-响应\nclient.Add<StandardCodec>();\nvar response = await client.SendMessageAsync(payload);\n```\n\n### 4.5 基类选择\n\n| 基类 | 适用场景 | 典型代表 |\n|------|---------|---------|\n| `MessageCodec<T>` | 需要粘包拆包和/或请求-响应匹配（内置 `IMatchQueue`、`Encode`/`Decode`） | `StandardCodec`、`LengthFieldCodec` |\n| `Handler` | 简单转换、帧协议、文本协议（轻量，仅 `Read`/`Write`/`Open`/`Close`） | `JsonCodec`、`SplitDataCodec`、`WebSocketCodec` |\n\n### 4.6 编解码器设计规范\n\n#### 4.6.1 粘包拆包（PacketCodec 模式）\n\nTCP 是字节流协议，必须处理粘包拆包。统一模式（完整实现见 4.7 模板）：\n\n1. 每个连接独立的 `PacketCodec` 实例，存储在 `ss[\"Codec\"]` 中\n2. 通过 `GetLength2` 委托告诉 `PacketCodec` 如何计算完整帧长度\n3. `PacketCodec.Parse()` 返回完整帧列表，自动缓存不完整数据\n\n**`GetLength2` 规范**（签名 `Int32 GetLength(ReadOnlySpan<Byte> span)`）：返回帧完整长度（含头部），数据不足时返回 `0`。\n\n```csharp\npublic static Int32 GetLength(ReadOnlySpan<Byte> span)\n{\n    if (span.Length < 4) return 0;\n    var reader = new SpanReader(span) { IsLittleEndian = true };\n    reader.Advance(2);\n    return 4 + reader.ReadUInt16();  // 头部4字节 + 负载长度\n}\n```\n\n#### 4.6.2 编码与内存管理\n\n- **`ExpandHeader(size)`**：编码时优先复用负载缓冲区前置空间写入头部，零拷贝；空间不足时创建 `OwnerPacket`，原包作为 `Next` 链节点\n- **`SpanWriter`**：配合 `ExpandHeader` 写入头部字段，注意 `IsLittleEndian` 大小端\n- **兜底释放**：`MessageCodec<T>.Write` 基类自动 `TryDispose`；`Handler` 子类需在 `Write` 的 `finally` 中手动调用\n- **对象池**：`DefaultMessage.Rent()` / `DefaultMessage.Return()` 减少 GC 压力\n\n#### 4.6.3 请求-响应匹配\n\n`MessageCodec<T>` 内置 `IMatchQueue`，流程：`Write` → `AddToQueue` 入队 → `Decode` 解码 → `Queue.Match` 按 `IsMatch` 匹配 → 唤醒 `SendMessageAsync` 的 `Task`。\n\n- 重载 `AddToQueue`：控制哪些消息入队（通常只有请求消息）\n- 重载 `IsMatch`：根据序列号等字段匹配请求和响应（见 4.7 模板）\n- `QueueSize`：匹配队列大小，默认 256\n- `Timeout`：等待响应超时，默认 30_000ms\n- `UserPacket`：为 `true` 时向上层传递 `Payload` 而非整个 `IMessage`，用于编码器级联\n\n#### 4.6.4 Close 清理\n\n**必须**在 `Close` 中执行 `ss[\"Codec\"] = null` 清理 `PacketCodec`，否则 `MemoryStream` 缓存泄漏（见 4.7 模板）。\n\n#### 4.6.5 上下文扩展（IExtend）\n\n管道处理器通过 `IExtend` 在会话/上下文上传递元数据：\n\n| 键 | 用途 | 示例 |\n|---|------|------|\n| `\"Codec\"` | 每连接的 `PacketCodec` 实例 | 编解码器的 `Decode`/`Close` 中读写 |\n| `\"Flag\"` | 数据类型标记 `DataKinds` | `JsonCodec.Write` 设置 → `StandardCodec.Write` 消费 |\n| `\"_raw_message\"` | 原始请求消息 | `MessageCodec.Read` 设置 → `Write` 中创建响应时消费 |\n| `\"TaskSource\"` | `TaskCompletionSource` | 框架内部，`AddToQueue` 消费 |\n\n#### 4.6.6 多层管道级联\n\n- 底层编解码器处理粘包拆包和请求-响应匹配，上层处理数据格式转换\n- `UserPacket = true` 让底层向上层传递 `Payload` 而非整个 `IMessage`\n- 上层通过 `ext[\"Flag\"]` 向底层传递数据类型标记\n\n### 4.7 自定义编解码器模板\n\n#### 方式一：继承 MessageCodec<T>（需要粘包/请求响应匹配）\n\n```csharp\n/// <summary>自定义协议编解码器</summary>\npublic class MyCodec : MessageCodec<MyMessage>\n{\n    /// <summary>编码消息为数据包</summary>\n    protected override Object? Encode(IHandlerContext context, MyMessage msg)\n    {\n        return msg.ToPacket();\n    }\n\n    /// <summary>解码数据包为消息</summary>\n    protected override IEnumerable<MyMessage>? Decode(IHandlerContext context, IPacket pk)\n    {\n        if (context.Owner is not IExtend ss) yield break;\n\n        if (ss[\"Codec\"] is not PacketCodec pc)\n        {\n            ss[\"Codec\"] = pc = new PacketCodec\n            {\n                GetLength2 = MyMessage.GetLength,\n                MaxCache = MaxCache,\n                Tracer = (context.Owner as ISocket)?.Tracer\n            };\n        }\n\n        foreach (var item in pc.Parse(pk))\n        {\n            var msg = new MyMessage();\n            if (msg.Read(item)) yield return msg;\n        }\n    }\n\n    /// <summary>是否匹配响应</summary>\n    protected override Boolean IsMatch(Object? request, Object? response) =>\n        request is MyMessage req && response is MyMessage res\n        && req.Sequence == res.Sequence;\n\n    /// <summary>连接关闭时清理</summary>\n    public override Boolean Close(IHandlerContext context, String reason)\n    {\n        if (context.Owner is IExtend ss) ss[\"Codec\"] = null;\n\n        return base.Close(context, reason);\n    }\n}\n```\n\n#### 方式二：继承 Handler（简单转换/帧协议）\n\n```csharp\n/// <summary>自定义帧编解码器</summary>\npublic class MyFrameCodec : Handler\n{\n    /// <summary>读取数据（接收时）</summary>\n    public override Object? Read(IHandlerContext context, Object message)\n    {\n        if (message is IPacket pk)\n        {\n            // 解码：二进制 → 业务对象\n            var frame = MyFrame.Parse(pk);\n            message = frame;\n        }\n\n        return base.Read(context, message);\n    }\n\n    /// <summary>写入数据（发送时）</summary>\n    public override Object? Write(IHandlerContext context, Object message)\n    {\n        IPacket? owner = null;\n        if (message is MyFrame frame)\n        {\n            // 编码：业务对象 → 二进制\n            message = owner = frame.ToPacket();\n        }\n\n        try\n        {\n            return base.Write(context, message);\n        }\n        finally\n        {\n            owner.TryDispose();  // 兜底释放\n        }\n    }\n\n    /// <summary>连接关闭时清理缓存</summary>\n    public override Boolean Close(IHandlerContext context, String reason)\n    {\n        if (context.Owner is IExtend ss) ss[\"Codec\"] = null;\n\n        return base.Close(context, reason);\n    }\n}\n```\n\n---\n\n## 5. 常见模式与最佳实践\n\n### 5.1 端口选择\n\n- 测试代码使用端口 `0`（系统自动分配随机端口），避免端口冲突\n- 正式服务指定固定端口\n- 启动后可通过 `server.Port` 获取实际监听端口\n\n### 5.2 协议选择\n\n| 场景 | 推荐 |\n|------|------|\n| 可靠传输、长连接 | `NetType.Tcp` |\n| 低延迟、广播、允许丢包 | `NetType.Udp` |\n| 同时支持（默认） | `NetType.Unknown` |\n| Web 浏览器通信 | `NetType.WebSocket` |\n\n### 5.3 会话管理\n\n- `UseSession = true`（默认）：维护会话集合，支持群发、按 ID 查找\n- `UseSession = false`：不维护会话集合，减少内存开销，适合海量短连接\n- `SessionTimeout`：设置会话超时时间（秒），超时无数据自动断开\n- 会话中通过 `Items` 字典存储自定义数据\n\n### 5.4 日志分层\n\n| 属性 | 用途 | 建议 |\n|------|------|------|\n| `Log` | 服务器应用层日志 | 始终设置 |\n| `SessionLog` | 会话级别日志 | 调试时设置 |\n| `SocketLog` | 底层 Socket 日志 | 仅 DEBUG 时设置 |\n| `LogSend` / `LogReceive` | 收发数据内容日志 | 仅 DEBUG 时开启 |\n| `Tracer` | 应用层 APM | 生产环境追踪 |\n| `SocketTracer` | Socket 层 APM | 排查底层问题 |\n\n### 5.5 资源释放\n\n- 服务端：调用 `server.Stop(reason)` 或 `server.Dispose()`\n- 客户端：调用 `client.Close(reason)` 或 `client.Dispose()`\n- 会话自动随连接断开释放，无需手动管理\n- `ISocketClient` 实现 `IDisposable`，推荐 `using` 模式\n\n### 5.6 INetHandler 业务处理器\n\n通过重载 `NetServer.CreateHandler` 注入自定义业务处理器：\n\n```csharp\nclass MyServer : NetServer<MySession>\n{\n    /// <summary>为会话创建网络数据处理器</summary>\n    public override INetHandler? CreateHandler(INetSession session) => new MyHandler();\n}\n```\n\n处理器在会话 `Start` 时初始化，`OnReceive` 前调用 `Process`，适合前置协议解析。\n\n---\n\n## 6. 常见错误\n\n- ❌ 在 `OnReceive` 中执行长时间阻塞操作（会影响其他连接的数据接收）\n- ❌ 不加管道编解码器直接调用 `SendMessageAsync`（无法匹配响应）\n- ❌ 混淆 `Send` 与 `SendMessage`：前者直接发原始数据，后者经过管道编码\n- ❌ 混淆 `SendMessage` 与 `SendReply`：响应消息必须用 `SendReply` 关联请求上下文\n- ❌ 忘记调用 `base.OnConnected()` / `base.OnDisconnected(reason)` / `base.OnReceive(e)`\n- ❌ 在会话中使用 `Task.Result` 或 `Task.Wait()`（导致死锁和线程池饥饿）\n- ❌ 使用固定端口编写测试（端口冲突），应使用 `Port = 0`\n- ❌ 服务端 SSL 未指定证书\n\n---\n\n## 7. 完整示例\n\n### 7.1 带 StandardCodec 的 Echo 服务\n\n```csharp\n// 服务端\nvar server = new NetServer\n{\n    Port = 8080,\n    ProtocolType = NetType.Tcp,\n    Log = XTrace.Log,\n};\nserver.Add<StandardCodec>();\nserver.Received += (sender, e) =>\n{\n    if (sender is INetSession session && e.Message is IPacket pk)\n        session.SendReply(pk, e);\n};\nserver.Start();\n\n// 客户端\nvar client = new NetUri($\"tcp://127.0.0.1:{server.Port}\").CreateRemote();\nclient.Add<StandardCodec>();\nclient.Open();\n\nvar response = await client.SendMessageAsync(new ArrayPacket(\"Hello\".GetBytes()));\n```\n\n### 7.2 自定义会话服务器\n\n```csharp\nclass ChatServer : NetServer<ChatSession> { }\n\nclass ChatSession : NetSession<ChatServer>\n{\n    protected override void OnConnected()\n    {\n        base.OnConnected();\n        Send($\"欢迎 [{Remote}] 进入聊天室！\\r\\n\");\n    }\n\n    protected override void OnReceive(ReceivedEventArgs e)\n    {\n        base.OnReceive(e);\n        var msg = e.Packet?.ToStr();\n        if (msg.IsNullOrEmpty()) return;\n\n        // 广播给所有在线用户\n        var host = (this as INetSession).Host;\n        host.SendAllMessage($\"[{ID}] {msg}\");\n    }\n\n    protected override void OnDisconnected(String reason)\n    {\n        base.OnDisconnected(reason);\n        WriteLog(\"用户离开：{0}\", reason);\n    }\n}\n```\n\n---\n\n（完）\n"
  },
  {
    "path": ".github/workflows/publish-beta.yml",
    "content": "name: publish-beta\n\non:\n  push:\n    branches: [ master,dev ]\n    paths:\n        - 'NewLife.RocketMQ/**'\n  workflow_dispatch:\n\njobs:\n  build-publish:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup dotNET\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          6.x\n          7.x\n          8.x\n          9.x\n          10.x\n    - name: Build\n      run: |\n        dotnet pack --version-suffix $(date \"+%Y.%m%d-beta%H%M\") -c Release -o out NewLife.RocketMQ/NewLife.RocketMQ.csproj\n    - name: Publish\n      run: |\n        # dotnet nuget push ./out/*.nupkg --skip-duplicate --source https://nuget.pkg.github.com/NewLifeX/index.json --api-key ${{ github.token }}\n        dotnet nuget push ./out/*.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.nugetKey }}\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: publish\n\non:\n  push:\n    tags: [ v* ]\n  workflow_dispatch:\n\njobs:\n  build-publish:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup dotNET\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          6.x\n          7.x\n          8.x\n          9.x\n          10.x\n    - name: Build\n      run: |\n        dotnet pack -c Release -o out NewLife.RocketMQ/NewLife.RocketMQ.csproj\n    - name: Publish\n      run: |\n        # dotnet nuget push ./out/*.nupkg --skip-duplicate --source https://nuget.pkg.github.com/NewLifeX/index.json --api-key ${{ github.token }}\n        dotnet nuget push ./out/*.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.nugetKey }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches: [ '*' ]\n  pull_request:\n    branches: [ '*' ]\n\njobs:\n  build-test:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup dotNET\n      uses: actions/setup-dotnet@v4\n      with:\n        dotnet-version: |\n          6.x\n          7.x\n          8.x\n          9.x\n          10.x\n    - name: Build\n      run: |\n        dotnet build -c Release\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User-specific files\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n\n# Visual Studio 2015 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUNIT\n*.VisualState.xml\nTestResult.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# DNX\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n*_i.c\n*_p.c\n*_i.h\n*.ilk\n*.meta\n*.obj\n*.pch\n*.pdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# JustCode is a .NET coding add-in\n.JustCode\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# TODO: Comment the next line if you want to checkin your web deploy settings\n# but database connection strings (with potential passwords) will be unencrypted\n#*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# The packages folder can be ignored because of Package Restore\n**/packages/*\n# except build/, which is used as an MSBuild target.\n!**/packages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/packages/repositories.config\n# NuGet v3's project.json files produces more ignoreable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\nnode_modules/\norleans.codegen.cs\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\n\n# SQL Server files\n*.mdf\n*.ldf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CodeRush\n.cr/\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n/BinTest\n/BinTest - 副本/Config\n/生产\n/BinUnitTest\n"
  },
  {
    "path": "ChangeLog.md",
    "content": "# 更新日志\n\n## v3.0.2026.0501 (2026-05-01)\n\n### 问题修复\n- **[fix]** 修复 Pop/Ack/ChangeInvisibleTime 操作缺少 `queueId` 参数导致服务端处理异常的问题\n- **便利方法**：`MessageExt` 新增多个便利访问方法，简化消息属性读取\n\n### 依赖更新\n- 升级 NewLife.Core 依赖包到最新版本（2026-04-xx）\n\n---\n\n## v3.0.2026.0305 (2026-03-05)\n\n### 云适配重构（重大版本）\n- **架构重构**：全面升级为 v3.0 云适配架构，新增 `ICloudProvider` 接口统一阿里云、华为云、腾讯云适配\n- **事务消息**：新增 RocketMQ 事务消息发布与回查接口，支持分布式事务场景\n- **请求-应答模式**：新增 Request-Reply 同步调用模式，支持消息级 RPC\n\n### gRPC 协议支持\n- **gRPC 5.x Proxy**：新增 gRPC 协议支持，零依赖不引入第三方 Protobuf/gRPC 库\n- **SpanReader/SpanWriter 重构**：将 gRPC 协议编解码器重构为基于 `SpanReader`/`SpanWriter` 的零分配实现，提升性能\n- **gRPC Telemetry**：新增 gRPC Telemetry 链路追踪支持\n\n### 新增功能\n- **VIP 通道**：支持 VIP Channel 高优先级消息通道\n- **批量确认**：支持批量 Ack 操作，减少网络往返\n- **5.x MsgId**：支持 RocketMQ 5.x 消息 ID 格式生成与解析\n- **客户端拉取超时**：新增 `Consumer.PullTimeout` 客户端侧应用层超时保护，防止 4.9.8 无响应导致消费线程永久阻塞\n\n### 测试覆盖\n- 新增 152 个单元测试，覆盖协议层、模型层、工具类等核心组件\n- 完善架构文档与需求文档\n\n---\n\n## v2.7.2026.0301 (2026-03-01)\n\n### 问题修复\n- 新增`Consumer.PullTimeout`属性，默认值0表示自动取`SuspendTimeout+10_000ms`，作为客户端拉取消息的应用层超时保护，防止RocketMQ 4.9.8在SuspendTimeout后无响应导致消费线程永久阻塞\n\n## v2.7.2026.0201 (2026-02-01)\n\n### 依赖更新\n- 升级 NewLife.Core 依赖包到最新版本（2026-01-24）\n- 升级 NewLife.Core 依赖包（2026-01-14）\n- 升级 NewLife.Core 依赖包（2026-01-12）\n\n## v2.7.2026.0102 (2026-01-03)\n\n初始发布版本\n"
  },
  {
    "path": "DLL/NewLife.Core.xml",
    "content": "<?xml version=\"1.0\"?>\n<doc>\n    <assembly>\n        <name>NewLife.Core</name>\n    </assembly>\n    <members>\n        <member name=\"T:NewLife.Agent.AgentServiceBase`1\">\n            <typeparam name=\"TService\">服务类型</typeparam>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase`1.ServiceMain\">\n            <summary>服务主函数</summary>\n        </member>\n        <member name=\"T:NewLife.Agent.AgentServiceBase\">\n            <summary>服务程序基类</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.AgentServiceBase.DisplayName\">\n            <summary>显示名</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.AgentServiceBase.Description\">\n            <summary>描述</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.Main\">\n            <summary>服务主函数</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.ShowStatus\">\n            <summary>显示状态</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.ShowMenu\">\n            <summary>显示菜单</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.AddMenu(System.Char,System.String,System.Action)\">\n            <summary>添加菜单</summary>\n            <param name=\"key\"></param>\n            <param name=\"name\"></param>\n            <param name=\"callbak\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.StartWork(System.String)\">\n            <summary>开始工作</summary>\n            <param name=\"reason\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.StopWork(System.String)\">\n            <summary>停止服务</summary>\n            <param name=\"reason\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.DoCheck(System.Object)\">\n            <summary>服务管理线程封装</summary>\n            <param name=\"data\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.CheckMemory\">\n            <summary>检查内存是否超标</summary>\n            <returns>是否超标重启</returns>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.CheckThread\">\n            <summary>检查服务进程的总线程数是否超标</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.CheckHandle\">\n            <summary>检查服务进程的句柄数是否超标</summary>\n            <returns></returns>\n        </member>\n        <member name=\"F:NewLife.Agent.AgentServiceBase.Start\">\n            <summary>服务开始时间</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.CheckAutoRestart\">\n            <summary>检查自动重启</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.Restart(System.String)\">\n            <summary>重启服务</summary>\n            <param name=\"reason\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.OnStart(System.String[])\">\n            <summary>服务启动事件</summary>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.OnStop\">\n            <summary>服务停止事件</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.OnShutdown\">\n            <summary>在系统关闭时执行。 指定在系统关闭之前应该发生什么。</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.OnPowerEvent(System.ServiceProcess.PowerBroadcastStatus)\">\n            <summary>在计算机的电源状态已发生更改时执行。 这适用于便携式计算机，当他们进入挂起模式，这不是系统关闭相同。</summary>\n            <param name=\"powerStatus\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.OnSessionChange(System.ServiceProcess.SessionChangeDescription)\">\n            <summary>在终端服务器会话中接收的更改事件时执行</summary>\n            <param name=\"changeDescription\"></param>\n        </member>\n        <member name=\"P:NewLife.Agent.AgentServiceBase.WatchDogs\">\n            <summary>看门狗要保护的服务</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.CheckWatchDog\">\n            <summary>检查看门狗。</summary>\n            <remarks>\n            XAgent看门狗功能由管理线程完成，每分钟一次。\n            检查指定的任务是否已经停止，如果已经停止，则启动它。\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.Install(System.Boolean)\">\n            <summary>安装、卸载 服务</summary>\n            <param name=\"isinstall\">是否安装</param>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.GetBinPath(System.String)\">\n            <summary>获取安装服务的命令参数</summary>\n            <param name=\"exe\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Agent.AgentServiceBase.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.AgentServiceBase.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Agent.ServiceHelper\">\n            <summary>服务助手</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.ServiceHelper.ExeName\">\n            <summary>Exe程序名</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.ControlService(NewLife.Agent.AgentServiceBase,System.Boolean)\">\n            <summary>启动、停止 服务</summary>\n            <param name=\"service\">服务对象</param>\n            <param name=\"isstart\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.RunCmd(System.String,System.Boolean,System.Boolean)\">\n            <summary>执行一个命令</summary>\n            <param name=\"cmd\"></param>\n            <param name=\"showWindow\"></param>\n            <param name=\"waitForExit\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.RunSC(System.String)\">\n            <summary>执行SC命令</summary>\n            <param name=\"cmd\"></param>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.IsInstalled(System.String)\">\n            <summary>是否已安装</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.IsRunning(System.String)\">\n            <summary>是否已启动</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.IsServiceInstalled(System.String)\">\n            <summary>是否已安装</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.IsServiceRunning(System.String)\">\n            <summary>是否已启动</summary>\n        </member>\n        <member name=\"M:NewLife.Agent.ServiceHelper.WriteLine(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Agent.Setting\">\n            <summary>服务设置</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.ServiceName\">\n            <summary>服务名</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.DisplayName\">\n            <summary>显示名</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.Description\">\n            <summary>服务描述</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.MaxMemory\">\n            <summary>最大占用内存。超过最大占用时，整个服务进程将会重启，以释放资源。默认8096M</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.MaxThread\">\n            <summary>最大线程数。超过最大占用时，整个服务进程将会重启，以释放资源。默认1000个</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.MaxHandle\">\n            <summary>最大句柄数。超过最大占用时，整个服务进程将会重启，以释放资源。默认10000</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.AutoRestart\">\n            <summary>自动重启时间。到达自动重启时间时，整个服务进程将会重启，以释放资源。默认0分，表示无限</summary>\n        </member>\n        <member name=\"P:NewLife.Agent.Setting.WatchDog\">\n            <summary>看门狗，保护其它服务，每分钟检查一次。多个服务名逗号分隔</summary>\n        </member>\n        <member name=\"T:NewLife.IP.Ip\">\n            <summary>IP搜索</summary>\n        </member>\n        <member name=\"P:NewLife.IP.Ip.DbFile\">\n            <summary>数据文件</summary>\n        </member>\n        <member name=\"M:NewLife.IP.Ip.GetAddress(System.String)\">\n            <summary>获取IP地址</summary>\n            <param name=\"ip\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IP.Ip.GetAddress(System.Net.IPAddress)\">\n            <summary>获取IP地址</summary>\n            <param name=\"addr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.IP.Zip.Stream\">\n            <summary>数据流</summary>\n        </member>\n        <member name=\"M:NewLife.IP.Zip.Finalize\">\n            <summary>析构</summary>\n        </member>\n        <member name=\"M:NewLife.IP.Zip.Dispose\">\n            <summary>销毁</summary>\n        </member>\n        <member name=\"T:NewLife.IP.Zip.IndexInfo\">\n            <summary>索引结构</summary>\n        </member>\n        <member name=\"T:NewLife.Caching.Cache\">\n            <summary>缓存</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Default\">\n            <summary>默认缓存</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Expire\">\n            <summary>默认缓存时间。默认0秒表示不过期</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Item(System.String)\">\n            <summary>获取和设置缓存，永不过期</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Count\">\n            <summary>缓存个数</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Cache.Keys\">\n            <summary>所有键</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.#ctor\">\n            <summary>构造函数</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Init(System.String)\">\n            <summary>初始化配置</summary>\n            <param name=\"config\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.ContainsKey(System.String)\">\n            <summary>是否包含缓存项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Set``1(System.String,``0,System.Int32)\">\n            <summary>设置缓存项</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Set``1(System.String,``0,System.TimeSpan)\">\n            <summary>设置缓存项</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Get``1(System.String)\">\n            <summary>获取缓存项</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Remove(System.String[])\">\n            <summary>批量移除缓存项</summary>\n            <param name=\"keys\">键集合</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Clear\">\n            <summary>清空所有缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.SetExpire(System.String,System.TimeSpan)\">\n            <summary>设置缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <param name=\"expire\">过期时间，秒</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetExpire(System.String)\">\n            <summary>获取缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetAll``1(System.Collections.Generic.IEnumerable{System.String})\">\n            <summary>批量获取缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"keys\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.SetAll``1(System.Collections.Generic.IDictionary{System.String,``0},System.Int32)\">\n            <summary>批量设置缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"values\"></param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetList``1(System.String)\">\n            <summary>获取列表</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetDictionary``1(System.String)\">\n            <summary>获取哈希</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetQueue``1(System.String)\">\n            <summary>获取队列</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.GetSet``1(System.String)\">\n            <summary>获取Set</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Add``1(System.String,``0,System.Int32)\">\n            <summary>添加，已存在时不更新</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Replace``1(System.String,``0)\">\n            <summary>设置新值并获取旧值，原子操作</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Increment(System.String,System.Int64)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Increment(System.String,System.Double)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Decrement(System.String,System.Int64)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Decrement(System.String,System.Double)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Commit\">\n            <summary>提交变更。部分提供者需要刷盘</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.AcquireLock(System.String,System.Int32)\">\n            <summary>申请分布式锁</summary>\n            <param name=\"key\"></param>\n            <param name=\"msTimeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.Bench(System.Boolean,System.Int32)\">\n            <summary>多线程性能测试</summary>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作。默认0不分批</param>\n            <remarks>\n            Memory性能测试[顺序]，逻辑处理器 32 个 2,000MHz Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz\n            \n            测试 10,000,000 项，  1 线程\n            赋值 10,000,000 项，  1 线程，耗时   3,764ms 速度 2,656,748 ops\n            读取 10,000,000 项，  1 线程，耗时   1,296ms 速度 7,716,049 ops\n            删除 10,000,000 项，  1 线程，耗时   1,230ms 速度 8,130,081 ops\n            \n            测试 20,000,000 项，  2 线程\n            赋值 20,000,000 项，  2 线程，耗时   3,088ms 速度 6,476,683 ops\n            读取 20,000,000 项，  2 线程，耗时   1,051ms 速度 19,029,495 ops\n            删除 20,000,000 项，  2 线程，耗时   1,011ms 速度 19,782,393 ops\n            \n            测试 40,000,000 项，  4 线程\n            赋值 40,000,000 项，  4 线程，耗时   3,060ms 速度 13,071,895 ops\n            读取 40,000,000 项，  4 线程，耗时   1,023ms 速度 39,100,684 ops\n            删除 40,000,000 项，  4 线程，耗时     994ms 速度 40,241,448 ops\n            \n            测试 80,000,000 项，  8 线程\n            赋值 80,000,000 项，  8 线程，耗时   3,124ms 速度 25,608,194 ops\n            读取 80,000,000 项，  8 线程，耗时   1,171ms 速度 68,317,677 ops\n            删除 80,000,000 项，  8 线程，耗时   1,199ms 速度 66,722,268 ops\n            \n            测试 320,000,000 项， 32 线程\n            赋值 320,000,000 项， 32 线程，耗时  13,857ms 速度 23,093,021 ops\n            读取 320,000,000 项， 32 线程，耗时   1,950ms 速度 164,102,564 ops\n            删除 320,000,000 项， 32 线程，耗时   3,359ms 速度 95,266,448 ops\n            \n            测试 320,000,000 项， 64 线程\n            赋值 320,000,000 项， 64 线程，耗时   9,648ms 速度 33,167,495 ops\n            读取 320,000,000 项， 64 线程，耗时   1,974ms 速度 162,107,396 ops\n            删除 320,000,000 项， 64 线程，耗时   1,907ms 速度 167,802,831 ops\n            \n            测试 320,000,000 项，256 线程\n            赋值 320,000,000 项，256 线程，耗时  12,429ms 速度 25,746,238 ops\n            读取 320,000,000 项，256 线程，耗时   1,907ms 速度 167,802,831 ops\n            删除 320,000,000 项，256 线程，耗时   2,350ms 速度 136,170,212 ops\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.BenchOne(System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>使用指定线程测试指定次数</summary>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.BenchGet(System.String,System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>读取测试</summary>\n            <param name=\"key\">键</param>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.BenchSet(System.String,System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>赋值测试</summary>\n            <param name=\"key\">键</param>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.BenchInc(System.String,System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>累加测试</summary>\n            <param name=\"key\">键</param>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.BenchRemove(System.String,System.Int64,System.Int32,System.Boolean)\">\n            <summary>删除测试</summary>\n            <param name=\"key\">键</param>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Cache.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Caching.CacheLock\">\n            <summary>分布式锁</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.CacheLock.Key\">\n            <summary>键</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.CacheLock.#ctor(NewLife.Caching.ICache,System.String)\">\n            <summary>实例化</summary>\n            <param name=\"client\"></param>\n            <param name=\"key\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.CacheLock.Acquire(System.Int32)\">\n            <summary>申请锁</summary>\n            <param name=\"msTimeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.CacheLock.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"T:NewLife.Caching.ICache\">\n            <summary>缓存接口</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.ICache.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.ICache.Expire\">\n            <summary>默认缓存时间。默认0秒表示不过期</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.ICache.Item(System.String)\">\n            <summary>获取和设置缓存，永不过期</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.ICache.Count\">\n            <summary>缓存个数</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.ICache.Keys\">\n            <summary>所有键</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.ContainsKey(System.String)\">\n            <summary>是否包含缓存项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Set``1(System.String,``0,System.Int32)\">\n            <summary>设置缓存项</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.ICache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Set``1(System.String,``0,System.TimeSpan)\">\n            <summary>设置缓存项</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Get``1(System.String)\">\n            <summary>获取缓存项</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Remove(System.String[])\">\n            <summary>批量移除缓存项</summary>\n            <param name=\"keys\">键集合</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Clear\">\n            <summary>清空所有缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.SetExpire(System.String,System.TimeSpan)\">\n            <summary>设置缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <param name=\"expire\">过期时间</param>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetExpire(System.String)\">\n            <summary>获取缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetAll``1(System.Collections.Generic.IEnumerable{System.String})\">\n            <summary>批量获取缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"keys\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.SetAll``1(System.Collections.Generic.IDictionary{System.String,``0},System.Int32)\">\n            <summary>批量设置缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"values\"></param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.ICache.Expire\"/></param>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetList``1(System.String)\">\n            <summary>获取列表</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetDictionary``1(System.String)\">\n            <summary>获取哈希</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetQueue``1(System.String)\">\n            <summary>获取队列</summary>\n            <typeparam name=\"T\">元素类型</typeparam>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.GetSet``1(System.String)\">\n            <summary>获取Set</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Add``1(System.String,``0,System.Int32)\">\n            <summary>添加，已存在时不更新</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Replace``1(System.String,``0)\">\n            <summary>设置新值并获取旧值，原子操作</summary>\n            <remarks>\n            常常配合Increment使用，用于累加到一定数后重置归零，又避免多线程冲突。\n            </remarks>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Increment(System.String,System.Int64)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Increment(System.String,System.Double)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Decrement(System.String,System.Int64)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Decrement(System.String,System.Double)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Commit\">\n            <summary>提交变更。部分提供者需要刷盘</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.AcquireLock(System.String,System.Int32)\">\n            <summary>申请分布式锁</summary>\n            <param name=\"key\">要锁定的key</param>\n            <param name=\"msTimeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.ICache.Bench(System.Boolean,System.Int32)\">\n            <summary>多线程性能测试</summary>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作。默认0不分批</param>\n        </member>\n        <member name=\"T:NewLife.Caching.IProducerConsumer`1\">\n            <summary>生产者消费者接口</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Caching.IProducerConsumer`1.Add(System.Collections.Generic.IEnumerable{`0})\">\n            <summary>生产添加</summary>\n            <param name=\"values\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.IProducerConsumer`1.Take(System.Int32)\">\n            <summary>消费获取</summary>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Caching.MemoryCache\">\n            <summary>默认字典缓存</summary>\n        </member>\n        <member name=\"F:NewLife.Caching.MemoryCache._cache\">\n            <summary>缓存核心</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.Capacity\">\n            <summary>容量。容量超标时，采用LRU机制删除，默认100_000</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.Period\">\n            <summary>定时清理时间，默认60秒</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.#ctor\">\n            <summary>实例化一个内存字典缓存</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.Count\">\n            <summary>缓存项。原子计数</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.Keys\">\n            <summary>所有键。实际返回只读列表新实例，数据量较大时注意性能</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Init(System.String)\">\n            <summary>初始化配置</summary>\n            <param name=\"config\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetOrAdd``1(System.String,``0,System.Int32)\">\n            <summary>获取或添加缓存项</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.ContainsKey(System.String)\">\n            <summary>是否包含缓存项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Set``1(System.String,``0,System.Int32)\">\n            <summary>添加缓存项，已存在时更新</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Get``1(System.String)\">\n            <summary>获取缓存项，不存在时返回默认值</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Remove(System.String[])\">\n            <summary>批量移除缓存项</summary>\n            <param name=\"keys\">键集合</param>\n            <returns>实际移除个数</returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Clear\">\n            <summary>清空所有缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.SetExpire(System.String,System.TimeSpan)\">\n            <summary>设置缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <param name=\"expire\">过期时间</param>\n            <returns>设置是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetExpire(System.String)\">\n            <summary>获取缓存项有效期，不存在时返回Zero</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Add``1(System.String,``0,System.Int32)\">\n            <summary>添加，已存在时不更新，常用于锁争夺</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Replace``1(System.String,``0)\">\n            <summary>设置新值并获取旧值，原子操作</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Increment(System.String,System.Int64)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Increment(System.String,System.Double)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Decrement(System.String,System.Int64)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.Decrement(System.String,System.Double)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetList``1(System.String)\">\n            <summary>获取列表</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetDictionary``1(System.String)\">\n            <summary>获取哈希</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetQueue``1(System.String)\">\n            <summary>获取队列</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetSet``1(System.String)\">\n            <summary>获取Set</summary>\n            <remarks>基于HashSet，非线程安全</remarks>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.GetOrAddItem(System.String,System.Func{System.String,System.Object})\">\n            <summary>获取 或 添加 缓存项</summary>\n            <param name=\"key\"></param>\n            <param name=\"valueFactory\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Caching.MemoryCache.CacheItem\">\n            <summary>缓存项</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.CacheItem.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.CacheItem.ExpiredTime\">\n            <summary>过期时间</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.CacheItem.Expired\">\n            <summary>是否过期</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.MemoryCache.CacheItem.VisitTime\">\n            <summary>访问时间</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.#ctor(System.Object,System.Int32)\">\n            <summary>构造缓存项</summary>\n            <param name=\"value\"></param>\n            <param name=\"expire\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.Set(System.Object,System.Int32)\">\n            <summary>设置数值和过期时间</summary>\n            <param name=\"value\"></param>\n            <param name=\"expire\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.Visit\">\n            <summary>更新访问时间并返回数值</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.SetExpire(System.Int32)\">\n            <summary>设置过期时间</summary>\n            <param name=\"expire\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.Inc(System.Object)\">\n            <summary>递增</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.CacheItem.Dec(System.Object)\">\n            <summary>递减</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"F:NewLife.Caching.MemoryCache.clearTimer\">\n            <summary>清理会话计时器</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.RemoveNotAlive(System.Object)\">\n            <summary>移除过期的缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryCache.BenchOne(System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>使用指定线程测试指定次数</summary>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"T:NewLife.Caching.MemoryQueue`1\">\n            <summary>生产者消费者</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryQueue`1.#ctor\">\n            <summary>实例化内存队列</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryQueue`1.#ctor(System.Collections.Concurrent.IProducerConsumerCollection{`0})\">\n            <summary>实例化内存队列</summary>\n            <param name=\"collection\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryQueue`1.Add(System.Collections.Generic.IEnumerable{`0})\">\n            <summary>生产添加</summary>\n            <param name=\"values\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.MemoryQueue`1.Take(System.Int32)\">\n            <summary>消费获取</summary>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Caching.Redis\">\n            <summary>Redis缓存</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Create(System.String,System.Int32)\">\n            <summary>创建指定服务器的实例</summary>\n            <param name=\"server\">服务器地址。支持前面加上密码，@分隔</param>\n            <param name=\"db\">使用的数据库</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Create(System.String,System.String,System.Int32)\">\n            <summary>创建指定服务器的实例，支持密码</summary>\n            <param name=\"server\">服务器地址。支持前面加上密码，@分隔</param>\n            <param name=\"password\">密码</param>\n            <param name=\"db\">使用的数据库</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Server\">\n            <summary>服务器</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Password\">\n            <summary>密码</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Db\">\n            <summary>目标数据库。默认0</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Retry\">\n            <summary>出错重试次数。如果出现协议解析错误，可以重试的次数，默认3</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.FullPipeline\">\n            <summary>完全管道。读取操作是否合并进入管道，默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.AutoPipeline\">\n            <summary>自动管道。管道操作达到一定数量时，自动提交，默认0</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Counter\">\n            <summary>性能计数器</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Init(System.String)\">\n            <summary>初始化</summary>\n            <param name=\"config\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.CreateSub(System.Int32)\">\n            <summary>为同一服务器创建不同Db的子级库</summary>\n            <param name=\"db\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Pool\">\n            <summary>连接池</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Execute``1(System.String,System.Func{NewLife.Caching.RedisClient,``0},System.Boolean)\">\n            <summary>执行命令</summary>\n            <typeparam name=\"TResult\">返回类型</typeparam>\n            <param name=\"key\">命令key，用于选择集群节点</param>\n            <param name=\"func\">回调函数</param>\n            <param name=\"write\">是否写入操作</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.StartPipeline\">\n            <summary>开始管道模式</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.StopPipeline(System.Boolean)\">\n            <summary>结束管道模式</summary>\n            <param name=\"requireResult\">要求结果。默认false</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Commit\">\n            <summary>提交变更。处理某些残留在管道里的命令</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Count\">\n            <summary>缓存个数</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Keys\">\n            <summary>所有键</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Set``1(System.String,``0,System.Int32)\">\n            <summary>单个实体项</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Get``1(System.String)\">\n            <summary>获取单体</summary>\n            <param name=\"key\">键</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Remove(System.String[])\">\n            <summary>批量移除缓存项</summary>\n            <param name=\"keys\">键集合</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Clear\">\n            <summary>清空所有缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.ContainsKey(System.String)\">\n            <summary>是否存在</summary>\n            <param name=\"key\">键</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.SetExpire(System.String,System.TimeSpan)\">\n            <summary>设置缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <param name=\"expire\">过期时间</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.GetExpire(System.String)\">\n            <summary>获取缓存项有效期</summary>\n            <param name=\"key\">键</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.GetAll``1(System.Collections.Generic.IEnumerable{System.String})\">\n            <summary>批量获取缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"keys\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.SetAll``1(System.Collections.Generic.IDictionary{System.String,``0},System.Int32)\">\n            <summary>批量设置缓存项</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"values\"></param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Add``1(System.String,``0,System.Int32)\">\n            <summary>添加，已存在时不更新</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <param name=\"expire\">过期时间，秒。小于0时采用默认缓存时间<seealso cref=\"P:NewLife.Caching.Cache.Expire\"/></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Replace``1(System.String,``0)\">\n            <summary>设置新值并获取旧值，原子操作</summary>\n            <typeparam name=\"T\">值类型</typeparam>\n            <param name=\"key\">键</param>\n            <param name=\"value\">值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Increment(System.String,System.Int64)\">\n            <summary>累加，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Increment(System.String,System.Double)\">\n            <summary>累加，原子操作，乘以100后按整数操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Decrement(System.String,System.Int64)\">\n            <summary>递减，原子操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Decrement(System.String,System.Double)\">\n            <summary>递减，原子操作，乘以100后按整数操作</summary>\n            <param name=\"key\">键</param>\n            <param name=\"value\">变化量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.Bench(System.Boolean,System.Int32)\">\n            <summary>性能测试</summary>\n            <remarks>\n            Redis性能测试[随机]，批大小[100]，逻辑处理器 40 个 2,400MHz Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz\n            测试 100,000 项，  1 线程\n            赋值 100,000 项，  1 线程，耗时     418ms 速度   239,234 ops\n            读取 100,000 项，  1 线程，耗时     520ms 速度   192,307 ops\n            删除 100,000 项，  1 线程，耗时     125ms 速度   800,000 ops\n            测试 200,000 项，  2 线程\n            赋值 200,000 项，  2 线程，耗时     548ms 速度   364,963 ops\n            读取 200,000 项，  2 线程，耗时     549ms 速度   364,298 ops\n            删除 200,000 项，  2 线程，耗时     315ms 速度   634,920 ops\n            测试 400,000 项，  4 线程\n            赋值 400,000 项，  4 线程，耗时     694ms 速度   576,368 ops\n            读取 400,000 项，  4 线程，耗时     697ms 速度   573,888 ops\n            删除 400,000 项，  4 线程，耗时     438ms 速度   913,242 ops\n            测试 800,000 项，  8 线程\n            赋值 800,000 项，  8 线程，耗时   1,206ms 速度   663,349 ops\n            读取 800,000 项，  8 线程，耗时   1,236ms 速度   647,249 ops\n            删除 800,000 项，  8 线程，耗时     791ms 速度 1,011,378 ops\n            测试 4,000,000 项， 40 线程\n            赋值 4,000,000 项， 40 线程，耗时   4,848ms 速度   825,082 ops\n            读取 4,000,000 项， 40 线程，耗时   5,399ms 速度   740,877 ops\n            删除 4,000,000 项， 40 线程，耗时   6,281ms 速度   636,841 ops\n            测试 4,000,000 项， 64 线程\n            赋值 4,000,000 项， 64 线程，耗时   6,806ms 速度   587,716 ops\n            读取 4,000,000 项， 64 线程，耗时   5,365ms 速度   745,573 ops\n            删除 4,000,000 项， 64 线程，耗时   6,716ms 速度   595,592 ops\n            </remarks>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.BenchOne(System.Int64,System.Int32,System.Boolean,System.Int32)\">\n            <summary>使用指定线程测试指定次数</summary>\n            <param name=\"times\">次数</param>\n            <param name=\"threads\">线程</param>\n            <param name=\"rand\">随机读写</param>\n            <param name=\"batch\">批量操作</param>\n        </member>\n        <member name=\"P:NewLife.Caching.Redis.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.Redis.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Caching.RedisClient\">\n            <summary>Redis客户端</summary>\n            <remarks>\n            以极简原则进行设计，每个客户端不支持并行命令处理，可通过多客户端多线程解决。\n            收发共用64k缓冲区，所以命令请求和响应不能超过64k。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Client\">\n            <summary>客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Server\">\n            <summary>内容类型</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Password\">\n            <summary>密码</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Logined\">\n            <summary>是否已登录</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.LoginTime\">\n            <summary>登录时间</summary>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Busy\">\n            <summary>是否正在处理命令</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetStream(System.Boolean)\">\n            <summary>异步请求</summary>\n            <param name=\"create\">新建连接</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetRequest(System.IO.Stream,System.String,NewLife.Data.Packet[])\">\n            <summary>发出请求</summary>\n            <param name=\"ms\"></param>\n            <param name=\"cmd\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetResponse(System.IO.Stream,System.Int32)\">\n            <summary>接收响应</summary>\n            <param name=\"ns\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.ExecuteCommand(System.String,NewLife.Data.Packet[])\">\n            <summary>发出请求</summary>\n            <param name=\"cmd\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Reset\">\n            <summary>重置。干掉历史残留数据</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Execute(System.String,System.Object[])\">\n            <summary>执行命令。返回字符串、Packet、Packet[]</summary>\n            <param name=\"cmd\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Execute``1(System.String,System.Object[])\">\n            <summary>执行命令。返回基本类型、对象、对象数组</summary>\n            <param name=\"cmd\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.TryChangeType(System.Object,System.Type,System.Object@)\">\n            <summary>尝试转换类型</summary>\n            <param name=\"value\"></param>\n            <param name=\"type\"></param>\n            <param name=\"target\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.PipelineCommands\">\n            <summary>管道命令个数</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.StartPipeline\">\n            <summary>开始管道模式</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.StopPipeline(System.Boolean)\">\n            <summary>结束管道模式</summary>\n            <param name=\"requireResult\">要求结果</param>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Ping\">\n            <summary>心跳</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Select(System.Int32)\">\n            <summary>选择Db</summary>\n            <param name=\"db\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Auth(System.String)\">\n            <summary>验证密码</summary>\n            <param name=\"password\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Quit\">\n            <summary>退出</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetInfo\">\n            <summary>获取信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Set``1(System.String,``0,System.Int32)\">\n            <summary>设置</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <param name=\"value\"></param>\n            <param name=\"secTimeout\">超时时间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.Get``1(System.String)\">\n            <summary>读取</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.SetAll``1(System.Collections.Generic.IDictionary{System.String,``0})\">\n            <summary>批量设置</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"values\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetAll``1(System.Collections.Generic.IEnumerable{System.String})\">\n            <summary>批量获取</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"keys\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.ToBytes(System.Object)\">\n            <summary>数值转字节数组</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.FromBytes(NewLife.Data.Packet,System.Type)\">\n            <summary>字节数组转对象</summary>\n            <param name=\"pk\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.FromBytes``1(NewLife.Data.Packet)\">\n            <summary>字节数组转对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.GetHeaderBytes(System.String,System.Int32)\">\n            <summary>获取命令对应的字节数组，全局缓存</summary>\n            <param name=\"cmd\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Caching.RedisClient.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Caching.RedisClient.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.ConcurrentHashSet`1\">\n            <summary>并行哈希集合</summary>\n            <remarks>\n            主要用于频繁添加删除而又要遍历的场合\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Collections.ConcurrentHashSet`1.IsEmpty\">\n            <summary>是否空集合</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ConcurrentHashSet`1.Count\">\n            <summary>元素个数</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ConcurrentHashSet`1.Contain(`0)\">\n            <summary>是否包含元素</summary>\n            <param name=\"item\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ConcurrentHashSet`1.TryAdd(`0)\">\n            <summary>尝试添加</summary>\n            <param name=\"item\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ConcurrentHashSet`1.TryRemove(`0)\">\n            <summary>尝试删除</summary>\n            <param name=\"item\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.DictionaryCache`2\">\n            <summary>字典缓存。当指定键的缓存项不存在时，调用委托获取值，并写入缓存。</summary>\n            <remarks>常用匿名函数或者Lambda表达式作为委托。</remarks>\n            <typeparam name=\"TKey\">键类型</typeparam>\n            <typeparam name=\"TValue\">值类型</typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.Expire\">\n            <summary>过期时间。单位是秒，默认0秒，表示永不过期</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.Period\">\n            <summary>定时清理时间，默认0秒，表示不清理过期项</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.Capacity\">\n            <summary>容量。容量超标时，采用LRU机制删除，默认10_000</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.AllowNull\">\n            <summary>是否允许缓存控制，避免缓存穿透。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.FindMethod\">\n            <summary>查找数据的方法</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.#ctor\">\n            <summary>实例化一个字典缓存</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.#ctor(System.Collections.Generic.IEqualityComparer{`0})\">\n            <summary>实例化一个字典缓存</summary>\n            <param name=\"comparer\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.#ctor(System.Func{`0,`1},System.Collections.Generic.IEqualityComparer{`0})\">\n            <summary>实例化一个字典缓存</summary>\n            <param name=\"findMethod\"></param>\n            <param name=\"comparer\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.DictionaryCache`2.CacheItem\">\n            <summary>缓存项</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.CacheItem.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.CacheItem.ExpiredTime\">\n            <summary>过期时间</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.CacheItem.Expired\">\n            <summary>是否过期</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.CacheItem.VisitTime\">\n            <summary>访问时间</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.CacheItem.Visit\">\n            <summary>更新访问时间并返回数值</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.Item(`0)\">\n            <summary>重写索引器。取值时如果没有该项则返回默认值；赋值时如果已存在该项则覆盖，否则添加。</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.GetOrAdd(`0)\">\n            <summary>获取 GetOrAdd</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.Get(`0)\">\n            <summary>获取 GetOrAdd</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.Set(`0,`1)\">\n            <summary>设置 AddOrUpdate</summary>\n            <param name=\"key\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.TryAdd(`0,`1,System.Boolean,`1@)\">\n            <summary>尝试添加，或返回旧值</summary>\n            <param name=\"key\"></param>\n            <param name=\"value\"></param>\n            <param name=\"updateIfExists\"></param>\n            <param name=\"resultingValue\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.GetItem(`0,System.Func{`0,`1})\">\n            <summary>扩展获取数据项，当数据项不存在时，通过调用委托获取数据项。线程安全。</summary>\n            <param name=\"key\">键</param>\n            <param name=\"func\">获取值的委托，该委托以键作为参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.Remove(`0)\">\n            <summary>移除指定缓存项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.Clear\">\n            <summary>清空</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.DictionaryCache`2.Count\">\n            <summary>缓存项。原子计数</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.ContainsKey(`0)\">\n            <summary>是否包含指定键</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.CopyTo(NewLife.Collections.DictionaryCache{`0,`1})\">\n            <summary>赋值到目标缓存</summary>\n            <param name=\"cache\"></param>\n        </member>\n        <member name=\"F:NewLife.Collections.DictionaryCache`2._timer\">\n            <summary>清理会话计时器</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.RemoveNotAlive(System.Object)\">\n            <summary>移除过期的缓存项</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.DictionaryCache`2.GetEnumerator\">\n            <summary>枚举</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.ICluster`2\">\n            <summary>集群管理</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.ICluster`2.GetItems\">\n            <summary>资源列表</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ICluster`2.Open\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ICluster`2.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ICluster`2.Get\">\n            <summary>从集群中获取资源</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ICluster`2.Put(`1)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.ClusterException\">\n            <summary>集群异常</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ClusterException.Resource\">\n            <summary>资源</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ClusterException.#ctor(System.String,System.Exception)\">\n            <summary>实例化</summary>\n            <param name=\"res\"></param>\n            <param name=\"inner\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.ClusterHelper\">\n            <summary>集群助手</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ClusterHelper.Invoke``3(NewLife.Collections.ICluster{``0,``1},System.Func{``1,``2})\">\n            <summary>借助集群资源处理事务</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"cluster\"></param>\n            <param name=\"func\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ClusterHelper.InvokeAll``3(NewLife.Collections.ICluster{``0,``1},System.Func{``1,``2})\">\n            <summary>对集群进行多次调用</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"cluster\"></param>\n            <param name=\"func\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.IPool`1\">\n            <summary>对象池接口</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.IPool`1.Max\">\n            <summary>对象池大小</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.IPool`1.Get\">\n            <summary>获取</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.IPool`1.Put(`0)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.IPool`1.Clear\">\n            <summary>清空</summary>\n        </member>\n        <member name=\"T:NewLife.Collections.Pool\">\n            <summary>对象池扩展</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.StringBuilder\">\n            <summary>字符串构建器池</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.Put(System.Text.StringBuilder,System.Boolean)\">\n            <summary>归还一个字符串构建器到对象池</summary>\n            <param name=\"sb\"></param>\n            <param name=\"requireResult\">是否需要返回结果</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.Pool.StringBuilderPool\">\n            <summary>字符串构建器池</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.StringBuilderPool.InitialCapacity\">\n            <summary>初始容量。默认100个</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.StringBuilderPool.MaximumCapacity\">\n            <summary>最大容量。超过该大小时不进入池内，默认4k</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.StringBuilderPool.OnCreate\">\n            <summary>创建</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.StringBuilderPool.Put(System.Text.StringBuilder)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.MemoryStream\">\n            <summary>内存流池</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.Put(System.IO.MemoryStream,System.Boolean)\">\n            <summary>归还一个内存流到对象池</summary>\n            <param name=\"ms\"></param>\n            <param name=\"requireResult\">是否需要返回结果</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.Pool.MemoryStreamPool\">\n            <summary>内存流池</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.MemoryStreamPool.InitialCapacity\">\n            <summary>初始容量。默认1024个</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool.MemoryStreamPool.MaximumCapacity\">\n            <summary>最大容量。超过该大小时不进入池内，默认64k</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.MemoryStreamPool.OnCreate\">\n            <summary>创建</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool.MemoryStreamPool.Put(System.IO.MemoryStream)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.NullableDictionary`2\">\n            <summary>可空字典。获取数据时如果指定键不存在可返回空而不是抛出异常</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Collections.NullableDictionary`2.#ctor\">\n            <summary>实例化一个可空字典</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.NullableDictionary`2.#ctor(System.Collections.Generic.IEqualityComparer{`0})\">\n            <summary>指定比较器实例化一个可空字典</summary>\n            <param name=\"comparer\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.NullableDictionary`2.#ctor(System.Collections.Generic.IDictionary{`0,`1},System.Collections.Generic.IEqualityComparer{`0})\">\n            <summary>实例化一个可空字典</summary>\n            <param name=\"dic\"></param>\n            <param name=\"comparer\"></param>\n        </member>\n        <member name=\"P:NewLife.Collections.NullableDictionary`2.Item(`0)\">\n            <summary>获取或设置与指定的属性是否有脏数据。</summary>\n            <param name=\"item\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Collections.ObjectPool`1\">\n            <summary>资源池。支持空闲释放，主要用于数据库连接池和网络连接池</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.FreeCount\">\n            <summary>空闲个数</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.BusyCount\">\n            <summary>繁忙个数</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Max\">\n            <summary>最大个数。默认100，0表示无上限</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Min\">\n            <summary>最小个数。默认1</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.IdleTime\">\n            <summary>空闲清理时间。最小个数之上的资源超过空闲时间时被清理，默认10s</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.AllIdleTime\">\n            <summary>完全空闲清理时间。最小个数之下的资源超过空闲时间时被清理，默认0s永不清理</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1._free\">\n            <summary>基础空闲集合。只保存最小个数，最热部分</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1._free2\">\n            <summary>扩展空闲集合。保存最小个数以外部分</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1._busy\">\n            <summary>借出去的放在这</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.#ctor\">\n            <summary>实例化一个资源池</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Item.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Item.LastTime\">\n            <summary>过期时间</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.Get\">\n            <summary>借出</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.OnGet(`0)\">\n            <summary>借出时是否可用</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.GetItem\">\n            <summary>申请资源包装项，Dispose时自动归还到池中</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.Put(`0)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.OnPut(`0)\">\n            <summary>归还时是否可用</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.Clear\">\n            <summary>清空已有对象</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.OnDispose(`0)\">\n            <summary>销毁</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.OnCreate\">\n            <summary>创建实例</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Total\">\n            <summary>总请求数</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Success\">\n            <summary>成功数</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1._NewCount\">\n            <summary>新创建数</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1._ReleaseCount\">\n            <summary>释放数</summary>\n        </member>\n        <member name=\"F:NewLife.Collections.ObjectPool`1.Cost\">\n            <summary>平均耗时。单位ms</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.ObjectPool`1.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.ObjectPool`1.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.PoolItem`1\">\n            <summary>资源池包装项，自动归还资源到池中</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.PoolItem`1.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Collections.PoolItem`1.Pool\">\n            <summary>池</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.PoolItem`1.#ctor(NewLife.Collections.IPool{`0},`0)\">\n            <summary>包装项</summary>\n            <param name=\"pool\"></param>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.PoolItem`1.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"T:NewLife.Collections.Pool`1\">\n            <summary>轻量级对象池。数组无锁实现，高性能</summary>\n            <remarks>\n            内部 1+N 的存储结果，保留最热的一个对象在外层，便于快速存取。\n            数组具有极快的查找速度，结构体确保没有GC操作。\n            </remarks>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Collections.Pool`1.Max\">\n            <summary>对象池大小。默认CPU*2，初始化后改变无效</summary>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool`1.#ctor(System.Int32)\">\n            <summary>实例化对象池。默认大小CPU*2</summary>\n            <param name=\"max\"></param>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool`1.Get\">\n            <summary>获取</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool`1.Put(`0)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool`1.Clear\">\n            <summary>清空</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Collections.Pool`1.OnCreate\">\n            <summary>创建实例</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.IDisposable2\">\n            <summary>具有是否已释放和释放后事件的接口</summary>\n        </member>\n        <member name=\"P:NewLife.IDisposable2.Disposed\">\n            <summary>是否已经释放</summary>\n        </member>\n        <member name=\"E:NewLife.IDisposable2.OnDisposed\">\n            <summary>被销毁时触发事件</summary>\n        </member>\n        <member name=\"T:NewLife.DisposeBase\">\n            <summary>具有销毁资源处理的抽象基类</summary>\n            <example>\n            <code>\n            /// &lt;summary&gt;子类重载实现资源释放逻辑时必须首先调用基类方法&lt;/summary&gt;\n            /// &lt;param name=\"disposing\"&gt;从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）。\n            /// 因为该方法只会被调用一次，所以该参数的意义不太大。&lt;/param&gt;\n            protected override void OnDispose(bool disposing)\n            {\n                base.OnDispose(disposing);\n            \n                if (disposing)\n                {\n                    // 如果是构造函数进来，不执行这里的代码\n                }\n            }\n            </code>\n            </example>\n        </member>\n        <member name=\"M:NewLife.DisposeBase.Dispose\">\n            <summary>释放资源</summary>\n        </member>\n        <member name=\"P:NewLife.DisposeBase.Disposed\">\n            <summary>是否已经释放</summary>\n        </member>\n        <member name=\"E:NewLife.DisposeBase.OnDisposed\">\n            <summary>被销毁时触发事件</summary>\n        </member>\n        <member name=\"M:NewLife.DisposeBase.Dispose(System.Boolean)\">\n            <summary>释放资源，参数表示是否由Dispose调用。该方法保证OnDispose只被调用一次！</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.DisposeBase.OnDispose(System.Boolean)\">\n            <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>\n            <param name=\"disposing\">从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）。\n            因为该方法只会被调用一次，所以该参数的意义不太大。</param>\n        </member>\n        <member name=\"M:NewLife.DisposeBase.Finalize\">\n            <summary>析构函数</summary>\n            <remarks>\n            如果忘记调用Dispose，这里会释放非托管资源\n            如果曾经调用过Dispose，因为GC.SuppressFinalize(this)，不会再调用该析构函数\n            </remarks>\n        </member>\n        <member name=\"T:NewLife.DisposeHelper\">\n            <summary>销毁助手。扩展方法专用</summary>\n        </member>\n        <member name=\"M:NewLife.DisposeHelper.TryDispose(System.Object)\">\n            <summary>尝试销毁对象，如果有<see cref=\"T:System.IDisposable\"/>则调用</summary>\n            <param name=\"obj\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Common.PinYin\">\n            <summary>汉字拼音转换类</summary>\n        </member>\n        <member name=\"M:NewLife.Common.PinYin.GetFirst(System.Char)\">\n            <summary>取拼音第一个字段</summary>        \n            <param name=\"ch\"></param>        \n            <returns></returns>        \n        </member>\n        <member name=\"M:NewLife.Common.PinYin.GetFirst(System.String)\">\n            <summary>取拼音第一个字段</summary>\n            <param name=\"str\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Common.PinYin.GetFirstOne(System.String)\">\n            <summary>取拼音第一个字段</summary>\n            <param name=\"str\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Common.PinYin.Get(System.Char)\">\n            <summary>获取单字拼音</summary>\n            <param name=\"ch\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Common.PinYin.Get(System.String)\">\n            <summary>把汉字转换成拼音(全拼)</summary>\n            <param name=\"str\">汉字字符串</param>\n            <returns>转换后的拼音(全拼)字符串</returns>\n        </member>\n        <member name=\"T:NewLife.Common.SysConfig\">\n            <summary>系统设置。提供系统名称、版本等基本设置。</summary>\n        </member>\n        <member name=\"T:NewLife.Common.SysConfig`1\">\n            <summary>系统设置。提供系统名称、版本等基本设置。泛型基类，可继承扩展。</summary>\n            <typeparam name=\"TSetting\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.Name\">\n            <summary>系统名称</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.Version\">\n            <summary>系统版本</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.DisplayName\">\n            <summary>显示名称</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.Company\">\n            <summary>公司</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.Develop\">\n            <summary>开发者模式</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.Enable\">\n            <summary>启用</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.InstallTime\">\n            <summary>安装时间</summary>\n        </member>\n        <member name=\"M:NewLife.Common.SysConfig`1.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Common.SysConfig`1.OnNew\">\n            <summary>新建配置</summary>\n        </member>\n        <member name=\"P:NewLife.Common.SysConfig`1.SysAssembly\">\n            <summary>系统主程序集</summary>\n        </member>\n        <member name=\"T:NewLife.Runtime\">\n            <summary>运行时</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.IsConsole\">\n            <summary>是否控制台。用于判断是否可以执行一些控制台操作。</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.Mono\">\n            <summary>是否Mono环境</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.IsWeb\">\n            <summary>是否Web环境</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.Windows\">\n            <summary>是否Windows环境</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.Linux\">\n            <summary>是否Linux环境</summary>\n        </member>\n        <member name=\"P:NewLife.Runtime.OSX\">\n            <summary>是否OSX环境</summary>\n        </member>\n        <member name=\"T:NewLife.Compression.SevenZip\">\n            <summary>7Zip</summary>\n        </member>\n        <member name=\"M:NewLife.Compression.SevenZip.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Compression.SevenZip.Compress(System.String,System.String)\">\n            <summary>压缩文件</summary>\n            <param name=\"path\"></param>\n            <param name=\"destFile\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Compression.SevenZip.Extract(System.String,System.String,System.Boolean)\">\n            <summary>解压缩文件</summary>\n            <param name=\"file\"></param>\n            <param name=\"destDir\"></param>\n            <param name=\"overwrite\">是否覆盖目标同名文件</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Compression.SevenZip.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Compression.SevenZip.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Data.BinaryTree\">\n            <summary>二叉树</summary>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.GetAll(System.Int32)\">\n            <summary>遍历所有二叉树</summary>\n            <param name=\"size\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.Build(NewLife.Data.BinaryTree.Node,System.Double[],System.Collections.Generic.List{System.Func{System.Linq.Expressions.Expression,System.Linq.Expressions.Expression,System.Linq.Expressions.Expression}},System.Collections.Generic.List{System.Func{System.Linq.Expressions.Expression,System.Linq.Expressions.Expression}})\">\n            <summary>构建表达式树</summary>\n            <param name=\"node\"></param>\n            <param name=\"numbers\"></param>\n            <param name=\"ops\"></param>\n            <param name=\"sops\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.FullPermute``1(``0[])\">\n            <summary>遍历全排列</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"arr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.OperatorPermute(System.Collections.Generic.List{System.Func{System.Linq.Expressions.Expression,System.Linq.Expressions.Expression,System.Linq.Expressions.Expression}},System.Int32)\">\n            <summary>从4种运算符中挑选3个运算符</summary>\n            <param name=\"operators\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.Cbrt(System.Double)\">\n            <summary>立方根</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.BinaryTree.Execute(System.Double[],System.Double)\">\n            <summary>数学运算</summary>\n            <param name=\"numbers\"></param>\n            <param name=\"result\"></param>\n        </member>\n        <member name=\"T:NewLife.Data.DbRow\">\n            <summary>数据行</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbRow.Table\">\n            <summary>数据表</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbRow.Index\">\n            <summary>行索引</summary>\n        </member>\n        <member name=\"M:NewLife.Data.DbRow.#ctor(NewLife.Data.DbTable,System.Int32)\">\n            <summary>构造数据行</summary>\n            <param name=\"table\"></param>\n            <param name=\"index\"></param>\n        </member>\n        <member name=\"P:NewLife.Data.DbRow.Item(System.Int32)\">\n            <summary>基于列索引访问</summary>\n            <param name=\"column\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Data.DbRow.Item(System.String)\">\n            <summary>基于列名访问</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbRow.Get``1(System.String)\">\n            <summary>读取指定行的字段值</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.DbTable\">\n            <summary>数据表</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbTable.Columns\">\n            <summary>数据列</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbTable.Types\">\n            <summary>数据列类型</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbTable.Rows\">\n            <summary>数据行</summary>\n        </member>\n        <member name=\"P:NewLife.Data.DbTable.Total\">\n            <summary>总函数</summary>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Read(System.Data.IDataReader)\">\n            <summary>读取数据</summary>\n            <param name=\"dr\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Read(System.IO.Stream)\">\n            <summary>从数据流读取</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.ReadHeader(NewLife.Serialization.Binary)\">\n            <summary>读取头部</summary>\n            <param name=\"bn\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.ReadData(NewLife.Serialization.Binary,System.Int32)\">\n            <summary>读取数据</summary>\n            <param name=\"bn\"></param>\n            <param name=\"rows\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Read(NewLife.Data.Packet)\">\n            <summary>读取</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.LoadFile(System.String)\">\n            <summary>从文件加载</summary>\n            <param name=\"file\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Write(System.IO.Stream)\">\n            <summary>写入数据流</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.WriteHeader(NewLife.Serialization.Binary)\">\n            <summary>写入头部到数据流</summary>\n            <param name=\"bn\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.WriteData(NewLife.Serialization.Binary)\">\n            <summary>写入数据部分到数据流</summary>\n            <param name=\"bn\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.ToPacket\">\n            <summary>转数据包</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.SaveFile(System.String)\">\n            <summary>保存到文件</summary>\n            <param name=\"file\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Get``1(System.Int32,System.String)\">\n            <summary>读取指定行的字段值</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"row\"></param>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.TryGet``1(System.Int32,System.String,``0@)\">\n            <summary>尝试读取指定行的字段值</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"row\"></param>\n            <param name=\"name\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.GetColumn(System.String)\">\n            <summary>根据名称找字段序号</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.ToString\">\n            <summary>数据集</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.Clone\">\n            <summary>克隆</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.DbTable.GetEnumerator\">\n            <summary>获取枚举</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.GeoAddress\">\n            <summary>地理地址</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Location\">\n            <summary>坐标</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Address\">\n            <summary>地址</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Code\">\n            <summary>行政区域编码</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Country\">\n            <summary>国家</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Province\">\n            <summary>省份</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.City\">\n            <summary>城市</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.District\">\n            <summary>区县</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Township\">\n            <summary>乡镇</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Towncode\">\n            <summary>乡镇编码</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Street\">\n            <summary>街道</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.StreetNumber\">\n            <summary></summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Level\">\n            <summary>级别</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Precise\">\n            <summary>精确打点</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoAddress.Confidence\">\n            <summary>可信度。[0-100]</summary>\n        </member>\n        <member name=\"M:NewLife.Data.GeoAddress.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.GeoArea\">\n            <summary>地理区域</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.Code\">\n            <summary>编码</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.ParentCode\">\n            <summary>父级</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.Center\">\n            <summary>中心</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.Polyline\">\n            <summary>边界</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoArea.Level\">\n            <summary>级别</summary>\n        </member>\n        <member name=\"M:NewLife.Data.GeoArea.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.GeoPoint\">\n            <summary>经纬度坐标</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoPoint.Longitude\">\n            <summary>经度</summary>\n        </member>\n        <member name=\"P:NewLife.Data.GeoPoint.Latitude\">\n            <summary>纬度</summary>\n        </member>\n        <member name=\"M:NewLife.Data.GeoPoint.#ctor\">\n            <summary>经纬度坐标</summary>\n        </member>\n        <member name=\"M:NewLife.Data.GeoPoint.#ctor(System.String)\">\n            <summary>经纬度坐标</summary>\n            <param name=\"location\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.GeoPoint.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.IData\">\n            <summary>数据帧接口</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IData.Packet\">\n            <summary>数据包</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IData.Remote\">\n            <summary>远程地址</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IData.Message\">\n            <summary>消息</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IData.UserState\">\n            <summary>用户数据</summary>\n        </member>\n        <member name=\"T:NewLife.Data.IExtend\">\n            <summary>具有扩展数据的接口</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IExtend.Items\">\n            <summary>数据项</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IExtend.Item(System.String)\">\n            <summary>设置 或 获取 数据项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.IFilter\">\n            <summary>数据过滤器</summary>\n        </member>\n        <member name=\"P:NewLife.Data.IFilter.Next\">\n            <summary>下一个过滤器</summary>\n        </member>\n        <member name=\"M:NewLife.Data.IFilter.Execute(NewLife.Data.FilterContext)\">\n            <summary>对封包执行过滤器</summary>\n            <param name=\"context\"></param>\n        </member>\n        <member name=\"T:NewLife.Data.FilterContext\">\n            <summary>过滤器上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Data.FilterContext.Packet\">\n            <summary>封包</summary>\n        </member>\n        <member name=\"T:NewLife.Data.FilterHelper\">\n            <summary>过滤器助手</summary>\n        </member>\n        <member name=\"M:NewLife.Data.FilterHelper.Find(NewLife.Data.IFilter,System.Type)\">\n            <summary>在链条里面查找指定类型的过滤器</summary>\n            <param name=\"filter\"></param>\n            <param name=\"filterType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.FilterBase\">\n            <summary>数据过滤器基类</summary>\n        </member>\n        <member name=\"P:NewLife.Data.FilterBase.Next\">\n            <summary>下一个过滤器</summary>\n        </member>\n        <member name=\"M:NewLife.Data.FilterBase.Execute(NewLife.Data.FilterContext)\">\n            <summary>对封包执行过滤器</summary>\n            <param name=\"context\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.FilterBase.OnExecute(NewLife.Data.FilterContext)\">\n            <summary>执行过滤</summary>\n            <param name=\"context\"></param>\n            <returns>返回是否执行下一个过滤器</returns>\n        </member>\n        <member name=\"T:NewLife.Data.Packet\">\n            <summary>数据包</summary>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Data\">\n            <summary>数据</summary>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Offset\">\n            <summary>偏移</summary>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Count\">\n            <summary>长度</summary>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Next\">\n            <summary>下一个链式包</summary>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Total\">\n            <summary>总长度</summary>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.#ctor(System.Byte[],System.Int32,System.Int32)\">\n            <summary>根据数据区实例化</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.#ctor(System.ArraySegment{System.Byte})\">\n            <summary>根据数组段实例化</summary>\n            <param name=\"seg\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.#ctor(System.IO.Stream)\">\n            <summary>从可扩展内存流实例化，尝试窃取内存流内部的字节数组，失败后拷贝</summary>\n            <remarks>因数据包内数组窃取自内存流，需要特别小心，避免多线程共用</remarks>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"P:NewLife.Data.Packet.Item(System.Int32)\">\n            <summary>获取/设置 指定位置的字节</summary>\n            <param name=\"index\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.Set(System.Byte[],System.Int32,System.Int32)\">\n            <summary>设置新的数据区</summary>\n            <param name=\"data\">数据区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">字节个数</param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.Slice(System.Int32,System.Int32)\">\n            <summary>截取子数据区</summary>\n            <param name=\"offset\">相对偏移</param>\n            <param name=\"count\">字节个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.IndexOf(System.Byte[],System.Int32,System.Int32)\">\n            <summary>查找目标数组</summary>\n            <param name=\"data\">目标数组</param>\n            <param name=\"offset\">本数组起始偏移</param>\n            <param name=\"count\">本数组搜索个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.Append(NewLife.Data.Packet)\">\n            <summary>附加一个包到当前包链的末尾</summary>\n            <param name=\"pk\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToArray\">\n            <summary>返回字节数组。如果是完整数组直接返回，否则截取</summary>\n            <remarks>不一定是全新数据，如果需要全新数据请克隆</remarks>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ReadBytes(System.Int32,System.Int32)\">\n            <summary>从封包中读取指定数据</summary>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToSegment\">\n            <summary>返回数据段</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToSegments\">\n            <summary>返回数据段集合</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.GetStream\">\n            <summary>获取封包的数据流形式</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.CopyTo(System.IO.Stream)\">\n            <summary>把封包写入到数据流</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.WriteTo(System.Byte[],System.Int32,System.Int32)\">\n            <summary>把封包写入到目标数组</summary>\n            <param name=\"buffer\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.CopyToAsync(System.IO.Stream)\">\n            <summary>异步复制到目标数据流</summary>\n            <param name=\"stream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.Clone\">\n            <summary>深度克隆一份数据包，拷贝数据区</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToStr(System.Text.Encoding,System.Int32,System.Int32)\">\n            <summary>以字符串表示</summary>\n            <param name=\"encoding\">字符串编码，默认URF-8</param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToHex(System.Int32,System.String,System.Int32)\">\n            <summary>以十六进制编码表示</summary>\n            <param name=\"maxLength\">最大显示多少个字节。默认-1显示全部</param>\n            <param name=\"separate\">分隔符</param>\n            <param name=\"groupSize\">分组大小，为0时对每个字节应用分隔符，否则对每个分组使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.op_Implicit(System.Byte[])~NewLife.Data.Packet\">\n            <summary>重载类型转换，字节数组直接转为Packet对象</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.op_Implicit(System.ArraySegment{System.Byte})~NewLife.Data.Packet\">\n            <summary>重载类型转换，一维数组直接转为Packet对象</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.Packet.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Data.PageParameter\">\n            <summary>分页参数信息</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.Sort\">\n            <summary>获取 或 设置 排序字段，前台接收，便于做安全性校验</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.Desc\">\n            <summary>获取 或 设置 是否降序</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.PageIndex\">\n            <summary>获取 或 设置 页面索引。从1开始，默认1</summary>\n            <remarks>如果设定了开始行，分页时将不再使用PageIndex</remarks>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.PageSize\">\n            <summary>获取 或 设置 页面大小。默认20，若为0表示不分页</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.TotalCount\">\n            <summary>获取 或 设置 总记录数</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.PageCount\">\n            <summary>获取 页数</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.OrderBy\">\n            <summary>获取 或 设置 组合起来的排序字句。如果没有设置则取Sort+Desc，后台设置，不经过安全性校验</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.StartRow\">\n            <summary>获取 或 设置 开始行</summary>\n            <remarks>如果设定了开始行，分页时将不再使用PageIndex</remarks>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.RetrieveTotalCount\">\n            <summary>获取 或 设置 是否获取总记录数，默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.State\">\n            <summary>获取 或 设置 状态。用于传递统计等数据</summary>\n        </member>\n        <member name=\"P:NewLife.Data.PageParameter.RetrieveState\">\n            <summary>获取 或 设置 是否获取统计，默认false</summary>\n        </member>\n        <member name=\"M:NewLife.Data.PageParameter.#ctor\">\n            <summary>实例化分页参数</summary>\n        </member>\n        <member name=\"M:NewLife.Data.PageParameter.#ctor(NewLife.Data.PageParameter)\">\n            <summary>通过另一个分页参数来实例化当前分页参数</summary>\n            <param name=\"pm\"></param>\n        </member>\n        <member name=\"M:NewLife.Data.PageParameter.CopyFrom(NewLife.Data.PageParameter)\">\n            <summary>从另一个分页参数拷贝到当前分页参数</summary>\n            <param name=\"pm\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Data.PageParameter.GetKey\">\n            <summary>获取表示分页参数唯一性的键值，可用作缓存键</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.EventArgs`1\">\n            <summary>泛型事件参数</summary>\n            <typeparam name=\"TArg\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.EventArgs`1.Arg\">\n            <summary>参数</summary>\n        </member>\n        <member name=\"M:NewLife.EventArgs`1.#ctor(`0)\">\n            <summary>使用参数初始化</summary>\n            <param name=\"arg\"></param>\n        </member>\n        <member name=\"M:NewLife.EventArgs`1.Pop(`0@)\">\n            <summary>弹出</summary>\n            <param name=\"arg\"></param>\n        </member>\n        <member name=\"T:NewLife.EventArgs`2\">\n            <summary>泛型事件参数</summary>\n            <typeparam name=\"TArg1\"></typeparam>\n            <typeparam name=\"TArg2\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.EventArgs`2.Arg1\">\n            <summary>参数</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`2.Arg2\">\n            <summary>参数2</summary>\n        </member>\n        <member name=\"M:NewLife.EventArgs`2.#ctor(`0,`1)\">\n            <summary>使用参数初始化</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n        </member>\n        <member name=\"M:NewLife.EventArgs`2.Pop(`0@,`1@)\">\n            <summary>弹出</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n        </member>\n        <member name=\"T:NewLife.EventArgs`3\">\n            <summary>泛型事件参数</summary>\n            <typeparam name=\"TArg1\"></typeparam>\n            <typeparam name=\"TArg2\"></typeparam>\n            <typeparam name=\"TArg3\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.EventArgs`3.Arg1\">\n            <summary>参数</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`3.Arg2\">\n            <summary>参数2</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`3.Arg3\">\n            <summary>参数3</summary>\n        </member>\n        <member name=\"M:NewLife.EventArgs`3.#ctor(`0,`1,`2)\">\n            <summary>使用参数初始化</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n            <param name=\"arg3\"></param>\n        </member>\n        <member name=\"M:NewLife.EventArgs`3.Pop(`0@,`1@,`2@)\">\n            <summary>弹出</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n            <param name=\"arg3\"></param>\n        </member>\n        <member name=\"T:NewLife.EventArgs`4\">\n            <summary>泛型事件参数</summary>\n            <typeparam name=\"TArg1\"></typeparam>\n            <typeparam name=\"TArg2\"></typeparam>\n            <typeparam name=\"TArg3\"></typeparam>\n            <typeparam name=\"TArg4\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.EventArgs`4.Arg1\">\n            <summary>参数</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`4.Arg2\">\n            <summary>参数2</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`4.Arg3\">\n            <summary>参数3</summary>\n        </member>\n        <member name=\"P:NewLife.EventArgs`4.Arg4\">\n            <summary>参数4</summary>\n        </member>\n        <member name=\"M:NewLife.EventArgs`4.#ctor(`0,`1,`2,`3)\">\n            <summary>使用参数初始化</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n            <param name=\"arg3\"></param>\n            <param name=\"arg4\"></param>\n        </member>\n        <member name=\"M:NewLife.EventArgs`4.Pop(`0@,`1@,`2@,`3@)\">\n            <summary>弹出</summary>\n            <param name=\"arg1\"></param>\n            <param name=\"arg2\"></param>\n            <param name=\"arg3\"></param>\n            <param name=\"arg4\"></param>\n        </member>\n        <member name=\"T:NewLife.WeakAction`1\">\n            <summary>弱引用Action</summary>\n            <remarks>\n            常见的事件和委托，都包括两部分：对象和方法，当然如果委托到静态方法上，对象是为空的。\n            如果把事件委托到某个对象的方法上，同时就间接的引用了这个对象，导致其一直无法被回收，从而造成内存泄漏。\n            弱引用Action，原理就是把委托拆分，然后弱引用对象部分，需要调用委托的时候，再把对象“拉”回来，如果被回收了，就没有必要再调用它的方法了。\n            </remarks>\n            <typeparam name=\"TArgs\"></typeparam>\n        </member>\n        <member name=\"F:NewLife.WeakAction`1.Target\">\n            <summary>目标对象。弱引用，使得调用方对象可以被GC回收</summary>\n        </member>\n        <member name=\"F:NewLife.WeakAction`1.Method\">\n            <summary>委托方法</summary>\n        </member>\n        <member name=\"F:NewLife.WeakAction`1.Handler\">\n            <summary>经过包装的新的委托</summary>\n        </member>\n        <member name=\"F:NewLife.WeakAction`1.UnHandler\">\n            <summary>取消注册的委托</summary>\n        </member>\n        <member name=\"F:NewLife.WeakAction`1.Once\">\n            <summary>是否只使用一次，如果只使用一次，执行委托后马上取消注册</summary>\n        </member>\n        <member name=\"P:NewLife.WeakAction`1.IsAlive\">\n            <summary>是否可用</summary>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.#ctor(System.Object,System.Reflection.MethodInfo)\">\n            <summary>实例化</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"method\">目标方法</param>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.#ctor(System.Object,System.Reflection.MethodInfo,System.Action{System.Action{`0}},System.Boolean)\">\n            <summary>实例化</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"method\">目标方法</param>\n            <param name=\"unHandler\">取消注册回调</param>\n            <param name=\"once\">是否一次性事件</param>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.#ctor(System.Delegate)\">\n            <summary>实例化</summary>\n            <param name=\"handler\">事件处理器</param>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.#ctor(System.Delegate,System.Action{System.Action{`0}},System.Boolean)\">\n            <summary>使用事件处理器、取消注册回调、是否一次性事件来初始化</summary>\n            <param name=\"handler\">事件处理器</param>\n            <param name=\"unHandler\">取消注册回调</param>\n            <param name=\"once\">是否一次性事件</param>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.Invoke(`0)\">\n            <summary>调用委托</summary>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.op_Implicit(NewLife.WeakAction{`0})~System.Action{`0}\">\n            <summary>把弱引用事件处理器转换为普通事件处理器</summary>\n            <param name=\"handler\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.WeakAction`1.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.XException\">\n            <summary>X组件异常</summary>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor(System.String)\">\n            <summary>初始化</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor(System.String,System.Object[])\">\n            <summary>初始化</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor(System.String,System.Exception)\">\n            <summary>初始化</summary>\n            <param name=\"message\"></param>\n            <param name=\"innerException\"></param>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor(System.Exception,System.String,System.Object[])\">\n            <summary>初始化</summary>\n            <param name=\"innerException\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.XException.#ctor(System.Exception)\">\n            <summary>初始化</summary>\n            <param name=\"innerException\"></param>\n        </member>\n        <member name=\"T:NewLife.ExceptionEventArgs\">\n            <summary>异常事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.ExceptionEventArgs.Action\">\n            <summary>发生异常时进行的动作</summary>\n        </member>\n        <member name=\"P:NewLife.ExceptionEventArgs.Exception\">\n            <summary>异常</summary>\n        </member>\n        <member name=\"T:NewLife.ExceptionHelper\">\n            <summary>异常助手</summary>\n        </member>\n        <member name=\"M:NewLife.ExceptionHelper.IsDisposed(System.Exception)\">\n            <summary>是否对象已被释放异常</summary>\n            <param name=\"ex\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Expressions.MathExpression\">\n            <summary>数学表达式</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.MathExpression.AddChar\">\n            <summary>加法</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.MathExpression.SubtractChar\">\n            <summary>减法</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.MathExpression.MultiplyChar\">\n            <summary>乘法</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.MathExpression.DivideChar\">\n            <summary>除法</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.GetOperationLevel(System.String)\">\n            <summary>计算运算符优先级</summary>\n            <param name=\"op\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.AdapteAndReplace(System.String)\">\n            <summary>适配和替换</summary>\n            <param name=\"expression\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.Complie(System.String,System.Object[])\">\n            <summary>解逆波兰表达式</summary>\n            <param name=\"expression\">标准逆波兰表达式</param>\n            <param name=\"args\"></param>\n            <returns>逆波兰表达式的解</returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.IsValid(System.String[])\">\n            <summary>是否有效</summary>\n            <param name=\"arr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.ToDouble(System.String)\">\n            <summary>转为浮点数</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.MathExpression.ComplieRpnExp(System.Double,System.Double,System.String)\">\n            <summary>\n            计算逆波兰表达式\n            </summary>\n            <param name=\"last\">最后压入数字堆栈的数字</param>\n            <param name=\"first\">首先压入数字堆栈的数字</param>\n            <param name=\"op\">操作运算符</param>\n            <returns>返回计算结果</returns>\n        </member>\n        <member name=\"T:NewLife.Expressions.RpnExpression\">\n            <summary>逆波兰表达式</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.RpnExpression.LeftBracket\">\n            <summary>左括号</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.RpnExpression.RightBracket\">\n            <summary>右括号</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.RpnExpression.JoinChar\">\n            <summary>连接符</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.RpnExpression.EmptyChar\">\n            <summary>空格</summary>\n        </member>\n        <member name=\"P:NewLife.Expressions.RpnExpression.OperationChars\">\n            <summary>操作符数组</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.IsBracket(System.String)\">\n            <summary>是否括号</summary>\n            <param name=\"ch\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.IsBracket(System.Char)\">\n            <summary>是否括号</summary>\n            <param name=\"ch\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.GetOperationLevel(System.String)\">\n            <summary>计算操作等级</summary>\n            <param name=\"op\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.IsBracketMatch(System.String)\">\n            <summary>是否括号匹配</summary>\n            <param name=\"expression\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.AdapteAndReplace(System.String)\">\n            <summary>适配器和替换</summary>\n            <param name=\"expression\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Expressions.RpnExpression.Value\">\n            <summary>值</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.ToExpression(System.String)\">\n            <summary>将中缀表达式转换为逆波兰表达式</summary>\n            <param name=\"expression\">标准中缀表达式</param>\n            <returns>标准逆波兰表达式</returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.IsValid(System.String[])\">\n            <summary>是否有效</summary>\n            <param name=\"splitArray\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.RpnExpression.Complie(System.String,System.Object[])\">\n            <summary>编译计算</summary>\n            <param name=\"expression\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Expressions.UnionExpresion\">\n            <summary>与或非表达式</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.UnionExpresion.AndChar\">\n            <summary>位与</summary>\n        </member>\n        <member name=\"F:NewLife.Expressions.UnionExpresion.OrChar\">\n            <summary>位或</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.GetOperationLevel(System.String)\">\n            <summary>操作符等级</summary>\n            <param name=\"op\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.AdapteAndReplace(System.String)\">\n            <summary>适配替换</summary>\n            <param name=\"expression\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Expressions.UnionExpresion.Container\">\n            <summary>容器</summary>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.Complie(System.String,System.Object[])\">\n            <summary>解逆波兰表达式</summary>\n            <param name=\"expression\">标准逆波兰表达式</param>\n            <param name=\"args\"></param>\n            <returns>逆波兰表达式的解</returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.IsValid(System.String[])\">\n            <summary>是否有效</summary>\n            <param name=\"arr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Expressions.UnionExpresion.GetAllMarks\">\n            <summary>所有掩码</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Extension.SpeakProvider.SpeakAsyncCancelAll\">\n            <summary>\n            停止话音播报\n            </summary>\n        </member>\n        <member name=\"T:NewLife.Http.HttpBase\">\n            <summary>Http请求响应基类</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.ContentLength\">\n            <summary>内容长度</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.ContentType\">\n            <summary>内容类型</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.Headers\">\n            <summary>头部集合</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.Item(System.String)\">\n            <summary>获取/设置 头部</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.Expire\">\n            <summary>过期时间</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.IsCompleted\">\n            <summary>是否已完整</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpBase.BodyLength\">\n            <summary>主体长度</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpBase.OnParse(System.String)\">\n            <summary>分析第一行</summary>\n            <param name=\"firstLine\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.HttpBase.Build(NewLife.Data.Packet)\">\n            <summary>创建请求响应包</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpBase.BuildHeader(System.Int32)\">\n            <summary>创建头部</summary>\n            <param name=\"length\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpCodec\">\n            <summary>Http编解码器</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpCodec.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpCodec.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpMessage\">\n            <summary>Http消息</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpMessage.Reply\">\n            <summary>是否响应</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpMessage.Error\">\n            <summary>是否有错</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpMessage.OneWay\">\n            <summary>单向请求</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpMessage.Header\">\n            <summary>头部数据</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpMessage.Payload\">\n            <summary>负载数据</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpMessage.CreateReply\">\n            <summary>根据请求创建配对的响应消息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpMessage.Read(NewLife.Data.Packet)\">\n            <summary>从数据包中读取消息</summary>\n            <param name=\"pk\"></param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpMessage.ToPacket\">\n            <summary>把消息转为封包</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpEncoder\">\n            <summary>Http编码器</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.Encode(System.String,System.Int32,System.Object)\">\n            <summary>编码</summary>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.DecodeParameters(System.String,NewLife.Data.Packet)\">\n            <summary>解码参数</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.DecodeResult(System.String,NewLife.Data.Packet)\">\n            <summary>解码结果</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.Convert(System.Object,System.Type)\">\n            <summary>转换为目标类型</summary>\n            <param name=\"obj\"></param>\n            <param name=\"targetType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.CreateRequest(System.String,System.Object)\">\n            <summary>创建请求</summary>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.CreateResponse(NewLife.Messaging.IMessage,System.String,System.Int32,System.Object)\">\n            <summary>创建响应</summary>\n            <param name=\"msg\"></param>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpEncoder.Decode(NewLife.Messaging.IMessage,System.String@,System.Int32@,NewLife.Data.Packet@)\">\n            <summary>解码 请求/响应</summary>\n            <param name=\"msg\">消息</param>\n            <param name=\"action\">服务动作</param>\n            <param name=\"code\">错误码</param>\n            <param name=\"value\">参数或结果</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpHelper\">\n            <summary>Http帮助类</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.MakeRequest(System.String,System.Uri,System.Collections.Generic.IDictionary{System.String,System.Object},NewLife.Data.Packet)\">\n            <summary>创建请求包</summary>\n            <param name=\"method\"></param>\n            <param name=\"uri\"></param>\n            <param name=\"headers\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.MakeResponse(System.Net.HttpStatusCode,System.Collections.Generic.IDictionary{System.String,System.Object},NewLife.Data.Packet)\">\n            <summary>创建响应包</summary>\n            <param name=\"code\"></param>\n            <param name=\"headers\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.ParseHeader(NewLife.Data.Packet)\">\n            <summary>分析头部</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.MakeHandshake(NewLife.Http.HttpRequest)\">\n            <summary>建立握手包</summary>\n            <param name=\"request\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.Handshake(System.String,NewLife.Http.HttpResponse)\">\n            <summary>握手</summary>\n            <param name=\"key\"></param>\n            <param name=\"response\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.ParseWS(NewLife.Data.Packet)\">\n            <summary>分析WS数据包</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpHelper.MakeWS(NewLife.Data.Packet)\">\n            <summary>创建WS请求包</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpRequest\">\n            <summary>Http请求</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.Method\">\n            <summary>Http方法</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.Url\">\n            <summary>资源路径</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.UserAgent\">\n            <summary>用户代理</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.Compressed\">\n            <summary>是否压缩</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.KeepAlive\">\n            <summary>保持连接</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.Accept\">\n            <summary>可接受内容</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.AcceptLanguage\">\n            <summary>接受语言</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpRequest.Referer\">\n            <summary>引用路径</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpRequest.OnParse(System.String)\">\n            <summary>分析第一行</summary>\n            <param name=\"firstLine\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.HttpRequest.BuildHeader(System.Int32)\">\n            <summary>创建头部</summary>\n            <param name=\"length\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Http.HttpResponse\">\n            <summary>Http响应</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpResponse.IsSSL\">\n            <summary>是否启用SSL</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpResponse.StatusCode\">\n            <summary>状态码</summary>\n        </member>\n        <member name=\"P:NewLife.Http.HttpResponse.StatusDescription\">\n            <summary>状态描述</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpResponse.OnParse(System.String)\">\n            <summary>分析第一行</summary>\n            <param name=\"firstLine\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.HttpResponse.BuildHeader(System.Int32)\">\n            <summary>创建头部</summary>\n            <param name=\"length\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.HttpResponse.Valid\">\n            <summary>验证，如果失败则抛出异常</summary>\n        </member>\n        <member name=\"T:NewLife.Http.HttpServer\">\n            <summary>Http服务器</summary>\n        </member>\n        <member name=\"M:NewLife.Http.HttpServer.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"T:NewLife.Http.TinyHttpClient\">\n            <summary>迷你Http客户端。不支持https和302跳转</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.Client\">\n            <summary>客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.ContentType\">\n            <summary>内容类型</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.ContentLength\">\n            <summary>内容长度</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.KeepAlive\">\n            <summary>保持连接</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.StatusCode\">\n            <summary>状态码</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.Timeout\">\n            <summary>超时时间。默认15s</summary>\n        </member>\n        <member name=\"P:NewLife.Http.TinyHttpClient.Headers\">\n            <summary>头部集合</summary>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.SendDataAsync(System.Uri,NewLife.Data.Packet)\">\n            <summary>异步请求</summary>\n            <param name=\"uri\"></param>\n            <param name=\"request\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.SendAsync(System.Uri,System.Byte[])\">\n            <summary>异步发出请求，并接收响应</summary>\n            <param name=\"uri\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.SendData(System.Uri,NewLife.Data.Packet)\">\n            <summary>同步请求</summary>\n            <param name=\"uri\"></param>\n            <param name=\"request\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.Send(System.Uri,System.Byte[])\">\n            <summary>异步发出请求，并接收响应</summary>\n            <param name=\"uri\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.BuildRequest(System.Uri,System.Byte[])\">\n            <summary>构造请求头</summary>\n            <param name=\"uri\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.ParseResponse(NewLife.Data.Packet)\">\n            <summary>解析响应</summary>\n            <param name=\"rs\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.GetPool(System.String)\">\n            <summary>根据主机获取对象池</summary>\n            <param name=\"host\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.GetStringAsync(System.String)\">\n            <summary>异步获取</summary>\n            <param name=\"url\">地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Http.TinyHttpClient.GetString(System.String)\">\n            <summary>同步获取</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.IO.CsvFile\">\n            <summary>Csv文件</summary>\n            <remarks>\n            支持整体读写以及增量式读写，目标是读写超大Csv文件\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.IO.CsvFile.Encoding\">\n            <summary>文件编码</summary>\n        </member>\n        <member name=\"P:NewLife.IO.CsvFile.Separator\">\n            <summary>分隔符。默认逗号</summary>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.#ctor(System.IO.Stream)\">\n            <summary>数据流实例化</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.#ctor(System.String,System.Boolean)\">\n            <summary>Csv文件实例化</summary>\n            <param name=\"file\"></param>\n            <param name=\"write\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.ReadLine\">\n            <summary>读取一行</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.ReadAll\">\n            <summary>读取所有行</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.WriteAll(System.Collections.Generic.IEnumerable{System.Collections.Generic.IEnumerable{System.Object}})\">\n            <summary>写入全部</summary>\n            <param name=\"data\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.CsvFile.WriteLine(System.Collections.Generic.IEnumerable{System.Object})\">\n            <summary>写入一行</summary>\n            <param name=\"line\"></param>\n        </member>\n        <member name=\"T:NewLife.IO.EncodingHelper\">\n            <summary>编码助手</summary>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.Detect(System.String)\">\n            <summary>检测文件编码</summary>\n            <param name=\"filename\">文件名</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.DetectEncoding(System.IO.FileInfo)\">\n            <summary>检测文件编码</summary>\n            <param name=\"file\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.Detect(System.IO.Stream,System.Int64)\">\n            <summary>检测数据流编码</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"sampleSize\">BOM检测失败时用于启发式探索的数据大小</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.Detect(System.Byte[])\">\n            <summary>检测字节数组编码</summary>\n            <param name=\"data\">字节数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.DetectBOM(System.Byte[])\">\n            <summary>检测BOM字节序</summary>\n            <param name=\"boms\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.DetectASCII(System.Byte[])\">\n            <summary>检测是否ASCII</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.DetectUnicode(System.Byte[])\">\n            <summary>启发式探测Unicode编码</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.IsCommonASCII(System.Byte)\">\n            <summary>是否可见ASCII</summary>\n            <param name=\"bt\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.IO.EncodingHelper.DetectSuspiciousUTF8SequenceLength(System.Byte[],System.Int64)\">\n            <summary>检测可能的UTF8序列长度</summary>\n            <param name=\"buf\"></param>\n            <param name=\"pos\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.IO.FileSource\">\n            <summary>文件资源</summary>\n        </member>\n        <member name=\"M:NewLife.IO.FileSource.ReleaseFile(System.Reflection.Assembly,System.String,System.String,System.Boolean)\">\n            <summary>释放文件</summary>\n            <param name=\"asm\"></param>\n            <param name=\"fileName\"></param>\n            <param name=\"destFile\"></param>\n            <param name=\"overWrite\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.FileSource.ReleaseFolder(System.Reflection.Assembly,System.String,System.String,System.Boolean)\">\n            <summary>释放文件夹</summary>\n            <param name=\"asm\"></param>\n            <param name=\"prefix\"></param>\n            <param name=\"dest\"></param>\n            <param name=\"overWrite\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.FileSource.ReleaseFolder(System.Reflection.Assembly,System.String,System.String,System.Boolean,System.Func{System.String,System.String})\">\n            <summary>释放文件夹</summary>\n            <param name=\"asm\"></param>\n            <param name=\"prefix\"></param>\n            <param name=\"dest\"></param>\n            <param name=\"overWrite\"></param>\n            <param name=\"filenameResolver\"></param>\n        </member>\n        <member name=\"M:NewLife.IO.FileSource.GetFileResource(System.Reflection.Assembly,System.String)\">\n            <summary>获取文件资源</summary>\n            <param name=\"asm\"></param>\n            <param name=\"filename\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Json.JsonConfig`1\">\n            <summary>Json配置文件基类</summary>\n            <remarks>\n            标准用法：TConfig.Current\n            \n            配置实体类通过<see cref=\"T:NewLife.Json.JsonConfigFileAttribute\"/>特性指定配置文件路径以及自动更新时间。\n            Current将加载配置文件，如果文件不存在或者加载失败，将实例化一个对象返回。\n            \n            考虑到自动刷新，不提供LoadFile和SaveFile等方法，可通过扩展方法ToXmlFileEntity和ToXmlFile实现。\n            \n            用户也可以通过配置实体类的静态构造函数修改基类的<see cref=\"P:NewLife.Json.JsonConfig`1._.ConfigFile\"/>和<see cref=\"P:NewLife.Json.JsonConfig`1._.ReloadTime\"/>来动态配置加载信息。\n            </remarks>\n            <typeparam name=\"TConfig\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1.Current\">\n            <summary>当前实例。通过置空可以使其重新加载。</summary>\n        </member>\n        <member name=\"T:NewLife.Json.JsonConfig`1._\">\n            <summary>一些设置。派生类可以在自己的静态构造函数中指定</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1._.Debug\">\n            <summary>是否调试</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1._.ConfigFile\">\n            <summary>配置文件路径</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1._.ReloadTime\">\n            <summary>重新加载时间。单位：毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1._.SaveNew\">\n            <summary>没有配置文件时是否保存新配置。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1.ConfigFile\">\n            <summary>配置文件</summary>\n        </member>\n        <member name=\"F:NewLife.Json.JsonConfig`1.lastWrite\">\n            <summary>最后写入时间</summary>\n        </member>\n        <member name=\"F:NewLife.Json.JsonConfig`1.expire\">\n            <summary>过期时间。如果在这个时间之后再次访问，将检查文件修改时间</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1.IsUpdated\">\n            <summary>是否已更新。通过文件写入时间判断</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.SetExpire\">\n            <summary>设置过期重新加载配置的时间</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfig`1.IsNew\">\n            <summary>是否新的配置文件</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.Load(System.String)\">\n            <summary>加载指定配置文件</summary>\n            <param name=\"filename\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.OnLoaded\">\n            <summary>从配置文件中读取完成后触发</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.Save(System.String)\">\n            <summary>保存到配置文件中去</summary>\n            <param name=\"filename\"></param>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.Save\">\n            <summary>保存到配置文件中去</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.SaveAsync\">\n            <summary>异步保存</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfig`1.OnNew\">\n            <summary>新创建配置文件时执行</summary>\n        </member>\n        <member name=\"T:NewLife.Json.JsonConfigFileAttribute\">\n            <summary>Json配置文件特性</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfigFileAttribute.FileName\">\n            <summary>配置文件名</summary>\n        </member>\n        <member name=\"P:NewLife.Json.JsonConfigFileAttribute.ReloadTime\">\n            <summary>重新加载时间。单位：毫秒</summary>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfigFileAttribute.#ctor(System.String)\">\n            <summary>指定配置文件名</summary>\n            <param name=\"fileName\"></param>\n        </member>\n        <member name=\"M:NewLife.Json.JsonConfigFileAttribute.#ctor(System.String,System.Int32)\">\n            <summary>指定配置文件名和重新加载时间（毫秒）</summary>\n            <param name=\"fileName\"></param>\n            <param name=\"reloadTime\"></param>\n        </member>\n        <member name=\"T:NewLife.Log.ActionLog\">\n            <summary>依托于动作的日志类</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ActionLog.Method\">\n            <summary>方法</summary>\n        </member>\n        <member name=\"M:NewLife.Log.ActionLog.#ctor(System.Action{System.String,System.Object[]})\">\n            <summary>使用指定方法否则动作日志</summary>\n            <param name=\"action\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.ActionLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.ActionLog.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.CodeTimer\">\n            <summary>代码性能计时器</summary>\n            <remarks>参考了老赵（http://www.cnblogs.com/jeffreyzhao/archive/2009/03/10/codetimer.html）和eaglet（http://www.cnblogs.com/eaglet/archive/2009/03/10/1407791.html）两位的作品</remarks>\n            <remarks>为了保证性能比较的公平性，采用了多种指标，并使用计时器重写等手段来避免各种不必要的损耗</remarks>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.Time(System.Int32,System.Action{System.Int32},System.Boolean)\">\n            <summary>计时</summary>\n            <param name=\"times\">次数</param>\n            <param name=\"action\">需要计时的委托</param>\n            <param name=\"needTimeOne\">是否需要预热</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.TimeLine(System.String,System.Int32,System.Action{System.Int32},System.Boolean)\">\n            <summary>计时，并用控制台输出行</summary>\n            <param name=\"title\">标题</param>\n            <param name=\"times\">次数</param>\n            <param name=\"action\">需要计时的委托</param>\n            <param name=\"needTimeOne\">是否需要预热</param>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.ShowHeader(System.String)\">\n            <summary>显示头部</summary>\n            <param name=\"title\"></param>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.Times\">\n            <summary>次数</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.Action\">\n            <summary>迭代方法，如不指定，则使用Time(int index)</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.ShowProgress\">\n            <summary>是否显示控制台进度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.Index\">\n            <summary>进度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.CpuCycles\">\n            <summary>CPU周期</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.ThreadTime\">\n            <summary>线程时间，单位是ms</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.Gen\">\n            <summary>GC代数</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CodeTimer.Elapsed\">\n            <summary>执行时间</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.#ctor\">\n            <summary>实例化一个代码计时器</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.Time\">\n            <summary>计时核心方法，处理进程和线程优先级</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.TimeTrue\">\n            <summary>真正的计时</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.TimeOne\">\n            <summary>执行一次迭代，预热所有方法</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.Init\">\n            <summary>迭代前执行，计算时间</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.Time(System.Int32)\">\n            <summary>每一次迭代，计算时间</summary>\n            <param name=\"index\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.Finish\">\n            <summary>迭代后执行，计算时间</summary>\n        </member>\n        <member name=\"F:NewLife.Log.CodeTimer.msBase\">\n            <summary>基准时间</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CodeTimer.ToString\">\n            <summary>已重载。输出依次分别是：执行时间、CPU线程时间、时钟周期、GC代数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.CompositeLog\">\n            <summary>复合日志提供者，多种方式输出</summary>\n        </member>\n        <member name=\"P:NewLife.Log.CompositeLog.Logs\">\n            <summary>日志提供者集合</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.#ctor(NewLife.Log.ILog)\">\n            <summary>实例化</summary>\n            <param name=\"log\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.#ctor(NewLife.Log.ILog,NewLife.Log.ILog)\">\n            <summary>实例化</summary>\n            <param name=\"log1\"></param>\n            <param name=\"log2\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.Add(NewLife.Log.ILog)\">\n            <summary>添加一个日志提供者</summary>\n            <param name=\"log\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.Remove(NewLife.Log.ILog)\">\n            <summary>删除日志提供者</summary>\n            <param name=\"log\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.Get``1\">\n            <summary>从复合日志提供者中提取指定类型的日志提供者</summary>\n            <typeparam name=\"TLog\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.CompositeLog.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.ConsoleLog\">\n            <summary>控制台输出日志</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ConsoleLog.UseColor\">\n            <summary>是否使用多种颜色，默认使用</summary>\n        </member>\n        <member name=\"M:NewLife.Log.ConsoleLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.ConsoleLog.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.ICounter\">\n            <summary>性能计数器接口</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ICounter.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ICounter.Times\">\n            <summary>次数</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ICounter.Speed\">\n            <summary>速度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ICounter.Cost\">\n            <summary>平均耗时，单位us</summary>\n        </member>\n        <member name=\"M:NewLife.Log.ICounter.Increment(System.Int64,System.Int64)\">\n            <summary>增加</summary>\n            <param name=\"value\">增加的数量</param>\n            <param name=\"usCost\">耗时，单位us</param>\n        </member>\n        <member name=\"T:NewLife.Log.CounterHelper\">\n            <summary>计数器助手</summary>\n        </member>\n        <member name=\"M:NewLife.Log.CounterHelper.StartCount(NewLife.Log.ICounter)\">\n            <summary>开始计时</summary>\n            <param name=\"counter\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.CounterHelper.StopCount(NewLife.Log.ICounter,System.Nullable{System.Int64})\">\n            <summary>结束计时</summary>\n            <param name=\"counter\"></param>\n            <param name=\"startTicks\"></param>\n        </member>\n        <member name=\"T:NewLife.Log.ILog\">\n            <summary>日志接口</summary>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Write(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\">日志级别</param>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Debug(System.String,System.Object[])\">\n            <summary>调试日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Info(System.String,System.Object[])\">\n            <summary>信息日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Warn(System.String,System.Object[])\">\n            <summary>警告日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Error(System.String,System.Object[])\">\n            <summary>错误日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.ILog.Fatal(System.String,System.Object[])\">\n            <summary>严重错误日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"P:NewLife.Log.ILog.Enable\">\n            <summary>是否启用日志</summary>\n        </member>\n        <member name=\"P:NewLife.Log.ILog.Level\">\n            <summary>日志等级，只输出大于等于该级别的日志，默认Info，打开NewLife.Debug时默认为最低的Debug</summary>\n        </member>\n        <member name=\"T:NewLife.Log.Logger\">\n            <summary>日志基类。提供日志的基本实现</summary>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Debug(System.String,System.Object[])\">\n            <summary>调试日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Info(System.String,System.Object[])\">\n            <summary>信息日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Warn(System.String,System.Object[])\">\n            <summary>警告日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Error(System.String,System.Object[])\">\n            <summary>错误日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Fatal(System.String,System.Object[])\">\n            <summary>严重错误日志</summary>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"args\">格式化参数</param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Write(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.Format(System.String,System.Object[])\">\n            <summary>格式化参数，特殊处理异常和时间</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Log.Logger.Enable\">\n            <summary>是否启用日志。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Log.Logger.Level\">\n            <summary>日志等级，只输出大于等于该级别的日志，默认Info，打开NewLife.Debug时默认为最低的Debug</summary>\n        </member>\n        <member name=\"P:NewLife.Log.Logger.Null\">\n            <summary>空日志实现</summary>\n        </member>\n        <member name=\"M:NewLife.Log.Logger.GetHead\">\n            <summary>输出日志头，包含所有环境信息</summary>\n        </member>\n        <member name=\"T:NewLife.Log.LogLevel\">\n            <summary>日志等级</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.All\">\n            <summary>打开所有日志记录</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Debug\">\n            <summary>最低调试。细粒度信息事件对调试应用程序非常有帮助</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Info\">\n            <summary>普通消息。在粗粒度级别上突出强调应用程序的运行过程</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Warn\">\n            <summary>警告</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Error\">\n            <summary>错误</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Fatal\">\n            <summary>严重错误</summary>\n        </member>\n        <member name=\"F:NewLife.Log.LogLevel.Off\">\n            <summary>关闭所有日志记录</summary>\n        </member>\n        <member name=\"T:NewLife.Log.NetworkLog\">\n            <summary>网络日志</summary>\n        </member>\n        <member name=\"P:NewLife.Log.NetworkLog.Client\">\n            <summary>网络套接字</summary>\n        </member>\n        <member name=\"P:NewLife.Log.NetworkLog.Remote\">\n            <summary>远程服务器地址</summary>\n        </member>\n        <member name=\"M:NewLife.Log.NetworkLog.#ctor\">\n            <summary>实例化网络日志。默认广播到514端口</summary>\n        </member>\n        <member name=\"M:NewLife.Log.NetworkLog.#ctor(System.Net.IPEndPoint)\">\n            <summary>指定日志服务器地址来实例化网络日志</summary>\n            <param name=\"server\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.NetworkLog.Dispose\">\n            <summary>销毁</summary>\n        </member>\n        <member name=\"M:NewLife.Log.NetworkLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Log.PerfCounter\">\n            <summary>性能计数器。次数、TPS、平均耗时</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Enable\">\n            <summary>是否启用。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Value\">\n            <summary>数值</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Times\">\n            <summary>次数</summary>\n        </member>\n        <member name=\"F:NewLife.Log.PerfCounter._TotalCost\">\n            <summary>耗时，单位us</summary>\n        </member>\n        <member name=\"M:NewLife.Log.PerfCounter.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.PerfCounter.Increment(System.Int64,System.Int64)\">\n            <summary>增加</summary>\n            <param name=\"value\">增加的数量</param>\n            <param name=\"usCost\">耗时，单位us</param>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Interval\">\n            <summary>采样间隔，默认1000毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Duration\">\n            <summary>持续采样时间，默认60秒</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Speed\">\n            <summary>当前速度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.MaxSpeed\">\n            <summary>最大速度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.Cost\">\n            <summary>最后一个采样周期的平均耗时，单位us</summary>\n        </member>\n        <member name=\"P:NewLife.Log.PerfCounter.MaxCost\">\n            <summary>持续采样时间内的最大平均耗时，单位us</summary>\n        </member>\n        <member name=\"M:NewLife.Log.PerfCounter.DoWork(System.Object)\">\n            <summary>定期采样，保存最近60组到数组队列里面</summary>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.PerfCounter.ToString\">\n            <summary>已重载。输出统计信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.TextControlLog\">\n            <summary>文本控件输出日志</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TextControlLog.Control\">\n            <summary>文本控件</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TextControlLog.MaxLines\">\n            <summary>最大行数，超过该行数讲清空文本控件。默认1000行</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextControlLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.TextControlLog.WriteLog(System.Windows.Forms.Control,System.String,System.Int32)\">\n            <summary>在WinForm控件上输出日志，主要考虑非UI线程操作</summary>\n            <remarks>不是常用功能，为了避免干扰常用功能，保持UseWinForm开头</remarks>\n            <param name=\"control\">要绑定日志输出的WinForm控件</param>\n            <param name=\"msg\">日志</param>\n            <param name=\"maxLines\">最大行数</param>\n        </member>\n        <member name=\"T:NewLife.Log.TextFileLog\">\n            <summary>文本文件日志类。提供向文本文件写日志的能力</summary>\n            <remarks>\n            2015-06-01 为了继承TextFileLog，增加了无参构造函数，修改了异步写日志方法为虚方法，可以进行重载\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.#ctor\">\n            <summary>该构造函数没有作用，为了继承而设置</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.Create(System.String,System.String)\">\n            <summary>每个目录的日志实例应该只有一个，所以采用静态创建</summary>\n            <param name=\"path\">日志目录或日志文件路径</param>\n            <param name=\"fileFormat\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.CreateFile(System.String)\">\n            <summary>每个目录的日志实例应该只有一个，所以采用静态创建</summary>\n            <param name=\"path\">日志目录或日志文件路径</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.Dispose\">\n            <summary>销毁</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TextFileLog.LogFile\">\n            <summary>日志文件</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TextFileLog.LogPath\">\n            <summary>日志目录</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TextFileLog.FileFormat\">\n            <summary>日志文件格式</summary>\n        </member>\n        <member name=\"F:NewLife.Log.TextFileLog.isFirst\">\n            <summary>是否当前进程的第一次写日志</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.InitLog\">\n            <summary>初始化日志记录文件</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.WriteFile\">\n            <summary>写文件</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.CloseFile\">\n            <summary>关闭文件</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.OnWrite(NewLife.Log.LogLevel,System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"level\"></param>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.TextFileLog.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Log.TimeCost\">\n            <summary>统计代码的时间消耗</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TimeCost.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TimeCost.Max\">\n            <summary>最大时间。毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TimeCost.Log\">\n            <summary>日志输出</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TimeCost.#ctor(System.String,System.Int32)\">\n            <summary>指定最大执行时间来构造一个代码时间统计</summary>\n            <param name=\"name\"></param>\n            <param name=\"msMax\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.TimeCost.OnDispose(System.Boolean)\">\n            <summary>析构</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.TimeCost.Start\">\n            <summary>开始</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TimeCost.Stop\">\n            <summary>停止</summary>\n        </member>\n        <member name=\"T:NewLife.Log.TraceStream\">\n            <summary>跟踪流。包装一个基础数据流，主要用于重写Read/Write等行为，跟踪程序操作数据流的过程</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.BaseStream\">\n            <summary>基础流</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.TraceMembers\">\n            <summary>跟踪的成员</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.IsLittleEndian\">\n            <summary>是否小端字节序。x86系列则采用Little-Endian方式存储数据；网络协议都是Big-Endian；</summary>\n            <remarks>\n            网络协议都是Big-Endian；\n            Java编译的都是Big-Endian；\n            Motorola的PowerPC是Big-Endian；\n            x86系列则采用Little-Endian方式存储数据；\n            ARM同时支持 big和little，实际应用中通常使用Little-Endian。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.ShowPositionStep\">\n            <summary>显示位置的步长，位移超过此长度后输出位置。默认16，设为0不输出位置</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.Write(System.Byte[],System.Int32,System.Int32)\">\n            <summary>写入</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">数量</param>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.WriteByte(System.Byte)\">\n            <summary>写入一个字节</summary>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.Read(System.Byte[],System.Int32,System.Int32)\">\n            <summary>读取</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">数量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.ReadByte\">\n            <summary>读取一个字节</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.BeginRead(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)\">\n            <summary>异步开始读</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">数量</param>\n            <param name=\"callback\"></param>\n            <param name=\"state\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.BeginWrite(System.Byte[],System.Int32,System.Int32,System.AsyncCallback,System.Object)\">\n            <summary>异步开始写</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">数量</param>\n            <param name=\"callback\"></param>\n            <param name=\"state\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.EndRead(System.IAsyncResult)\">\n            <summary>异步读结束</summary>\n            <param name=\"asyncResult\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.EndWrite(System.IAsyncResult)\">\n            <summary>异步写结束</summary>\n            <param name=\"asyncResult\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.Seek(System.Int64,System.IO.SeekOrigin)\">\n            <summary>设置流位置</summary>\n            <param name=\"offset\">偏移</param>\n            <param name=\"origin\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.Close\">\n            <summary>关闭数据流</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.Flush\">\n            <summary>刷新缓冲区</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.SetLength(System.Int64)\">\n            <summary>设置长度</summary>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.CanRead\">\n            <summary>可读</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.CanSeek\">\n            <summary>可搜索</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.CanTimeout\">\n            <summary>可超时</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.CanWrite\">\n            <summary>可写</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.ReadTimeout\">\n            <summary>可读</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.WriteTimeout\">\n            <summary>读写超时</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.Length\">\n            <summary>长度</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.Position\">\n            <summary>位置</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.#ctor\">\n            <summary>实例化跟踪流</summary>\n        </member>\n        <member name=\"M:NewLife.Log.TraceStream.#ctor(System.IO.Stream)\">\n            <summary>实例化跟踪流</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"E:NewLife.Log.TraceStream.OnAction\">\n            <summary>操作时触发</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.UseConsole\">\n            <summary>是否使用控制台</summary>\n        </member>\n        <member name=\"P:NewLife.Log.TraceStream.Encoding\">\n            <summary>编码</summary>\n        </member>\n        <member name=\"T:NewLife.Log.WriteLogEventArgs\">\n            <summary>写日志事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.Level\">\n            <summary>日志等级</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.Message\">\n            <summary>日志信息</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.Exception\">\n            <summary>异常</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.Time\">\n            <summary>时间</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.ThreadID\">\n            <summary>线程编号</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.IsPool\">\n            <summary>是否线程池线程</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.IsWeb\">\n            <summary>是否Web线程</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.ThreadName\">\n            <summary>线程名</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.TaskID\">\n            <summary>任务编号</summary>\n        </member>\n        <member name=\"M:NewLife.Log.WriteLogEventArgs.#ctor\">\n            <summary>实例化一个日志事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.Current\">\n            <summary>线程专有实例。线程静态，每个线程只用一个，避免GC浪费</summary>\n        </member>\n        <member name=\"M:NewLife.Log.WriteLogEventArgs.Set(NewLife.Log.LogLevel)\">\n            <summary>初始化为新日志</summary>\n            <param name=\"level\">日志等级</param>\n            <returns>返回自身，链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Log.WriteLogEventArgs.Set(System.String,System.Exception)\">\n            <summary>初始化为新日志</summary>\n            <param name=\"message\">日志</param>\n            <param name=\"exception\">异常</param>\n            <returns>返回自身，链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Log.WriteLogEventArgs.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Log.WriteLogEventArgs.CurrentThreadName\">\n            <summary>设置当前线程输出日志时的线程名</summary>\n        </member>\n        <member name=\"T:NewLife.Log.XTrace\">\n            <summary>日志类，包含跟踪调试功能</summary>\n            <remarks>\n            该静态类包括写日志、写调用栈和Dump进程内存等调试功能。\n            \n            默认写日志到文本文件，可通过修改<see cref=\"P:NewLife.Log.XTrace.Log\"/>属性来增加日志输出方式。\n            对于控制台工程，可以直接通过UseConsole方法，把日志输出重定向为控制台输出，并且可以为不同线程使用不同颜色。\n            </remarks>\n        </member>\n        <member name=\"F:NewLife.Log.XTrace._Log\">\n            <summary>文本文件日志</summary>\n        </member>\n        <member name=\"P:NewLife.Log.XTrace.Log\">\n            <summary>日志提供者，默认使用文本文件日志</summary>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.WriteLine(System.String)\">\n            <summary>输出日志</summary>\n            <param name=\"msg\">信息</param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.WriteLine(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.WriteException(System.Exception)\">\n            <summary>输出异常日志</summary>\n            <param name=\"ex\">异常信息</param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.InitLog\">\n            <summary>\n            2012.11.05 修正初次调用的时候，由于同步BUG，导致Log为空的问题。\n            </summary>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.UseConsole(System.Boolean,System.Boolean)\">\n            <summary>使用控制台输出日志，只能调用一次</summary>\n            <param name=\"useColor\">是否使用颜色，默认使用</param>\n            <param name=\"useFileLog\">是否同时使用文件日志，默认使用</param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.UseWinForm(System.Boolean)\">\n            <summary>拦截WinForm异常并记录日志，可指定是否用<see cref=\"T:System.Windows.Forms.MessageBox\"/>显示。</summary>\n            <param name=\"showErrorMessage\">发为捕获异常时，是否显示提示，默认显示</param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.UseWinFormControl(System.Windows.Forms.Control,System.Boolean,System.Int32)\">\n            <summary>在WinForm控件上输出日志，主要考虑非UI线程操作</summary>\n            <remarks>不是常用功能，为了避免干扰常用功能，保持UseWinForm开头</remarks>\n            <param name=\"control\">要绑定日志输出的WinForm控件</param>\n            <param name=\"useFileLog\">是否同时使用文件日志，默认使用</param>\n            <param name=\"maxLines\">最大行数</param>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.Combine(System.Windows.Forms.Control,NewLife.Log.ILog,System.Int32)\">\n            <summary>控件绑定到日志，生成混合日志</summary>\n            <param name=\"control\"></param>\n            <param name=\"log\"></param>\n            <param name=\"maxLines\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Log.XTrace.Debug\">\n            <summary>是否调试。</summary>\n        </member>\n        <member name=\"P:NewLife.Log.XTrace.LogPath\">\n            <summary>文本日志目录</summary>\n        </member>\n        <member name=\"P:NewLife.Log.XTrace.TempPath\">\n            <summary>临时目录</summary>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.WriteVersion\">\n            <summary>输出核心库和启动程序的版本号</summary>\n        </member>\n        <member name=\"M:NewLife.Log.XTrace.WriteVersion(System.Reflection.Assembly)\">\n            <summary>输出程序集版本</summary>\n            <param name=\"asm\"></param>\n        </member>\n        <member name=\"T:NewLife.Messaging.DefaultMessage\">\n            <summary>标准消息SRMP</summary>\n            <remarks>\n            标准网络封包协议：1 Flag + 1 Sequence + 2 Length + N Payload\n            1个字节标识位，标识请求、响应、错误、加密、压缩等；\n            1个字节序列号，用于请求响应包配对；\n            2个字节数据长度N，小端，指示后续负载数据长度（不包含头部4个字节），解决粘包问题；\n            N个字节负载数据，数据内容完全由业务决定，最大长度65535=64k。\n            如：\n            Open => OK\n            01-01-04-00-\"Open\" => 81-01-02-00-\"OK\"\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Messaging.DefaultMessage.Flag\">\n            <summary>标记位</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.DefaultMessage.Sequence\">\n            <summary>序列号，匹配请求和响应</summary>\n        </member>\n        <member name=\"M:NewLife.Messaging.DefaultMessage.CreateReply\">\n            <summary>根据请求创建配对的响应消息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.DefaultMessage.Read(NewLife.Data.Packet)\">\n            <summary>从数据包中读取消息</summary>\n            <param name=\"pk\"></param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.DefaultMessage.ToPacket\">\n            <summary>把消息转为封包</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.DefaultMessage.GetLength(NewLife.Data.Packet)\">\n            <summary>获取数据包长度</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.DefaultMessage.ToString\">\n            <summary>消息摘要</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Messaging.IMessage\">\n            <summary>消息命令</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.IMessage.Reply\">\n            <summary>是否响应</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.IMessage.Error\">\n            <summary>是否有错</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.IMessage.OneWay\">\n            <summary>单向请求</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.IMessage.Payload\">\n            <summary>负载数据</summary>\n        </member>\n        <member name=\"M:NewLife.Messaging.IMessage.CreateReply\">\n            <summary>根据请求创建配对的响应消息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.IMessage.Read(NewLife.Data.Packet)\">\n            <summary>从数据包中读取消息</summary>\n            <param name=\"pk\"></param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.IMessage.ToPacket\">\n            <summary>把消息转为封包</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Messaging.Message\">\n            <summary>消息命令基类</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.Message.Reply\">\n            <summary>是否响应</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.Message.Error\">\n            <summary>是否有错</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.Message.OneWay\">\n            <summary>单向请求</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.Message.Payload\">\n            <summary>负载数据</summary>\n        </member>\n        <member name=\"M:NewLife.Messaging.Message.CreateReply\">\n            <summary>根据请求创建配对的响应消息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.Message.Read(NewLife.Data.Packet)\">\n            <summary>从数据包中读取消息</summary>\n            <param name=\"pk\"></param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.Message.ToPacket\">\n            <summary>把消息转为封包</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Messaging.MessageEventArgs\">\n            <summary>收到消息时的事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.MessageEventArgs.Packet\">\n            <summary>数据包</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.MessageEventArgs.Message\">\n            <summary>消息</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.MessageEventArgs.UserState\">\n            <summary>用户数据。比如远程地址等</summary>\n        </member>\n        <member name=\"T:NewLife.Messaging.PacketCodec\">\n            <summary>数据包编码器</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.PacketCodec.Stream\">\n            <summary>缓存流</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.PacketCodec.GetLength\">\n            <summary>获取长度的委托</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.PacketCodec.Last\">\n            <summary>最后一次解包成功，而不是最后一次接收</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.PacketCodec.Expire\">\n            <summary>缓存有效期。超过该时间后仍未匹配数据包的缓存数据将被抛弃</summary>\n        </member>\n        <member name=\"P:NewLife.Messaging.PacketCodec.MaxCache\">\n            <summary>最大缓存待处理数据。默认0无限制</summary>\n        </member>\n        <member name=\"M:NewLife.Messaging.PacketCodec.Parse(NewLife.Data.Packet)\">\n            <summary>分析数据流，得到一帧数据</summary>\n            <param name=\"pk\">待分析数据包</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Messaging.PacketCodec.CheckCache\">\n            <summary>检查缓存</summary>\n        </member>\n        <member name=\"T:NewLife.Model.IActor\">\n            <summary>无锁并行编程模型</summary>\n            <remarks>\n            独立线程轮询消息队列，简单设计避免影响默认线程池。\n            适用于任务颗粒较大的场合，例如IO操作。\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Model.IActor.Tell(System.Object,NewLife.Model.IActor)\">\n            <summary>添加消息，驱动内部处理</summary>\n            <param name=\"message\">消息</param>\n            <param name=\"sender\">发送者</param>\n            <returns>返回待处理消息数</returns>\n        </member>\n        <member name=\"T:NewLife.Model.ActorContext\">\n            <summary>Actor上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Model.ActorContext.Sender\">\n            <summary>发送者</summary>\n        </member>\n        <member name=\"P:NewLife.Model.ActorContext.Message\">\n            <summary>消息</summary>\n        </member>\n        <member name=\"T:NewLife.Model.Actor\">\n            <summary>无锁并行编程模型</summary>\n            <remarks>\n            独立线程轮询消息队列，简单设计避免影响默认线程池。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Model.Actor.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Actor.Active\">\n            <summary>是否启用</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Actor.BoundedCapacity\">\n            <summary>受限容量。最大可堆积的消息数</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Actor.BatchSize\">\n            <summary>批大小。每次处理消息数，默认1，大于1表示启用批量处理模式</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Actor.MailBox\">\n            <summary>存放消息的邮箱。默认FIFO实现，外部可覆盖</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.ToString\">\n            <summary>已重载。显示名称</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Start\">\n            <summary>通知开始处理</summary>\n            <remarks>\n            添加消息时自动触发\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.OnStart\">\n            <summary>开始时，返回执行线程包装任务，默认LongRunning</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Stop(System.Int32)\">\n            <summary>通知停止添加消息，并等待处理完成</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Tell(System.Object,NewLife.Model.IActor)\">\n            <summary>添加消息，驱动内部处理</summary>\n            <param name=\"message\">消息</param>\n            <param name=\"sender\">发送者</param>\n            <returns>返回待处理消息数</returns>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.DoWork\">\n            <summary>循环消费消息</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Loop\">\n            <summary>循环消费消息</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Receive(NewLife.Model.ActorContext)\">\n            <summary>处理消息</summary>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Model.Actor.Receive(NewLife.Model.ActorContext[])\">\n            <summary>批量处理消息</summary>\n            <param name=\"contexts\">上下文集合</param>\n        </member>\n        <member name=\"T:NewLife.Model.DeferredQueue\">\n            <summary>延迟队列。缓冲合并对象，批量处理</summary>\n            <remarks>\n            借助实体字典，缓冲实体对象，定期给字典换新，实现批量处理。\n            \n            有可能外部拿到对象后，正在修改，内部恰巧执行批量处理，导致外部的部分修改未能得到处理。\n            解决办法是增加一个提交机制，外部用完后提交修改，内部需要处理时，等待一个时间。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Entities\">\n            <summary>实体字典</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.TraceCount\">\n            <summary>跟踪数。达到该值时输出跟踪日志，默认1000</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Period\">\n            <summary>周期。默认10_000毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.MaxEntity\">\n            <summary>最大个数。超过该个数时，进入队列将产生堵塞。默认100_000</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.BatchSize\">\n            <summary>批大小。默认5_000</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.WaitForBusy\">\n            <summary>等待借出对象确认修改的时间，默认3000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Speed\">\n            <summary>保存速度，每秒保存多少个实体</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Async\">\n            <summary>是否异步处理。true表示异步处理，共用DQ定时调度；false表示同步处理，独立线程</summary>\n        </member>\n        <member name=\"P:NewLife.Model.DeferredQueue.Times\">\n            <summary>合并保存的总次数</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.OnDispose(System.Boolean)\">\n            <summary>销毁。统计队列销毁时保存数据</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.Init\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.OnInit\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.TryAdd(System.String,System.Object)\">\n            <summary>尝试添加</summary>\n            <param name=\"key\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.GetOrAdd``1(System.String,System.Func{System.String,``0})\">\n            <summary>获取 或 添加 实体对象，在外部修改对象值</summary>\n            <remarks>\n            外部正在修改对象时，内部不允许执行批量处理\n            </remarks>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"key\"></param>\n            <param name=\"valueFactory\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"F:NewLife.Model.DeferredQueue._busy\">\n            <summary>等待确认修改的借出对象数</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.Commit(System.String)\">\n            <summary>提交对象的修改，外部不再使用该对象</summary>\n            <param name=\"key\"></param>\n        </member>\n        <member name=\"F:NewLife.Model.DeferredQueue._count\">\n            <summary>当前缓存个数</summary>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.ProcessAll(System.Collections.Generic.ICollection{System.Object})\">\n            <summary>定时处理全部数据</summary>\n            <param name=\"list\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.Process(System.Collections.Generic.IList{System.Object})\">\n            <summary>处理一批</summary>\n            <param name=\"list\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.DeferredQueue.OnError(System.Collections.Generic.IList{System.Object},System.Exception)\">\n            <summary>发生错误</summary>\n            <param name=\"list\"></param>\n            <param name=\"ex\"></param>\n        </member>\n        <member name=\"T:NewLife.Model.IAuthUser\">\n            <summary>认证用户接口，具有登录验证、注册、在线等基本信息</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.Password\">\n            <summary>密码</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.Online\">\n            <summary>在线</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.Logins\">\n            <summary>登录次数</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.LastLogin\">\n            <summary>最后登录</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.LastLoginIP\">\n            <summary>最后登录IP</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.RegisterTime\">\n            <summary>注册时间</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IAuthUser.RegisterIP\">\n            <summary>注册IP</summary>\n        </member>\n        <member name=\"M:NewLife.Model.IAuthUser.Save\">\n            <summary>保存</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Model.ManageUserHelper\">\n            <summary>用户接口工具类</summary>\n        </member>\n        <member name=\"M:NewLife.Model.ManageUserHelper.CheckEqual(NewLife.Model.IAuthUser,System.String)\">\n            <summary>比较密码相等</summary>\n            <param name=\"user\"></param>\n            <param name=\"pass\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ManageUserHelper.CheckMD5(NewLife.Model.IAuthUser,System.String)\">\n            <summary>比较密码MD5</summary>\n            <param name=\"user\"></param>\n            <param name=\"pass\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ManageUserHelper.CheckRC4(NewLife.Model.IAuthUser,System.String)\">\n            <summary>比较密码RC4</summary>\n            <param name=\"user\"></param>\n            <param name=\"pass\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ManageUserHelper.SaveLogin(NewLife.Model.IAuthUser,NewLife.Net.INetSession)\">\n            <summary>保存登录信息</summary>\n            <param name=\"user\"></param>\n            <param name=\"session\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.ManageUserHelper.SaveRegister(NewLife.Model.IAuthUser,NewLife.Net.INetSession)\">\n            <summary>保存注册信息</summary>\n            <param name=\"user\"></param>\n            <param name=\"session\"></param>\n        </member>\n        <member name=\"T:NewLife.Model.IFactory`1\">\n            <summary>用于创建对象的工厂接口</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Model.IFactory`1.Create(System.Object)\">\n            <summary>创建对象实例</summary>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Model.Factory`1\">\n            <summary>反射创建对象的工厂</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Model.Factory`1.Create(System.Object)\">\n            <summary>创建对象实例</summary>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Model.IHandler\">\n            <summary>处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IHandler.Prev\">\n            <summary>上一个处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IHandler.Next\">\n            <summary>下一个处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.IHandler.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.IHandler.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.IHandler.Open(NewLife.Model.IHandlerContext)\">\n            <summary>打开连接</summary>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Model.IHandler.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>关闭连接</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"reason\">原因</param>\n        </member>\n        <member name=\"M:NewLife.Model.IHandler.Error(NewLife.Model.IHandlerContext,System.Exception)\">\n            <summary>发生错误</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"exception\">异常</param>\n        </member>\n        <member name=\"T:NewLife.Model.Handler\">\n            <summary>处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Handler.Prev\">\n            <summary>上一个处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Handler.Next\">\n            <summary>下一个处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Handler.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.Handler.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.Handler.Open(NewLife.Model.IHandlerContext)\">\n            <summary>打开连接</summary>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Model.Handler.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>关闭连接</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"reason\">原因</param>\n        </member>\n        <member name=\"M:NewLife.Model.Handler.Error(NewLife.Model.IHandlerContext,System.Exception)\">\n            <summary>发生错误</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"exception\">异常</param>\n        </member>\n        <member name=\"T:NewLife.Model.IHandlerContext\">\n            <summary>处理器上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IHandlerContext.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IHandlerContext.Owner\">\n            <summary>上下文拥有者</summary>\n        </member>\n        <member name=\"M:NewLife.Model.IHandlerContext.FireRead(System.Object)\">\n            <summary>读取管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.IHandlerContext.FireWrite(System.Object)\">\n            <summary>写入管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"T:NewLife.Model.HandlerContext\">\n            <summary>处理器上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Model.HandlerContext.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Model.HandlerContext.Owner\">\n            <summary>上下文拥有者</summary>\n        </member>\n        <member name=\"P:NewLife.Model.HandlerContext.Items\">\n            <summary>数据项</summary>\n        </member>\n        <member name=\"P:NewLife.Model.HandlerContext.Item(System.String)\">\n            <summary>设置 或 获取 数据项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.HandlerContext.FireRead(System.Object)\">\n            <summary>读取管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.HandlerContext.FireWrite(System.Object)\">\n            <summary>写入管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"T:NewLife.Model.IManageUser\">\n            <summary>用户接口</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IManageUser.ID\">\n            <summary>编号</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IManageUser.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IManageUser.NickName\">\n            <summary>昵称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IManageUser.Enable\">\n            <summary>启用</summary>\n        </member>\n        <member name=\"T:NewLife.Model.IObjectContainer\">\n            <summary>对象容器接口</summary>\n            <remarks>\n            1，如果容器里面没有这个类型，则返回空；\n            2，如果容器里面包含这个类型，<see cref=\"M:NewLife.Model.IObjectContainer.ResolveInstance(System.Type,System.Object)\"/>返回单例；\n            3，如果容器里面包含这个类型，<see cref=\"M:NewLife.Model.IObjectContainer.Resolve(System.Type,System.Object)\"/>创建对象返回多实例；\n            4，如果有带参数构造函数，则从容器内获取各个参数的实例，最后创建对象返回。\n            \n            这里有一点跟大多数对象容器非常不同，其它对象容器会控制对象的生命周期，在对象不再使用时收回到容器里面。\n            这里的对象容器主要是为了用于解耦，所以只有最简单的功能实现。\n            \n            代码注册的默认优先级是0；\n            配置注册的默认优先级是1；\n            自动注册的外部实现（非排除项）的默认优先级是1，排除项的优先级是0；\n            所以，配置注册的优先级最高\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.Register(System.Type,System.Type,System.Object,System.Object,System.Int32)\">\n            <summary>注册类型和名称</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"to\">实现类型</param>\n            <param name=\"instance\">实例</param>\n            <param name=\"id\">标识</param>\n            <param name=\"priority\">优先级</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.AutoRegister(System.Type,System.Type[])\">\n            <summary>遍历所有程序集的所有类型，自动注册实现了指定接口或基类的类型。如果没有注册任何实现，则默认注册第一个排除类型</summary>\n            <remarks>自动注册一般用于单实例功能扩展型接口</remarks>\n            <param name=\"from\">接口或基类</param>\n            <param name=\"excludeTypes\">要排除的类型，一般是内部默认实现</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.Resolve(System.Type,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.ResolveInstance(System.Type,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.ResolveType(System.Type,System.Object)\">\n            <summary>解析接口指定名称的实现类型</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IObjectContainer.ResolveAll(System.Type)\">\n            <summary>解析接口所有已注册的对象映射</summary>\n            <param name=\"from\">接口类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Model.IObjectMap\">\n            <summary>对象映射接口</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IObjectMap.Identity\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IObjectMap.Type\">\n            <summary>实现类型</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IObjectMap.Instance\">\n            <summary>对象实例</summary>\n        </member>\n        <member name=\"T:NewLife.Model.IPipeline\">\n            <summary>管道。进站顺序，出站逆序</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IPipeline.Head\">\n            <summary>头部处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.IPipeline.Tail\">\n            <summary>尾部处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.AddFirst(NewLife.Model.IHandler)\">\n            <summary>添加处理器到开头</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.AddLast(NewLife.Model.IHandler)\">\n            <summary>添加处理器到末尾</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.AddBefore(NewLife.Model.IHandler,NewLife.Model.IHandler)\">\n            <summary>添加处理器到指定名称之前</summary>\n            <param name=\"baseHandler\">基准处理器</param>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.AddAfter(NewLife.Model.IHandler,NewLife.Model.IHandler)\">\n            <summary>添加处理器到指定名称之后</summary>\n            <param name=\"baseHandler\">基准处理器</param>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Remove(NewLife.Model.IHandler)\">\n            <summary>删除处理器</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Open(NewLife.Model.IHandlerContext)\">\n            <summary>打开连接</summary>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>关闭连接</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"reason\">原因</param>\n        </member>\n        <member name=\"M:NewLife.Model.IPipeline.Error(NewLife.Model.IHandlerContext,System.Exception)\">\n            <summary>发生错误</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"exception\">异常</param>\n        </member>\n        <member name=\"T:NewLife.Model.Pipeline\">\n            <summary>管道。进站顺序，出站逆序</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Pipeline.Head\">\n            <summary>头部处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.Pipeline.Tail\">\n            <summary>尾部处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.AddFirst(NewLife.Model.IHandler)\">\n            <summary>添加处理器到开头</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.AddLast(NewLife.Model.IHandler)\">\n            <summary>添加处理器到末尾</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.AddBefore(NewLife.Model.IHandler,NewLife.Model.IHandler)\">\n            <summary>添加处理器到指定名称之前</summary>\n            <param name=\"baseHandler\">基准处理器</param>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.AddAfter(NewLife.Model.IHandler,NewLife.Model.IHandler)\">\n            <summary>添加处理器到指定名称之后</summary>\n            <param name=\"baseHandler\">基准处理器</param>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Remove(NewLife.Model.IHandler)\">\n            <summary>删除处理器</summary>\n            <param name=\"handler\">处理器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据，顺序过滤消息，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据，逆序过滤消息，返回结果作为下一个处理器消息</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"message\">消息</param>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Open(NewLife.Model.IHandlerContext)\">\n            <summary>打开连接</summary>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>关闭连接</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"reason\">原因</param>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.Error(NewLife.Model.IHandlerContext,System.Exception)\">\n            <summary>发生错误</summary>\n            <param name=\"context\">上下文</param>\n            <param name=\"exception\">异常</param>\n        </member>\n        <member name=\"M:NewLife.Model.Pipeline.GetEnumerator\">\n            <summary>枚举器</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Model.IPlugin\">\n            <summary>通用插件接口</summary>\n            <remarks>\n            为了方便构建一个简单通用的插件系统，先规定如下：\n            1，负责加载插件的宿主，在加载插件后会进行插件实例化，此时可在插件构造函数中做一些事情，但不应该开始业务处理，因为宿主的准备工作可能尚未完成\n            2，宿主一切准备就绪后，会顺序调用插件的Init方法，并将宿主标识传入，插件通过标识区分是否自己的目标宿主。插件的Init应尽快完成。\n            3，如果插件实现了<see cref=\"T:System.IDisposable\"/>接口，宿主最后会清理资源。\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Model.IPlugin.Init(System.String,System.IServiceProvider)\">\n            <summary>初始化</summary>\n            <param name=\"identity\">插件宿主标识</param>\n            <param name=\"provider\">服务提供者</param>\n            <returns>返回初始化是否成功。如果当前宿主不是所期待的宿主，这里返回false</returns>\n        </member>\n        <member name=\"T:NewLife.Model.PluginAttribute\">\n            <summary>插件特性。用于判断某个插件实现类是否支持某个宿主</summary>\n        </member>\n        <member name=\"P:NewLife.Model.PluginAttribute.Identity\">\n            <summary>插件宿主标识</summary>\n        </member>\n        <member name=\"M:NewLife.Model.PluginAttribute.#ctor(System.String)\">\n            <summary>实例化</summary>\n            <param name=\"identity\"></param>\n        </member>\n        <member name=\"T:NewLife.Model.PluginManager\">\n            <summary>插件管理器</summary>\n        </member>\n        <member name=\"P:NewLife.Model.PluginManager.Identity\">\n            <summary>宿主标识，用于供插件区分不同宿主</summary>\n        </member>\n        <member name=\"P:NewLife.Model.PluginManager.Provider\">\n            <summary>宿主服务提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Model.PluginManager.Plugins\">\n            <summary>插件集合</summary>\n        </member>\n        <member name=\"P:NewLife.Model.PluginManager.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Model.PluginManager.#ctor\">\n            <summary>实例化一个插件管理器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.PluginManager.#ctor(System.Object)\">\n            <summary>使用宿主对象实例化一个插件管理器</summary>\n            <param name=\"host\"></param>\n        </member>\n        <member name=\"M:NewLife.Model.PluginManager.OnDispose(System.Boolean)\">\n            <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>\n            <param name=\"disposing\">从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）。\n            因为该方法只会被调用一次，所以该参数的意义不太大。</param>\n        </member>\n        <member name=\"M:NewLife.Model.PluginManager.Load\">\n            <summary>加载插件。此时是加载所有插件，无法识别哪些是需要的</summary>\n        </member>\n        <member name=\"M:NewLife.Model.PluginManager.Init\">\n            <summary>开始初始化。初始化之后，不属于当前宿主的插件将会被过滤掉</summary>\n        </member>\n        <member name=\"T:NewLife.Model.IServer\">\n            <summary>服务接口。</summary>\n            <remarks>服务代理XAgent可以附加代理实现了IServer接口的服务。</remarks>\n        </member>\n        <member name=\"M:NewLife.Model.IServer.Start\">\n            <summary>开始</summary>\n        </member>\n        <member name=\"M:NewLife.Model.IServer.Stop(System.String)\">\n            <summary>停止</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"T:NewLife.Model.ObjectContainer\">\n            <summary>实现 <seealso cref=\"T:NewLife.Model.IObjectContainer\"/> 接口的对象容器</summary>\n            <remarks>\n            1，如果容器里面没有这个类型，则返回空；\n            2，如果容器里面包含这个类型，<see cref=\"M:NewLife.Model.ObjectContainer.ResolveInstance(System.Type,System.Object)\"/>返回单例；\n            3，如果容器里面包含这个类型，<see cref=\"M:NewLife.Model.ObjectContainer.Resolve(System.Type,System.Object)\"/>创建对象返回多实例；\n            4，如果有带参数构造函数，则从容器内获取各个参数的实例，最后创建对象返回。\n            \n            这里有一点跟大多数对象容器非常不同，其它对象容器会控制对象的生命周期，在对象不再使用时收回到容器里面。\n            这里的对象容器主要是为了用于解耦，所以只有最简单的功能实现。\n            \n            代码注册的默认优先级是0；\n            配置注册的默认优先级是1；\n            自动注册的外部实现（非排除项）的默认优先级是1，排除项的优先级是0；\n            所以，配置注册的优先级最高\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Model.ObjectContainer.Current\">\n            <summary>当前容器</summary>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.#ctor\">\n            <summary>初始化一个对象容器实例，自动从配置文件中加载注册</summary>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.Find(System.Type,System.Boolean)\">\n            <summary>不存在又不添加时返回空列表</summary>\n            <param name=\"type\"></param>\n            <param name=\"add\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Model.ObjectContainer.Map.Identity\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Model.ObjectContainer.Map.Type\">\n            <summary>实现类型</summary>\n        </member>\n        <member name=\"P:NewLife.Model.ObjectContainer.Map.Priority\">\n            <summary>优先级</summary>\n        </member>\n        <member name=\"P:NewLife.Model.ObjectContainer.Map.Instance\">\n            <summary>实例</summary>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.Register(System.Type,System.Type,System.Object,System.Object,System.Int32)\">\n            <summary>注册</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"to\">实现类型</param>\n            <param name=\"instance\">实例</param>\n            <param name=\"id\">标识</param>\n            <param name=\"priority\">优先级</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.AutoRegister(System.Type,System.Type[])\">\n            <summary>遍历所有程序集的所有类型，自动注册实现了指定接口或基类的类型。如果没有注册任何实现，则默认注册第一个排除类型</summary>\n            <remarks>自动注册一般用于单实例功能扩展型接口</remarks>\n            <param name=\"from\">接口或基类</param>\n            <param name=\"excludeTypes\">要排除的类型，一般是内部默认实现</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.Resolve(System.Type,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.ResolveInstance(System.Type,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.ResolveType(System.Type,System.Object)\">\n            <summary>解析接口指定名称的实现类型</summary>\n            <param name=\"from\">接口类型</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.ResolveAll(System.Type)\">\n            <summary>解析接口所有已注册的对象映射</summary>\n            <param name=\"from\">接口类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Model.ObjectContainer.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.IMatchQueue\">\n            <summary>消息匹配队列接口。用于把响应数据包配对到请求包</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.IMatchQueue.Add(System.Object,System.Object,System.Int32,System.Threading.Tasks.TaskCompletionSource{System.Object})\">\n            <summary>加入请求队列</summary>\n            <param name=\"owner\">拥有者</param>\n            <param name=\"request\">请求消息</param>\n            <param name=\"msTimeout\">超时取消时间</param>\n            <param name=\"source\">任务源</param>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.IMatchQueue.Match(System.Object,System.Object,System.Object,System.Func{System.Object,System.Object,System.Boolean})\">\n            <summary>检查请求队列是否有匹配该响应的请求</summary>\n            <param name=\"owner\">拥有者</param>\n            <param name=\"response\">响应消息</param>\n            <param name=\"result\">任务结果</param>\n            <param name=\"callback\">用于检查匹配的回调</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.IMatchQueue.Clear\">\n            <summary>清空队列</summary>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.DefaultMatchQueue\">\n            <summary>消息匹配队列。子类可重载以自定义请求响应匹配逻辑</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.DefaultMatchQueue.Add(System.Object,System.Object,System.Int32,System.Threading.Tasks.TaskCompletionSource{System.Object})\">\n            <summary>加入请求队列</summary>\n            <param name=\"owner\">拥有者</param>\n            <param name=\"request\">请求的数据</param>\n            <param name=\"msTimeout\">超时取消时间</param>\n            <param name=\"source\">任务源</param>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.DefaultMatchQueue.Match(System.Object,System.Object,System.Object,System.Func{System.Object,System.Object,System.Boolean})\">\n            <summary>检查请求队列是否有匹配该响应的请求</summary>\n            <param name=\"owner\">拥有者</param>\n            <param name=\"response\">响应消息</param>\n            <param name=\"result\">任务结果</param>\n            <param name=\"callback\">用于检查匹配的回调</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.DefaultMatchQueue.Check(System.Object)\">\n            <summary>定时检查发送队列，超时未收到响应则重发</summary>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.DefaultMatchQueue.Clear\">\n            <summary>清空队列</summary>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.LengthFieldCodec\">\n            <summary>长度字段作为头部</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.LengthFieldCodec.Offset\">\n            <summary>长度所在位置</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.LengthFieldCodec.Size\">\n            <summary>长度占据字节数，1/2/4个字节，0表示压缩编码整数，默认2</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.LengthFieldCodec.Expire\">\n            <summary>过期时间，超过该时间后按废弃数据处理，默认500ms</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.LengthFieldCodec.Encode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>编码</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.LengthFieldCodec.Decode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>解码</summary>\n            <param name=\"context\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.LengthFieldCodec.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>连接关闭时，清空粘包编码器</summary>\n            <param name=\"context\"></param>\n            <param name=\"reason\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.MessageCodec`1\">\n            <summary>消息封包</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.MessageCodec`1.Queue\">\n            <summary>消息队列。用于匹配请求响应包</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.MessageCodec`1.Timeout\">\n            <summary>调用超时时间。默认30_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.MessageCodec`1.UserPacket\">\n            <summary>使用数据包，写入时数据包转消息，读取时消息自动解包返回数据负载。默认true</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.Encode(NewLife.Model.IHandlerContext,`0)\">\n            <summary>编码</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.AddToQueue(NewLife.Model.IHandlerContext,`0)\">\n            <summary>加入队列</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>连接关闭时，清空粘包编码器</summary>\n            <param name=\"context\"></param>\n            <param name=\"reason\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.Decode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>解码</summary>\n            <param name=\"context\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.IsMatch(System.Object,System.Object)\">\n            <summary>是否匹配响应</summary>\n            <param name=\"request\"></param>\n            <param name=\"response\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.MessageCodec`1.GetLength(NewLife.Data.Packet,System.Int32,System.Int32)\">\n            <summary>从数据流中获取整帧数据长度</summary>\n            <param name=\"pk\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"size\"></param>\n            <returns>数据帧长度（包含头部长度位）</returns>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.SplitDataCodec\">\n            <summary>\n            按指定分割字节来处理粘包的处理器\n            </summary>\n            <remarks>\n            默认以\"0x0D 0x0A\"即换行来分割，分割的包包含分割字节本身，使用时请注意。\n            默认分割方式：ISocket.Add&lt;SplitDataCodec&gt;()\n            自定义分割方式：ISocket.Add(new SplitDataHandler { SplitData = 自定义分割字节数组 })\n            自定义最大缓存大小方式：ISocket.Add(new SplitDataHandler { MaxCacheDataLength = 2048 })\n            自定义方式：ISocket.Add(new SplitDataHandler { MaxCacheDataLength = 2048, SplitData = 自定义分割字节数组 })\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.SplitDataCodec.SplitData\">\n            <summary>\n            粘包分割字节数据（默认0x0D,0x0A）\n            </summary>\n        </member>\n        <member name=\"P:NewLife.Net.Handlers.SplitDataCodec.MaxCacheDataLength\">\n            <summary>\n            最大缓存待处理数据（字节）\n            </summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.SplitDataCodec.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>读取数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.SplitDataCodec.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>连接关闭时，清空粘包编码器</summary>\n            <param name=\"context\"></param>\n            <param name=\"reason\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.SplitDataCodec.Decode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>解码</summary>\n            <param name=\"context\"></param>\n            <param name=\"pk\">包</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.SplitDataCodec.GetLineLength(NewLife.Data.Packet)\">\n            <summary>\n            获取包含分割字节在内的数据长度\n            </summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.Handlers.StandardCodec\">\n            <summary>标准网络封包。头部4字节定长</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.StandardCodec.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>写入数据</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.StandardCodec.AddToQueue(NewLife.Model.IHandlerContext,NewLife.Messaging.IMessage)\">\n            <summary>加入队列</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.StandardCodec.Decode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>解码</summary>\n            <param name=\"context\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.StandardCodec.IsMatch(System.Object,System.Object)\">\n            <summary>是否匹配响应</summary>\n            <param name=\"request\"></param>\n            <param name=\"response\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Handlers.StandardCodec.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>连接关闭时，清空粘包编码器</summary>\n            <param name=\"context\"></param>\n            <param name=\"reason\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.INetSession\">\n            <summary>网络服务会话接口</summary>\n            <remarks>\n            所有应用服务器以会话<see cref=\"T:NewLife.Net.INetSession\"/>作为业务处理核心。\n            应用服务器收到新会话请求后，通过<see cref=\"M:NewLife.Net.INetSession.Start\"/>启动一个会话处理。\n            会话进行业务处理的过程中，可以通过多个Send方法向客户端发送数据。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.INetSession.ID\">\n            <summary>编号</summary>\n        </member>\n        <member name=\"P:NewLife.Net.INetSession.Host\">\n            <summary>主服务</summary>\n        </member>\n        <member name=\"P:NewLife.Net.INetSession.Server\">\n            <summary>Socket服务器。当前通讯所在的Socket服务器，其实是TcpServer/UdpServer</summary>\n        </member>\n        <member name=\"P:NewLife.Net.INetSession.Session\">\n            <summary>客户端。跟客户端通讯的那个Socket，其实是服务端TcpSession/UdpSession</summary>\n        </member>\n        <member name=\"P:NewLife.Net.INetSession.Remote\">\n            <summary>客户端地址</summary>\n        </member>\n        <member name=\"M:NewLife.Net.INetSession.Start\">\n            <summary>开始会话处理。</summary>\n        </member>\n        <member name=\"M:NewLife.Net.INetSession.Send(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <param name=\"pk\">数据包</param>\n        </member>\n        <member name=\"M:NewLife.Net.INetSession.Send(System.IO.Stream)\">\n            <summary>发送数据流</summary>\n            <param name=\"stream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.INetSession.Send(System.String,System.Text.Encoding)\">\n            <summary>发送字符串</summary>\n            <param name=\"msg\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.INetSession.SendAsync(System.Object)\">\n            <summary>异步发送并等待响应</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"E:NewLife.Net.INetSession.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"T:NewLife.Net.NetSessionEventArgs\">\n            <summary>会话事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSessionEventArgs.Session\">\n            <summary>会话</summary>\n        </member>\n        <member name=\"T:NewLife.Net.ISocket\">\n            <summary>基础Socket接口</summary>\n            <remarks>\n            封装所有基础接口的共有特性！\n            \n            核心设计理念：事件驱动，接口统一，简单易用！\n            异常处理理念：确保主流程简单易用，特殊情况的异常通过事件处理！\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Name\">\n            <summary>名称。主要用于日志输出</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Client\">\n            <summary>基础Socket对象</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Local\">\n            <summary>本地地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.ThrowException\">\n            <summary>是否抛出异常，默认false不抛出。Send/Receive时可能发生异常，该设置决定是直接抛出异常还是通过<see cref=\"E:NewLife.Net.ISocket.Error\"/>事件</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.ProcessAsync\">\n            <summary>异步处理接收到的数据。</summary>\n            <remarks>异步处理有可能造成数据包乱序，特别是Tcp。true利于提升网络吞吐量。false避免拷贝，提升处理速度</remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.StatSend\">\n            <summary>发送统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.StatReceive\">\n            <summary>接收统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.LogSend\">\n            <summary>是否输出发送日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocket.LogReceive\">\n            <summary>是否输出接收日志。默认false</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ISocket.WriteLog(System.String,System.Object[])\">\n            <summary>已重载。日志加上前缀</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"E:NewLife.Net.ISocket.Error\">\n            <summary>错误发生/断开连接时</summary>\n        </member>\n        <member name=\"T:NewLife.Net.ISocketRemote\">\n            <summary>远程通信Socket，仅具有收发功能</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketRemote.ID\">\n            <summary>\n            标识\n            </summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketRemote.Remote\">\n            <summary>远程地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketRemote.StartTime\">\n            <summary>通信开始时间</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketRemote.LastTime\">\n            <summary>最后一次通信时间，主要表示会话活跃时间，包括收发</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketRemote.BufferSize\">\n            <summary>缓冲区大小</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketRemote.Send(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <remarks>\n            目标地址由<seealso cref=\"P:NewLife.Net.ISocketRemote.Remote\"/>决定\n            </remarks>\n            <param name=\"pk\">数据包</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketRemote.Receive\">\n            <summary>接收数据。阻塞当前线程等待返回</summary>\n            <returns></returns>\n        </member>\n        <member name=\"E:NewLife.Net.ISocketRemote.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketRemote.SendMessageAsync(System.Object)\">\n            <summary>异步发送数据并等待响应</summary>\n            <param name=\"message\">消息</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketRemote.SendMessage(System.Object)\">\n            <summary>发送消息</summary>\n            <param name=\"message\">消息</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketRemote.Process(NewLife.Data.IData)\">\n            <summary>处理数据帧</summary>\n            <param name=\"data\">数据帧</param>\n        </member>\n        <member name=\"T:NewLife.Net.SocketRemoteHelper\">\n            <summary>远程通信Socket扩展</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.GetStat(NewLife.Net.ISocketRemote)\">\n            <summary>获取统计信息</summary>\n            <param name=\"socket\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.Send(NewLife.Net.ISocketRemote,System.IO.Stream)\">\n            <summary>发送数据流</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"stream\">数据流</param>\n            <returns>返回是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.Send(NewLife.Net.ISocketRemote,System.String,System.Text.Encoding)\">\n            <summary>发送字符串</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"msg\">要发送的字符串</param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.SendMulti(NewLife.Net.ISocketRemote,NewLife.Data.Packet,System.Int32,System.Int32)\">\n            <summary>异步多次发送数据</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"pk\">数据包</param>\n            <param name=\"times\">次数</param>\n            <param name=\"msInterval\">间隔</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.ReceiveString(NewLife.Net.ISocketRemote,System.Text.Encoding)\">\n            <summary>接收字符串</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.Add``1(NewLife.Net.ISocket)\">\n            <summary>添加处理器</summary>\n            <typeparam name=\"THandler\"></typeparam>\n            <param name=\"session\">会话</param>\n        </member>\n        <member name=\"M:NewLife.Net.SocketRemoteHelper.Add(NewLife.Net.ISocket,NewLife.Model.IHandler)\">\n            <summary>添加处理器</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"handler\">处理器</param>\n        </member>\n        <member name=\"T:NewLife.Net.ISocketClient\">\n            <summary>Socket客户端</summary>\n            <remarks>\n            具备打开关闭\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketClient.Timeout\">\n            <summary>超时。默认3000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketClient.Active\">\n            <summary>是否活动</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketClient.Open\">\n            <summary>打开</summary>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.ISocketClient.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"E:NewLife.Net.ISocketClient.Opened\">\n            <summary>打开后触发。</summary>\n        </member>\n        <member name=\"E:NewLife.Net.ISocketClient.Closed\">\n            <summary>关闭后触发。可实现掉线重连</summary>\n        </member>\n        <member name=\"T:NewLife.Net.ISocketServer\">\n            <summary>Socket服务器接口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketServer.Active\">\n            <summary>是否活动</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketServer.SessionTimeout\">\n            <summary>会话超时时间。默认20*60秒</summary>\n            <remarks>\n            对于每一个会话连接，如果超过该时间仍然没有收到任何数据，则断开会话连接。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketServer.StatSession\">\n            <summary>会话统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketServer.Sessions\">\n            <summary>会话集合。用地址端口作为标识，业务应用自己维持地址端口与业务主键的对应关系。</summary>\n        </member>\n        <member name=\"E:NewLife.Net.ISocketServer.NewSession\">\n            <summary>新会话时触发</summary>\n        </member>\n        <member name=\"T:NewLife.Net.SocketServerHelper\">\n            <summary>服务端通信Socket扩展</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SocketServerHelper.GetStat(NewLife.Net.ISocketServer)\">\n            <summary>获取统计信息</summary>\n            <param name=\"socket\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.ISocketSession\">\n            <summary>用于与对方进行通讯的Socket会话，仅具有收发功能，也专用于上层应用收发数据</summary>\n            <remarks>\n            Socket会话发送数据不需要指定远程地址，因为内部已经具有。\n            接收数据时，Tcp接收全部数据，而Udp只接受来自所属远方的数据。\n            \n            Socket会话不具有连接和断开的能力，所以需要外部连接好之后再创建Socket会话。\n            但是会话可以销毁，来代替断开。\n            对于Udp额外创建的会话来说，仅仅销毁会话而已。\n            \n            所以，它必须具有收发数据的能力。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ISocketSession.Server\">\n            <summary>Socket服务器。当前通讯所在的Socket服务器，其实是TcpServer/UdpServer</summary>\n        </member>\n        <member name=\"T:NewLife.Net.SessionEventArgs\">\n            <summary>会话事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionEventArgs.Session\">\n            <summary>会话</summary>\n        </member>\n        <member name=\"T:NewLife.Net.ITransport\">\n            <summary>帧数据传输接口</summary>\n            <remarks>实现者确保数据以包的形式传输，屏蔽数据的粘包和拆包</remarks>\n        </member>\n        <member name=\"P:NewLife.Net.ITransport.Timeout\">\n            <summary>超时</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ITransport.Open\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ITransport.Close\">\n            <summary>关闭</summary>\n        </member>\n        <member name=\"M:NewLife.Net.ITransport.Send(NewLife.Data.Packet)\">\n            <summary>写入数据</summary>\n            <param name=\"pk\">数据包</param>\n        </member>\n        <member name=\"M:NewLife.Net.ITransport.Receive\">\n            <summary>读取数据</summary>\n            <returns></returns>\n        </member>\n        <member name=\"E:NewLife.Net.ITransport.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"T:NewLife.Net.NetException\">\n            <summary>网络异常</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetException.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetException.#ctor(System.String)\">\n            <summary>初始化</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetException.#ctor(System.String,System.Object[])\">\n            <summary>初始化</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetException.#ctor(System.String,System.Exception)\">\n            <summary>初始化</summary>\n            <param name=\"message\"></param>\n            <param name=\"innerException\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetException.#ctor(System.Exception)\">\n            <summary>初始化</summary>\n            <param name=\"innerException\"></param>\n        </member>\n        <member name=\"T:NewLife.Net.NetHandlerContext\">\n            <summary>网络处理器上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetHandlerContext.Session\">\n            <summary>远程连接</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetHandlerContext.Data\">\n            <summary>数据帧</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetHandlerContext.FireRead(System.Object)\">\n            <summary>读取管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetHandlerContext.FireWrite(System.Object)\">\n            <summary>写入管道过滤后最终处理消息</summary>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"T:NewLife.Net.NetServer\">\n            <summary>网络服务器。可同时支持多个Socket服务器，同时支持IPv4和IPv6，同时支持Tcp和Udp</summary>\n            <remarks>\n            网络服务器模型，所有网络应用服务器可以通过继承该类实现。\n            该类仅实现了业务应用对网络流的操作，与具体网络协议无关。\n            \n            收到请求<see cref=\"M:NewLife.Net.NetServer.Server_NewSession(System.Object,NewLife.Net.SessionEventArgs)\"/>后，会建立<see cref=\"M:NewLife.Net.NetServer.CreateSession(NewLife.Net.ISocketSession)\"/>会话，并加入到会话集合<see cref=\"P:NewLife.Net.NetServer.Sessions\"/>中，然后启动<see cref=\"M:NewLife.Net.NetServer.Start\"/>会话处理；\n            \n            快速用法：\n            指定端口后直接<see cref=\"M:NewLife.Net.NetServer.Start\"/>，NetServer将同时监听Tcp/Udp和IPv4/IPv6（会检查是否支持）四个端口。\n            \n            简单用法：\n            重载方法<see cref=\"M:NewLife.Net.NetServer.EnsureCreateServer\"/>来创建一个SocketServer并赋值给<see cref=\"P:NewLife.Net.NetServer.Server\"/>属性，<see cref=\"M:NewLife.Net.NetServer.EnsureCreateServer\"/>将会在<see cref=\"M:NewLife.Net.NetServer.OnStart\"/>时首先被调用。\n            \n            标准用法：\n            使用<see cref=\"M:NewLife.Net.NetServer.AttachServer(NewLife.Net.ISocketServer)\"/>方法向网络服务器添加Socket服务，其中第一个将作为默认Socket服务<see cref=\"P:NewLife.Net.NetServer.Server\"/>。\n            如果Socket服务集合<see cref=\"P:NewLife.Net.NetServer.Servers\"/>为空，将依据地址<see cref=\"P:NewLife.Net.NetServer.Local\"/>、端口<see cref=\"P:NewLife.Net.NetServer.Port\"/>、地址族<see cref=\"P:NewLife.Net.NetServer.AddressFamily\"/>、协议<see cref=\"P:NewLife.Net.NetServer.ProtocolType\"/>创建默认Socket服务。\n            如果地址族<see cref=\"P:NewLife.Net.NetServer.AddressFamily\"/>指定为IPv4和IPv6以外的值，将同时创建IPv4和IPv6两个Socket服务；\n            如果协议<see cref=\"P:NewLife.Net.NetServer.ProtocolType\"/>指定为Tcp和Udp以外的值，将同时创建Tcp和Udp两个Socket服务；\n            默认情况下，地址族<see cref=\"P:NewLife.Net.NetServer.AddressFamily\"/>和协议<see cref=\"P:NewLife.Net.NetServer.ProtocolType\"/>都是其它值，所以一共将会创建四个Socket服务（Tcp、Tcpv6、Udp、Udpv6）。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Name\">\n            <summary>服务名</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Local\">\n            <summary>本地结点</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.ProtocolType\">\n            <summary>协议类型</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.AddressFamily\">\n            <summary>寻址方案</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Servers\">\n            <summary>服务器集合</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Server\">\n            <summary>服务器。返回服务器集合中的第一个服务器</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Active\">\n            <summary>是否活动</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.SessionTimeout\">\n            <summary>会话超时时间。默认0秒，使用SocketServer默认值</summary>\n            <remarks>\n            对于每一个会话连接，如果超过该时间仍然没有收到任何数据，则断开会话连接。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.UseSession\">\n            <summary>使用会话集合，允许遍历会话。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.StatSession\">\n            <summary>会话统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.StatSend\">\n            <summary>发送统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.StatReceive\">\n            <summary>接收统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.LogSend\">\n            <summary>是否输出发送日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.LogReceive\">\n            <summary>是否输出接收日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Items\">\n            <summary>用户会话数据</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Item(System.String)\">\n            <summary>获取/设置 用户会话数据</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.#ctor\">\n            <summary>实例化一个网络服务器</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.#ctor(System.Int32)\">\n            <summary>通过指定监听地址和端口实例化一个网络服务器</summary>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.#ctor(System.Net.IPAddress,System.Int32)\">\n            <summary>通过指定监听地址和端口实例化一个网络服务器</summary>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.#ctor(System.Net.IPAddress,System.Int32,NewLife.Net.NetType)\">\n            <summary>通过指定监听地址和端口，还有协议，实例化一个网络服务器，默认支持Tcp协议和Udp协议</summary>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n            <param name=\"protocolType\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnDispose(System.Boolean)\">\n            <summary>已重载。释放会话集合等资源</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.AttachServer(NewLife.Net.ISocketServer)\">\n            <summary>添加Socket服务器</summary>\n            <param name=\"server\"></param>\n            <returns>添加是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.AddServer(System.Net.IPAddress,System.Int32,NewLife.Net.NetType,System.Net.Sockets.AddressFamily)\">\n            <summary>同时添加指定端口的IPv4和IPv6服务器，如果协议不是指定的Tcp或Udp，则同时添加Tcp和Udp服务器</summary>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n            <param name=\"protocol\"></param>\n            <param name=\"family\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.EnsureCreateServer\">\n            <summary>确保建立服务器</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.Add``1\">\n            <summary>添加处理器</summary>\n            <typeparam name=\"THandler\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.Add(NewLife.Model.IHandler)\">\n            <summary>添加处理器</summary>\n            <param name=\"handler\">处理器</param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.Start\">\n            <summary>开始服务</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnStart\">\n            <summary>开始时调用的方法</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.Stop(System.String)\">\n            <summary>停止服务</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnStop\">\n            <summary>停止时调用的方法</summary>\n        </member>\n        <member name=\"E:NewLife.Net.NetServer.NewSession\">\n            <summary>新会话，对于TCP是新连接，对于UDP是新客户端</summary>\n        </member>\n        <member name=\"E:NewLife.Net.NetServer.Received\">\n            <summary>某个会话的数据到达。sender是ISocketSession</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.Server_NewSession(System.Object,NewLife.Net.SessionEventArgs)\">\n            <summary>接受连接时，对于Udp是收到数据时（同时触发OnReceived）。</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnNewSession(NewLife.Net.ISocketSession)\">\n            <summary>收到连接时，建立会话，并挂接数据接收和错误处理事件</summary>\n            <param name=\"session\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnReceived(System.Object,NewLife.Net.ReceivedEventArgs)\">\n            <summary>收到数据时</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnReceive(NewLife.Net.INetSession,NewLife.Data.Packet)\">\n            <summary>收到数据时，最原始的数据处理，但不影响会话内部的数据处理</summary>\n            <param name=\"session\"></param>\n            <param name=\"pk\"></param>\n        </member>\n        <member name=\"E:NewLife.Net.NetServer.Error\">\n            <summary>错误发生/断开连接时。sender是ISocketSession</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.OnError(System.Object,NewLife.ExceptionEventArgs)\">\n            <summary>触发异常</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Sessions\">\n            <summary>会话集合。用自增的数字ID作为标识，业务应用自己维持ID与业务主键的对应关系。</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.SessionCount\">\n            <summary>会话数</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.MaxSessionCount\">\n            <summary>最高会话数</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.AddSession(NewLife.Net.INetSession)\">\n            <summary>添加会话。子类可以在添加会话前对会话进行一些处理</summary>\n            <param name=\"session\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.CreateSession(NewLife.Net.ISocketSession)\">\n            <summary>创建会话</summary>\n            <param name=\"session\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.GetSession(System.Int32)\">\n            <summary>根据会话ID查找会话</summary>\n            <param name=\"sessionid\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.SendAllAsync(System.Byte[])\">\n            <summary>异步群发</summary>\n            <param name=\"buffer\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.CreateServer(System.Net.IPAddress,System.Int32,NewLife.Net.NetType,System.Net.Sockets.AddressFamily)\">\n            <summary>创建Tcp/Udp、IPv4/IPv6服务</summary>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n            <param name=\"protocol\"></param>\n            <param name=\"family\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.GetStat\">\n            <summary>获取统计信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.SocketLog\">\n            <summary>用于内部Socket服务器的日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.SessionLog\">\n            <summary>用于网络会话的日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetServer.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.WriteError(System.String,System.Object[])\">\n            <summary>输出错误日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.NetServer`1\">\n            <summary>网络服务器</summary>\n            <typeparam name=\"TSession\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer`1.CreateSession(NewLife.Net.ISocketSession)\">\n            <summary>创建会话</summary>\n            <param name=\"session\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetServer`1.GetSession(System.Int32)\">\n            <summary>获取指定标识的会话</summary>\n            <param name=\"id\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.NetSession`1\">\n            <summary>网络服务的会话</summary>\n            <typeparam name=\"TServer\">网络服务类型</typeparam>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession`1.Host\">\n            <summary>主服务</summary>\n        </member>\n        <member name=\"T:NewLife.Net.NetSession\">\n            <summary>网络服务的会话</summary>\n            <remarks>\n            实际应用可通过重载OnReceive实现收到数据时的业务逻辑。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.ID\">\n            <summary>编号</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.NewLife#Net#INetSession#Host\">\n            <summary>主服务</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Session\">\n            <summary>客户端。跟客户端通讯的那个Socket，其实是服务端TcpSession/UdpServer</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Server\">\n            <summary>服务端</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Remote\">\n            <summary>客户端地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Items\">\n            <summary>用户会话数据</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Item(System.String)\">\n            <summary>获取/设置 用户会话数据</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.Start\">\n            <summary>开始会话处理。</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.OnDispose(System.Boolean)\">\n            <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>\n            <param name=\"disposing\">从Dispose调用（释放所有资源）还是析构函数调用（释放非托管资源）</param>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.OnReceive(NewLife.Net.ReceivedEventArgs)\">\n            <summary>收到客户端发来的数据，触发<seealso cref=\"E:NewLife.Net.NetSession.Received\"/>事件，重载者可直接处理数据</summary>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"E:NewLife.Net.NetSession.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.Send(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <param name=\"pk\">数据包</param>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.Send(System.IO.Stream)\">\n            <summary>发送数据流</summary>\n            <param name=\"stream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.Send(System.String,System.Text.Encoding)\">\n            <summary>发送字符串</summary>\n            <param name=\"msg\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.SendAsync(System.Object)\">\n            <summary>异步发送并等待响应</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.OnError(System.Object,NewLife.ExceptionEventArgs)\">\n            <summary>错误处理</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.LogSession\">\n            <summary>是否记录会话日志</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetSession.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.WriteError(System.String,System.Object[])\">\n            <summary>输出错误日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetSession.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.NetType\">\n            <summary>协议类型</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.Unknown\">\n            <summary>未知协议</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.Tcp\">\n            <summary>传输控制协议</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.Udp\">\n            <summary>用户数据报协议</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.Http\">\n            <summary>Http协议</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.Https\">\n            <summary>Https协议</summary>\n        </member>\n        <member name=\"F:NewLife.Net.NetType.WebSocket\">\n            <summary>WebSocket协议</summary>\n        </member>\n        <member name=\"T:NewLife.Net.NetUri\">\n            <summary>网络资源标识，指定协议、地址、端口、地址族（IPv4/IPv6）</summary>\n            <remarks>\n            仅序列化<see cref=\"P:NewLife.Net.NetUri.Type\"/>和<see cref=\"P:NewLife.Net.NetUri.EndPoint\"/>，其它均是配角！\n            有可能<see cref=\"P:NewLife.Net.NetUri.Host\"/>代表主机域名，而<see cref=\"P:NewLife.Net.NetUri.Address\"/>指定主机IP地址。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.Type\">\n            <summary>协议类型</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.Address\">\n            <summary>地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.EndPoint\">\n            <summary>终结点</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.IsTcp\">\n            <summary>是否Tcp协议</summary>\n        </member>\n        <member name=\"P:NewLife.Net.NetUri.IsUdp\">\n            <summary>是否Udp协议</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.#ctor(System.String)\">\n            <summary>实例化</summary>\n            <param name=\"uri\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.#ctor(NewLife.Net.NetType,System.Net.IPEndPoint)\">\n            <summary>实例化</summary>\n            <param name=\"protocol\"></param>\n            <param name=\"endpoint\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.#ctor(NewLife.Net.NetType,System.Net.IPAddress,System.Int32)\">\n            <summary>实例化</summary>\n            <param name=\"protocol\"></param>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.#ctor(NewLife.Net.NetType,System.String,System.Int32)\">\n            <summary>实例化</summary>\n            <param name=\"protocol\"></param>\n            <param name=\"host\"></param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.Parse(System.String)\">\n            <summary>分析</summary>\n            <param name=\"uri\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.ParseAddress(System.String)\">\n            <summary>分析地址</summary>\n            <param name=\"hostname\">主机地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.NetUri.op_Implicit(System.String)~NewLife.Net.NetUri\">\n            <summary>重载类型转换，字符串直接转为NetUri对象</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.ReceivedEventArgs\">\n            <summary>收到数据时的事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ReceivedEventArgs.Packet\">\n            <summary>数据包</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ReceivedEventArgs.Remote\">\n            <summary>远程地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ReceivedEventArgs.Message\">\n            <summary>解码后的消息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.ReceivedEventArgs.UserState\">\n            <summary>用户数据</summary>\n        </member>\n        <member name=\"T:NewLife.Net.SerialPortConfig\">\n            <summary>串口配置</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.PortName\">\n            <summary>串口名</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.BaudRate\">\n            <summary>波特率</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.DataBits\">\n            <summary>数据位</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.StopBits\">\n            <summary>停止位</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.Parity\">\n            <summary>奇偶校验</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.Encoding\">\n            <summary>文本编码</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.WebEncoding\">\n            <summary>编码</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.HexShow\">\n            <summary>十六进制显示</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.HexNewLine\">\n            <summary>十六进制自动换行</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.HexSend\">\n            <summary>十六进制发送</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.LastUpdate\">\n            <summary>最后更新时间</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.Extend\">\n            <summary>扩展数据</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.DtrEnable\">\n            <summary>DtrEnable</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.RtsEnable\">\n            <summary>RtsEnable</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialPortConfig.BreakState\">\n            <summary>BreakState</summary>\n        </member>\n        <member name=\"T:NewLife.Net.SerialTransport\">\n            <summary>串口传输</summary>\n            <example>\n            标准例程：\n            <code>\n            var st = new SerialTransport();\n            st.PortName = \"COM65\";  // 通讯口\n            st.FrameSize = 16;      // 数据帧大小\n            \n            st.Received += (s, e) =>\n            {\n                Console.WriteLine(\"收到 {0}\", e.ToHex());\n            };\n            // 开始异步操作\n            st.Open();\n            \n            //var buf = \"01080000801A\".ToHex();\n            var buf = \"0111C02C\".ToHex();\n            for (int i = 0; i &lt; 100; i++)\n            {\n                Console.WriteLine(\"发送 {0}\", buf.ToHex());\n                st.Send(buf);\n            \n                Thread.Sleep(1000);\n            }\n            </code>\n            </example>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.Serial\">\n            <summary>串口对象</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.PortName\">\n            <summary>端口名称。默认COM1</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.BaudRate\">\n            <summary>波特率。默认115200</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.Parity\">\n            <summary>奇偶校验位。默认None</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.DataBits\">\n            <summary>数据位。默认8</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.StopBits\">\n            <summary>停止位。默认One</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.Timeout\">\n            <summary>超时时间。超过该大小未收到数据，说明是另一帧。默认10ms</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.Description\">\n            <summary>描述信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.ByteTimeout\">\n            <summary>字节超时。数据包间隔，默认20ms</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.#ctor\">\n            <summary>串口传输</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.EnsureCreate\">\n            <summary>确保创建</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.Open\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.Close\">\n            <summary>关闭</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.Send(NewLife.Data.Packet)\">\n            <summary>写入数据</summary>\n            <param name=\"pk\">数据包</param>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.SendAsync(NewLife.Data.Packet)\">\n            <summary>异步发送数据并等待响应</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.Receive\">\n            <summary>接收数据</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.OnReceive(NewLife.Data.Packet)\">\n            <summary>处理收到的数据。默认匹配同步接收委托</summary>\n            <param name=\"pk\"></param>\n        </member>\n        <member name=\"E:NewLife.Net.SerialTransport.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"E:NewLife.Net.SerialTransport.Disconnected\">\n            <summary>断开时触发，可能是人为断开，也可能是串口链路断开</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.CheckDisconnect(System.Object)\">\n            <summary>检查串口是否已经断开</summary>\n            <remarks>\n            FX串口异步操作有严重的泄漏缺陷，如果外部硬件长时间断开，\n            SerialPort.IsOpen检测不到，并且会无限大占用内存。\n            </remarks>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.GetPortNames\">\n            <summary>获取带有描述的串口名，没有时返回空数组</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.GetNames\">\n            <summary>获取串口列表，名称和描述</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.Choose(System.String)\">\n            <summary>从串口列表选择串口，支持自动选择关键字</summary>\n            <param name=\"keyWord\">串口名称或者描述符的关键字</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Net.SerialTransport.Log\">\n            <summary>日志对象</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SerialTransport.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.SessionBase\">\n            <summary>会话基类</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.ID\">\n            <summary>标识</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Local\">\n            <summary>本地绑定信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Remote\">\n            <summary>远程结点地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Timeout\">\n            <summary>超时。默认3000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Active\">\n            <summary>是否活动</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Client\">\n            <summary>底层Socket</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.ThrowException\">\n            <summary>是否抛出异常，默认false不抛出。Send/Receive时可能发生异常，该设置决定是直接抛出异常还是通过<see cref=\"E:NewLife.Net.SessionBase.Error\"/>事件</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.StatSend\">\n            <summary>发送数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.StatReceive\">\n            <summary>接收数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.StartTime\">\n            <summary>通信开始时间</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.LastTime\">\n            <summary>最后一次通信时间，主要表示活跃时间，包括收发</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.DynamicPort\">\n            <summary>是否使用动态端口。如果Port为0则为动态端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.MaxAsync\">\n            <summary>最大并行接收数。Tcp默认1，Udp默认CPU*1.6，0关闭异步接收使用同步接收</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.ProcessAsync\">\n            <summary>异步处理接收到的数据，Tcp默认false，Udp默认true。</summary>\n            <remarks>异步处理有可能造成数据包乱序，特别是Tcp。true利于提升网络吞吐量。false避免拷贝，提升处理速度</remarks>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.BufferSize\">\n            <summary>缓冲区大小。默认8k</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.#ctor\">\n            <summary>构造函数，初始化默认名称</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.Open\">\n            <summary>打开</summary>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnOpen\">\n            <summary>打开</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.CheckDynamic\">\n            <summary>检查是否动态端口。如果是动态端口，则把随机得到的端口拷贝到Port</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnClose(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns></returns>\n        </member>\n        <member name=\"E:NewLife.Net.SessionBase.Opened\">\n            <summary>打开后触发。</summary>\n        </member>\n        <member name=\"E:NewLife.Net.SessionBase.Closed\">\n            <summary>关闭后触发。可实现掉线重连</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.Send(NewLife.Data.Packet)\">\n            <summary>直接发送数据包 Byte[]/Packet</summary>\n            <remarks>\n            目标地址由<seealso cref=\"P:NewLife.Net.SessionBase.Remote\"/>决定\n            </remarks>\n            <param name=\"pk\">数据包</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnSend(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <remarks>\n            目标地址由<seealso cref=\"P:NewLife.Net.SessionBase.Remote\"/>决定\n            </remarks>\n            <param name=\"pk\">数据包</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.Receive\">\n            <summary>接收数据</summary>\n            <returns></returns>\n        </member>\n        <member name=\"F:NewLife.Net.SessionBase._RecvCount\">\n            <summary>当前异步接收个数</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ReceiveAsync\">\n            <summary>开始异步接收</summary>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ReleaseRecv(System.Net.Sockets.SocketAsyncEventArgs,System.String)\">\n            <summary>释放一个事件参数</summary>\n            <param name=\"se\"></param>\n            <param name=\"reason\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ReceiveAsync(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)\">\n            <summary>用一个事件参数来开始异步接收</summary>\n            <param name=\"se\">事件参数</param>\n            <param name=\"io\">是否在IO线程调用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ProcessEvent(System.Net.Sockets.SocketAsyncEventArgs)\">\n            <summary>同步或异步收到数据</summary>\n            <param name=\"se\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.ProcessReceive(NewLife.Data.Packet,System.Net.IPEndPoint)\">\n            <summary>接收预处理，粘包拆包</summary>\n            <param name=\"pk\"></param>\n            <param name=\"remote\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnPreReceive(NewLife.Data.Packet,System.Net.IPEndPoint)\">\n            <summary>预处理</summary>\n            <param name=\"pk\">数据包</param>\n            <param name=\"remote\">远程地址</param>\n            <returns>将要处理该数据包的会话</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnReceive(NewLife.Net.ReceivedEventArgs)\">\n            <summary>处理收到的数据。默认匹配同步接收委托</summary>\n            <param name=\"e\">接收事件参数</param>\n            <returns>是否已处理，已处理的数据不再向下传递</returns>\n        </member>\n        <member name=\"E:NewLife.Net.SessionBase.Received\">\n            <summary>数据到达事件</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.RaiseReceive(System.Object,NewLife.Net.ReceivedEventArgs)\">\n            <summary>触发数据到达事件</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\">接收事件参数</param>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnReceiveError(System.Net.Sockets.SocketAsyncEventArgs)\">\n            <summary>收到异常时如何处理。默认关闭会话</summary>\n            <param name=\"se\"></param>\n            <returns>是否当作异常处理并结束会话</returns>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Pipeline\">\n            <summary>消息管道。收发消息都经过管道处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.CreateContext(NewLife.Net.ISocketRemote)\">\n            <summary>创建上下文</summary>\n            <param name=\"session\">远程会话</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.SendMessage(System.Object)\">\n            <summary>通过管道发送消息</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.SendMessageAsync(System.Object)\">\n            <summary>通过管道发送消息并等待响应</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.NewLife#Net#ISocketRemote#Process(NewLife.Data.IData)\">\n            <summary>处理数据帧</summary>\n            <param name=\"data\">数据帧</param>\n        </member>\n        <member name=\"E:NewLife.Net.SessionBase.Error\">\n            <summary>错误发生/断开连接时</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.OnError(System.String,System.Exception)\">\n            <summary>触发异常</summary>\n            <param name=\"action\">动作</param>\n            <param name=\"ex\">异常</param>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Items\">\n            <summary>数据项</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Item(System.String)\">\n            <summary>设置 或 获取 数据项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.Log\">\n            <summary>日志对象。禁止设为空对象</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.LogSend\">\n            <summary>是否输出发送日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionBase.LogReceive\">\n            <summary>是否输出接收日志。默认false</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionBase.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Net.SessionCollection\">\n            <summary>会话集合。带有自动清理不活动会话的功能</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionCollection.Server\">\n            <summary>服务端</summary>\n        </member>\n        <member name=\"P:NewLife.Net.SessionCollection.ClearPeriod\">\n            <summary>清理周期。单位毫秒，默认10秒。</summary>\n        </member>\n        <member name=\"F:NewLife.Net.SessionCollection.clearTimer\">\n            <summary>清理会话计时器</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionCollection.Add(NewLife.Net.ISocketSession)\">\n            <summary>添加新会话，并设置会话编号</summary>\n            <param name=\"session\"></param>\n            <returns>返回添加新会话是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionCollection.Get(System.String)\">\n            <summary>获取会话，加锁</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SessionCollection.CloseAll\">\n            <summary>关闭所有</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SessionCollection.RemoveNotAlive(System.Object)\">\n            <summary>移除不活动的会话</summary>\n        </member>\n        <member name=\"T:NewLife.Net.Setting\">\n            <summary>网络设置</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Setting.Debug\">\n            <summary>网络调试</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Setting.SessionTimeout\">\n            <summary>会话超时时间。默认20*60秒</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Setting.BufferSize\">\n            <summary>缓冲区大小。默认64k</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Setting.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"T:NewLife.Net.SocketHelper\">\n            <summary>Socket扩展</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.SendAsync(System.Net.Sockets.Socket,System.Byte[])\">\n            <summary>异步发送数据</summary>\n            <param name=\"socket\"></param>\n            <param name=\"buffer\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.SendToAsync(System.Net.Sockets.Socket,System.Byte[],System.Net.IPEndPoint)\">\n            <summary>异步发送数据</summary>\n            <param name=\"socket\"></param>\n            <param name=\"buffer\"></param>\n            <param name=\"remote\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Send(System.Net.Sockets.Socket,System.IO.Stream,System.Net.IPEndPoint)\">\n            <summary>发送数据流</summary>\n            <param name=\"socket\"></param>\n            <param name=\"stream\"></param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Send(System.Net.Sockets.Socket,System.Byte[],System.Net.IPEndPoint)\">\n            <summary>向指定目的地发送信息</summary>\n            <param name=\"socket\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Send(System.Net.Sockets.Socket,System.String,System.Text.Encoding,System.Net.IPEndPoint)\">\n            <summary>向指定目的地发送信息</summary>\n            <param name=\"socket\"></param>\n            <param name=\"message\"></param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Broadcast(System.Net.Sockets.Socket,System.Byte[],System.Int32)\">\n            <summary>广播数据包</summary>\n            <param name=\"socket\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Broadcast(System.Net.Sockets.Socket,System.String,System.Int32)\">\n            <summary>广播字符串</summary>\n            <param name=\"socket\"></param>\n            <param name=\"message\"></param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.ReceiveString(System.Net.Sockets.Socket,System.Text.Encoding)\">\n            <summary>接收字符串</summary>\n            <param name=\"socket\"></param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.CheckBroadcast(System.Net.Sockets.Socket,System.Net.IPAddress)\">\n            <summary>检查并开启广播</summary>\n            <param name=\"socket\"></param>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.Shutdown(System.Net.Sockets.Socket,System.Boolean)\">\n            <summary>关闭连接</summary>\n            <param name=\"socket\"></param>\n            <param name=\"reuseAddress\"></param>\n        </member>\n        <member name=\"P:NewLife.Net.SocketHelper.mSafeHandle\">\n            <summary>SafeHandle字段</summary>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.IsNotClosed(System.Net.Sockets.SocketAsyncEventArgs)\">\n            <summary>Socket是否未被关闭</summary>\n            <param name=\"se\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.SocketHelper.GetException(System.Net.Sockets.SocketAsyncEventArgs)\">\n            <summary>根据异步事件获取可输出异常，屏蔽常见异常</summary>\n            <param name=\"se\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.TcpServer\">\n            <summary>TCP服务器</summary>\n            <remarks>\n            核心工作：启动服务<see cref=\"M:NewLife.Net.TcpServer.Start\"/>时，监听端口，并启用多个（逻辑处理器数的10倍）异步接受操作<see cref=\"M:NewLife.Net.TcpServer.AcceptAsync(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)\"/>。\n            \n            服务器完全处于异步工作状态，任何操作都不可能被阻塞。\n            \n            注意：服务器接受连接请求后，不会开始处理数据，而是由<see cref=\"E:NewLife.Net.TcpServer.NewSession\"/>事件订阅者决定何时开始处理数据。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Local\">\n            <summary>本地绑定信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.SessionTimeout\">\n            <summary>会话超时时间</summary>\n            <remarks>\n            对于每一个会话连接，如果超过该时间仍然没有收到任何数据，则断开会话连接。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.ProcessAsync\">\n            <summary>异步处理接收到的数据，默认false。</summary>\n            <remarks>异步处理有可能造成数据包乱序，特别是Tcp。true利于提升网络吞吐量。false避免拷贝，提升处理速度</remarks>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Client\">\n            <summary>底层Socket</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Active\">\n            <summary>是否活动</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.ThrowException\">\n            <summary>是否抛出异常，默认false不抛出。Send/Receive时可能发生异常，该设置决定是直接抛出异常还是通过<see cref=\"E:NewLife.Net.TcpServer.Error\"/>事件</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.MaxAsync\">\n            <summary>最大并行接收连接数。默认CPU*1.6</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.EnableHttp\">\n            <summary>启用Http，数据处理时截去请求响应头，默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.StatSession\">\n            <summary>会话统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.StatSend\">\n            <summary>发送统计</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.StatReceive\">\n            <summary>接收统计</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.#ctor\">\n            <summary>构造TCP服务器对象</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.#ctor(System.Int32)\">\n            <summary>构造TCP服务器对象</summary>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.OnDispose(System.Boolean)\">\n            <summary>已重载。释放会话集合等资源</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.Start\">\n            <summary>开始</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.Stop(System.String)\">\n            <summary>停止</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"E:NewLife.Net.TcpServer.NewSession\">\n            <summary>新会话时触发</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.AcceptAsync(System.Net.Sockets.SocketAsyncEventArgs,System.Boolean)\">\n            <summary>开启异步接受新连接</summary>\n            <param name=\"se\"></param>\n            <param name=\"io\">是否IO线程</param>\n            <returns>开启异步是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.OnAccept(System.Net.Sockets.Socket)\">\n            <summary>收到新连接时处理</summary>\n            <param name=\"client\"></param>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Sessions\">\n            <summary>会话集合。用地址端口作为标识，业务应用自己维持地址端口与业务主键的对应关系。</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.CreateSession(System.Net.Sockets.Socket)\">\n            <summary>创建会话</summary>\n            <param name=\"client\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"E:NewLife.Net.TcpServer.Error\">\n            <summary>错误发生/断开连接时</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.OnError(System.String,System.Exception)\">\n            <summary>触发异常</summary>\n            <param name=\"action\">动作</param>\n            <param name=\"ex\">异常</param>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.Log\">\n            <summary>日志对象</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.LogSend\">\n            <summary>是否输出发送日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpServer.LogReceive\">\n            <summary>是否输出接收日志。默认false</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpServer.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.TcpSession\">\n            <summary>增强TCP客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.DisconnectWhenEmptyData\">\n            <summary>收到空数据时抛出异常并断开连接。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.NewLife#Net#ISocketSession#Server\">\n            <summary>Socket服务器。当前通讯所在的Socket服务器，其实是TcpServer/UdpServer。该属性决定本会话是客户端会话还是服务的会话</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.AutoReconnect\">\n            <summary>自动重连次数，默认3。发生异常断开连接时，自动重连服务端。</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.MatchEmpty\">\n            <summary>是否匹配空包。Http协议需要</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.NoDelay\">\n            <summary>不延迟直接发送。Tcp为了合并小包而设计，客户端默认false，服务端默认true</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.#ctor\">\n            <summary>实例化增强TCP</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.#ctor(System.Int32)\">\n            <summary>使用监听口初始化</summary>\n            <param name=\"listenPort\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.#ctor(System.Net.Sockets.Socket)\">\n            <summary>用TCP客户端初始化</summary>\n            <param name=\"client\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.OnOpen\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.OnClose(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.OnSend(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <remarks>\n            目标地址由<seealso cref=\"P:NewLife.Net.SessionBase.Remote\"/>决定\n            </remarks>\n            <param name=\"pk\">数据包</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.OnPreReceive(NewLife.Data.Packet,System.Net.IPEndPoint)\">\n            <summary>预处理</summary>\n            <param name=\"pk\">数据包</param>\n            <param name=\"remote\">远程地址</param>\n            <returns>将要处理该数据包的会话</returns>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.OnReceive(NewLife.Net.ReceivedEventArgs)\">\n            <summary>处理收到的数据</summary>\n            <param name=\"e\">接收事件参数</param>\n        </member>\n        <member name=\"F:NewLife.Net.TcpSession._Reconnect\">\n            <summary>重连次数</summary>\n        </member>\n        <member name=\"P:NewLife.Net.TcpSession.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"M:NewLife.Net.TcpSession.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.UdpServer\">\n            <summary>增强的UDP</summary>\n            <remarks>\n            如果已经打开异步接收，还要使用同步接收，则同步Receive内部不再调用底层Socket，而是等待截走异步数据。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.UdpServer.SessionTimeout\">\n            <summary>会话超时时间</summary>\n            <remarks>\n            对于每一个会话连接，如果超过该时间仍然没有收到任何数据，则断开会话连接。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.UdpServer.LastRemote\">\n            <summary>最后一次同步接收数据得到的远程地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpServer.Loopback\">\n            <summary>是否接收来自自己广播的环回数据。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpServer.StatSession\">\n            <summary>会话统计</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.#ctor\">\n            <summary>实例化增强UDP</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.#ctor(System.Int32)\">\n            <summary>使用监听口初始化</summary>\n            <param name=\"listenPort\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnOpen\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnClose(System.String)\">\n            <summary>关闭</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnSend(NewLife.Data.Packet)\">\n            <summary>发送数据</summary>\n            <remarks>\n            目标地址由<seealso cref=\"P:NewLife.Net.SessionBase.Remote\"/>决定\n            </remarks>\n            <param name=\"pk\">数据包</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.SendMessageAsync(System.Object)\">\n            <summary>发送消息并等待响应。必须调用会话的发送，否则配对会失败</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnPreReceive(NewLife.Data.Packet,System.Net.IPEndPoint)\">\n            <summary>预处理</summary>\n            <param name=\"pk\">数据包</param>\n            <param name=\"remote\">远程地址</param>\n            <returns>将要处理该数据包的会话</returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnReceive(NewLife.Net.ReceivedEventArgs)\">\n            <summary>处理收到的数据</summary>\n            <param name=\"e\">接收事件参数</param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.OnReceiveError(System.Net.Sockets.SocketAsyncEventArgs)\">\n            <summary>收到异常时如何处理。Tcp/Udp客户端默认关闭会话，但是Udp服务端不能关闭服务器，仅关闭会话</summary>\n            <param name=\"se\"></param>\n            <returns>是否当作异常处理并结束会话</returns>\n        </member>\n        <member name=\"E:NewLife.Net.UdpServer.NewSession\">\n            <summary>新会话时触发</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpServer.Sessions\">\n            <summary>会话集合。用地址端口作为标识，业务应用自己维持地址端口与业务主键的对应关系。</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.CreateSession(System.Net.IPEndPoint)\">\n            <summary>创建会话</summary>\n            <param name=\"remoteEP\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpServer.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.UdpHelper\">\n            <summary>Udp扩展</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.Send(System.Net.Sockets.UdpClient,System.IO.Stream,System.Net.IPEndPoint)\">\n            <summary>发送数据流</summary>\n            <param name=\"udp\"></param>\n            <param name=\"stream\"></param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.Send(System.Net.Sockets.UdpClient,System.Byte[],System.Net.IPEndPoint)\">\n            <summary>向指定目的地发送信息</summary>\n            <param name=\"udp\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.Send(System.Net.Sockets.UdpClient,System.String,System.Text.Encoding,System.Net.IPEndPoint)\">\n            <summary>向指定目的地发送信息</summary>\n            <param name=\"udp\"></param>\n            <param name=\"message\"></param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <param name=\"remoteEP\"></param>\n            <returns>返回自身，用于链式写法</returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.Broadcast(System.Net.Sockets.UdpClient,System.Byte[],System.Int32)\">\n            <summary>广播数据包</summary>\n            <param name=\"udp\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.Broadcast(System.Net.Sockets.UdpClient,System.String,System.Int32)\">\n            <summary>广播字符串</summary>\n            <param name=\"udp\"></param>\n            <param name=\"message\"></param>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpHelper.ReceiveString(System.Net.Sockets.UdpClient,System.Text.Encoding)\">\n            <summary>接收字符串</summary>\n            <param name=\"udp\"></param>\n            <param name=\"encoding\">文本编码，默认null表示UTF-8编码</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Net.UdpSession\">\n            <summary>Udp会话。仅用于服务端与某一固定远程地址通信</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.ID\">\n            <summary>会话编号</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Server\">\n            <summary>服务器</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.NewLife#Net#ISocket#Client\">\n            <summary>底层Socket</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Local\">\n            <summary>本地地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Remote\">\n            <summary>远程地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Timeout\">\n            <summary>超时。默认3000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Pipeline\">\n            <summary>管道</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.NewLife#Net#ISocketSession#Server\">\n            <summary>Socket服务器。当前通讯所在的Socket服务器，其实是TcpServer/UdpServer</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.ThrowException\">\n            <summary>是否抛出异常，默认false不抛出。Send/Receive时可能发生异常，该设置决定是直接抛出异常还是通过<see cref=\"E:NewLife.Net.UdpSession.Error\"/>事件</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.ProcessAsync\">\n            <summary>异步处理接收到的数据，默认true利于提升网络吞吐量。</summary>\n            <remarks>异步处理有可能造成数据包乱序，特别是Tcp。false避免拷贝，提升处理速度</remarks>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.StatSend\">\n            <summary>发送数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.StatReceive\">\n            <summary>接收数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.StartTime\">\n            <summary>通信开始时间</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.LastTime\">\n            <summary>最后一次通信时间，主要表示活跃时间，包括收发</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.BufferSize\">\n            <summary>缓冲区大小。默认8k</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.SendMessage(System.Object)\">\n            <summary>发送消息</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.SendMessageAsync(System.Object)\">\n            <summary>发送消息并等待响应</summary>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.Receive\">\n            <summary>接收数据</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.NewLife#Net#ISocketRemote#Process(NewLife.Data.IData)\">\n            <summary>处理数据帧</summary>\n            <param name=\"data\">数据帧</param>\n        </member>\n        <member name=\"E:NewLife.Net.UdpSession.Error\">\n            <summary>错误发生/断开连接时</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.OnError(System.String,System.Exception)\">\n            <summary>触发异常</summary>\n            <param name=\"action\">动作</param>\n            <param name=\"ex\">异常</param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Items\">\n            <summary>数据项</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Item(System.String)\">\n            <summary>设置 或 获取 数据项</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.LogSend\">\n            <summary>是否输出发送日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.LogReceive\">\n            <summary>是否输出接收日志。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Net.UdpSession.LogPrefix\">\n            <summary>日志前缀</summary>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.UdpSession.WriteDebugLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Net.Upgrade\">\n            <summary>升级更新</summary>\n            <remarks>\n            自动更新的难点在于覆盖正在使用的exe/dll文件，通过改名可以解决\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Server\">\n            <summary>服务器地址</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Version\">\n            <summary>版本</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Compile\">\n            <summary>本地编译时间</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.AutoStart\">\n            <summary>更新完成以后自动启动主程序</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.UpdatePath\">\n            <summary>更新目录</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Links\">\n            <summary>超链接信息，其中第一个为最佳匹配项</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.SourceFile\">\n            <summary>更新源文件</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.#ctor\">\n            <summary>实例化一个升级对象实例，获取当前应用信息</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.Check\">\n            <summary>获取版本信息，检查是否需要更新</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.Download\">\n            <summary>开始更新</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.Update\">\n            <summary>检查并执行更新操作</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.CopyAndReplace(System.String)\">\n            <summary>正在使用锁定的文件不可删除，但可以改名</summary>\n            <param name=\"source\"></param>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.DeleteBuckup\">\n            <summary>删除备份文件</summary>\n        </member>\n        <member name=\"P:NewLife.Net.Upgrade.Log\">\n            <summary>日志对象</summary>\n        </member>\n        <member name=\"M:NewLife.Net.Upgrade.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Reflection.AssemblyX\">\n            <summary>程序集辅助类。使用Create创建，保证每个程序集只有一个辅助类</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Asm\">\n            <summary>程序集</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Version\">\n            <summary>程序集版本</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Title\">\n            <summary>程序集标题</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.FileVersion\">\n            <summary>文件版本</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Compile\">\n            <summary>编译时间</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.CompileVersion\">\n            <summary>编译版本</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Company\">\n            <summary>公司名称</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Description\">\n            <summary>说明</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Location\">\n            <summary>获取包含清单的已加载文件的路径或 UNC 位置。</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.Create(System.Reflection.Assembly)\">\n            <summary>创建程序集辅助对象</summary>\n            <param name=\"asm\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Types\">\n            <summary>类型集合，当前程序集的所有类型，包括私有和内嵌，非内嵌请直接调用Asm.GetTypes()</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.IsSystemAssembly\">\n            <summary>是否系统程序集</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.Entry\">\n            <summary>入口程序集</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.GetType(System.String)\">\n            <summary>从程序集中查找指定名称的类型</summary>\n            <param name=\"typeName\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.GetTypeInternal(System.String)\">\n            <summary>在程序集中查找类型</summary>\n            <param name=\"typeName\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.FindPlugins``1\">\n            <summary>查找插件</summary>\n            <typeparam name=\"TPlugin\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.FindPlugins(System.Type)\">\n            <summary>查找插件，带缓存</summary>\n            <param name=\"baseType\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.FindAllPlugins(System.Type,System.Boolean,System.Boolean)\">\n            <summary>查找所有非系统程序集中的所有插件</summary>\n            <remarks>继承类所在的程序集会引用baseType所在的程序集，利用这一点可以做一定程度的性能优化。</remarks>\n            <param name=\"baseType\"></param>\n            <param name=\"isLoadAssembly\">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>\n            <param name=\"excludeGlobalTypes\">指示是否应检查来自所有引用程序集的类型。如果为 false，则检查来自所有引用程序集的类型。 否则，只检查来自非全局程序集缓存 (GAC) 引用的程序集的类型。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.IsReferencedFrom(System.Reflection.Assembly,System.String)\">\n            <summary><paramref name=\"asm\"/> 是否引用了 <paramref name=\"baseAsmName\"/></summary>\n            <param name=\"asm\">程序集</param>\n            <param name=\"baseAsmName\">被引用程序集全名</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.GetType(System.String,System.Boolean)\">\n            <summary>根据名称获取类型</summary>\n            <param name=\"typeName\">类型名</param>\n            <param name=\"isLoadAssembly\">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.GetAssemblies(System.AppDomain)\">\n            <summary>获取指定程序域所有程序集</summary>\n            <param name=\"domain\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Reflection.AssemblyX.AssemblyPaths\">\n            <summary>程序集目录集合</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.ReflectionOnlyGetAssemblies\">\n            <summary>获取当前程序域所有只反射程序集的辅助类</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.ReflectionOnlyLoad(System.String)\">\n            <summary>只反射加载指定路径的所有程序集</summary>\n            <param name=\"path\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.ReflectionOnlyLoadFrom(System.String,System.Version)\">\n            <summary>只反射加载指定路径的所有程序集</summary>\n            <param name=\"file\"></param>\n            <param name=\"ver\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.GetMyAssemblies\">\n            <summary>获取当前应用程序的所有程序集，不包括系统程序集，仅限本目录</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.OnResolve(System.String)\">\n            <summary>在对程序集的解析失败时发生</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.AssemblyX.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.DynamicInternal\">\n            <summary>包装程序集内部类的动态对象</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicInternal.TryConvert(System.Dynamic.ConvertBinder,System.Object@)\">\n            <summary>类型转换</summary>\n            <param name=\"binder\"></param>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicInternal.TryGetMember(System.Dynamic.GetMemberBinder,System.Object@)\">\n            <summary>成员取值</summary>\n            <param name=\"binder\"></param>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicInternal.TryInvokeMember(System.Dynamic.InvokeMemberBinder,System.Object[],System.Object@)\">\n            <summary>调用成员</summary>\n            <param name=\"binder\"></param>\n            <param name=\"args\"></param>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicInternal.Wrap(System.Object)\">\n            <summary>包装</summary>\n            <param name=\"obj\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicInternal.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.DynamicXml\">\n            <summary>动态Xml</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.DynamicXml.Node\">\n            <summary>节点</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicXml.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicXml.#ctor(System.Xml.Linq.XElement)\">\n            <summary>实例化</summary>\n            <param name=\"node\"></param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicXml.#ctor(System.String)\">\n            <summary>实例化</summary>\n            <param name=\"name\"></param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicXml.TrySetMember(System.Dynamic.SetMemberBinder,System.Object)\">\n            <summary>设置</summary>\n            <param name=\"binder\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DynamicXml.TryGetMember(System.Dynamic.GetMemberBinder,System.Object@)\">\n            <summary>获取</summary>\n            <param name=\"binder\"></param>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.IIndexAccessor\">\n            <summary>\n            索引器接访问口。\n            该接口用于通过名称快速访问对象属性或字段（属性优先）。\n            </summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.IIndexAccessor.Item(System.String)\">\n            <summary>获取/设置 指定名称的属性或字段的值</summary>\n            <param name=\"name\">名称</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.IReflect\">\n            <summary>反射接口</summary>\n            <remarks>该接口仅用于扩展，不建议外部使用</remarks>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetType(System.String,System.Boolean)\">\n            <summary>根据名称获取类型</summary>\n            <param name=\"typeName\">类型名</param>\n            <param name=\"isLoadAssembly\">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetMethod(System.Type,System.String,System.Type[])\">\n            <summary>获取方法</summary>\n            <remarks>用于具有多个签名的同名方法的场合，不确定是否存在性能问题，不建议普通场合使用</remarks>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"paramTypes\">参数类型数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetMethods(System.Type,System.String,System.Int32)\">\n            <summary>获取指定名称的方法集合，支持指定参数个数来匹配过滤</summary>\n            <param name=\"type\"></param>\n            <param name=\"name\"></param>\n            <param name=\"paramCount\">参数个数，-1表示不过滤参数个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetProperty(System.Type,System.String,System.Boolean)\">\n            <summary>获取属性</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetField(System.Type,System.String,System.Boolean)\">\n            <summary>获取字段</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetMember(System.Type,System.String,System.Boolean)\">\n            <summary>获取成员</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetFields(System.Type,System.Boolean)\">\n            <summary>获取字段</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetProperties(System.Type,System.Boolean)\">\n            <summary>获取属性</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.CreateInstance(System.Type,System.Object[])\">\n            <summary>反射创建指定类型的实例</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"parameters\">参数数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.Invoke(System.Object,System.Reflection.MethodBase,System.Object[])\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.InvokeWithParams(System.Object,System.Reflection.MethodBase,System.Collections.IDictionary)\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数字典</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetValue(System.Object,System.Reflection.PropertyInfo)\">\n            <summary>获取目标对象的属性值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"property\">属性</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetValue(System.Object,System.Reflection.FieldInfo)\">\n            <summary>获取目标对象的字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"field\">字段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.SetValue(System.Object,System.Reflection.PropertyInfo,System.Object)\">\n            <summary>设置目标对象的属性值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"property\">属性</param>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.SetValue(System.Object,System.Reflection.FieldInfo,System.Object)\">\n            <summary>设置目标对象的字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"field\">字段</param>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.Copy(System.Object,System.Object,System.Boolean,System.String[])\">\n            <summary>从源对象拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"src\">源对象</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n            <param name=\"excludes\">要忽略的成员</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.Copy(System.Object,System.Collections.Generic.IDictionary{System.String,System.Object},System.Boolean)\">\n            <summary>从源字典拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"dic\">源字典</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetElementType(System.Type)\">\n            <summary>获取一个类型的元素类型</summary>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.ChangeType(System.Object,System.Type)\">\n            <summary>类型转换</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"conversionType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetName(System.Type,System.Boolean)\">\n            <summary>获取类型的友好名称</summary>\n            <param name=\"type\">指定类型</param>\n            <param name=\"isfull\">是否全名，包含命名空间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.As(System.Type,System.Type)\">\n            <summary>是否能够转为指定基类</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetSubclasses(System.Reflection.Assembly,System.Type)\">\n            <summary>在指定程序集中查找指定基类或接口的所有子类实现</summary>\n            <param name=\"asm\">指定程序集</param>\n            <param name=\"baseType\">基类或接口，为空时返回所有类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.IReflect.GetAllSubclasses(System.Type,System.Boolean)\">\n            <summary>在所有程序集中查找指定基类或接口的子类实现</summary>\n            <param name=\"baseType\">基类或接口</param>\n            <param name=\"isLoadAssembly\">是否加载为加载程序集</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.DefaultReflect\">\n            <summary>默认反射实现</summary>\n            <remarks>该接口仅用于扩展，不建议外部使用</remarks>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetType(System.String,System.Boolean)\">\n            <summary>根据名称获取类型</summary>\n            <param name=\"typeName\">类型名</param>\n            <param name=\"isLoadAssembly\">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetMethod(System.Type,System.String,System.Type[])\">\n            <summary>获取方法</summary>\n            <remarks>用于具有多个签名的同名方法的场合，不确定是否存在性能问题，不建议普通场合使用</remarks>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"paramTypes\">参数类型数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetMethods(System.Type,System.String,System.Int32)\">\n            <summary>获取指定名称的方法集合，支持指定参数个数来匹配过滤</summary>\n            <param name=\"type\"></param>\n            <param name=\"name\"></param>\n            <param name=\"paramCount\">参数个数，-1表示不过滤参数个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetProperty(System.Type,System.String,System.Boolean)\">\n            <summary>获取属性</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetField(System.Type,System.String,System.Boolean)\">\n            <summary>获取字段</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetMember(System.Type,System.String,System.Boolean)\">\n            <summary>获取成员</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetFields(System.Type,System.Boolean)\">\n            <summary>获取字段</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetProperties(System.Type,System.Boolean)\">\n            <summary>获取属性</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.CreateInstance(System.Type,System.Object[])\">\n            <summary>反射创建指定类型的实例</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"parameters\">参数数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.Invoke(System.Object,System.Reflection.MethodBase,System.Object[])\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.InvokeWithParams(System.Object,System.Reflection.MethodBase,System.Collections.IDictionary)\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数字典</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetValue(System.Object,System.Reflection.PropertyInfo)\">\n            <summary>获取目标对象的属性值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"property\">属性</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetValue(System.Object,System.Reflection.FieldInfo)\">\n            <summary>获取目标对象的字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"field\">字段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.SetValue(System.Object,System.Reflection.PropertyInfo,System.Object)\">\n            <summary>设置目标对象的属性值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"property\">属性</param>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.SetValue(System.Object,System.Reflection.FieldInfo,System.Object)\">\n            <summary>设置目标对象的字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"field\">字段</param>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.Copy(System.Object,System.Object,System.Boolean,System.String[])\">\n            <summary>从源对象拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"src\">源对象</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n            <param name=\"excludes\">要忽略的成员</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.Copy(System.Object,System.Collections.Generic.IDictionary{System.String,System.Object},System.Boolean)\">\n            <summary>从源字典拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"dic\">源字典</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetElementType(System.Type)\">\n            <summary>获取一个类型的元素类型</summary>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.ChangeType(System.Object,System.Type)\">\n            <summary>类型转换</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"conversionType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetName(System.Type,System.Boolean)\">\n            <summary>获取类型的友好名称</summary>\n            <param name=\"type\">指定类型</param>\n            <param name=\"isfull\">是否全名，包含命名空间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.As(System.Type,System.Type)\">\n            <summary>是否子类</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetSubclasses(System.Reflection.Assembly,System.Type)\">\n            <summary>在指定程序集中查找指定基类的子类</summary>\n            <param name=\"asm\">指定程序集</param>\n            <param name=\"baseType\">基类或接口，为空时返回所有类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetAllSubclasses(System.Type,System.Boolean)\">\n            <summary>在所有程序集中查找指定基类或接口的子类实现</summary>\n            <param name=\"baseType\">基类或接口</param>\n            <param name=\"isLoadAssembly\">是否加载为加载程序集</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.DefaultReflect.GetType(System.Object@)\">\n            <summary>获取类型，如果target是Type类型，则表示要反射的是静态成员</summary>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.Reflect\">\n            <summary>反射工具类</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.Reflect.Provider\">\n            <summary>当前反射提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetTypeEx(System.String,System.Boolean)\">\n            <summary>根据名称获取类型。可搜索当前目录DLL，自动加载</summary>\n            <param name=\"typeName\">类型名</param>\n            <param name=\"isLoadAssembly\">是否从未加载程序集中获取类型。使用仅反射的方法检查目标类型，如果存在，则进行常规加载</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetMethodEx(System.Type,System.String,System.Type[])\">\n            <summary>获取方法</summary>\n            <remarks>用于具有多个签名的同名方法的场合，不确定是否存在性能问题，不建议普通场合使用</remarks>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"paramTypes\">参数类型数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetMethodsEx(System.Type,System.String,System.Int32)\">\n            <summary>获取指定名称的方法集合，支持指定参数个数来匹配过滤</summary>\n            <param name=\"type\"></param>\n            <param name=\"name\"></param>\n            <param name=\"paramCount\">参数个数，-1表示不过滤参数个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetPropertyEx(System.Type,System.String,System.Boolean)\">\n            <summary>获取属性。搜索私有、静态、基类，优先返回大小写精确匹配成员</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetFieldEx(System.Type,System.String,System.Boolean)\">\n            <summary>获取字段。搜索私有、静态、基类，优先返回大小写精确匹配成员</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetMemberEx(System.Type,System.String,System.Boolean)\">\n            <summary>获取成员。搜索私有、静态、基类，优先返回大小写精确匹配成员</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"name\">名称</param>\n            <param name=\"ignoreCase\">忽略大小写</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetFields(System.Type,System.Boolean)\">\n            <summary>获取用于序列化的字段</summary>\n            <remarks>过滤<seealso cref=\"T:NonSerializedAttribute\"/>特性的字段</remarks>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetProperties(System.Type,System.Boolean)\">\n            <summary>获取用于序列化的属性</summary>\n            <remarks>过滤<seealso cref=\"T:XmlIgnoreAttribute\"/>特性的属性和索引器</remarks>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.CreateInstance(System.Type,System.Object[])\">\n            <summary>反射创建指定类型的实例</summary>\n            <param name=\"type\">类型</param>\n            <param name=\"parameters\">参数数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.Invoke(System.Object,System.String,System.Object[])\">\n            <summary>反射调用指定对象的方法。target为类型时调用其静态方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"name\">方法名</param>\n            <param name=\"parameters\">方法参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.TryInvoke(System.Object,System.String,System.Object@,System.Object[])\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"name\">方法名</param>\n            <param name=\"value\">数值</param>\n            <param name=\"parameters\">方法参数</param>\n            <remarks>反射调用是否成功</remarks>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.Invoke(System.Object,System.Reflection.MethodBase,System.Object[])\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.InvokeWithParams(System.Object,System.Reflection.MethodBase,System.Collections.IDictionary)\">\n            <summary>反射调用指定对象的方法</summary>\n            <param name=\"target\">要调用其方法的对象，如果要调用静态方法，则target是类型</param>\n            <param name=\"method\">方法</param>\n            <param name=\"parameters\">方法参数字典</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetValue(System.Object,System.String,System.Boolean)\">\n            <summary>获取目标对象指定名称的属性/字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"name\">名称</param>\n            <param name=\"throwOnError\">出错时是否抛出异常</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.TryGetValue(System.Object,System.String,System.Object@)\">\n            <summary>获取目标对象指定名称的属性/字段值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"name\">名称</param>\n            <param name=\"value\">数值</param>\n            <returns>是否成功获取数值</returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetValue(System.Object,System.Reflection.MemberInfo)\">\n            <summary>获取目标对象的成员值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"member\">成员</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.SetValue(System.Object,System.String,System.Object)\">\n            <summary>设置目标对象指定名称的属性/字段值，若不存在返回false</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"name\">名称</param>\n            <param name=\"value\">数值</param>\n            <remarks>反射调用是否成功</remarks>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.SetValue(System.Object,System.Reflection.MemberInfo,System.Object)\">\n            <summary>设置目标对象的成员值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"member\">成员</param>\n            <param name=\"value\">数值</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.Copy(System.Object,System.Object,System.Boolean,System.String[])\">\n            <summary>从源对象拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"src\">源对象</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n            <param name=\"excludes\">要忽略的成员</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.Copy(System.Object,System.Collections.Generic.IDictionary{System.String,System.Object},System.Boolean)\">\n            <summary>从源字典拷贝数据到目标对象</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"dic\">源字典</param>\n            <param name=\"deep\">递归深度拷贝，直接拷贝成员值而不是引用</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetElementTypeEx(System.Type)\">\n            <summary>获取一个类型的元素类型</summary>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.ChangeType(System.Object,System.Type)\">\n            <summary>类型转换</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"conversionType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.ChangeType``1(System.Object)\">\n            <summary>类型转换</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"value\">数值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetName(System.Type,System.Boolean)\">\n            <summary>获取类型的友好名称</summary>\n            <param name=\"type\">指定类型</param>\n            <param name=\"isfull\">是否全名，包含命名空间</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetTypeArray(System.Object[])\">\n            <summary>从参数数组中获取类型数组</summary>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetMemberType(System.Reflection.MemberInfo)\">\n            <summary>获取成员的类型，字段和属性是它们的类型，方法是返回类型，类型是自身</summary>\n            <param name=\"member\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetTypeCode(System.Type)\">\n            <summary>获取类型代码</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.IsInt(System.Type)\">\n            <summary>是否整数</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.IsList(System.Type)\">\n            <summary>是否泛型列表</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.IsDictionary(System.Type)\">\n            <summary>是否泛型字典</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.As(System.Type,System.Type)\">\n            <summary>是否能够转为指定基类</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.As``1(System.Type)\">\n            <summary>是否能够转为指定基类</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetSubclasses(System.Reflection.Assembly,System.Type)\">\n            <summary>在指定程序集中查找指定基类的子类</summary>\n            <param name=\"asm\">指定程序集</param>\n            <param name=\"baseType\">基类或接口</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetAllSubclasses(System.Type,System.Boolean)\">\n            <summary>在所有程序集中查找指定基类或接口的子类实现</summary>\n            <param name=\"baseType\">基类或接口</param>\n            <param name=\"isLoadAssembly\">是否加载为加载程序集</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.GetType(System.Object@)\">\n            <summary>获取类型，如果target是Type类型，则表示要反射的是静态成员</summary>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.IsNullable(System.Type)\">\n            <summary>判断某个类型是否可空类型</summary>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.Reflect.As``1(System.Reflection.MethodInfo,System.Object)\">\n            <summary>把一个方法转为泛型委托，便于快速反射调用</summary>\n            <typeparam name=\"TFunc\"></typeparam>\n            <param name=\"method\"></param>\n            <param name=\"target\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Reflection.ScriptEngine\">\n            <summary>脚本引擎</summary>\n            <remarks>\n            三大用法：\n            1，单个表达式，根据参数计算表达式结果并返回\n            2，多个语句，最后有返回语句\n            3，多个方法，有一个名为Execute的静态方法作为入口方法\n            \n            脚本引擎禁止实例化，必须通过<see cref=\"M:NewLife.Reflection.ScriptEngine.Create(System.String,System.Boolean)\"/>方法创建，以代码为键进行缓存，避免重复创建反复编译形成泄漏。\n            其中<see cref=\"M:NewLife.Reflection.ScriptEngine.Create(System.String,System.Boolean)\"/>方法的第二个参数为true表示前两种用法，为false表示第三种用法。\n            </remarks>\n            <example>\n            最简单而完整的用法：\n            <code>\n            // 根据代码创建脚本实例，相同代码只编译一次\n            var se = ScriptEngine.Create(\"a+b\");\n            // 如果Method为空说明未编译，可设置参数\n            if (se.Method == null)\n            {\n                se.Parameters.Add(\"a\", typeof(Int32));\n                se.Parameters.Add(\"b\", typeof(Int32));\n            }\n            // 脚本固定返回Object类型，需要自己转换\n            var n = (Int32)se.Invoke(2, 3);\n            Console.WriteLine(\"2+3={0}\", n);\n            </code>\n            \n            无参数快速调用：\n            <code>\n            var n = (Int32)ScriptEngine.Execute(\"2*3\");\n            </code>\n            \n            约定参数快速调用：\n            <code>\n            var n = (Int32)ScriptEngine.Execute(\"p0*p1\", new Object[] { 2, 3 });\n            Console.WriteLine(\"2*3={0}\", n);\n            </code>\n            </example>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.Code\">\n            <summary>代码</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.IsExpression\">\n            <summary>是否表达式</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.Parameters\">\n            <summary>参数集合。编译后就不可修改。</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.FinalCode\">\n            <summary>最终代码</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.Type\">\n            <summary>编译得到的类型</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.Method\">\n            <summary>根据代码编译出来可供直接调用的入口方法，Eval/Main</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.NameSpaces\">\n            <summary>命名空间集合</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.ReferencedAssemblies\">\n            <summary>引用程序集集合</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"P:NewLife.Reflection.ScriptEngine.WorkingDirectory\">\n            <summary>工作目录。执行时，将会作为环境变量的当前目录和PathHelper目录，执行后还原</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.#ctor(System.String,System.Boolean)\">\n            <summary>构造函数私有，禁止外部越过Create方法直接创建实例</summary>\n            <param name=\"code\">代码片段</param>\n            <param name=\"isExpression\">是否表达式，表达式将编译成为一个Main方法</param>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Create(System.String,System.Boolean)\">\n            <summary>为指定代码片段创建脚本引擎实例。采用缓存，避免同一脚本重复创建引擎。</summary>\n            <param name=\"code\">代码片段</param>\n            <param name=\"isExpression\">是否表达式，表达式将编译成为一个Main方法</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Execute(System.String)\">\n            <summary>执行表达式，返回结果</summary>\n            <param name=\"code\">代码片段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Execute(System.String,System.String[],System.Type[],System.Object[])\">\n            <summary>执行表达式，返回结果</summary>\n            <param name=\"code\">代码片段</param>\n            <param name=\"names\">参数名称</param>\n            <param name=\"parameterTypes\">参数类型</param>\n            <param name=\"parameters\">参数值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Execute(System.String,System.Collections.Generic.IDictionary{System.String,System.Object})\">\n            <summary>执行表达式，返回结果</summary>\n            <param name=\"code\">代码片段</param>\n            <param name=\"parameters\">参数名值对</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Execute(System.String,System.Object[])\">\n            <summary>执行表达式，返回结果。参数名默认为p0/p1/p2/pn</summary>\n            <param name=\"code\"></param>\n            <param name=\"parameters\">参数数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.GenerateCode\">\n            <summary>生成代码。根据<see cref=\"P:NewLife.Reflection.ScriptEngine.Code\"/>完善得到最终代码<see cref=\"P:NewLife.Reflection.ScriptEngine.FinalCode\"/></summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.GetFullCode\">\n            <summary>获取完整源代码</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Compile\">\n            <summary>编译</summary>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Compile(System.String,System.CodeDom.Compiler.CompilerParameters)\">\n            <summary>编译</summary>\n            <param name=\"classCode\"></param>\n            <param name=\"options\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.Invoke(System.Object[])\">\n            <summary>按照传入参数执行代码</summary>\n            <param name=\"parameters\">参数</param>\n            <returns>结果</returns>\n        </member>\n        <member name=\"M:NewLife.Reflection.ScriptEngine.ParseNameSpace(System.String)\">\n            <summary>分析命名空间</summary>\n            <param name=\"code\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiAction\">\n            <summary>Api动作</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.Name\">\n            <summary>动作名称</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.Type\">\n            <summary>动作所在类型</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.Method\">\n            <summary>方法</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.Controller\">\n            <summary>控制器对象</summary>\n            <remarks>如果指定控制器对象，则每次调用前不再实例化对象</remarks>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.IsPacketParameter\">\n            <summary>是否二进制参数</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAction.IsPacketReturn\">\n            <summary>是否二进制返回</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiAction.#ctor(System.Reflection.MethodInfo,System.Type)\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiAction.GetName(System.Type,System.Reflection.MethodInfo)\">\n            <summary>获取名称</summary>\n            <param name=\"type\"></param>\n            <param name=\"method\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiAction.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiAttribute\">\n            <summary>标识Api</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiAttribute.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiAttribute.#ctor(System.String)\">\n            <summary>实例化</summary>\n            <param name=\"name\"></param>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiClient\">\n            <summary>应用接口客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.Active\">\n            <summary>是否已打开</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.Servers\">\n            <summary>服务端地址集合。负载均衡</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.Cluster\">\n            <summary>客户端连接集群</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.UsePool\">\n            <summary>是否使用连接池。true时建立多个到服务端的连接，默认false使用单一连接</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.NewLife#Remoting#IApiSession#Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.LastActive\">\n            <summary>最后活跃时间</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.NewLife#Remoting#IApiSession#AllSessions\">\n            <summary>所有服务器所有会话，包含自己</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.StatSend\">\n            <summary>发送数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.StatReceive\">\n            <summary>接收数据包统计信息</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.#ctor\">\n            <summary>实例化应用接口客户端</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.#ctor(System.String)\">\n            <summary>实例化应用接口客户端</summary>\n            <param name=\"uris\">服务端地址集合，逗号分隔</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.Open\">\n            <summary>打开客户端</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.FindAction(System.String)\">\n            <summary>查找Api动作</summary>\n            <param name=\"action\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.CreateController(NewLife.Remoting.ApiAction)\">\n            <summary>创建控制器实例</summary>\n            <param name=\"api\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.InvokeAsync(System.Type,System.String,System.Object,System.Byte)\">\n            <summary>异步调用，等待返回结果</summary>\n            <param name=\"resultType\">返回类型</param>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.InvokeAsync``1(System.String,System.Object,System.Byte)\">\n            <summary>异步调用，等待返回结果</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.Invoke``1(System.String,System.Object,System.Byte)\">\n            <summary>同步调用，阻塞等待</summary>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.InvokeOneWay(System.String,System.Object,System.Byte)\">\n            <summary>单向发送。同步调用，不等待返回</summary>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.InvokeWithClientAsync``1(NewLife.Net.ISocketClient,System.String,System.Object,System.Byte)\">\n            <summary>指定客户端的异步调用，等待返回结果</summary>\n            <remarks>常用于在OnLoginAsync中实现连接后登录功能</remarks>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"client\">客户端</param>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.OnNewSession(NewLife.Remoting.IApiSession,System.Object)\">\n            <summary>新会话。客户端每次连接或断线重连后，可用InvokeWithClientAsync做登录</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"state\">状态。客户端ISocketClient</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.OnLoginAsync(NewLife.Net.ISocketClient,System.Boolean)\">\n            <summary>连接后自动登录</summary>\n            <param name=\"client\">客户端</param>\n            <param name=\"force\">强制登录</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.LoginAsync\">\n            <summary>登录</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiClient.OnCreate(System.String)\">\n            <summary>创建客户端之后，打开连接之前</summary>\n            <param name=\"svr\"></param>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiClient.StatPeriod\">\n            <summary>显示统计信息的周期。默认600秒，0表示不显示统计信息</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiException\">\n            <summary>远程调用异常</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiException.Code\">\n            <summary>代码</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiException.#ctor(System.Int32,System.String)\">\n            <summary>实例化远程调用异常</summary>\n            <param name=\"code\"></param>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiException.#ctor(System.Int32,System.Exception)\">\n            <summary>实例化远程调用异常</summary>\n            <param name=\"code\"></param>\n            <param name=\"ex\"></param>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiHost\">\n            <summary>Api主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Encoder\">\n            <summary>编码器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Handler\">\n            <summary>处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Timeout\">\n            <summary>调用超时时间。请求发出后，等待响应的最大时间，默认15_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.StatInvoke\">\n            <summary>发送数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.StatProcess\">\n            <summary>接收数据包统计信息</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Items\">\n            <summary>用户会话数据</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Item(System.String)\">\n            <summary>获取/设置 用户会话数据</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Manager\">\n            <summary>接口动作管理器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.Register``1\">\n            <summary>注册服务提供类。该类的所有公开方法将直接暴露</summary>\n            <typeparam name=\"TService\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.Register(System.Object,System.String)\">\n            <summary>注册服务</summary>\n            <param name=\"controller\">控制器对象</param>\n            <param name=\"method\">动作名称。为空时遍历控制器所有公有成员方法</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.ShowService\">\n            <summary>显示可用服务</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.GetMessageCodec\">\n            <summary>获取消息编码器。重载以指定不同的封包协议</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.NewLife#Remoting#IApiHost#Process(NewLife.Remoting.IApiSession,NewLife.Messaging.IMessage)\">\n            <summary>处理消息</summary>\n            <param name=\"session\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.OnProcess(NewLife.Remoting.IApiSession,System.String,NewLife.Data.Packet)\">\n            <summary>执行</summary>\n            <param name=\"session\"></param>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.OnNewSession(NewLife.Remoting.IApiSession,System.Object)\">\n            <summary>新会话。服务端收到新连接，客户端每次连接或断线重连后，可用于做登录</summary>\n            <param name=\"session\">会话</param>\n            <param name=\"state\">状态。客户端ISocketClient</param>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.EncoderLog\">\n            <summary>编码器日志</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHost.ShowError\">\n            <summary>显示调用和处理错误。默认false</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHost.ToString\">\n            <summary>已重载。返回具有本类特征的字符串</summary>\n            <returns>String</returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHttpServer.Init(System.Object,NewLife.Remoting.IApiHost)\">\n            <summary>初始化</summary>\n            <param name=\"config\"></param>\n            <param name=\"host\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetServer.Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetServer.AllSessions\">\n            <summary>当前服务器所有会话</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetServer.Timeout\">\n            <summary>调用超时时间。默认30_000ms</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiNetServer.Init(System.Object,NewLife.Remoting.IApiHost)\">\n            <summary>初始化</summary>\n            <param name=\"config\"></param>\n            <param name=\"host\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetSession.NewLife#Remoting#IApiSession#Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetSession.LastActive\">\n            <summary>最后活跃时间</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiNetSession.AllSessions\">\n            <summary>所有服务器所有会话，包含自己</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiNetSession.Start\">\n            <summary>开始会话处理</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiNetSession.FindAction(System.String)\">\n            <summary>查找Api动作</summary>\n            <param name=\"action\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiNetSession.CreateController(NewLife.Remoting.ApiAction)\">\n            <summary>创建控制器实例</summary>\n            <param name=\"api\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiNetSession.InvokeAsync``1(System.String,System.Object,System.Byte)\">\n            <summary>远程调用</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiServer\">\n            <summary>应用接口服务器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiServer.Active\">\n            <summary>是否正在工作</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiServer.Port\">\n            <summary>端口</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiServer.Server\">\n            <summary>服务器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.#ctor\">\n            <summary>实例化一个应用接口服务器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.#ctor(System.Int32)\">\n            <summary>使用指定端口实例化网络服务应用接口提供者</summary>\n            <param name=\"port\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.#ctor(NewLife.Net.NetUri)\">\n            <summary>实例化</summary>\n            <param name=\"uri\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.OnDispose(System.Boolean)\">\n            <summary>销毁时停止服务</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.Use(NewLife.Net.NetUri)\">\n            <summary>添加服务器</summary>\n            <param name=\"uri\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.EnsureCreate\">\n            <summary>确保已创建服务器对象</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.Start\">\n            <summary>开始服务</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiServer.Stop(System.String)\">\n            <summary>停止服务</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiServer.StatPeriod\">\n            <summary>显示统计信息的周期。默认600秒，0表示不显示统计信息</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.ClientPoolCluster\">\n            <summary>客户端连接池负载均衡集群</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ClientPoolCluster.GetItems\">\n            <summary>服务器地址列表</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ClientPoolCluster.OnCreate\">\n            <summary>创建回调</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ClientPoolCluster.Pool\">\n            <summary>连接池</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.#ctor\">\n            <summary>实例化连接池集群</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.Open\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.Get\">\n            <summary>从集群中获取资源</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.Put(NewLife.Net.ISocketClient)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"F:NewLife.Remoting.ClientPoolCluster._index\">\n            <summary>Round-Robin 负载均衡</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientPoolCluster.CreateClient\">\n            <summary>为连接池创建连接</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ClientSingleCluster\">\n            <summary>客户端单连接故障转移集群</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ClientSingleCluster.GetItems\">\n            <summary>服务器地址列表</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ClientSingleCluster.OnCreate\">\n            <summary>创建回调</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientSingleCluster.Open\">\n            <summary>打开</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientSingleCluster.Close(System.String)\">\n            <summary>关闭</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientSingleCluster.Get\">\n            <summary>从集群中获取资源</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientSingleCluster.Put(NewLife.Net.ISocketClient)\">\n            <summary>归还</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"F:NewLife.Remoting.ClientSingleCluster._index\">\n            <summary>Round-Robin 负载均衡</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ClientSingleCluster.CreateClient\">\n            <summary>为连接池创建连接</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiController\">\n            <summary>API控制器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiController.Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiController.All\">\n            <summary>获取所有接口</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiController.Info(System.String)\">\n            <summary>服务器信息，用户健康检测</summary>\n            <param name=\"state\">状态信息</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ControllerContext\">\n            <summary>控制器上下文</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Controller\">\n            <summary>控制器实例</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Action\">\n            <summary>处理动作</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.ActionName\">\n            <summary>真实动作名称</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Session\">\n            <summary>会话</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Request\">\n            <summary>请求</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Parameters\">\n            <summary>请求参数</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.ActionParameters\">\n            <summary>获取或设置操作方法参数。</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Result\">\n            <summary>获取或设置由操作方法返回的结果。</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Exception\">\n            <summary>获取或设置在操作方法的执行过程中发生的异常（如果有）。</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.ExceptionHandled\">\n            <summary>获取或设置一个值，该值指示是否处理异常。</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ControllerContext.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ControllerContext.Current\">\n            <summary>当前线程上下文</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ControllerContext.Reset\">\n            <summary>重置为默认状态</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.IActionFilter\">\n            <summary>定义操作筛选器中使用的方法。</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IActionFilter.OnActionExecuting(NewLife.Remoting.ControllerContext)\">\n            <summary>在执行操作方法之前调用。</summary>\n            <param name=\"filterContext\"></param>\n        </member>\n        <member name=\"M:NewLife.Remoting.IActionFilter.OnActionExecuted(NewLife.Remoting.ControllerContext)\">\n            <summary>在执行操作方法后调用。</summary>\n            <param name=\"filterContext\"></param>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApi\">\n            <summary>Api接口</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApi.Session\">\n            <summary>会话</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApiHandler\">\n            <summary>Api处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiHandler.Execute(NewLife.Remoting.IApiSession,System.String,NewLife.Data.Packet)\">\n            <summary>执行</summary>\n            <param name=\"session\"></param>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiHandler\">\n            <summary>默认处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiHandler.Host\">\n            <summary>Api接口主机</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHandler.Execute(NewLife.Remoting.IApiSession,System.String,NewLife.Data.Packet)\">\n            <summary>执行</summary>\n            <param name=\"session\"></param>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHandler.Prepare(NewLife.Remoting.IApiSession,System.String,NewLife.Data.Packet,NewLife.Remoting.ApiAction)\">\n            <summary>准备上下文</summary>\n            <param name=\"session\"></param>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <param name=\"api\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHandler.GetParams(System.Reflection.MethodInfo,System.Collections.Generic.IDictionary{System.String,System.Object},NewLife.Remoting.IEncoder)\">\n            <summary>获取参数</summary>\n            <param name=\"method\"></param>\n            <param name=\"args\"></param>\n            <param name=\"encoder\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApiHost\">\n            <summary>Api主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.Encoder\">\n            <summary>编码器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.Handler\">\n            <summary>处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.Manager\">\n            <summary>接口动作管理器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiHost.GetMessageCodec\">\n            <summary>获取消息编码器。重载以指定不同的封包协议</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiHost.Process(NewLife.Remoting.IApiSession,NewLife.Messaging.IMessage)\">\n            <summary>处理消息</summary>\n            <param name=\"session\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.StatInvoke\">\n            <summary>发送统计</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.StatProcess\">\n            <summary>接收统计</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiHost.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiHost.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Remoting.ApiHostHelper\">\n            <summary>Api主机助手</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHostHelper.InvokeAsync(NewLife.Remoting.IApiHost,System.Object,System.Type,System.String,System.Object,System.Byte)\">\n            <summary>调用</summary>\n            <param name=\"host\"></param>\n            <param name=\"session\"></param>\n            <param name=\"resultType\">结果类型</param>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHostHelper.Invoke(NewLife.Remoting.IApiHost,System.Object,System.String,System.Object,System.Byte)\">\n            <summary>调用</summary>\n            <param name=\"host\"></param>\n            <param name=\"session\"></param>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHostHelper.CreateController(NewLife.Remoting.IApiHost,NewLife.Remoting.IApiSession,NewLife.Remoting.ApiAction)\">\n            <summary>创建控制器实例</summary>\n            <param name=\"host\"></param>\n            <param name=\"session\"></param>\n            <param name=\"api\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiHostHelper.GetStat(NewLife.Remoting.IApiHost)\">\n            <summary>获取统计信息</summary>\n            <param name=\"host\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApiManager\">\n            <summary>接口管理器</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiManager.Services\">\n            <summary>可提供服务的方法</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiManager.Register``1\">\n            <summary>注册服务提供类。该类的所有公开方法将直接暴露</summary>\n            <typeparam name=\"TService\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiManager.Register(System.Object,System.String)\">\n            <summary>注册服务</summary>\n            <param name=\"controller\">控制器对象</param>\n            <param name=\"method\">动作名称。为空时遍历控制器所有公有成员方法</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiManager.Find(System.String)\">\n            <summary>查找服务</summary>\n            <param name=\"action\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.ApiManager.Services\">\n            <summary>可提供服务的方法</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiManager.Register``1\">\n            <summary>注册服务提供类。该类的所有公开方法将直接暴露</summary>\n            <typeparam name=\"TService\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiManager.Register(System.Object,System.String)\">\n            <summary>注册服务</summary>\n            <param name=\"controller\">控制器对象</param>\n            <param name=\"method\">动作名称。为空时遍历控制器所有公有成员方法</param>\n        </member>\n        <member name=\"M:NewLife.Remoting.ApiManager.Find(System.String)\">\n            <summary>查找服务</summary>\n            <param name=\"action\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApiServer\">\n            <summary>应用接口服务器接口</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiServer.Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiServer.AllSessions\">\n            <summary>当前服务器所有会话</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiServer.Init(System.Object,NewLife.Remoting.IApiHost)\">\n            <summary>初始化</summary>\n            <param name=\"config\"></param>\n            <param name=\"host\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiServer.Start\">\n            <summary>开始</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiServer.Stop(System.String)\">\n            <summary>停止</summary>\n            <param name=\"reason\">关闭原因。便于日志分析</param>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiServer.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.IApiSession\">\n            <summary>Api会话</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiSession.Host\">\n            <summary>主机</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiSession.LastActive\">\n            <summary>最后活跃时间</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiSession.AllSessions\">\n            <summary>所有服务器所有会话，包含自己</summary>\n        </member>\n        <member name=\"P:NewLife.Remoting.IApiSession.Item(System.String)\">\n            <summary>获取/设置 用户会话数据</summary>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiSession.FindAction(System.String)\">\n            <summary>查找Api动作</summary>\n            <param name=\"action\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiSession.CreateController(NewLife.Remoting.ApiAction)\">\n            <summary>创建控制器实例</summary>\n            <param name=\"api\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiSession.SendAsync(NewLife.Messaging.IMessage)\">\n            <summary>发送消息。低级接口，由框架使用</summary>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiSession.Send(NewLife.Messaging.IMessage)\">\n            <summary>发送消息。低级接口，由框架使用</summary>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IApiSession.InvokeAsync``1(System.String,System.Object,System.Byte)\">\n            <summary>远程调用</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"action\">服务操作</param>\n            <param name=\"args\">参数</param>\n            <param name=\"flag\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Remoting.IEncoder\">\n            <summary>编码器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.CreateRequest(System.String,System.Object)\">\n            <summary>创建请求</summary>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.CreateResponse(NewLife.Messaging.IMessage,System.String,System.Int32,System.Object)\">\n            <summary>创建响应</summary>\n            <param name=\"msg\"></param>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.Decode(NewLife.Messaging.IMessage,System.String@,System.Int32@,NewLife.Data.Packet@)\">\n            <summary>解码 请求/响应</summary>\n            <param name=\"msg\">消息</param>\n            <param name=\"action\">服务动作</param>\n            <param name=\"code\">错误码</param>\n            <param name=\"value\">参数或结果</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.DecodeParameters(System.String,NewLife.Data.Packet)\">\n            <summary>解码参数</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.DecodeResult(System.String,NewLife.Data.Packet)\">\n            <summary>解码结果</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.IEncoder.Convert(System.Object,System.Type)\">\n            <summary>转换为目标类型</summary>\n            <param name=\"obj\"></param>\n            <param name=\"targetType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.IEncoder.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"T:NewLife.Remoting.EncoderBase\">\n            <summary>编码器基类</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.EncoderBase.Decode(NewLife.Messaging.IMessage,System.String@,System.Int32@,NewLife.Data.Packet@)\">\n            <summary>解码 请求/响应</summary>\n            <param name=\"msg\">消息</param>\n            <param name=\"action\">服务动作</param>\n            <param name=\"code\">错误码</param>\n            <param name=\"value\">参数或结果</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Remoting.EncoderBase.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.EncoderBase.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Remoting.JsonEncoder\">\n            <summary>Json编码器</summary>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.Encode(System.String,System.Int32,NewLife.Data.Packet)\">\n            <summary>编码 请求/响应</summary>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.Encode(System.String,System.Int32,System.Object)\">\n            <summary>编码</summary>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.DecodeParameters(System.String,NewLife.Data.Packet)\">\n            <summary>解码参数</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.DecodeResult(System.String,NewLife.Data.Packet)\">\n            <summary>解码结果</summary>\n            <param name=\"action\"></param>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.Convert(System.Object,System.Type)\">\n            <summary>转换为目标类型</summary>\n            <param name=\"obj\"></param>\n            <param name=\"targetType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.CreateRequest(System.String,System.Object)\">\n            <summary>创建请求</summary>\n            <param name=\"action\"></param>\n            <param name=\"args\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Remoting.JsonEncoder.CreateResponse(NewLife.Messaging.IMessage,System.String,System.Int32,System.Object)\">\n            <summary>创建响应</summary>\n            <param name=\"msg\"></param>\n            <param name=\"action\"></param>\n            <param name=\"code\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.Certificate\">\n            <summary>证书</summary>\n            <remarks>http://blogs.msdn.com/b/dcook/archive/2008/11/25/creating-a-self-signed-certificate-in-c.aspx</remarks>\n        </member>\n        <member name=\"M:NewLife.Security.Certificate.CreateSelfSignCertificatePfx(System.String)\">\n            <summary>建立自签名证书</summary>\n            <param name=\"x500\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Certificate.CreateSelfSignCertificatePfx(System.String,System.DateTime,System.DateTime)\">\n            <summary>建立自签名证书</summary>\n            <param name=\"x500\"></param>\n            <param name=\"startTime\"></param>\n            <param name=\"endTime\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Certificate.CreateSelfSignCertificatePfx(System.String,System.DateTime,System.DateTime,System.String)\">\n            <summary>建立自签名证书</summary>\n            <param name=\"x500\"></param>\n            <param name=\"startTime\"></param>\n            <param name=\"endTime\"></param>\n            <param name=\"insecurePassword\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Certificate.CreateSelfSignCertificatePfx(System.String,System.DateTime,System.DateTime,System.Security.SecureString)\">\n            <summary>建立自签名证书</summary>\n            <param name=\"x500\">例如CN=SelfSignCertificate;C=China;OU=NewLife;O=Development Team;E=nnhy@vip.qq.com，其中CN是显示名</param>\n            <param name=\"startTime\"></param>\n            <param name=\"endTime\"></param>\n            <param name=\"password\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Certificate.CreateSelfSignCertificatePfx(System.Security.Cryptography.X509Certificates.X500DistinguishedName,System.DateTime,System.DateTime,System.Security.SecureString)\">\n            <summary>建立自签名证书</summary>\n            <param name=\"distName\"></param>\n            <param name=\"startTime\"></param>\n            <param name=\"endTime\"></param>\n            <param name=\"password\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.Crc16\">\n            <summary>CRC16校验</summary>\n        </member>\n        <member name=\"F:NewLife.Security.Crc16.CrcTable\">\n            <summary>CRC16表</summary>\n        </member>\n        <member name=\"P:NewLife.Security.Crc16.Value\">\n            <summary>校验值</summary>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Reset\">\n            <summary>重置清零</summary>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Update(System.Int16)\">\n            <summary>添加整数进行校验</summary>\n            <param name = \"value\">\n            the byte is taken as the lower 8 bits of value\n            </param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Update(System.Byte[],System.Int32,System.Int32)\">\n            <summary>添加字节数组进行校验  CRC16-CCITT x16+x12+x5+1 1021  ISO HDLC, ITU X.25, V.34/V.41/V.42, PPP-FCS</summary>\n            <remarks>字符串123456789的Crc16是31C3</remarks>\n            <param name = \"buffer\">数据缓冲区</param>\n            <param name = \"offset\">偏移量</param>\n            <param name = \"count\">字节个数</param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Update(System.IO.Stream,System.Int64)\">\n            <summary>添加数据流进行校验  CRC-16 x16+x15+x2+1 8005 IBM SDLC</summary>\n            <param name=\"stream\"></param>\n            <param name=\"count\">数量</param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Compute(System.Byte[],System.Int32,System.Int32)\">\n            <summary>计算校验码</summary>\n            <param name=\"buf\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Compute(System.IO.Stream,System.Int32)\">\n            <summary>计算数据流校验码</summary>\n            <param name=\"stream\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Crc16.Compute(System.IO.Stream,System.Int64,System.Int32)\">\n            <summary>计算数据流校验码，指定起始位置和字节数偏移量</summary>\n            <remarks>\n            一般用于计算数据包校验码，需要回过头去开始校验，并且可能需要跳过最后的校验码长度。\n            position小于0时，数据流从当前位置开始计算校验；\n            position大于等于0时，数据流移到该位置开始计算校验，最后由count决定可能差几个字节不参与计算；\n            </remarks>\n            <param name=\"stream\"></param>\n            <param name=\"position\">如果大于等于0，则表示从该位置开始计算</param>\n            <param name=\"count\">字节数偏移量，一般用负数表示</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.Crc32\">\n             <summary>CRC32校验</summary>\n             <remarks>\n             Generate a table for a byte-wise 32-bit CRC calculation on the polynomial:\n             x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.\n            \n             Polynomials over GF(2) are represented in binary, one bit per coefficient,\n             with the lowest powers in the most significant bit.  Then adding polynomials\n             is just exclusive-or, and multiplying a polynomial by x is a right shift by\n             one.  If we call the above polynomial p, and represent a byte as the\n             polynomial q, also with the lowest power in the most significant bit (so the\n             byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,\n             where a mod b means the remainder after dividing a by b.\n            \n             This calculation is done using the shift-register method of multiplying and\n             taking the remainder.  The register is initialized to zero, and for each\n             incoming bit, x^32 is added mod p to the register if the bit is a one (where\n             x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by\n             x (which is shifting right by one and adding x^32 mod p if the bit shifted\n             out is a one).  We start with the highest power (least significant bit) of\n             q and repeat for all eight bits of q.\n            \n             The table is simply the CRC of all possible eight bit values.  This is all\n             the information needed to generate CRC's on data a byte at a time for all\n             combinations of CRC register values and incoming bytes.\n             </remarks>\n        </member>\n        <member name=\"F:NewLife.Security.Crc32.Table\">\n            <summary>校验表</summary>\n        </member>\n        <member name=\"F:NewLife.Security.Crc32.crc\">\n            <summary>校验值</summary>\n        </member>\n        <member name=\"P:NewLife.Security.Crc32.Value\">\n            <summary>校验值</summary>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Reset\">\n            <summary>重置清零</summary>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Update(System.Int32)\">\n            <summary>添加整数进行校验</summary>\n            <param name = \"value\">\n            the byte is taken as the lower 8 bits of value\n            </param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Update(System.Byte[],System.Int32,System.Int32)\">\n            <summary>添加字节数组进行校验</summary>\n            <param name = \"buffer\">\n            The buffer which contains the data\n            </param>\n            <param name = \"offset\">\n            The offset in the buffer where the data starts\n            </param>\n            <param name = \"count\">\n            The number of data bytes to update the CRC with.\n            </param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Update(System.IO.Stream,System.Int64)\">\n            <summary>添加数据流进行校验</summary>\n            <param name=\"stream\"></param>\n            <param name=\"count\">数量</param>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Compute(System.Byte[],System.Int32,System.Int32)\">\n            <summary>计算校验码</summary>\n            <param name=\"buf\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.Compute(System.IO.Stream,System.Int32)\">\n            <summary>计算数据流校验码</summary>\n            <param name=\"stream\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Crc32.ComputeRange(System.IO.Stream,System.Int64,System.Int32)\">\n            <summary>计算数据流校验码，指定起始位置和字节数偏移量</summary>\n            <remarks>\n            一般用于计算数据包校验码，需要回过头去开始校验，并且可能需要跳过最后的校验码长度。\n            position小于0时，数据流从当前位置开始计算校验；\n            position大于等于0时，数据流移到该位置开始计算校验，最后由count决定可能差几个字节不参与计算；\n            </remarks>\n            <param name=\"stream\"></param>\n            <param name=\"position\">如果大于等于0，则表示从该位置开始计算</param>\n            <param name=\"count\">字节数偏移量，一般用负数表示</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.DSAHelper\">\n            <summary>DSA算法</summary>\n        </member>\n        <member name=\"M:NewLife.Security.DSAHelper.GenerateKey(System.Int32)\">\n            <summary>产生非对称密钥对（私钥和公钥）</summary>\n            <param name=\"keySize\">密钥长度，默认1024位强密钥</param>\n            <returns>私钥和公钥</returns>\n        </member>\n        <member name=\"M:NewLife.Security.DSAHelper.Sign(System.Byte[],System.String)\">\n            <summary>签名</summary>\n            <param name=\"buf\"></param>\n            <param name=\"priKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.DSAHelper.Verify(System.Byte[],System.String,System.Byte[])\">\n            <summary>验证</summary>\n            <param name=\"buf\"></param>\n            <param name=\"pukKey\"></param>\n            <param name=\"rgbSignature\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.DSAHelper.FromXmlStringX(System.Security.Cryptography.DSACryptoServiceProvider,System.String)\">\n            <summary>从Xml加载DSA密钥</summary>\n            <param name=\"rsa\"></param>\n            <param name=\"xmlString\"></param>\n        </member>\n        <member name=\"M:NewLife.Security.DSAHelper.ToXmlStringX(System.Security.Cryptography.DSACryptoServiceProvider,System.Boolean)\">\n            <summary>保存DSA密钥到Xml</summary>\n            <param name=\"rsa\"></param>\n            <param name=\"includePrivateParameters\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.Rand\">\n            <summary>随机数</summary>\n        </member>\n        <member name=\"M:NewLife.Security.Rand.Next(System.Int32)\">\n            <summary>返回一个小于所指定最大值的非负随机数</summary>\n            <param name=\"max\">返回的随机数的上界（随机数不能取该上界值）</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Rand.Next(System.Int32,System.Int32)\">\n            <summary>返回一个指定范围内的随机数</summary>\n            <remarks>\n            调用平均耗时37.76ns，其中GC耗时77.56%\n            </remarks>\n            <param name=\"min\">返回的随机数的下界（随机数可取该下界值）</param>\n            <param name=\"max\">返回的随机数的上界（随机数不能取该上界值）</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Rand.NextBytes(System.Int32)\">\n            <summary>返回指定长度随机字节数组</summary>\n            <remarks>\n            调用平均耗时5.46ns，其中GC耗时15%\n            </remarks>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.Rand.NextString(System.Int32,System.Boolean)\">\n            <summary>返回指定长度随机字符串</summary>\n            <param name=\"length\">长度</param>\n            <param name=\"symbol\">是否包含符号</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Security.RC4\">\n            <summary>RC4对称加密算法</summary>\n            <remarks>\n            RC4于1987年提出，和DES算法一样，是一种对称加密算法，也就是说使用的密钥为单钥（或称为私钥）。\n            但不同于DES的是，RC4不是对明文进行分组处理，而是字节流的方式依次加密明文中的每一个字节，解密的时候也是依次对密文中的每一个字节进行解密。\n            \n            RC4算法的特点是算法简单，运行速度快，而且密钥长度是可变的，可变范围为1-256字节(8-2048比特)，\n            在如今技术支持的前提下，当密钥长度为128比特时，用暴力法搜索密钥已经不太可行，所以可以预见RC4的密钥范围任然可以在今后相当长的时间里抵御暴力搜索密钥的攻击。\n            实际上，如今也没有找到对于128bit密钥长度的RC4加密算法的有效攻击方法。\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Security.RC4.Encrypt(System.Byte[],System.Byte[])\">\n            <summary>加密</summary>\n            <param name=\"data\">数据</param>\n            <param name=\"pass\">密码</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RC4.GetKey(System.Byte[],System.Int32)\">\n            <summary>打乱密码</summary>  \n            <param name=\"pass\">密码</param>  \n            <param name=\"len\">密码箱长度</param>  \n            <returns>打乱后的密码</returns>  \n        </member>\n        <member name=\"T:NewLife.Security.RSAHelper\">\n            <summary>RSA算法</summary>\n            <remarks>\n            RSA加密或签名小数据块时，密文长度128，速度也很快。\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.GenerateKey(System.Int32)\">\n            <summary>产生非对称密钥对</summary>\n            <remarks>\n            RSAParameters的各个字段采用大端字节序，转为BigInteger的之前一定要倒序。\n            RSA加密后密文最小长度就是密钥长度，所以1024密钥最小密文长度是128字节。\n            </remarks>\n            <param name=\"keySize\">密钥长度，默认1024位强密钥</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Encrypt(System.Byte[],System.String,System.Boolean)\">\n            <summary>RSA加密</summary>\n            <param name=\"buf\"></param>\n            <param name=\"pubKey\"></param>\n            <param name=\"fOAEP\">如果为 true，则使用 OAEP 填充（仅可用于运行 Windows XP 及更高版本的计算机）执行直接 System.Security.Cryptography.RSA加密；否则，如果为 false，则使用 PKCS#1 v1.5 填充。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Decrypt(System.Byte[],System.String,System.Boolean)\">\n            <summary>RSA解密</summary>\n            <param name=\"buf\"></param>\n            <param name=\"priKey\"></param>\n            <param name=\"fOAEP\">如果为 true，则使用 OAEP 填充（仅可用于运行 Microsoft Windows XP 及更高版本的计算机）执行直接 System.Security.Cryptography.RSA解密；否则，如果为 false 则使用 PKCS#1 v1.5 填充。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.EncryptWithDES(System.Byte[],System.String)\">\n            <summary>配合DES加密</summary>\n            <param name=\"buf\"></param>\n            <param name=\"pubKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.DecryptWithDES(System.Byte[],System.String)\">\n            <summary>配合DES解密</summary>\n            <param name=\"buf\"></param>\n            <param name=\"priKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Encrypt``1(System.Byte[],System.String)\">\n            <summary>配合对称算法加密</summary>\n            <typeparam name=\"TSymmetricAlgorithm\"></typeparam>\n            <param name=\"buf\"></param>\n            <param name=\"pubKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Decrypt``1(System.Byte[],System.String)\">\n            <summary>配合对称算法解密</summary>\n            <typeparam name=\"TSymmetricAlgorithm\"></typeparam>\n            <param name=\"buf\"></param>\n            <param name=\"priKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Sign(System.Byte[],System.String)\">\n            <summary>签名</summary>\n            <param name=\"buf\"></param>\n            <param name=\"priKey\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.Verify(System.Byte[],System.String,System.Byte[])\">\n            <summary>验证</summary>\n            <param name=\"buf\"></param>\n            <param name=\"pukKey\"></param>\n            <param name=\"rgbSignature\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Security.RSAHelper.SetRandom(System.Byte[])\">\n            <summary>使用随机数设置</summary>\n            <param name=\"buf\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.Accessor\">\n            <summary>访问器基类</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.Read(System.IO.Stream,System.Object)\">\n            <summary>从数据流中读取消息</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"context\">上下文</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.Write(System.IO.Stream,System.Object)\">\n            <summary>把消息写入到数据流中</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"context\">上下文</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.ToPacket\">\n            <summary>消息转为字节数组</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.CreateFormatter(System.Boolean)\">\n            <summary>创建序列化器</summary>\n            <param name=\"isRead\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.ToString\">\n            <summary>输出消息实体</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.GetMember(System.Reflection.PropertyInfo,System.Int32,System.Text.StringBuilder)\">\n            <summary>获取成员输出</summary>\n            <param name=\"pi\"></param>\n            <param name=\"len\"></param>\n            <param name=\"sb\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor.GetMemberValue(System.Reflection.PropertyInfo)\">\n            <summary>获取用于输出的成员值</summary>\n            <param name=\"pi\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.Accessor`1\">\n            <summary>访问器泛型基类</summary>\n            <typeparam name=\"T\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor`1.Read(System.IO.Stream)\">\n            <summary>从流中读取消息</summary>\n            <param name=\"stream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Accessor`1.Read(NewLife.Data.Packet)\">\n            <summary>从字节数组中读取消息</summary>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryCodec`1\">\n            <summary>二进制编码解码器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.BinaryCodec`1.EncodedInt\">\n            <summary>使用7位编码整数。默认true使用</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryCodec`1.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>对象转二进制</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryCodec`1.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>二进制转对象</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryCodec2\">\n            <summary>二进制编码解码器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.BinaryCodec2.EncodedInt\">\n            <summary>使用7位编码整数。默认true使用</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryCodec2.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>对象转二进制</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryCodec2.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>二进制转对象</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.Binary\">\n            <summary>二进制序列化</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.EncodeInt\">\n            <summary>使用7位编码整数。默认false不使用</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.IsLittleEndian\">\n            <summary>小端字节序。默认false大端</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.UseFieldSize\">\n            <summary>使用指定大小的FieldSizeAttribute特性，默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.UseRef\">\n            <summary>使用对象引用，默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.SizeWidth\">\n            <summary>大小宽度。可选0/1/2/4，默认0表示压缩编码整数</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.IgnoreMembers\">\n            <summary>要忽略的成员</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Binary.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.AddHandler(NewLife.Serialization.IBinaryHandler)\">\n            <summary>添加处理器</summary>\n            <param name=\"handler\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.AddHandler``1(System.Int32)\">\n            <summary>添加处理器</summary>\n            <typeparam name=\"THandler\"></typeparam>\n            <param name=\"priority\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.GetHandler``1\">\n            <summary>获取处理器</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.Write(System.Byte)\">\n            <summary>写入字节</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.Write(System.Byte[],System.Int32,System.Int32)\">\n            <summary>将字节数组部分写入当前流，不写入数组长度。</summary>\n            <param name=\"buffer\">包含要写入的数据的字节数组。</param>\n            <param name=\"offset\">buffer 中开始写入的起始点。</param>\n            <param name=\"count\">要写入的字节数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.WriteSize(System.Int32)\">\n            <summary>写入大小，如果有FieldSize则返回，否则写入编码的大小</summary>\n            <param name=\"size\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.WriteEncoded(System.Int32)\">\n            <summary>写7位压缩编码整数</summary>\n            <remarks>\n            以7位压缩格式写入32位整数，小于7位用1个字节，小于14位用2个字节。\n            由每次写入的一个字节的第一位标记后面的字节是否还是当前数据，所以每个字节实际可利用存储空间只有后7位。\n            </remarks>\n            <param name=\"value\">数值</param>\n            <returns>实际写入字节数</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.Read(System.Type)\">\n            <summary>读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.Read``1\">\n            <summary>读取指定类型对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadByte\">\n            <summary>读取字节</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadBytes(System.Int32)\">\n            <summary>从当前流中将 count 个字节读入字节数组</summary>\n            <param name=\"count\">要读取的字节数。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadSize\">\n            <summary>读取大小</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadIntBytes(System.Int32)\">\n            <summary>读取整数的字节数组，某些写入器（如二进制写入器）可能需要改变字节顺序</summary>\n            <param name=\"count\">数量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadInt16\">\n            <summary>从当前流中读取 2 字节有符号整数，并使流的当前位置提升 2 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadInt32\">\n            <summary>从当前流中读取 4 字节有符号整数，并使流的当前位置提升 4 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadEncodedInt16\">\n            <summary>以压缩格式读取16位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadEncodedInt32\">\n            <summary>以压缩格式读取32位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.ReadEncodedInt64\">\n            <summary>以压缩格式读取64位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.EnableTrace\">\n            <summary>使用跟踪流。实际上是重新包装一次Stream，必须在设置Stream后，使用之前</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.FastRead``1(System.IO.Stream,System.Boolean)\">\n            <summary>快速读取</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"stream\">数据流</param>\n            <param name=\"encodeInt\">使用7位编码整数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Binary.FastWrite(System.Object,System.Boolean)\">\n            <summary>快速写入</summary>\n            <param name=\"value\">对象</param>\n            <param name=\"encodeInt\">使用7位编码整数</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryColor\">\n            <summary>颜色处理器。</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryColor.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryColor.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryColor.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryComposite\">\n            <summary>复合对象处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryComposite.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryComposite.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryComposite.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryComposite.GetMembers(System.Type,System.Boolean)\">\n            <summary>获取成员</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryDictionary\">\n            <summary>字典数据编码</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryDictionary.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryDictionary.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryDictionary.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryFont\">\n            <summary>字体处理器。</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryFont.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryFont.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryFont.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryGeneral\">\n            <summary>二进制基础类型处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns>是否处理成功</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Byte)\">\n            <summary>将一个无符号字节写入</summary>\n            <param name=\"value\">要写入的无符号字节。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Byte[])\">\n            <summary>将字节数组写入，如果设置了UseSize，则先写入数组长度。</summary>\n            <param name=\"buffer\">包含要写入的数据的字节数组。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Byte[],System.Int32,System.Int32)\">\n            <summary>将字节数组部分写入当前流，不写入数组长度。</summary>\n            <param name=\"buffer\">包含要写入的数据的字节数组。</param>\n            <param name=\"offset\">buffer 中开始写入的起始点。</param>\n            <param name=\"count\">要写入的字节数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Byte[],System.Int32)\">\n            <summary>写入字节数组，自动计算长度</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"count\">数量</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Int16)\">\n            <summary>将 2 字节有符号整数写入当前流，并将流的位置提升 2 个字节。</summary>\n            <param name=\"value\">要写入的 2 字节有符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Int32)\">\n            <summary>将 4 字节有符号整数写入当前流，并将流的位置提升 4 个字节。</summary>\n            <param name=\"value\">要写入的 4 字节有符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Int64)\">\n            <summary>将 8 字节有符号整数写入当前流，并将流的位置提升 8 个字节。</summary>\n            <param name=\"value\">要写入的 8 字节有符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.WriteIntBytes(System.Byte[])\">\n            <summary>判断字节顺序</summary>\n            <param name=\"buffer\">缓冲区</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.UInt16)\">\n            <summary>将 2 字节无符号整数写入当前流，并将流的位置提升 2 个字节。</summary>\n            <param name=\"value\">要写入的 2 字节无符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.UInt32)\">\n            <summary>将 4 字节无符号整数写入当前流，并将流的位置提升 4 个字节。</summary>\n            <param name=\"value\">要写入的 4 字节无符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.UInt64)\">\n            <summary>将 8 字节无符号整数写入当前流，并将流的位置提升 8 个字节。</summary>\n            <param name=\"value\">要写入的 8 字节无符号整数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Single)\">\n            <summary>将 4 字节浮点值写入当前流，并将流的位置提升 4 个字节。</summary>\n            <param name=\"value\">要写入的 4 字节浮点值。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Double)\">\n            <summary>将 8 字节浮点值写入当前流，并将流的位置提升 8 个字节。</summary>\n            <param name=\"value\">要写入的 8 字节浮点值。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Decimal)\">\n            <summary>将一个十进制值写入当前流，并将流位置提升十六个字节。</summary>\n            <param name=\"value\">要写入的十进制值。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Char)\">\n            <summary>将 Unicode 字符写入当前流，并根据所使用的 Encoding 和向流中写入的特定字符，提升流的当前位置。</summary>\n            <param name=\"ch\">要写入的非代理项 Unicode 字符。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.Char[],System.Int32,System.Int32)\">\n            <summary>将字符数组部分写入当前流，并根据所使用的 Encoding（可能还根据向流中写入的特定字符），提升流的当前位置。</summary>\n            <param name=\"chars\">包含要写入的数据的字符数组。</param>\n            <param name=\"index\">chars 中开始写入的起始点。</param>\n            <param name=\"count\">要写入的字符数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.Write(System.String)\">\n            <summary>写入字符串</summary>\n            <param name=\"value\">要写入的值。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadByte\">\n            <summary>从当前流中读取下一个字节，并使流的当前位置提升 1 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadBytes(System.Int32)\">\n            <summary>从当前流中将 count 个字节读入字节数组，如果count小于0，则先读取字节数组长度。</summary>\n            <param name=\"count\">要读取的字节数。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadIntBytes(System.Int32)\">\n            <summary>读取整数的字节数组，某些写入器（如二进制写入器）可能需要改变字节顺序</summary>\n            <param name=\"count\">数量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadInt16\">\n            <summary>从当前流中读取 2 字节有符号整数，并使流的当前位置提升 2 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadInt32\">\n            <summary>从当前流中读取 4 字节有符号整数，并使流的当前位置提升 4 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadInt64\">\n            <summary>从当前流中读取 8 字节有符号整数，并使流的当前位置向前移动 8 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadUInt16\">\n            <summary>使用 Little-Endian 编码从当前流中读取 2 字节无符号整数，并将流的位置提升 2 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadUInt32\">\n            <summary>从当前流中读取 4 字节无符号整数并使流的当前位置提升 4 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadUInt64\">\n            <summary>从当前流中读取 8 字节无符号整数并使流的当前位置提升 8 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadSingle\">\n            <summary>从当前流中读取 4 字节浮点值，并使流的当前位置提升 4 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadDouble\">\n            <summary>从当前流中读取 8 字节浮点值，并使流的当前位置提升 8 个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadChar\">\n            <summary>从当前流中读取下一个字符，并根据所使用的 Encoding 和从流中读取的特定字符，提升流的当前位置。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadString\">\n            <summary>从当前流中读取一个字符串。字符串有长度前缀，一次 7 位地被编码为整数。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadDecimal\">\n            <summary>从当前流中读取十进制数值，并将该流的当前位置提升十六个字节。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadEncodedInt16\">\n            <summary>以压缩格式读取16位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadEncodedInt32\">\n            <summary>以压缩格式读取32位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.ReadEncodedInt64\">\n            <summary>以压缩格式读取64位整数</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.WriteEncoded(System.Int16)\">\n            <summary>\n            以7位压缩格式写入16位整数，小于7位用1个字节，小于14位用2个字节。\n            由每次写入的一个字节的第一位标记后面的字节是否还是当前数据，所以每个字节实际可利用存储空间只有后7位。\n            </summary>\n            <param name=\"value\">数值</param>\n            <returns>实际写入字节数</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.WriteEncoded(System.Int32)\">\n            <summary>\n            以7位压缩格式写入32位整数，小于7位用1个字节，小于14位用2个字节。\n            由每次写入的一个字节的第一位标记后面的字节是否还是当前数据，所以每个字节实际可利用存储空间只有后7位。\n            </summary>\n            <param name=\"value\">数值</param>\n            <returns>实际写入字节数</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryGeneral.WriteEncoded(System.Int64)\">\n            <summary>\n            以7位压缩格式写入64位整数，小于7位用1个字节，小于14位用2个字节。\n            由每次写入的一个字节的第一位标记后面的字节是否还是当前数据，所以每个字节实际可利用存储空间只有后7位。\n            </summary>\n            <param name=\"value\">数值</param>\n            <returns>实际写入字节数</returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryList\">\n            <summary>列表数据编码</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryList.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryList.Write(System.Object,System.Type)\">\n            <summary>写入</summary>\n            <param name=\"value\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryList.TryRead(System.Type,System.Object@)\">\n            <summary>读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryNormal\">\n            <summary>常用类型编码</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.Write(System.Object,System.Type)\">\n            <summary>写入</summary>\n            <param name=\"value\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.Write(System.Byte[],System.Int32)\">\n            <summary>写入字节数组，自动计算长度</summary>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"count\">数量</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.TryRead(System.Type,System.Object@)\">\n            <summary>读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.ReadBytes(System.Int32)\">\n            <summary>从当前流中将 count 个字节读入字节数组，如果count小于0，则先读取字节数组长度。</summary>\n            <param name=\"count\">要读取的字节数。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryNormal.ReadChars(System.Int32)\">\n            <summary>从当前流中读取 count 个字符，以字符数组的形式返回数据，并根据所使用的 Encoding 和从流中读取的特定字符，提升当前位置。</summary>\n            <param name=\"count\">要读取的字符数。</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryPair\">\n            <summary>二进制名值对</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.WritePair(System.String,System.Object)\">\n            <summary>写入名值对</summary>\n            <param name=\"name\"></param>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.ReadPair\">\n            <summary>读取原始名值对</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.ReadPair(System.IO.Stream,System.Text.Encoding)\">\n            <summary>读取原始名值对</summary>\n            <param name=\"ms\">数据流</param>\n            <param name=\"encoding\">编码</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.TryReadPair(System.Collections.Generic.IDictionary{System.String,System.Byte[]},System.String,System.Type,System.Object@)\">\n            <summary>从原始名值对读取数据</summary>\n            <param name=\"dic\"></param>\n            <param name=\"name\"></param>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryPair.GetMembers(System.Type,System.Boolean)\">\n            <summary>获取成员</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryUnknown\">\n            <summary>内部对象处理器。对于其它处理器无法支持的类型，一律由该处理器解决</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryUnknown.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryUnknown.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.BinaryUnknown.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.FieldSizeAttribute\">\n            <summary>字段大小特性。</summary>\n            <remarks>\n            可以通过Size指定字符串或数组的固有大小，为0表示自动计算；也可以通过指定参考字段ReferenceName，然后从其中获取大小。\n            支持_Header._Questions形式的多层次引用字段\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Serialization.FieldSizeAttribute.Size\">\n            <summary>大小。使用<see cref=\"P:NewLife.Serialization.FieldSizeAttribute.ReferenceName\"/>时，作为偏移量；0表示自动计算大小</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FieldSizeAttribute.ReferenceName\">\n            <summary>参考大小字段名</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.#ctor(System.Int32)\">\n            <summary>通过Size指定字符串或数组的固有大小，为0表示自动计算</summary>\n            <param name=\"size\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.#ctor(System.String)\">\n            <summary>指定参考字段ReferenceName，然后从其中获取大小</summary>\n            <param name=\"referenceName\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.#ctor(System.String,System.Int32)\">\n            <summary>指定参考字段ReferenceName，然后从其中获取大小</summary>\n            <param name=\"referenceName\"></param>\n            <param name=\"size\">在参考字段值基础上的增量，可以是正数负数</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.FindReference(System.Object,System.Reflection.MemberInfo,System.Object@)\">\n            <summary>找到所引用的参考字段</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"member\">目标对象的成员</param>\n            <param name=\"value\">数值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.SetReferenceSize(System.Object,System.Reflection.MemberInfo,System.Text.Encoding)\">\n            <summary>设置目标对象的引用大小值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"member\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.FieldSizeAttribute.GetReferenceSize(System.Object,System.Reflection.MemberInfo)\">\n            <summary>获取目标对象的引用大小值</summary>\n            <param name=\"target\">目标对象</param>\n            <param name=\"member\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IBinary\">\n            <summary>二进制序列化接口</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IBinary.EncodeInt\">\n            <summary>编码整数</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IBinary.IsLittleEndian\">\n            <summary>小端字节序。默认false大端</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IBinary.UseFieldSize\">\n            <summary>使用指定大小的FieldSizeAttribute特性，默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IBinary.IgnoreMembers\">\n            <summary>要忽略的成员</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IBinary.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.Write(System.Byte)\">\n            <summary>写入字节</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.Write(System.Byte[],System.Int32,System.Int32)\">\n            <summary>将字节数组部分写入当前流，不写入数组长度。</summary>\n            <param name=\"buffer\">包含要写入的数据的字节数组。</param>\n            <param name=\"offset\">buffer 中开始写入的起始点。</param>\n            <param name=\"count\">要写入的字节数。</param>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.WriteSize(System.Int32)\">\n            <summary>写入大小</summary>\n            <param name=\"size\">要写入的大小值</param>\n            <returns>返回特性指定的固定长度，如果没有则返回-1</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.ReadByte\">\n            <summary>读取字节</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.ReadBytes(System.Int32)\">\n            <summary>从当前流中将 count 个字节读入字节数组</summary>\n            <param name=\"count\">要读取的字节数。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IBinary.ReadSize\">\n            <summary>读取大小</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IBinaryHandler\">\n            <summary>二进制读写处理器接口</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.BinaryHandlerBase\">\n            <summary>二进制读写处理器基类</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.IAccessor\">\n            <summary>序列化访问器。接口实现者可以在这里完全自定义序列化行为</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IAccessor.Read(System.IO.Stream,System.Object)\">\n            <summary>从数据流中读取消息</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"context\">上下文</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IAccessor.Write(System.IO.Stream,System.Object)\">\n            <summary>把消息写入到数据流中</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"context\">上下文</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.AccessorHelper\">\n            <summary>访问器助手</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.AccessorHelper.ToPacket(NewLife.Serialization.IAccessor,System.Object)\">\n            <summary>支持访问器的对象转数据包</summary>\n            <param name=\"accessor\">访问器</param>\n            <param name=\"context\">上下文</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.AccessorHelper.AccessorRead(System.Type,NewLife.Data.Packet,System.Object)\">\n            <summary>通过访问器读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"pk\"></param>\n            <param name=\"context\">上下文</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.AccessorHelper.ToEntity``1(NewLife.Data.Packet,System.Object)\">\n            <summary>通过访问器转换数据包为实体对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"pk\"></param>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IFormatterX\">\n            <summary>序列化接口</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.Stream\">\n            <summary>数据流</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.Hosts\">\n            <summary>主对象</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.Member\">\n            <summary>成员</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.Encoding\">\n            <summary>文本编码</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.UseProperty\">\n            <summary>序列化属性而不是字段。默认true</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IFormatterX.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IFormatterX.Read(System.Type)\">\n            <summary>读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IFormatterX.Read``1\">\n            <summary>读取指定类型对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IFormatterX.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Serialization.IFormatterX.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.IHandler`1\">\n            <summary>序列化处理器接口</summary>\n            <typeparam name=\"THost\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Serialization.IHandler`1.Host\">\n            <summary>宿主读写器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IHandler`1.Priority\">\n            <summary>优先级</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IHandler`1.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IHandler`1.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.FormatterBase\">\n            <summary>序列化接口</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.Stream\">\n            <summary>数据流。默认实例化一个内存数据流</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.Hosts\">\n            <summary>主对象</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.Member\">\n            <summary>成员</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.Encoding\">\n            <summary>字符串编码，默认Default</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.UseProperty\">\n            <summary>序列化属性而不是字段。默认true</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.FormatterBase.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.FormatterBase.GetBytes\">\n            <summary>获取流里面的数据</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Serialization.FormatterBase.Log\">\n            <summary>日志提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.FormatterBase.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Serialization.HandlerBase`2\">\n            <summary>读写处理器基类</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.HandlerBase`2.Host\">\n            <summary>宿主读写器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.HandlerBase`2.Priority\">\n            <summary>优先级</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.HandlerBase`2.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.HandlerBase`2.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.HandlerBase`2.WriteLog(System.String,System.Object[])\">\n            <summary>输出日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Serialization.IMemberAccessor\">\n            <summary>成员序列化访问器。接口实现者可以在这里完全自定义序列化行为</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IMemberAccessor.Read(NewLife.Serialization.IFormatterX,System.Reflection.MemberInfo)\">\n            <summary>从数据流中读取消息</summary>\n            <param name=\"fm\">序列化</param>\n            <param name=\"member\">成员</param>\n            <returns>是否成功</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IMemberAccessor.Write(NewLife.Serialization.IFormatterX,System.Reflection.MemberInfo)\">\n            <summary>把消息写入到数据流中</summary>\n            <param name=\"fm\">序列化</param>\n            <param name=\"member\">成员</param>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonCodec`1\">\n            <summary>Json编码解码器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonCodec`1.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>对象转Json</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonCodec`1.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>Json转对象</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonCodec2\">\n            <summary>Json编码解码器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonCodec2.Write(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>对象转Json</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonCodec2.Read(NewLife.Model.IHandlerContext,System.Object)\">\n            <summary>Json转对象</summary>\n            <param name=\"context\"></param>\n            <param name=\"message\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IJson\">\n            <summary>IJson序列化接口</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IJson.Indented\">\n            <summary>是否缩进</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IJson.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJson.Write(System.String)\">\n            <summary>写入字符串</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJson.Write(System.Text.StringBuilder,System.Object)\">\n            <summary>写入</summary>\n            <param name=\"sb\"></param>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJson.Read(System.String)\">\n            <summary>读取</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJson.ReadByte\">\n            <summary>读取字节</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IJsonHandler\">\n            <summary>IJson读写处理器接口</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJsonHandler.GetString(System.Object)\">\n            <summary>获取对象的Json字符串表示形式。</summary>\n            <param name=\"value\"></param>\n            <returns>返回null表示不支持</returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonHandlerBase\">\n            <summary>IJson读写处理器基类</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHandlerBase.GetString(System.Object)\">\n            <summary>获取对象的Json字符串表示形式。</summary>\n            <param name=\"value\"></param>\n            <returns>返回null表示不支持</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHandlerBase.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns>是否处理成功</returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IJsonHost\">\n            <summary>Json序列化接口</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJsonHost.Write(System.Object,System.Boolean,System.Boolean,System.Boolean)\">\n            <summary>写入对象，得到Json字符串</summary>\n            <param name=\"value\"></param>\n            <param name=\"indented\">是否缩进。默认false</param>\n            <param name=\"nullValue\">是否写控制。默认true</param>\n            <param name=\"camelCase\">是否驼峰命名。默认false</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJsonHost.Read(System.String,System.Type)\">\n            <summary>从Json字符串中读取对象</summary>\n            <param name=\"json\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IJsonHost.Convert(System.Object,System.Type)\">\n            <summary>类型转换</summary>\n            <param name=\"obj\"></param>\n            <param name=\"targetType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonHelper\">\n            <summary>Json助手</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonHelper.Default\">\n            <summary>默认实现</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.ToJson(System.Object,System.Boolean)\">\n            <summary>写入对象，得到Json字符串</summary>\n            <param name=\"value\"></param>\n            <param name=\"indented\">是否缩进</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.ToJson(System.Object,System.Boolean,System.Boolean,System.Boolean)\">\n            <summary>写入对象，得到Json字符串</summary>\n            <param name=\"value\"></param>\n            <param name=\"indented\">是否缩进。默认false</param>\n            <param name=\"nullValue\">是否写控制。默认true</param>\n            <param name=\"camelCase\">是否驼峰命名。默认false</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.ToJsonEntity(System.String,System.Type)\">\n            <summary>从Json字符串中读取对象</summary>\n            <param name=\"json\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.ToJsonEntity``1(System.String)\">\n            <summary>从Json字符串中读取对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"json\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.Format(System.String)\">\n            <summary>格式化Json文本</summary>\n            <param name=\"json\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonHelper.Convert``1(System.Object)\">\n            <summary>Json类型对象转换实体类</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.Json\">\n            <summary>Json序列化</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Json.Indented\">\n            <summary>是否缩进</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Json.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.AddHandler(NewLife.Serialization.IJsonHandler)\">\n            <summary>添加处理器</summary>\n            <param name=\"handler\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.AddHandler``1(System.Int32)\">\n            <summary>添加处理器</summary>\n            <typeparam name=\"THandler\"></typeparam>\n            <param name=\"priority\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.GetHandler``1\">\n            <summary>获取处理器</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Write(System.String)\">\n            <summary>写入字符串</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Write(System.Text.StringBuilder,System.Object)\">\n            <summary>写入</summary>\n            <param name=\"sb\"></param>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Read(System.Type)\">\n            <summary>读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Read``1\">\n            <summary>读取指定类型对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.Read(System.String)\">\n            <summary>读取</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Json.ReadByte\">\n            <summary>读取字节</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonArray\">\n            <summary>列表数据编码</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonArray.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonArray.GetString(System.Object)\">\n            <summary>获取对象的Json字符串表示形式。</summary>\n            <param name=\"value\"></param>\n            <returns>返回null表示不支持</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonArray.Write(System.Object,System.Type)\">\n            <summary>写入</summary>\n            <param name=\"value\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonArray.TryRead(System.Type,System.Object@)\">\n            <summary>读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonComposite\">\n            <summary>复合对象处理器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonComposite.IgnoreMembers\">\n            <summary>要忽略的成员</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonComposite.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonComposite.GetString(System.Object)\">\n            <summary>获取对象的Json字符串表示形式。</summary>\n            <param name=\"value\"></param>\n            <returns>返回null表示不支持</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonComposite.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonComposite.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonComposite.GetMembers(System.Type,System.Boolean)\">\n            <summary>获取成员</summary>\n            <param name=\"type\"></param>\n            <param name=\"baseFirst\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonGeneral\">\n            <summary>Json基础类型处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonGeneral.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonGeneral.GetString(System.Object)\">\n            <summary>获取对象的Json字符串表示形式。</summary>\n            <param name=\"value\"></param>\n            <returns>返回null表示不支持</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonGeneral.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonParser\">\n            <summary>Json分析器</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonParser.Token\">\n            <summary>标识符</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Curly_Open\">\n            <summary>左大括号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Curly_Close\">\n            <summary>右大括号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Squared_Open\">\n            <summary>左方括号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Squared_Close\">\n            <summary>右方括号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Colon\">\n            <summary>冒号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Comma\">\n            <summary>逗号</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.String\">\n            <summary>字符串</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Number\">\n            <summary>数字</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.True\">\n            <summary>布尔真</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.False\">\n            <summary>布尔真</summary>\n        </member>\n        <member name=\"F:NewLife.Serialization.JsonParser.Token.Null\">\n            <summary>空值</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonParser.#ctor(System.String)\">\n            <summary>实例化</summary>\n            <param name=\"json\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonParser.Decode\">\n            <summary>解码</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonParser.SkipToken\">\n            <summary>读取一个Token</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonReader\">\n            <summary>Json读取器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonReader.UseUTCDateTime\">\n            <summary>是否使用UTC时间</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.Read``1(System.String)\">\n            <summary>读取Json到指定类型</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"json\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.Read(System.String,System.Type)\">\n            <summary>读取Json到指定类型</summary>\n            <param name=\"json\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.ToObject(System.Object,System.Type,System.Object)\">\n            <summary>Json字典或列表转为具体类型对象</summary>\n            <param name=\"jobj\">Json对象</param>\n            <param name=\"type\">模板类型</param>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.ParseList(System.Collections.Generic.IList{System.Object},System.Type,System.Object)\">\n            <summary>转为泛型列表</summary>\n            <param name=\"vlist\"></param>\n            <param name=\"type\"></param>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.ParseArray(System.Collections.Generic.IList{System.Object},System.Type,System.Object)\">\n            <summary>转为数组</summary>\n            <param name=\"list\"></param>\n            <param name=\"type\"></param>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.ParseDictionary(System.Collections.Generic.IDictionary{System.String,System.Object},System.Type,System.Collections.IDictionary)\">\n            <summary>转为泛型字典</summary>\n            <param name=\"dic\"></param>\n            <param name=\"type\"></param>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.ParseObject(System.Collections.Generic.IDictionary{System.String,System.Object},System.Type,System.Object)\">\n            <summary>字典转复杂对象，反射属性赋值</summary>\n            <param name=\"dic\"></param>\n            <param name=\"type\"></param>\n            <param name=\"target\">目标对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonReader.CreateDateTime(System.Object)\">\n            <summary>创建时间</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.JsonWriter\">\n            <summary>Json写入器</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonWriter.UseUTCDateTime\">\n            <summary>使用UTC时间。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonWriter.LowerCase\">\n            <summary>使用小写名称</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonWriter.CamelCase\">\n            <summary>使用驼峰命名</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.JsonWriter.NullValue\">\n            <summary>写入空值。默认true</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonWriter.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonWriter.ToJson(System.Object,System.Boolean,System.Boolean,System.Boolean)\">\n            <summary>对象序列化为Json字符串</summary>\n            <param name=\"obj\"></param>\n            <param name=\"indented\">是否缩进。默认false</param>\n            <param name=\"nullValue\">是否写控制。默认true</param>\n            <param name=\"camelCase\">是否驼峰命名。默认false</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.JsonWriter.FormatName(System.String)\">\n            <summary>根据小写和驼峰格式化名称</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.SerialHelper\">\n            <summary>序列化助手</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.SerialHelper.GetName(System.Reflection.PropertyInfo)\">\n            <summary>获取序列化名称</summary>\n            <param name=\"pi\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IXml\">\n            <summary>二进制序列化接口</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IXml.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.IXml.UseComment\">\n            <summary>使用注释</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.IXml.Write(System.Object,System.String,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"name\">名称</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IXml.GetWriter\">\n            <summary>获取Xml写入器</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.IXml.GetReader\">\n            <summary>获取Xml读取器</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.IXmlHandler\">\n            <summary>二进制读写处理器接口</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.XmlHandlerBase\">\n            <summary>Xml读写处理器基类</summary>\n        </member>\n        <member name=\"T:NewLife.Serialization.Xml\">\n            <summary>Xml序列化</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Xml.Depth\">\n            <summary>深度</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Xml.Handlers\">\n            <summary>处理器列表</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Xml.UseAttribute\">\n            <summary>使用特性</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Xml.UseComment\">\n            <summary>使用注释</summary>\n        </member>\n        <member name=\"P:NewLife.Serialization.Xml.CurrentName\">\n            <summary>当前名称</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.AddHandler(NewLife.Serialization.IXmlHandler)\">\n            <summary>添加处理器</summary>\n            <param name=\"handler\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.AddHandler``1(System.Int32)\">\n            <summary>添加处理器</summary>\n            <typeparam name=\"THandler\"></typeparam>\n            <param name=\"priority\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.Write(System.Object,System.String,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"name\">名称</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.WriteStart(System.Type)\">\n            <summary>写入开头</summary>\n            <param name=\"type\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.WriteEnd\">\n            <summary>写入结尾</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.GetWriter\">\n            <summary>获取Xml写入器</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.Read(System.Type)\">\n            <summary>读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.Read``1\">\n            <summary>读取指定类型对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取指定类型对象</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.ReadStart(System.Type)\">\n            <summary>读取开始</summary>\n            <param name=\"type\"></param>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.ReadEnd\">\n            <summary>读取结束</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.GetReader\">\n            <summary>获取Xml读取器</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.Xml.GetString\">\n            <summary>获取字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.XmlComposite\">\n            <summary>Xml复合对象处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlComposite.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlComposite.Write(System.Object,System.Type)\">\n            <summary>写入对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlComposite.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlComposite.GetMembers(System.Type)\">\n            <summary>获取成员</summary>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.XmlGeneral\">\n            <summary>Xml基础类型处理器</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlGeneral.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlGeneral.Write(System.Object,System.Type)\">\n            <summary>写入一个对象</summary>\n            <param name=\"value\">目标对象</param>\n            <param name=\"type\">类型</param>\n            <returns>是否处理成功</returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlGeneral.TryRead(System.Type,System.Object@)\">\n            <summary>尝试读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Serialization.XmlList\">\n            <summary>列表数据编码</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlList.#ctor\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlList.Write(System.Object,System.Type)\">\n            <summary>写入</summary>\n            <param name=\"value\"></param>\n            <param name=\"type\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Serialization.XmlList.TryRead(System.Type,System.Object@)\">\n            <summary>读取</summary>\n            <param name=\"type\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Setting\">\n            <summary>核心设置</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.Debug\">\n            <summary>是否启用全局调试。默认启用</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.LogLevel\">\n            <summary>日志等级，只输出大于等于该级别的日志，All/Debug/Info/Warn/Error/Fatal，默认Info</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.LogPath\">\n            <summary>文件日志目录</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.NetworkLog\">\n            <summary>网络日志。本地子网日志广播255.255.255.255:514</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.LogFileFormat\">\n            <summary>日志文件格式</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.TempPath\">\n            <summary>临时目录</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.PluginPath\">\n            <summary>插件目录</summary>\n        </member>\n        <member name=\"P:NewLife.Setting.PluginServer\">\n            <summary>插件服务器。将从该网页上根据关键字分析链接并下载插件</summary>\n        </member>\n        <member name=\"M:NewLife.Setting.OnLoaded\">\n            <summary>加载完成后</summary>\n        </member>\n        <member name=\"M:NewLife.Setting.GetPluginPath\">\n            <summary>获取插件目录</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Threading.ThreadPoolX\">\n            <summary>轻量级线程池。无等待和调度逻辑，直接创建线程竞争处理器资源</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.Init\">\n            <summary>初始化线程池\n            </summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueUserWorkItem(System.Action)\">\n            <summary>带异常处理的线程池任务调度，不允许异常抛出，以免造成应用程序退出</summary>\n            <param name=\"callback\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueUserWorkItem``1(System.Action{``0},``0)\">\n            <summary>带异常处理的线程池任务调度，不允许异常抛出，以免造成应用程序退出</summary>\n            <param name=\"callback\"></param>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadPoolX.Instance\">\n            <summary>静态实例</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadPoolX.Pool\">\n            <summary>内部池</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.MyThreadPool.OnCreate\">\n            <summary>创建实例</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueWorkItem(System.Action)\">\n            <summary>把委托放入线程池执行</summary>\n            <param name=\"callback\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueTask(System.Action)\">\n            <summary>在线程池中异步执行任务</summary>\n            <param name=\"function\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueTask``1(System.Func{``0})\">\n            <summary>在线程池中异步执行任务</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"function\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadPoolX.QueueTask``1(System.Func{System.Threading.CancellationToken,``0},System.Threading.CancellationToken)\">\n            <summary>在线程池中异步执行任务</summary>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"function\"></param>\n            <param name=\"cancellationToken\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Threading.ThreadItem\">\n            <summary>线程任务项</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadItem.ID\">\n            <summary>编号</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadItem.Thread\">\n            <summary>线程</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadItem.Host\">\n            <summary>主机线程池</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.ThreadItem.Active\">\n            <summary>活跃</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadItem.#ctor(NewLife.Threading.ThreadPoolX)\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadItem.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadItem.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Threading.ThreadItem.Execute(System.Action)\">\n            <summary>执行委托</summary>\n            <param name=\"callback\"></param>\n        </member>\n        <member name=\"T:NewLife.Threading.TimerScheduler\">\n            <summary>定时器调度器</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Create(System.String)\">\n            <summary>创建指定名称的调度器</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.Default\">\n            <summary>默认调度器</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.Current\">\n            <summary>当前调度器</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.Count\">\n            <summary>定时器个数</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.MaxCost\">\n            <summary>最大耗时。超过时报警告日志，默认500ms</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Add(NewLife.Threading.TimerX)\">\n            <summary>把定时器加入队列</summary>\n            <param name=\"timer\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Remove(NewLife.Threading.TimerX)\">\n            <summary>从队列删除定时器</summary>\n            <param name=\"timer\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Wake\">\n            <summary>唤醒处理</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Process(System.Object)\">\n            <summary>调度主程序</summary>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.CheckTime(NewLife.Threading.TimerX,System.DateTime)\">\n            <summary>检查定时器是否到期</summary>\n            <param name=\"timer\"></param>\n            <param name=\"now\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.Execute(System.Object)\">\n            <summary>处理每一个定时器</summary>\n            <param name=\"state\"></param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerScheduler.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerScheduler.Debug\">\n            <summary>是否开启调试，输出更多信息</summary>\n        </member>\n        <member name=\"T:NewLife.Threading.TimerX\">\n            <summary>不可重入的定时器。</summary>\n            <remarks>\n            为了避免系统的Timer可重入的问题，差别在于本地调用完成后才开始计算时间间隔。这实际上也是经常用到的。\n            \n            因为挂载在静态列表上，必须从外部主动调用<see cref=\"M:System.IDisposable.Dispose\"/>才能销毁定时器。\n            \n            该定时器不能放入太多任务，否则适得其反！\n            \n            TimerX必须维持对象，否则很容易被GC回收。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Scheduler\">\n            <summary>所属调度器</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Callback\">\n            <summary>获取/设置 回调</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.State\">\n            <summary>获取/设置 用户数据</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.NextTime\">\n            <summary>获取/设置 下一次调用时间</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Timers\">\n            <summary>获取/设置 调用次数</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Period\">\n            <summary>获取/设置 间隔周期。毫秒，设为0或-1则只调用一次</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Async\">\n            <summary>获取/设置 异步执行任务。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Absolutely\">\n            <summary>获取/设置 绝对精确时间执行。默认false</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Calling\">\n            <summary>调用中</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Cost\">\n            <summary>平均耗时。毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.CanExecute\">\n            <summary>判断任务是否执行的委托。一般跟异步配合使用，避免频繁从线程池借出线程</summary>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Current\">\n            <summary>当前定时器</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.#ctor(System.Threading.WaitCallback,System.Object,System.Int32,System.Int32,System.String)\">\n            <summary>实例化一个不可重入的定时器</summary>\n            <param name=\"callback\">委托</param>\n            <param name=\"state\">用户数据</param>\n            <param name=\"dueTime\">多久之后开始。毫秒</param>\n            <param name=\"period\">间隔周期。毫秒</param>\n            <param name=\"scheduler\">调度器</param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.#ctor(System.Threading.WaitCallback,System.Object,System.DateTime,System.Int32,System.String)\">\n            <summary>实例化一个绝对定时器</summary>\n            <param name=\"callback\">委托</param>\n            <param name=\"state\">用户数据</param>\n            <param name=\"startTime\">绝对开始时间</param>\n            <param name=\"period\">间隔周期。毫秒</param>\n            <param name=\"scheduler\">调度器</param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.Dispose\">\n            <summary>销毁定时器</summary>\n        </member>\n        <member name=\"F:NewLife.Threading.TimerX.hasSetNext\">\n            <summary>是否已设置下一次时间</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.SetNext(System.Int32)\">\n            <summary>设置下一次运行时间</summary>\n            <param name=\"ms\">小于等于0表示马上调度</param>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.Delay(System.Threading.WaitCallback,System.Int32)\">\n            <summary>延迟执行一个委托</summary>\n            <param name=\"callback\"></param>\n            <param name=\"ms\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Threading.TimerX.Now\">\n            <summary>当前时间。定时读取系统时间，避免频繁读取系统时间造成性能瓶颈</summary>\n        </member>\n        <member name=\"M:NewLife.Threading.TimerX.ToString\">\n            <summary>已重载</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.Link\">\n            <summary>超链接</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.FullName\">\n            <summary>全名</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Url\">\n            <summary>超链接</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.RawUrl\">\n            <summary>原始超链接</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Title\">\n            <summary>标题</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Version\">\n            <summary>版本</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Time\">\n            <summary>时间</summary>\n        </member>\n        <member name=\"P:NewLife.Web.Link.Html\">\n            <summary>原始Html</summary>\n        </member>\n        <member name=\"M:NewLife.Web.Link.Parse(System.String,System.String,System.Func{NewLife.Web.Link,System.Boolean})\">\n            <summary>分析HTML中的链接</summary>\n            <param name=\"html\">Html文本</param>\n            <param name=\"baseurl\">基础Url，用于生成超链接的完整Url</param>\n            <param name=\"filter\">用于基础过滤的过滤器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.Link.Parse(System.String)\">\n            <summary>分解文件</summary>\n            <param name=\"file\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.Link.ToString\">\n            <summary>已重载。</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.CompressionModule\">\n            <summary>页面压缩模块</summary>\n        </member>\n        <member name=\"M:NewLife.Web.CompressionModule.System#Web#IHttpModule#Init(System.Web.HttpApplication)\">\n            <summary>初始化模块，准备拦截请求。</summary>\n            <param name=\"context\"></param>\n        </member>\n        <member name=\"P:NewLife.Web.CompressionModule.WebCompressFiles\">\n            <summary>网页压缩文件</summary>\n        </member>\n        <member name=\"M:NewLife.Web.CompressionModule.CanCompress(System.String)\">\n            <summary>是否可压缩文件</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.CompressionModule.IsEncodingAccepted(System.Web.HttpRequest,System.String)\">\n            <summary>检查请求头，确认客户端是否支持压缩编码</summary>\n            <param name=\"req\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.CompressionModule.SetEncoding(System.Web.HttpResponse,System.String)\">\n            <summary>添加压缩编码到响应头</summary>\n            <param name=\"res\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.ErrorModule\">\n            <summary>全局错误处理模块</summary>\n        </member>\n        <member name=\"M:NewLife.Web.ErrorModule.System#Web#IHttpModule#Init(System.Web.HttpApplication)\">\n            <summary>初始化模块，准备拦截请求。</summary>\n            <param name=\"context\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.ErrorModule.NeedProcess(System.Web.HttpRequest,System.Exception)\">\n            <summary>是否需要处理</summary>\n            <param name=\"req\"></param>\n            <param name=\"ex\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.ErrorModule.OnError(System.Object,System.EventArgs)\">\n            <summary>错误处理方法</summary>\n            <param name=\"sender\"></param>\n            <param name=\"e\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.RunTimeModule\">\n            <summary>页面执行时间模块</summary>\n        </member>\n        <member name=\"M:NewLife.Web.RunTimeModule.System#Web#IHttpModule#Init(System.Web.HttpApplication)\">\n            <summary>初始化模块，准备拦截请求。</summary>\n            <param name=\"context\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.RunTimeModule.OnInit(System.Web.HttpContext)\">\n            <summary>初始化</summary>\n        </member>\n        <member name=\"P:NewLife.Web.RunTimeModule.RunTimeFormat\">\n            <summary>执行时间字符串</summary>\n        </member>\n        <member name=\"M:NewLife.Web.RunTimeModule.WriteRunTime(System.Object,System.EventArgs)\">\n            <summary>输出运行时间</summary>\n        </member>\n        <member name=\"M:NewLife.Web.RunTimeModule.Render(System.Web.HttpContext)\">\n            <summary>输出</summary>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.OAuthClient\">\n            <summary>OAuth 2.0 客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Server\">\n            <summary>验证服务器地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.AccessServer\">\n            <summary>令牌服务地址。可以不同于验证地址的内网直达地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Key\">\n            <summary>应用Key</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Secret\">\n            <summary>安全码</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.AuthUrl\">\n            <summary>验证地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.AccessUrl\">\n            <summary>访问令牌地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.ResponseType\">\n            <summary>响应类型</summary>\n            <remarks>\n            验证服务器跳转回来子系统时的类型，默认code，此时还需要子系统服务端请求验证服务器换取AccessToken；\n            可选token，此时验证服务器直接返回AccessToken，子系统不需要再次请求。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Scope\">\n            <summary>作用域</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Code\">\n            <summary>授权码</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.AccessToken\">\n            <summary>访问令牌</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.RefreshToken\">\n            <summary>刷新令牌</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.OpenID\">\n            <summary>统一标识</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Expire\">\n            <summary>过期时间</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Items\">\n            <summary>访问项</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Create(System.String)\">\n            <summary>根据名称创建客户端</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Apply(System.String)\">\n            <summary>应用参数设置</summary>\n            <param name=\"name\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Apply(NewLife.Web.OAuthItem)\">\n            <summary>应用参数设置</summary>\n            <param name=\"mi\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Authorize(System.String,System.String,System.Uri)\">\n            <summary>构建跳转验证地址</summary>\n            <param name=\"redirect\">验证完成后调整的目标地址</param>\n            <param name=\"state\">用户状态数据</param>\n            <param name=\"baseUri\">相对地址的基地址</param>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.GetAccessToken(System.String)\">\n            <summary>根据授权码获取访问令牌</summary>\n            <param name=\"code\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.OpenIDUrl\">\n            <summary>OpenID地址</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.GetOpenID\">\n            <summary>根据授权码获取访问令牌</summary>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.UserUrl\">\n            <summary>用户信息地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.UserID\">\n            <summary>用户ID</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.UserName\">\n            <summary>用户名</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.NickName\">\n            <summary>昵称</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Avatar\">\n            <summary>头像</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.GetUserInfo\">\n            <summary>获取用户信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Fill(NewLife.Model.IManageUser)\">\n            <summary>填充用户，登录成功并获取用户信息之后</summary>\n            <param name=\"user\"></param>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.LogoutUrl\">\n            <summary>注销地址</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Logout(System.String,System.String,System.Uri)\">\n            <summary>注销</summary>\n            <param name=\"redirect\">完成后调整的目标地址</param>\n            <param name=\"state\">用户状态数据</param>\n            <param name=\"baseUri\">相对地址的基地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.GetUrl(System.String)\">\n            <summary>替换地址模版参数</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.GetNameValues(System.String)\">\n            <summary>获取名值字典</summary>\n            <param name=\"html\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.LastHtml\">\n            <summary>最后一次请求的响应内容</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.Request(System.String)\">\n            <summary>创建客户端</summary>\n            <param name=\"url\">路径</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthClient.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthClient.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.OAuthConfig\">\n            <summary>配置</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthConfig.Debug\">\n            <summary>调试开关。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthConfig.AppUrl\">\n            <summary>应用地址。域名和端口，应用系统经过反向代理重定向时指定外部地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthConfig.Items\">\n            <summary>配置项</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthConfig.OnLoaded\">\n            <summary>已加载</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthConfig.Get(System.String)\">\n            <summary>获取</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthConfig.GetOrAdd(System.String)\">\n            <summary>获取或添加</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.OAuthItem\">\n            <summary>开放验证服务器配置项</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.Name\">\n            <summary>服务地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.Server\">\n            <summary>验证服务地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.AccessServer\">\n            <summary>令牌服务地址。可以不同于验证地址的内网直达地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.AppID\">\n            <summary>应用标识</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.Secret\">\n            <summary>密钥</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthItem.Scope\">\n            <summary>授权范围</summary>\n        </member>\n        <member name=\"T:NewLife.Web.OAuthServer\">\n            <summary>单点登录服务端</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthServer.Cache\">\n            <summary>缓存</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthServer.TokenProvider\">\n            <summary>令牌提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthServer.Expire\">\n            <summary>令牌有效期。默认24小时</summary>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthServer.Instance\">\n            <summary>实例</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthServer.Authorize(System.String,System.String,System.String,System.String,System.String)\">\n            <summary>验证用户身份</summary>\n            <remarks>\n            子系统需要验证访问者身份时，引导用户跳转到这里。\n            用户登录完成后，得到一个独一无二的code，并跳转回去子系统。\n            </remarks>\n            <param name=\"client_id\">应用标识</param>\n            <param name=\"redirect_uri\">回调地址</param>\n            <param name=\"response_type\">响应类型。默认code</param>\n            <param name=\"scope\">授权域</param>\n            <param name=\"state\">用户状态数据</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthServer.GetResult(System.Int32,NewLife.Model.IManageUser)\">\n            <summary>根据验证结果获取跳转回子系统的Url</summary>\n            <param name=\"key\"></param>\n            <param name=\"user\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthServer.GetToken(System.String,System.String,System.String)\">\n            <summary>根据Code获取令牌</summary>\n            <param name=\"client_id\"></param>\n            <param name=\"client_secret\"></param>\n            <param name=\"code\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthServer.Decode(System.String)\">\n            <summary>解码令牌</summary>\n            <param name=\"token\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Web.OAuthServer.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuthServer.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.OAuth.BaiduClient\">\n            <summary>身份验证提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.BaiduClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.BaiduClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.OAuth.GithubClient\">\n            <summary>身份验证提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.GithubClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.GithubClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.GithubClient.Request(System.String)\">\n            <summary>创建客户端</summary>\n            <param name=\"url\">路径</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.OAuth.QQClient\">\n            <summary>身份验证提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.QQClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.QQClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.OAuth.TaobaoClient\">\n            <summary>淘宝身份验证提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.TaobaoClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.TaobaoClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.OAuth.WeixinClient\">\n            <summary>身份验证提供者</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.WeixinClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.OAuth.WeixinClient.OnGetInfo(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>从响应数据中获取信息</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.Web.PluginHelper\">\n            <summary>插件助手</summary>\n        </member>\n        <member name=\"M:NewLife.Web.PluginHelper.LoadPlugin(System.String,System.String,System.String,System.String,System.String)\">\n            <summary>加载插件</summary>\n            <param name=\"typeName\"></param>\n            <param name=\"disname\"></param>\n            <param name=\"dll\"></param>\n            <param name=\"linkName\"></param>\n            <param name=\"urls\">提供下载地址的多个目标页面</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.TokenProvider\">\n            <summary>令牌提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Web.TokenProvider.Key\">\n            <summary>密钥。签发方用私钥，验证方用公钥</summary>\n        </member>\n        <member name=\"M:NewLife.Web.TokenProvider.ReadKey(System.String,System.Boolean)\">\n            <summary>读取密钥</summary>\n            <param name=\"file\">文件</param>\n            <param name=\"generate\">是否生成</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.TokenProvider.Encode(System.String,System.DateTime)\">\n            <summary>编码用户和有效期得到令牌</summary>\n            <param name=\"user\">用户</param>\n            <param name=\"expire\">有效期</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.TokenProvider.Decode(System.String,System.DateTime@)\">\n            <summary>令牌解码得到用户和有效期</summary>\n            <param name=\"token\">令牌</param>\n            <param name=\"expire\">有效期</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Web.WebClientX\">\n            <summary>扩展的Web客户端</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Cookie\">\n            <summary>Cookie容器</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Accept\">\n            <summary>可接受类型</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.AcceptLanguage\">\n            <summary>可接受语言</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Referer\">\n            <summary>引用页面</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Timeout\">\n            <summary>超时，默认15000毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.AutomaticDecompression\">\n            <summary>自动解压缩模式。</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.UserAgent\">\n            <summary>User-Agent 标头，指定有关客户端代理的信息</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Encoding\">\n            <summary>编码。网络时代，绝大部分使用utf8编码</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.KeepAlive\">\n            <summary>保持连接</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.ProxyAddress\">\n            <summary>代理服务器地址</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Proxy\">\n            <summary>网页代理</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.#ctor(System.Boolean,System.Boolean)\">\n            <summary>初始化常用的东西</summary>\n            <param name=\"ie\">是否模拟ie</param>\n            <param name=\"iscompress\">是否压缩</param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Request\">\n            <summary>请求</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Response\">\n            <summary>响应</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.EnsureCreate\">\n            <summary>创建客户端会话</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.SendAsync(System.String,System.Net.Http.HttpContent)\">\n            <summary>发送请求，获取响应</summary>\n            <param name=\"address\"></param>\n            <param name=\"content\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.DownloadDataAsync(System.String)\">\n            <summary>下载数据</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.DownloadStringAsync(System.String)\">\n            <summary>下载字符串</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.DownloadFileAsync(System.String,System.String)\">\n            <summary>下载文件</summary>\n            <param name=\"address\"></param>\n            <param name=\"fileName\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.UploadDataTaskAsync(System.String,System.Byte[])\">\n            <summary>异步上传数据</summary>\n            <param name=\"address\"></param>\n            <param name=\"data\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.UploadValuesAsync(System.String,System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,System.String}})\">\n            <summary>异步上传表单</summary>\n            <param name=\"address\"></param>\n            <param name=\"collection\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.UploadStringAsync(System.String,System.String)\">\n            <summary>异步上传字符串</summary>\n            <param name=\"address\"></param>\n            <param name=\"data\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.UploadJsonAsync(System.String,System.Object)\">\n            <summary>异步上传Json对象</summary>\n            <param name=\"address\"></param>\n            <param name=\"data\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.GetHtml(System.String)\">\n            <summary>获取指定地址的Html，自动处理文本编码</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.GetLinks(System.String)\">\n            <summary>获取指定地址的Html，分析所有超链接</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.DownloadLink(System.String,System.String,System.String)\">\n            <summary>分析指定页面指定名称的链接，并下载到目标目录，返回目标文件</summary>\n            <remarks>\n            根据版本或时间降序排序选择\n            </remarks>\n            <param name=\"urls\">指定页面</param>\n            <param name=\"name\">页面上指定名称的链接</param>\n            <param name=\"destdir\">要下载到的目标目录</param>\n            <returns>返回已下载的文件，无效时返回空</returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.DownloadLinkAndExtract(System.String,System.String,System.String,System.Boolean)\">\n            <summary>分析指定页面指定名称的链接，并下载到目标目录，解压Zip后返回目标文件</summary>\n            <param name=\"urls\">提供下载地址的多个目标页面</param>\n            <param name=\"name\">页面上指定名称的链接</param>\n            <param name=\"destdir\">要下载到的目标目录</param>\n            <param name=\"overwrite\">是否覆盖目标同名文件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.SetCookie\">\n            <summary>根据Http响应设置本地Cookie</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.GetCookie(System.Net.Http.HttpClient)\">\n            <summary>从本地获取Cookie并设置到Http请求头</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Pool\">\n            <summary>默认连接池</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.GetStringAsync(System.String)\">\n            <summary>访问地址获取字符串</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebClientX.SetAllowUnsafeHeaderParsing(System.Boolean)\">\n            <summary>设置是否允许不安全头部</summary>\n            <remarks>\n            微软WebClient默认要求严格的Http头部，否则报错\n            </remarks>\n            <param name=\"useUnsafe\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Web.WebClientX.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"T:NewLife.Web.WebDownload\">\n            <summary>提供网页下载支持，在服务端把一个数据流作为附件传给浏览器，带有断点续传和限速的功能</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.Stream\">\n            <summary>数据流</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.FileName\">\n            <summary>文件名</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.ContentType\">\n            <summary>内容类型</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.Mode\">\n            <summary>附件配置模式，是在浏览器直接打开，还是提示另存为</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.Speed\">\n            <summary>速度，每秒传输字节数，根据包大小，每响应一个包后睡眠指定毫秒数，0表示不限制</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.BrowserCache\">\n            <summary>是否启用浏览器缓存 默认禁用</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.BrowserCacheMaxAge\">\n            <summary>浏览器最大缓存时间 默认30天。通过Cache-Control头控制max-age，直接使用浏览器缓存，不会发出Http请求，对F5无效</summary>\n        </member>\n        <member name=\"P:NewLife.Web.WebDownload.ModifyTime\">\n            <summary>文件数据最后修改时间，浏览器缓存时用</summary>\n        </member>\n        <member name=\"T:NewLife.Web.WebDownload.DispositionMode\">\n            <summary>附件配置模式</summary>\n        </member>\n        <member name=\"F:NewLife.Web.WebDownload.DispositionMode.None\">\n            <summary>不设置</summary>\n        </member>\n        <member name=\"F:NewLife.Web.WebDownload.DispositionMode.Inline\">\n            <summary>内联模式，在浏览器直接打开</summary>\n        </member>\n        <member name=\"F:NewLife.Web.WebDownload.DispositionMode.Attachment\">\n            <summary>附件模式，提示另存为</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.ParseMode(System.String)\">\n            <summary>分析模式</summary>\n            <param name=\"mode\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.#ctor\">\n            <summary>构造函数</summary>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.#ctor(System.IO.Stream)\">\n            <summary>构造函数</summary>\n            <param name=\"stream\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.#ctor(System.String,System.Text.Encoding)\">\n            <summary>构造函数</summary>\n            <param name=\"html\"></param>\n            <param name=\"encoding\"></param>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.CheckCache(System.Web.HttpContext)\">\n            <summary>检查浏览器缓存是否依然有效，如果有效则跳过Render</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Web.WebDownload.Render(System.Web.HttpContext)\">\n            <summary>输出数据流</summary>\n        </member>\n        <member name=\"T:NewLife.Windows.SpeechRecognition\">\n            <summary>语音识别</summary>\n        </member>\n        <member name=\"P:NewLife.Windows.SpeechRecognition.Name\">\n            <summary>系统名称。用于引导前缀</summary>\n        </member>\n        <member name=\"F:NewLife.Windows.SpeechRecognition._Tip\">\n            <summary>最后一次进入引导前缀的时间。</summary>\n        </member>\n        <member name=\"P:NewLife.Windows.SpeechRecognition.Enable\">\n            <summary>是否可用</summary>\n        </member>\n        <member name=\"M:NewLife.Windows.SpeechRecognition.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"P:NewLife.Windows.SpeechRecognition.Current\">\n            <summary>当前实例</summary>\n        </member>\n        <member name=\"M:NewLife.Windows.SpeechRecognition.GetAllKeys\">\n            <summary>获取已注册的所有键值</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Windows.SpeechRecognition.Register(System.String,System.Action)\">\n            <summary>注册要语音识别的关键字到委托</summary>\n            <param name=\"text\"></param>\n            <param name=\"callback\"></param>\n        </member>\n        <member name=\"T:NewLife.Windows.ISpeech\">\n            <summary>语音接口</summary>\n        </member>\n        <member name=\"E:NewLife.Windows.ISpeech.SpeechRecognized\">\n            <summary>语音识别事件</summary>\n        </member>\n        <member name=\"M:NewLife.Windows.ISpeech.Init\">\n            <summary>初始化</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Windows.ISpeech.SetChoices(System.Collections.Generic.IEnumerable{System.String})\">\n            <summary>设置识别短语</summary>\n            <param name=\"phrases\"></param>\n        </member>\n        <member name=\"T:NewLife.Windows.RecognitionEventArgs\">\n            <summary>识别事件参数</summary>\n        </member>\n        <member name=\"P:NewLife.Windows.RecognitionEventArgs.Confidence\">\n            <summary>获取识别器分配的值，此值表示与给定输入匹配的可能性</summary>\n        </member>\n        <member name=\"P:NewLife.Windows.RecognitionEventArgs.Text\">\n            <summary>获取语音识别器从识别的输入生成的规范化文本</summary>\n        </member>\n        <member name=\"M:NewLife.Windows.RecognitionEventArgs.#ctor(System.Single,System.String)\">\n            <summary>实例化</summary>\n            <param name=\"conf\"></param>\n            <param name=\"text\"></param>\n        </member>\n        <member name=\"T:NewLife.Xml.SerializableDictionary`2\">\n            <summary>支持Xml序列化的泛型字典类 </summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n        </member>\n        <member name=\"M:NewLife.Xml.SerializableDictionary`2.#ctor\">\n            <summary></summary>\n        </member>\n        <member name=\"M:NewLife.Xml.SerializableDictionary`2.#ctor(System.Collections.Generic.IDictionary{`0,`1})\">\n            <summary></summary>\n            <param name=\"dictionary\"></param>\n        </member>\n        <member name=\"M:NewLife.Xml.SerializableDictionary`2.ReadXml(System.Xml.XmlReader)\">\n            <summary>读取Xml</summary>\n            <param name=\"reader\">Xml读取器</param>\n        </member>\n        <member name=\"M:NewLife.Xml.SerializableDictionary`2.WriteXml(System.Xml.XmlWriter)\">\n            <summary>写入Xml</summary>\n            <param name=\"writer\">Xml写入器</param>\n        </member>\n        <member name=\"T:NewLife.Xml.XmlConfig`1\">\n            <summary>Xml配置文件基类</summary>\n            <remarks>\n            标准用法：TConfig.Current\n            \n            配置实体类通过<see cref=\"T:NewLife.Xml.XmlConfigFileAttribute\"/>特性指定配置文件路径以及自动更新时间。\n            Current将加载配置文件，如果文件不存在或者加载失败，将实例化一个对象返回。\n            \n            考虑到自动刷新，不提供LoadFile和SaveFile等方法，可通过扩展方法ToXmlFileEntity和ToXmlFile实现。\n            \n            用户也可以通过配置实体类的静态构造函数修改基类的<see cref=\"P:NewLife.Xml.XmlConfig`1._.ConfigFile\"/>和<see cref=\"P:NewLife.Xml.XmlConfig`1._.ReloadTime\"/>来动态配置加载信息。\n            </remarks>\n            <typeparam name=\"TConfig\"></typeparam>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1.Current\">\n            <summary>当前实例。通过置空可以使其重新加载。</summary>\n        </member>\n        <member name=\"T:NewLife.Xml.XmlConfig`1._\">\n            <summary>一些设置。派生类可以在自己的静态构造函数中指定</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1._.Debug\">\n            <summary>是否调试</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1._.ConfigFile\">\n            <summary>配置文件路径</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1._.ReloadTime\">\n            <summary>重新加载时间。单位：毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1._.SaveNew\">\n            <summary>没有配置文件时是否保存新配置。默认true</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1.ConfigFile\">\n            <summary>配置文件</summary>\n        </member>\n        <member name=\"F:NewLife.Xml.XmlConfig`1.lastWrite\">\n            <summary>最后写入时间</summary>\n        </member>\n        <member name=\"F:NewLife.Xml.XmlConfig`1.expire\">\n            <summary>过期时间。如果在这个时间之后再次访问，将检查文件修改时间</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1.IsUpdated\">\n            <summary>是否已更新。通过文件写入时间判断</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.SetExpire\">\n            <summary>设置过期重新加载配置的时间</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfig`1.IsNew\">\n            <summary>是否新的配置文件</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.Load(System.String)\">\n            <summary>加载指定配置文件</summary>\n            <param name=\"filename\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.OnLoaded\">\n            <summary>从配置文件中读取完成后触发</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.Save(System.String)\">\n            <summary>保存到配置文件中去</summary>\n            <param name=\"filename\"></param>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.Save\">\n            <summary>保存到配置文件中去</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.SaveAsync\">\n            <summary>异步保存</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfig`1.OnNew\">\n            <summary>新创建配置文件时执行</summary>\n        </member>\n        <member name=\"T:NewLife.Xml.XmlConfigFileAttribute\">\n            <summary>Xml配置文件特性</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfigFileAttribute.FileName\">\n            <summary>配置文件名</summary>\n        </member>\n        <member name=\"P:NewLife.Xml.XmlConfigFileAttribute.ReloadTime\">\n            <summary>重新加载时间。单位：毫秒</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfigFileAttribute.#ctor(System.String)\">\n            <summary>指定配置文件名</summary>\n            <param name=\"fileName\"></param>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlConfigFileAttribute.#ctor(System.String,System.Int32)\">\n            <summary>指定配置文件名和重新加载时间（毫秒）</summary>\n            <param name=\"fileName\"></param>\n            <param name=\"reloadTime\"></param>\n        </member>\n        <member name=\"T:NewLife.Xml.XmlHelper\">\n            <summary>Xml辅助类</summary>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXml(System.Object,System.Text.Encoding,System.Boolean,System.Boolean)\">\n            <summary>序列化为Xml字符串</summary>\n            <param name=\"obj\">要序列化为Xml的对象</param>\n            <param name=\"encoding\">编码</param>\n            <param name=\"attachComment\">是否附加注释，附加成员的Description和DisplayName注释</param>\n            <param name=\"useAttribute\">是否使用特性输出</param>\n            <returns>Xml字符串</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXml(System.Object,System.IO.Stream,System.Text.Encoding,System.Boolean,System.Boolean)\">\n            <summary>序列化为Xml数据流</summary>\n            <param name=\"obj\">要序列化为Xml的对象</param>\n            <param name=\"stream\">目标数据流</param>\n            <param name=\"encoding\">编码</param>\n            <param name=\"attachComment\">是否附加注释，附加成员的Description和DisplayName注释</param>\n            <param name=\"useAttribute\">是否使用特性输出</param>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlFile(System.Object,System.String,System.Text.Encoding,System.Boolean)\">\n            <summary>序列化为Xml文件</summary>\n            <param name=\"obj\">要序列化为Xml的对象</param>\n            <param name=\"file\">目标Xml文件</param>\n            <param name=\"encoding\">编码</param>\n            <param name=\"attachComment\">是否附加注释，附加成员的Description和DisplayName注释</param>\n            <returns>Xml字符串</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlEntity``1(System.String)\">\n            <summary>字符串转为Xml实体对象</summary>\n            <typeparam name=\"TEntity\">实体类型</typeparam>\n            <param name=\"xml\">Xml字符串</param>\n            <returns>Xml实体对象</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlEntity(System.String,System.Type)\">\n            <summary>字符串转为Xml实体对象</summary>\n            <param name=\"xml\">Xml字符串</param>\n            <param name=\"type\">实体类型</param>\n            <returns>Xml实体对象</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlEntity``1(System.IO.Stream,System.Text.Encoding)\">\n            <summary>数据流转为Xml实体对象</summary>\n            <typeparam name=\"TEntity\">实体类型</typeparam>\n            <param name=\"stream\">数据流</param>\n            <param name=\"encoding\">编码</param>\n            <returns>Xml实体对象</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlEntity(System.IO.Stream,System.Type,System.Text.Encoding)\">\n            <summary>数据流转为Xml实体对象</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"type\">实体类型</param>\n            <param name=\"encoding\">编码</param>\n            <returns>Xml实体对象</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlFileEntity``1(System.String,System.Text.Encoding)\">\n            <summary>Xml文件转为Xml实体对象</summary>\n            <typeparam name=\"TEntity\">实体类型</typeparam>\n            <param name=\"file\">Xml文件</param>\n            <param name=\"encoding\">编码</param>\n            <returns>Xml实体对象</returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXmlDictionary(System.String)\">\n            <summary>简单Xml转为字符串字典</summary>\n            <param name=\"xml\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Xml.XmlHelper.ToXml(System.Collections.Generic.IDictionary{System.String,System.String},System.String)\">\n            <summary>字符串字典转为Xml</summary>\n            <param name=\"dic\"></param>\n            <param name=\"rootName\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Yun.AMap\">\n            <summary>高德地图</summary>\n            <remarks>\n            参考地址 http://lbs.amap.com/api/webservice/guide/api/georegeo/#geo\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.#ctor\">\n            <summary>高德地图</summary>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.InvokeAsync``1(System.String,System.String)\">\n            <summary>远程调用</summary>\n            <param name=\"url\">目标Url</param>\n            <param name=\"result\">结果字段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetGeocoderAsync(System.String,System.String)\">\n            <summary>查询地址的经纬度坐标</summary>\n            <param name=\"address\"></param>\n            <param name=\"city\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetGeoAsync(System.String,System.String,System.Boolean)\">\n            <summary>查询地址获取坐标</summary>\n            <param name=\"address\">地址</param>\n            <param name=\"city\">城市</param>\n            <param name=\"formatAddress\">是否格式化地址。高德地图默认已经格式化地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetGeocoderAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <remarks>\n            http://lbs.amap.com/api/webservice/guide/api/georegeo/#regeo\n            </remarks>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetGeoAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetDistanceAsync(NewLife.Data.GeoPoint,NewLife.Data.GeoPoint,System.Int32)\">\n            <summary>计算距离和驾车时间</summary>\n            <remarks>\n            http://lbs.amap.com/api/webservice/guide/api/direction\n            \n            type:\n            0：直线距离\n            1：驾车导航距离（仅支持国内坐标）。\n            必须指出，当为1时会考虑路况，故在不同时间请求返回结果可能不同。\n            此策略和driving接口的 strategy = 4策略一致\n            2：公交规划距离（仅支持同城坐标）\n            3：步行规划距离（仅支持5km之间的距离）\n            \n            distance    路径距离，单位：米\n            duration    预计行驶时间，单位：秒\n            </remarks>\n            <param name=\"origin\"></param>\n            <param name=\"destination\"></param>\n            <param name=\"type\">路径计算的方式和方法</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.GetAreaAsync(System.String,System.Int32,System.Int32)\">\n            <summary>行政区划</summary>\n            <remarks>\n            http://lbs.amap.com/api/webservice/guide/api/district\n            </remarks>\n            <param name=\"keywords\">查询关键字</param>\n            <param name=\"subdistrict\">设置显示下级行政区级数</param>\n            <param name=\"code\">按照指定行政区划进行过滤，填入后则只返回该省/直辖市信息</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.AMap.IsValidKey(System.String)\">\n            <summary>是否无效Key。可能禁用或超出限制</summary>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Yun.BaiduMap\">\n            <summary>百度地图</summary>\n            <remarks>\n            参考手册 http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding\n            </remarks>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.#ctor\">\n            <summary>高德地图</summary>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.InvokeAsync``1(System.String,System.String)\">\n            <summary>远程调用</summary>\n            <param name=\"url\">目标Url</param>\n            <param name=\"result\">结果字段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.GetGeocoderAsync(System.String,System.String)\">\n            <summary>查询地址的经纬度坐标</summary>\n            <param name=\"address\"></param>\n            <param name=\"city\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.GetGeoAsync(System.String,System.String,System.Boolean)\">\n            <summary>查询地址获取坐标</summary>\n            <param name=\"address\">地址</param>\n            <param name=\"city\">城市</param>\n            <param name=\"formatAddress\">是否格式化地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.GetGeocoderAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.GetGeoAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.GetDistanceAsync(NewLife.Data.GeoPoint,NewLife.Data.GeoPoint,System.Int32)\">\n            <summary>计算距离和驾车时间</summary>\n            <remarks>\n            http://lbsyun.baidu.com/index.php?title=webapi/route-matrix-api-v2\n            </remarks>\n            <param name=\"origin\"></param>\n            <param name=\"destination\"></param>\n            <param name=\"type\">路径计算的方式和方法</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.PlaceSearchAsync(System.String,System.String,System.String,System.Boolean)\">\n            <summary>行政区划区域检索</summary>\n            <remarks>\n            http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-placeapi\n            </remarks>\n            <param name=\"query\"></param>\n            <param name=\"tag\"></param>\n            <param name=\"region\"></param>\n            <param name=\"formatAddress\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.BaiduMap.IsValidKey(System.String)\">\n            <summary>是否无效Key。可能禁用或超出限制</summary>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.Yun.Driving\">\n            <summary>驾车距离和时间</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Driving.Distance\">\n            <summary>距离。单位千米</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Driving.Duration\">\n            <summary>路线耗时。单位秒</summary>\n        </member>\n        <member name=\"T:NewLife.Yun.IMap\">\n            <summary>地图提供者接口</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.IMap.AppKey\">\n            <summary>应用密钥</summary>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetStringAsync(System.String)\">\n            <summary>异步获取字符串</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetGeocoderAsync(System.String,System.String)\">\n            <summary>查询地址的经纬度坐标</summary>\n            <param name=\"address\"></param>\n            <param name=\"city\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetGeoAsync(System.String,System.String,System.Boolean)\">\n            <summary>查询地址获取坐标</summary>\n            <param name=\"address\">地址</param>\n            <param name=\"city\">城市</param>\n            <param name=\"formatAddress\">是否格式化地址</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetGeocoderAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetGeoAsync(NewLife.Data.GeoPoint)\">\n            <summary>根据坐标获取地址</summary>\n            <param name=\"point\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.IMap.GetDistanceAsync(NewLife.Data.GeoPoint,NewLife.Data.GeoPoint,System.Int32)\">\n            <summary>计算距离和驾车时间</summary>\n            <param name=\"origin\"></param>\n            <param name=\"destination\"></param>\n            <param name=\"type\">路径计算的方式和方法</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Yun.IMap.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"T:NewLife.Yun.Map\">\n            <summary>地图提供者</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.AppKey\">\n            <summary>应用密钥。多个key逗号分隔</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.KeyName\">\n            <summary>应用密码参数名</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.LastKey\">\n            <summary>最后密钥</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.CoordType\">\n            <summary>坐标系</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.LastUrl\">\n            <summary>最后网址</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.LastString\">\n            <summary>最后响应</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.LastResult\">\n            <summary>最后结果</summary>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.ThrowException\">\n            <summary>收到异常响应时是否抛出异常</summary>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.GetStringAsync(System.String)\">\n            <summary>异步获取字符串</summary>\n            <param name=\"url\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.InvokeAsync``1(System.String,System.String)\">\n            <summary>远程调用</summary>\n            <param name=\"url\">目标Url</param>\n            <param name=\"result\">结果字段</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.AcquireKey\">\n            <summary>申请密钥</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.RemoveKey(System.String)\">\n            <summary>移除不可用密钥</summary>\n            <param name=\"key\"></param>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.IsValidKey(System.String)\">\n            <summary>是否无效Key。可能禁用或超出限制</summary>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.Yun.Map.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.Yun.Map.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:System.Collections.Generic.CollectionHelper\">\n            <summary>集合扩展</summary>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.ToArray``1(System.Collections.Generic.ICollection{``0},System.Int32)\">\n            <summary>集合转为数组</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"collection\"></param>\n            <param name=\"index\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.ToKeyArray``2(System.Collections.Generic.IDictionary{``0,``1},System.Int32)\">\n            <summary>集合转为数组</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <param name=\"collection\"></param>\n            <param name=\"index\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.ToValueArray``2(System.Collections.Generic.IDictionary{``0,``1},System.Int32)\">\n            <summary>集合转为数组</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <param name=\"collection\"></param>\n            <param name=\"index\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.ToDictionary(System.Object)\">\n            <summary>目标匿名参数对象转为字典</summary>\n            <param name=\"target\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.Merge(System.Collections.Generic.IDictionary{System.String,System.Object},System.Object,System.Boolean,System.String[])\">\n            <summary>合并字典参数</summary>\n            <param name=\"dic\">字典</param>\n            <param name=\"target\">目标对象</param>\n            <param name=\"overwrite\">是否覆盖同名参数</param>\n            <param name=\"excludes\">排除项</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.ToNullable``2(System.Collections.Generic.IDictionary{``0,``1},System.Collections.Generic.IEqualityComparer{``0})\">\n            <summary>转为可空字典</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <param name=\"collection\"></param>\n            <param name=\"comparer\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.Take``1(System.Collections.Generic.Queue{``0},System.Int32)\">\n            <summary>从队列里面获取指定个数元素</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"collection\">消费集合</param>\n            <param name=\"count\">元素个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Collections.Generic.CollectionHelper.Take``1(System.Collections.Concurrent.IProducerConsumerCollection{``0},System.Int32)\">\n            <summary>从消费集合里面获取指定个数元素</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"collection\">消费集合</param>\n            <param name=\"count\">元素个数</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.Utility\">\n            <summary>工具类</summary>\n            <remarks>\n            采用静态架构，允许外部重载工具类的各种实现<seealso cref=\"T:System.DefaultConvert\"/>。\n            所有类型转换均支持默认值，默认值为该default(T)，在转换失败时返回默认值。\n            </remarks>\n        </member>\n        <member name=\"P:System.Utility.Convert\">\n            <summary>类型转换提供者</summary>\n            <remarks>重载默认提供者<seealso cref=\"T:System.DefaultConvert\"/>并赋值给<see cref=\"P:System.Utility.Convert\"/>可改变所有类型转换的行为</remarks>\n        </member>\n        <member name=\"M:System.Utility.ToInt(System.Object,System.Int32)\">\n            <summary>转为整数，转换失败时返回默认值。支持字符串、全角、字节数组（小端）、时间（Unix秒）</summary>\n            <remarks>Int16/UInt32/Int64等，可以先转为最常用的Int32后再二次处理</remarks>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToLong(System.Object,System.Int64)\">\n            <summary>转为长整数，转换失败时返回默认值。支持字符串、全角、字节数组（小端）、时间（Unix毫秒）</summary>\n            <remarks></remarks>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToDouble(System.Object,System.Double)\">\n            <summary>转为浮点数，转换失败时返回默认值。支持字符串、全角、字节数组（小端）</summary>\n            <remarks>Single可以先转为最常用的Double后再二次处理</remarks>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToBoolean(System.Object,System.Boolean)\">\n            <summary>转为布尔型，转换失败时返回默认值。支持大小写True/False、0和非零</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToDateTime(System.Object)\">\n            <summary>转为时间日期，转换失败时返回最小时间。支持字符串、整数（Unix秒）</summary>\n            <param name=\"value\">待转换对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToDateTime(System.Object,System.DateTime)\">\n            <summary>转为时间日期，转换失败时返回默认值</summary>\n            <remarks><see cref=\"F:System.DateTime.MinValue\"/>不是常量无法做默认值</remarks>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToFullString(System.DateTime)\">\n            <summary>时间日期转为yyyy-MM-dd HH:mm:ss完整字符串</summary>\n            <remarks>最常用的时间日期格式，可以无视各平台以及系统自定义的时间格式</remarks>\n            <param name=\"value\">待转换对象</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToFullString(System.DateTime,System.String)\">\n            <summary>时间日期转为yyyy-MM-dd HH:mm:ss完整字符串，支持指定最小时间的字符串</summary>\n            <remarks>最常用的时间日期格式，可以无视各平台以及系统自定义的时间格式</remarks>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"emptyValue\">字符串空值时（DateTime.MinValue）显示的字符串，null表示原样显示最小时间，String.Empty表示不显示</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.ToString(System.DateTime,System.String,System.String)\">\n            <summary>时间日期转为指定格式字符串</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"emptyValue\">字符串空值时显示的字符串，null表示原样显示最小时间，String.Empty表示不显示</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.GetTrue(System.Exception)\">\n            <summary>获取内部真实异常</summary>\n            <param name=\"ex\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Utility.GetMessage(System.Exception)\">\n            <summary>获取异常消息</summary>\n            <param name=\"ex\">异常</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.DefaultConvert\">\n            <summary>默认转换</summary>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToInt(System.Object,System.Int32)\">\n            <summary>转为整数，转换失败时返回默认值。支持字符串、全角、字节数组（小端）、时间（Unix秒）</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToLong(System.Object,System.Int64)\">\n            <summary>转为长整数。支持字符串、全角、字节数组（小端）、时间（Unix毫秒）</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToDouble(System.Object,System.Double)\">\n            <summary>转为浮点数</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToBoolean(System.Object,System.Boolean)\">\n            <summary>转为布尔型。支持大小写True/False、0和非零</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToDateTime(System.Object,System.DateTime)\">\n            <summary>转为时间日期，转换失败时返回最小时间。支持字符串、整数（Unix秒）</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"defaultValue\">默认值。待转换对象无效时使用</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToDBC(System.String)\">\n            <summary>全角为半角</summary>\n            <remarks>全角半角的关系是相差0xFEE0</remarks>\n            <param name=\"str\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToFullString(System.DateTime,System.String)\">\n            <summary>时间日期转为yyyy-MM-dd HH:mm:ss完整字符串</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"emptyValue\">字符串空值时显示的字符串，null表示原样显示最小时间，String.Empty表示不显示</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToString(System.DateTime,System.String,System.String)\">\n            <summary>时间日期转为指定格式字符串</summary>\n            <param name=\"value\">待转换对象</param>\n            <param name=\"format\">格式化字符串</param>\n            <param name=\"emptyValue\">字符串空值时显示的字符串，null表示原样显示最小时间，String.Empty表示不显示</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.GetTrue(System.Exception)\">\n            <summary>获取内部真实异常</summary>\n            <param name=\"ex\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.GetMessage(System.Exception)\">\n            <summary>获取异常消息</summary>\n            <param name=\"ex\">异常</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.DefaultConvert.ToGMK(System.Int64,System.String)\">\n            <summary>字节单位字符串</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"format\">格式化字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.BitHelper\">\n            <summary>数据位助手</summary>\n        </member>\n        <member name=\"M:System.BitHelper.SetBit(System.UInt16,System.Int32,System.Boolean)\">\n            <summary>设置数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <param name=\"flag\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.BitHelper.SetBits(System.UInt16,System.Int32,System.Int32,System.UInt16)\">\n            <summary>设置数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <param name=\"length\"></param>\n            <param name=\"bits\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.BitHelper.SetBit(System.Byte,System.Int32,System.Boolean)\">\n            <summary>设置数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <param name=\"flag\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.BitHelper.GetBit(System.UInt16,System.Int32)\">\n            <summary>获取数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.BitHelper.GetBits(System.UInt16,System.Int32,System.Int32)\">\n            <summary>获取数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <param name=\"length\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.BitHelper.GetBit(System.Byte,System.Int32)\">\n            <summary>获取数据位</summary>\n            <param name=\"value\">数值</param>\n            <param name=\"position\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.ConcurrentDictionaryExtensions\">\n            <summary>并行字典扩展</summary>\n        </member>\n        <member name=\"M:System.ConcurrentDictionaryExtensions.Remove``2(System.Collections.Concurrent.ConcurrentDictionary{``0,``1},``0)\">\n            <summary>从并行字典中删除</summary>\n            <typeparam name=\"TKey\"></typeparam>\n            <typeparam name=\"TValue\"></typeparam>\n            <param name=\"dict\"></param>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.EndPointExtensions\">\n            <summary>网络结点扩展</summary>\n        </member>\n        <member name=\"M:System.EndPointExtensions.ToAddress(System.Net.EndPoint)\">\n            <summary>\n            \n            </summary>\n            <param name=\"endpoint\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EndPointExtensions.ToAddress(System.Net.IPEndPoint)\">\n            <summary>\n            \n            </summary>\n            <param name=\"endpoint\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EndPointExtensions.ToEndPoint(System.String)\">\n            <summary>\n            \n            </summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EndPointExtensions.ToEndPoints(System.String)\">\n            <summary>\n            \n            </summary>\n            <param name=\"addresses\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.EnumHelper\">\n            <summary>枚举类型助手类</summary>\n        </member>\n        <member name=\"M:System.EnumHelper.Has(System.Enum,System.Enum)\">\n            <summary>枚举变量是否包含指定标识</summary>\n            <param name=\"value\">枚举变量</param>\n            <param name=\"flag\">要判断的标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EnumHelper.Set``1(System.Enum,``0,System.Boolean)\">\n            <summary>设置标识位</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"source\"></param>\n            <param name=\"flag\"></param>\n            <param name=\"value\">数值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EnumHelper.GetDescription(System.Enum)\">\n            <summary>获取枚举字段的注释</summary>\n            <param name=\"value\">数值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EnumHelper.GetDescriptions``1\">\n            <summary>获取枚举类型的所有字段注释</summary>\n            <typeparam name=\"TEnum\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.EnumHelper.GetDescriptions(System.Type)\">\n            <summary>获取枚举类型的所有字段注释</summary>\n            <param name=\"enumType\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.ListExtension\">\n            <summary>扩展List，支持遍历中修改元素</summary>\n        </member>\n        <member name=\"M:System.ListExtension.Find``1(System.Collections.Generic.IList{``0},System.Predicate{``0})\">\n            <summary>线程安全，搜索并返回第一个，支持遍历中修改元素</summary>\n            <param name=\"list\">实体列表</param>\n            <param name=\"match\">条件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ListExtension.FindAll``1(System.Collections.Generic.IList{``0},System.Predicate{``0})\">\n            <summary>线程安全，搜索并返回第一个，支持遍历中修改元素</summary>\n            <param name=\"list\">实体列表</param>\n            <param name=\"match\">条件</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.StringHelper\">\n            <summary>字符串助手类</summary>\n        </member>\n        <member name=\"M:System.StringHelper.EqualIgnoreCase(System.String,System.String[])\">\n            <summary>忽略大小写的字符串相等比较，判断是否以任意一个待比较字符串相等</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"strs\">待比较字符串数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.StartsWithIgnoreCase(System.String,System.String[])\">\n            <summary>忽略大小写的字符串开始比较，判断是否以任意一个待比较字符串开始</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"strs\">待比较字符串数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.EndsWithIgnoreCase(System.String,System.String[])\">\n            <summary>忽略大小写的字符串结束比较，判断是否以任意一个待比较字符串结束</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"strs\">待比较字符串数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.IsNullOrEmpty(System.String)\">\n            <summary>指示指定的字符串是 null 还是 String.Empty 字符串</summary>\n            <param name=\"value\">字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.IsNullOrWhiteSpace(System.String)\">\n            <summary>是否空或者空白字符串</summary>\n            <param name=\"value\">字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Split(System.String,System.String[])\">\n            <summary>拆分字符串，过滤空格，无效时返回空数组</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"separators\">分组分隔符，默认逗号分号</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.SplitAsInt(System.String,System.String[])\">\n            <summary>拆分字符串成为整型数组，默认逗号分号分隔，无效时返回空数组</summary>\n            <remarks>过滤空格、过滤无效、不过滤重复</remarks>\n            <param name=\"value\">字符串</param>\n            <param name=\"separators\">分组分隔符，默认逗号分号</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.SplitAsDictionary(System.String,System.String,System.String[])\">\n            <summary>拆分字符串成为不区分大小写的可空名值字典。逗号分号分组，等号分隔</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"nameValueSeparator\">名值分隔符，默认等于号</param>\n            <param name=\"separators\">分组分隔符，默认逗号分号</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.SplitAsDictionary(System.String,System.String,System.String,System.Boolean)\">\n            <summary>拆分字符串成为不区分大小写的可空名值字典。逗号分组，等号分隔</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"nameValueSeparator\">名值分隔符，默认等于号</param>\n            <param name=\"separator\">分组分隔符，默认分号</param>\n            <param name=\"trimQuotation\">去掉括号</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.SplitAsDictionaryT(System.String,System.Char,System.Char,System.Boolean)\">\n            <summary>\n            在.netCore需要区分该部分内容\n            </summary>\n            <param name=\"value\"></param>\n            <param name=\"nameValueSeparator\"></param>\n            <param name=\"separator\"></param>\n            <param name=\"trimQuotation\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Join(System.Collections.IEnumerable,System.String)\">\n            <summary>把一个列表组合成为一个字符串，默认逗号分隔</summary>\n            <param name=\"value\"></param>\n            <param name=\"separator\">组合分隔符，默认逗号</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Join``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.String})\">\n            <summary>把一个列表组合成为一个字符串，默认逗号分隔</summary>\n            <param name=\"value\"></param>\n            <param name=\"separator\">组合分隔符，默认逗号</param>\n            <param name=\"func\">把对象转为字符串的委托</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Join``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.Object})\">\n            <summary>把一个列表组合成为一个字符串，默认逗号分隔</summary>\n            <param name=\"value\"></param>\n            <param name=\"separator\">组合分隔符，默认逗号</param>\n            <param name=\"func\">把对象转为字符串的委托</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Separate(System.Text.StringBuilder,System.String)\">\n            <summary>追加分隔符字符串，忽略开头，常用于拼接</summary>\n            <param name=\"sb\">字符串构造者</param>\n            <param name=\"separator\">分隔符</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.GetBytes(System.String,System.Text.Encoding)\">\n            <summary>字符串转数组</summary>\n            <param name=\"value\">字符串</param>\n            <param name=\"encoding\">编码，默认utf-8无BOM</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.F(System.String,System.Object[])\">\n            <summary>格式化字符串。特别支持无格式化字符串的时间参数</summary>\n            <param name=\"value\">格式字符串</param>\n            <param name=\"args\">参数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.EnsureStart(System.String,System.String)\">\n            <summary>确保字符串以指定的另一字符串开始，不区分大小写</summary>\n            <param name=\"str\">字符串</param>\n            <param name=\"start\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.EnsureEnd(System.String,System.String)\">\n            <summary>确保字符串以指定的另一字符串结束，不区分大小写</summary>\n            <param name=\"str\">字符串</param>\n            <param name=\"end\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.TrimStart(System.String,System.String[])\">\n            <summary>从当前字符串开头移除另一字符串，不区分大小写，循环多次匹配前缀</summary>\n            <param name=\"str\">当前字符串</param>\n            <param name=\"starts\">另一字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.TrimEnd(System.String,System.String[])\">\n            <summary>从当前字符串结尾移除另一字符串，不区分大小写，循环多次匹配后缀</summary>\n            <param name=\"str\">当前字符串</param>\n            <param name=\"ends\">另一字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Substring(System.String,System.String,System.String,System.Int32,System.Int32[])\">\n            <summary>从字符串中检索子字符串，在指定头部字符串之后，指定尾部字符串之前</summary>\n            <remarks>常用于截取xml某一个元素等操作</remarks>\n            <param name=\"str\">目标字符串</param>\n            <param name=\"after\">头部字符串，在它之后</param>\n            <param name=\"before\">尾部字符串，在它之前</param>\n            <param name=\"startIndex\">搜索的开始位置</param>\n            <param name=\"positions\">位置数组，两个元素分别记录头尾位置</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Cut(System.String,System.Int32,System.String)\">\n            <summary>根据最大长度截取字符串，并允许以指定空白填充末尾</summary>\n            <param name=\"str\">字符串</param>\n            <param name=\"maxLength\">截取后字符串的最大允许长度，包含后面填充</param>\n            <param name=\"pad\">需要填充在后面的字符串，比如几个圆点</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.CutStart(System.String,System.String[])\">\n            <summary>从当前字符串开头移除另一字符串以及之前的部分</summary>\n            <param name=\"str\">当前字符串</param>\n            <param name=\"starts\">另一字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.CutEnd(System.String,System.String[])\">\n            <summary>从当前字符串结尾移除另一字符串以及之后的部分</summary>\n            <param name=\"str\">当前字符串</param>\n            <param name=\"ends\">另一字符串</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LevenshteinSearch(System.String,System.String[])\">\n            <summary>编辑距离搜索，从词组中找到最接近关键字的若干匹配项</summary>\n            <remarks>\n            算法代码由@Aimeast 独立完成。http://www.cnblogs.com/Aimeast/archive/2011/09/05/2167844.html\n            </remarks>\n            <param name=\"key\">关键字</param>\n            <param name=\"words\">词组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LevenshteinDistance(System.String,System.String)\">\n            <summary>编辑距离</summary>\n            <remarks>\n            又称Levenshtein距离（也叫做Edit Distance），是指两个字串之间，由一个转成另一个所需的最少编辑操作次数。\n            许可的编辑操作包括将一个字符替换成另一个字符，插入一个字符，删除一个字符。\n            算法代码由@Aimeast 独立完成。http://www.cnblogs.com/Aimeast/archive/2011/09/05/2167844.html\n            </remarks>\n            <param name=\"str1\"></param>\n            <param name=\"str2\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LCSSearch(System.String,System.String[])\">\n            <summary>最长公共子序列搜索，从词组中找到最接近关键字的若干匹配项</summary>\n            <remarks>\n            算法代码由@Aimeast 独立完成。http://www.cnblogs.com/Aimeast/archive/2011/09/05/2167844.html\n            </remarks>\n            <param name=\"key\"></param>\n            <param name=\"words\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LCSDistance(System.String,System.String[])\">\n            <summary>\n            最长公共子序列问题是寻找两个或多个已知数列最长的子序列。\n            一个数列 S，如果分别是两个或多个已知数列的子序列，且是所有符合此条件序列中最长的，则 S 称为已知序列的最长公共子序列。\n            The longest common subsequence (LCS) problem is to find the longest subsequence common to all sequences in a set of sequences (often just two). Note that subsequence is different from a substring, see substring vs. subsequence. It is a classic computer science problem, the basis of diff (a file comparison program that outputs the differences between two files), and has applications in bioinformatics.\n            </summary>\n            <remarks>\n            算法代码由@Aimeast 独立完成。http://www.cnblogs.com/Aimeast/archive/2011/09/05/2167844.html\n            </remarks>\n            <param name=\"word\"></param>\n            <param name=\"keys\">多个关键字。长度必须大于0，必须按照字符串长度升序排列。</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LCS``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.String})\">\n            <summary>根据列表项成员计算距离</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"list\"></param>\n            <param name=\"keys\"></param>\n            <param name=\"keySelector\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.LCSSearch``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.String},System.Int32)\">\n            <summary>在列表项中进行模糊搜索</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"list\"></param>\n            <param name=\"keys\"></param>\n            <param name=\"keySelector\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Match``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.String})\">\n            <summary>模糊匹配</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"list\"></param>\n            <param name=\"keys\"></param>\n            <param name=\"keySelector\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Match(System.String,System.String,System.Int32)\">\n            <summary>模糊匹配</summary>\n            <param name=\"str\"></param>\n            <param name=\"key\"></param>\n            <param name=\"maxError\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Match``1(System.Collections.Generic.IEnumerable{``0},System.String,System.Func{``0,System.String},System.Int32,System.Double)\">\n            <summary>模糊匹配</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"list\">列表项</param>\n            <param name=\"keys\">关键字</param>\n            <param name=\"keySelector\">匹配字符串选择</param>\n            <param name=\"count\">获取个数</param>\n            <param name=\"confidence\">权重阀值</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.StringHelper.Speak(System.String)\">\n            <summary>调用语音引擎说出指定话</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:System.StringHelper.SpeakAsync(System.String)\">\n            <summary>异步调用语音引擎说出指定话。可能导致后来的调用打断前面的语音</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"P:System.StringHelper.EnableSpeechTip\">\n            <summary>启用语音提示</summary>\n        </member>\n        <member name=\"M:System.StringHelper.SpeechTip(System.String)\">\n            <summary>语音提示操作</summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:System.StringHelper.SpeakAsyncCancelAll(System.String)\">\n            <summary>\n            停止所有语音播报\n            </summary>\n            <param name=\"value\"></param>\n        </member>\n        <member name=\"M:System.StringHelper.Run(System.String,System.String,System.Int32,System.Action{System.String},System.Action{System.Diagnostics.Process})\">\n            <summary>以隐藏窗口执行命令行</summary>\n            <param name=\"cmd\">文件名</param>\n            <param name=\"arguments\">命令参数</param>\n            <param name=\"msWait\">等待毫秒数</param>\n            <param name=\"output\">进程输出内容。默认为空时输出到日志</param>\n            <param name=\"onExit\">进程退出时执行</param>\n            <returns>进程退出代码</returns>\n        </member>\n        <member name=\"T:System.IOHelper\">\n            <summary>IO工具类</summary>\n        </member>\n        <member name=\"M:System.IOHelper.Compress(System.IO.Stream,System.IO.Stream)\">\n            <summary>压缩数据流</summary>\n            <param name=\"inStream\">输入流</param>\n            <param name=\"outStream\">输出流。如果不指定，则内部实例化一个内存流</param>\n            <remarks>返回输出流，注意此时指针位于末端</remarks>\n        </member>\n        <member name=\"M:System.IOHelper.Decompress(System.IO.Stream,System.IO.Stream)\">\n            <summary>解压缩数据流</summary>\n            <param name=\"inStream\">输入流</param>\n            <param name=\"outStream\">输出流。如果不指定，则内部实例化一个内存流</param>\n            <remarks>返回输出流，注意此时指针位于末端</remarks>\n        </member>\n        <member name=\"M:System.IOHelper.Compress(System.Byte[])\">\n            <summary>压缩字节数组</summary>\n            <param name=\"data\">字节数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Decompress(System.Byte[])\">\n            <summary>解压缩字节数组</summary>\n            <param name=\"data\">字节数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.CompressGZip(System.IO.Stream,System.IO.Stream)\">\n            <summary>压缩数据流</summary>\n            <param name=\"inStream\">输入流</param>\n            <param name=\"outStream\">输出流。如果不指定，则内部实例化一个内存流</param>\n            <remarks>返回输出流，注意此时指针位于末端</remarks>\n        </member>\n        <member name=\"M:System.IOHelper.DecompressGZip(System.IO.Stream,System.IO.Stream)\">\n            <summary>解压缩数据流</summary>\n            <param name=\"inStream\">输入流</param>\n            <param name=\"outStream\">输出流。如果不指定，则内部实例化一个内存流</param>\n            <remarks>返回输出流，注意此时指针位于末端</remarks>\n        </member>\n        <member name=\"M:System.IOHelper.CopyTo(System.IO.Stream,System.IO.Stream,System.Int32,System.Int32)\">\n            <summary>复制数据流</summary>\n            <param name=\"src\">源数据流</param>\n            <param name=\"des\">目的数据流</param>\n            <param name=\"bufferSize\">缓冲区大小，也就是每次复制的大小</param>\n            <param name=\"max\">最大复制字节数</param>\n            <returns>返回复制的总字节数</returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.IO.Stream,System.IO.Stream,System.Int32,System.Int32)\">\n            <summary>把一个数据流写入到另一个数据流</summary>\n            <param name=\"des\">目的数据流</param>\n            <param name=\"src\">源数据流</param>\n            <param name=\"bufferSize\">缓冲区大小，也就是每次复制的大小</param>\n            <param name=\"max\">最大复制字节数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.IO.Stream,System.Byte[])\">\n            <summary>把一个字节数组写入到一个数据流</summary>\n            <param name=\"des\">目的数据流</param>\n            <param name=\"src\">源数据流</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.WriteArray(System.IO.Stream,System.Byte[])\">\n            <summary>写入字节数组，先写入压缩整数表示的长度</summary>\n            <param name=\"des\"></param>\n            <param name=\"src\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadArray(System.IO.Stream)\">\n            <summary>读取字节数组，先读取压缩整数表示的长度</summary>\n            <param name=\"des\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.WriteDateTime(System.IO.Stream,System.DateTime)\">\n            <summary>写入Unix格式时间，1970年以来秒数，绝对时间，非UTC</summary>\n            <param name=\"stream\"></param>\n            <param name=\"dt\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadDateTime(System.IO.Stream)\">\n            <summary>读取Unix格式时间，1970年以来秒数，绝对时间，非UTC</summary>\n            <param name=\"stream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadBytes(System.Byte[],System.Int32,System.Int32)\">\n            <summary>复制数组</summary>\n            <param name=\"src\">源数组</param>\n            <param name=\"offset\">起始位置</param>\n            <param name=\"count\">复制字节数</param>\n            <returns>返回复制的总字节数</returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.Byte[],System.Int32,System.Byte[],System.Int32,System.Int32)\">\n            <summary>向字节数组写入一片数据</summary>\n            <param name=\"dst\">目标数组</param>\n            <param name=\"dstOffset\">目标偏移</param>\n            <param name=\"src\">源数组</param>\n            <param name=\"srcOffset\">源数组偏移</param>\n            <param name=\"count\">数量</param>\n            <returns>返回实际写入的字节个数</returns>\n        </member>\n        <member name=\"M:System.IOHelper.Combine(System.Byte[],System.Byte[],System.Int32,System.Int32)\">\n            <summary>合并两个数组</summary>\n            <param name=\"src\">源数组</param>\n            <param name=\"des\">目标数组</param>\n            <param name=\"offset\">起始位置</param>\n            <param name=\"count\">字节数</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadBytes(System.IO.Stream,System.Int64)\">\n            <summary>数据流转为字节数组</summary>\n            <remarks>\n            针对MemoryStream进行优化。内存流的Read实现是一个个字节复制，而ToArray是调用内部内存复制方法\n            如果要读完数据，又不支持定位，则采用内存流搬运\n            如果指定长度超过数据流长度，就让其报错，因为那是调用者所期望的值\n            </remarks>\n            <param name=\"stream\">数据流</param>\n            <param name=\"length\">长度，0表示读到结束</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToArray(System.IO.Stream)\">\n            <summary>数据流转为字节数组，从0开始，无视数据流的当前位置</summary>\n            <param name=\"stream\">数据流</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadTo(System.IO.Stream,System.Byte[],System.Int64,System.Int64)\">\n            <summary>从数据流中读取字节数组，直到遇到指定字节数组</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"buffer\">字节数组</param>\n            <param name=\"offset\">字节数组中的偏移</param>\n            <param name=\"length\">字节数组中的查找长度</param>\n            <returns>未找到时返回空，0位置范围大小为0的字节数组</returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadTo(System.IO.Stream,System.String,System.Text.Encoding)\">\n            <summary>从数据流中读取字节数组，直到遇到指定字节数组</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"str\"></param>\n            <param name=\"encoding\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadLine(System.IO.Stream,System.Text.Encoding)\">\n            <summary>从数据流中读取一行，直到遇到换行</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"encoding\"></param>\n            <returns>未找到返回null，0位置返回String.Empty</returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToStr(System.IO.Stream,System.Text.Encoding)\">\n            <summary>流转换为字符串</summary>\n            <param name=\"stream\">目标流</param>\n            <param name=\"encoding\">编码格式</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToStr(System.Byte[],System.Text.Encoding,System.Int32,System.Int32)\">\n            <summary>字节数组转换为字符串</summary>\n            <param name=\"buf\">字节数组</param>\n            <param name=\"encoding\">编码格式</param>\n            <param name=\"offset\">字节数组中的偏移</param>\n            <param name=\"count\">字节数组中的查找长度</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToUInt16(System.Byte[],System.Int32,System.Boolean)\">\n            <summary>从字节数据指定位置读取一个无符号16位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToUInt32(System.Byte[],System.Int32,System.Boolean)\">\n            <summary>从字节数据指定位置读取一个无符号32位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToUInt64(System.Byte[],System.Int32,System.Boolean)\">\n            <summary>从字节数据指定位置读取一个无符号64位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.Byte[],System.UInt16,System.Int32,System.Boolean)\">\n            <summary>向字节数组的指定位置写入一个无符号16位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"n\">数字</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.Byte[],System.UInt32,System.Int32,System.Boolean)\">\n            <summary>向字节数组的指定位置写入一个无符号32位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"n\">数字</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Write(System.Byte[],System.UInt64,System.Int32,System.Boolean)\">\n            <summary>向字节数组的指定位置写入一个无符号64位整数</summary>\n            <param name=\"data\"></param>\n            <param name=\"n\">数字</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"isLittleEndian\">是否小端字节序</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.UInt16,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.Int16,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.UInt32,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.Int32,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.UInt64,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetBytes(System.Int64,System.Boolean)\">\n            <summary>整数转为字节数组，注意大小端字节序</summary>\n            <param name=\"value\"></param>\n            <param name=\"isLittleEndian\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadEncodedInt(System.IO.Stream)\">\n            <summary>以压缩格式读取32位整数</summary>\n            <param name=\"stream\">数据流</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ReadEncodedInt64(System.IO.Stream)\">\n            <summary>以压缩格式读取32位整数</summary>\n            <param name=\"stream\">数据流</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.TryReadEncodedInt(System.IO.Stream,System.UInt32@)\">\n            <summary>尝试读取压缩编码整数</summary>\n            <param name=\"stream\"></param>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.WriteEncodedInt(System.IO.Stream,System.Int64)\">\n            <summary>\n            以7位压缩格式写入32位整数，小于7位用1个字节，小于14位用2个字节。\n            由每次写入的一个字节的第一位标记后面的字节是否还是当前数据，所以每个字节实际可利用存储空间只有后7位。\n            </summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"value\">数值</param>\n            <returns>实际写入字节数</returns>\n        </member>\n        <member name=\"M:System.IOHelper.GetEncodedInt(System.Int64)\">\n            <summary>获取压缩编码整数</summary>\n            <param name=\"value\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.IndexOf(System.IO.Stream,System.Byte[],System.Int64,System.Int64)\">\n            <summary>在数据流中查找字节数组的位置，流指针会移动到结尾</summary>\n            <param name=\"stream\">数据流</param>\n            <param name=\"buffer\">字节数组</param>\n            <param name=\"offset\">字节数组中的偏移</param>\n            <param name=\"length\">字节数组中的查找长度</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.IndexOf(System.Byte[],System.Byte[],System.Int64,System.Int64)\">\n            <summary>在字节数组中查找另一个字节数组的位置，不存在则返回-1</summary>\n            <param name=\"source\">字节数组</param>\n            <param name=\"buffer\">另一个字节数组</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"length\">查找长度</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.IndexOf(System.Byte[],System.Int64,System.Int64,System.Byte[],System.Int64,System.Int64)\">\n            <summary>在字节数组中查找另一个字节数组的位置，不存在则返回-1</summary>\n            <param name=\"source\">字节数组</param>\n            <param name=\"start\">源数组起始位置</param>\n            <param name=\"count\">查找长度</param>\n            <param name=\"buffer\">另一个字节数组</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"length\">查找长度</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.CompareTo(System.Byte[],System.Byte[])\">\n            <summary>比较两个字节数组大小。相等返回0，不等则返回不等的位置，如果位置为0，则返回1。</summary>\n            <param name=\"source\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.CompareTo(System.Byte[],System.Int64,System.Int64,System.Byte[],System.Int64,System.Int64)\">\n            <summary>比较两个字节数组大小。相等返回0，不等则返回不等的位置，如果位置为0，则返回1。</summary>\n            <param name=\"source\"></param>\n            <param name=\"start\"></param>\n            <param name=\"count\">数量</param>\n            <param name=\"buffer\">缓冲区</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"length\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Split(System.Byte[],System.Byte[])\">\n            <summary>字节数组分割</summary>\n            <param name=\"buf\"></param>\n            <param name=\"sps\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.StartsWith(System.IO.Stream,System.Byte[])\">\n            <summary>一个数据流是否以另一个数组开头。如果成功，指针移到目标之后，否则保持指针位置不变。</summary>\n            <param name=\"source\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.EndsWith(System.IO.Stream,System.Byte[])\">\n            <summary>一个数据流是否以另一个数组结尾。如果成功，指针移到目标之后，否则保持指针位置不变。</summary>\n            <param name=\"source\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.StartsWith(System.Byte[],System.Byte[])\">\n            <summary>一个数组是否以另一个数组开头</summary>\n            <param name=\"source\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.EndsWith(System.Byte[],System.Byte[])\">\n            <summary>一个数组是否以另一个数组结尾</summary>\n            <param name=\"source\"></param>\n            <param name=\"buffer\">缓冲区</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.Reverse(System.Byte[])\">\n            <summary>倒序、更换字节序</summary>\n            <param name=\"buf\">字节数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToHex(System.Byte[],System.Int32,System.Int32)\">\n            <summary>把字节数组编码为十六进制字符串</summary>\n            <param name=\"data\">字节数组</param>\n            <param name=\"offset\">偏移</param>\n            <param name=\"count\">数量。超过实际数量时，使用实际数量</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToHex(System.Byte[],System.String,System.Int32,System.Int32)\">\n            <summary>把字节数组编码为十六进制字符串，带有分隔符和分组功能</summary>\n            <param name=\"data\">字节数组</param>\n            <param name=\"separate\">分隔符</param>\n            <param name=\"groupSize\">分组大小，为0时对每个字节应用分隔符，否则对每个分组使用</param>\n            <param name=\"maxLength\">最大显示多少个字节。默认-1显示全部</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToHex(System.String,System.Int32,System.Int32)\">\n            <summary>解密</summary>\n            <param name=\"data\">Hex编码的字符串</param>\n            <param name=\"startIndex\">起始位置</param>\n            <param name=\"length\">长度</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToBase64(System.Byte[],System.Int32,System.Int32,System.Boolean)\">\n            <summary>字节数组转为Base64编码</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <param name=\"lineBreak\">是否换行显示</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToUrlBase64(System.Byte[],System.Int32,System.Int32)\">\n            <summary>字节数组转为Url改进型Base64编码</summary>\n            <param name=\"data\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IOHelper.ToBase64(System.String)\">\n            <summary>Base64字符串转为字节数组</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.IO.PathHelper\">\n            <summary>路径操作帮助</summary>\n        </member>\n        <member name=\"P:System.IO.PathHelper.BaseDirectory\">\n            <summary>基础目录。GetFullPath依赖于此，默认为当前应用程序域基础目录</summary>\n        </member>\n        <member name=\"M:System.IO.PathHelper.GetFullPath(System.String)\">\n            <summary>获取文件或目录的全路径，过滤相对目录</summary>\n            <remarks>不确保目录后面一定有分隔符，是否有分隔符由原始路径末尾决定</remarks>\n            <param name=\"path\">文件或目录</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.GetBasePath(System.String)\">\n            <summary>获取文件或目录基于应用程序域基目录的全路径，过滤相对目录</summary>\n            <remarks>不确保目录后面一定有分隔符，是否有分隔符由原始路径末尾决定</remarks>\n            <param name=\"path\">文件或目录</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.GetCurrentPath(System.String)\">\n            <summary>获取文件或目录基于当前目录的全路径，过滤相对目录</summary>\n            <remarks>不确保目录后面一定有分隔符，是否有分隔符由原始路径末尾决定</remarks>\n            <param name=\"path\">文件或目录</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.EnsureDirectory(System.String,System.Boolean)\">\n            <summary>确保目录存在，若不存在则创建</summary>\n            <remarks>\n            斜杠结尾的路径一定是目录，无视第二参数；\n            默认是文件，这样子只需要确保上一层目录存在即可，否则如果把文件当成了目录，目录的创建会导致文件无法创建。\n            </remarks>\n            <param name=\"path\">文件路径或目录路径，斜杠结尾的路径一定是目录，无视第二参数</param>\n            <param name=\"isfile\">该路径是否是否文件路径。文件路径需要取目录部分</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.CombinePath(System.String,System.String[])\">\n            <summary>合并多段路径</summary>\n            <param name=\"path\"></param>\n            <param name=\"ps\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.AsFile(System.String)\">\n            <summary>文件路径作为文件信息</summary>\n            <param name=\"file\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.ReadBytes(System.IO.FileInfo,System.Int32,System.Int32)\">\n            <summary>从文件中读取数据</summary>\n            <param name=\"file\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"count\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.WriteBytes(System.IO.FileInfo,System.Byte[],System.Int32)\">\n            <summary>把数据写入文件指定位置</summary>\n            <param name=\"file\"></param>\n            <param name=\"data\"></param>\n            <param name=\"offset\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.ReadText(System.IO.FileInfo,System.Text.Encoding)\">\n            <summary>读取所有文本，自动检测编码</summary>\n            <remarks>性能较File.ReadAllText略慢，可通过提前检测BOM编码来优化</remarks>\n            <param name=\"file\"></param>\n            <param name=\"encoding\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.WriteText(System.IO.FileInfo,System.String,System.Text.Encoding)\">\n            <summary>把文本写入文件，自动检测编码</summary>\n            <param name=\"file\"></param>\n            <param name=\"text\"></param>\n            <param name=\"encoding\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.CopyToIfNewer(System.IO.FileInfo,System.String)\">\n            <summary>复制到目标文件，目标文件必须已存在，且源文件较新</summary>\n            <param name=\"fi\">源文件</param>\n            <param name=\"destFileName\">目标文件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.OpenRead``1(System.IO.FileInfo,System.Func{System.IO.FileStream,``0})\">\n            <summary>打开并读取</summary>\n            <typeparam name=\"T\">返回类型</typeparam>\n            <param name=\"file\">文件信息</param>\n            <param name=\"func\">要对文件流操作的委托</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.OpenWrite``1(System.IO.FileInfo,System.Func{System.IO.FileStream,``0})\">\n            <summary>打开并写入</summary>\n            <typeparam name=\"T\">返回类型</typeparam>\n            <param name=\"file\">文件信息</param>\n            <param name=\"func\">要对文件流操作的委托</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.Extract(System.IO.FileInfo,System.String,System.Boolean)\">\n            <summary>解压缩</summary>\n            <param name=\"fi\"></param>\n            <param name=\"destDir\"></param>\n            <param name=\"overwrite\">是否覆盖目标同名文件</param>\n        </member>\n        <member name=\"M:System.IO.PathHelper.Compress(System.IO.FileInfo,System.String)\">\n            <summary>压缩文件</summary>\n            <param name=\"fi\"></param>\n            <param name=\"destFile\"></param>\n        </member>\n        <member name=\"M:System.IO.PathHelper.AsDirectory(System.String)\">\n            <summary>路径作为目录信息</summary>\n            <param name=\"dir\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.GetAllFiles(System.IO.DirectoryInfo,System.String,System.Boolean)\">\n            <summary>获取目录内所有符合条件的文件，支持多文件扩展匹配</summary>\n            <param name=\"di\">目录</param>\n            <param name=\"exts\">文件扩展列表。比如*.exe;*.dll;*.config</param>\n            <param name=\"allSub\">是否包含所有子孙目录文件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.CopyTo(System.IO.DirectoryInfo,System.String,System.String,System.Boolean,System.Action{System.String})\">\n            <summary>复制目录中的文件</summary>\n            <param name=\"di\">源目录</param>\n            <param name=\"destDirName\">目标目录</param>\n            <param name=\"exts\">文件扩展列表。比如*.exe;*.dll;*.config</param>\n            <param name=\"allSub\">是否包含所有子孙目录文件</param>\n            <param name=\"callback\">复制每一个文件之前的回调</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.CopyToIfNewer(System.IO.DirectoryInfo,System.String,System.String,System.Boolean,System.Action{System.String})\">\n            <summary>对比源目录和目标目录，复制双方都存在且源目录较新的文件</summary>\n            <param name=\"di\">源目录</param>\n            <param name=\"destDirName\">目标目录</param>\n            <param name=\"exts\">文件扩展列表。比如*.exe;*.dll;*.config</param>\n            <param name=\"allSub\">是否包含所有子孙目录文件</param>\n            <param name=\"callback\">复制每一个文件之前的回调</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.CopyIfNewer(System.IO.DirectoryInfo,System.String[],System.String,System.Boolean)\">\n            <summary>从多个目标目录复制较新文件到当前目录</summary>\n            <param name=\"di\">当前目录</param>\n            <param name=\"source\">多个目标目录</param>\n            <param name=\"exts\">文件扩展列表。比如*.exe;*.dll;*.config</param>\n            <param name=\"allSub\">是否包含所有子孙目录文件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.IO.PathHelper.Compress(System.IO.DirectoryInfo,System.String)\">\n            <summary>压缩</summary>\n            <param name=\"di\"></param>\n            <param name=\"destFile\"></param>\n        </member>\n        <member name=\"T:System.ModelExtension\">\n            <summary>模型扩展</summary>\n        </member>\n        <member name=\"M:System.ModelExtension.GetService``1(System.IServiceProvider)\">\n            <summary>获取指定类型的服务对象</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"provider\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.ObjectContainerHelper\">\n            <summary>对象容器助手。扩展方法专用</summary>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.Register``2(NewLife.Model.IObjectContainer,System.Object,System.Int32)\">\n            <summary>注册类型和名称</summary>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <typeparam name=\"TImplement\">实现类型</typeparam>\n            <param name=\"container\">对象容器</param>\n            <param name=\"id\">标识</param>\n            <param name=\"priority\">优先级</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.Register``1(NewLife.Model.IObjectContainer,System.Object,System.Object,System.Int32)\">\n            <summary>注册类型指定名称的实例</summary>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <param name=\"container\">对象容器</param>\n            <param name=\"instance\">实例</param>\n            <param name=\"id\">标识</param>\n            <param name=\"priority\">优先级</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.Resolve``1(NewLife.Model.IObjectContainer,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <param name=\"container\">对象容器</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.ResolveInstance``1(NewLife.Model.IObjectContainer,System.Object)\">\n            <summary>解析类型指定名称的实例</summary>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <param name=\"container\">对象容器</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.AutoRegister``2(NewLife.Model.IObjectContainer)\">\n            <summary>遍历所有程序集的所有类型，自动注册实现了指定接口或基类的类型。如果没有注册任何实现，则默认注册第一个排除类型</summary>\n            <remarks>自动注册一般用于单实例功能扩展型接口</remarks>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <typeparam name=\"TImplement\">要排除的类型，一般是内部默认实现</typeparam>\n            <param name=\"container\">对象容器</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.ObjectContainerHelper.ResolveType``1(NewLife.Model.IObjectContainer,System.Object)\">\n            <summary>解析接口指定名称的实现类型</summary>\n            <typeparam name=\"TInterface\">接口类型</typeparam>\n            <param name=\"container\">对象容器</param>\n            <param name=\"id\">标识</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.NetHelper\">\n            <summary>网络工具类</summary>\n        </member>\n        <member name=\"M:System.NetHelper.SetTcpKeepAlive(System.Net.Sockets.Socket,System.Boolean,System.Int32,System.Int32)\">\n            <summary>设置超时检测时间和检测间隔</summary>\n            <param name=\"socket\">要设置的Socket对象</param>\n            <param name=\"iskeepalive\">是否启用Keep-Alive</param>\n            <param name=\"starttime\">多长时间后开始第一次探测（单位：毫秒）</param>\n            <param name=\"interval\">探测时间间隔（单位：毫秒）</param>\n        </member>\n        <member name=\"M:System.NetHelper.ParseAddress(System.String)\">\n            <summary>分析地址，根据IP或者域名得到IP地址，缓存60秒，异步更新</summary>\n            <param name=\"hostname\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.ParseEndPoint(System.String,System.Int32)\">\n            <summary>分析网络终结点</summary>\n            <param name=\"address\">地址，可以不带端口</param>\n            <param name=\"defaultPort\">地址不带端口时指定的默认端口</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetRightAny(System.Net.IPAddress,System.Net.Sockets.AddressFamily)\">\n            <summary>针对IPv4和IPv6获取合适的Any地址</summary>\n            <remarks>除了Any地址以为，其它地址不具备等效性</remarks>\n            <param name=\"address\"></param>\n            <param name=\"family\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.IsAny(System.Net.IPAddress)\">\n            <summary>是否Any地址，同时处理IPv4和IPv6</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.IsAny(System.Net.EndPoint)\">\n            <summary>是否Any结点</summary>\n            <param name=\"endpoint\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.IsIPv4(System.Net.IPAddress)\">\n            <summary>是否IPv4地址</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.IsLocal(System.Net.IPAddress)\">\n            <summary>是否本地地址</summary>\n            <param name=\"address\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetRelativeAddress(System.Net.IPAddress,System.Net.IPAddress)\">\n            <summary>获取相对于指定远程地址的本地地址</summary>\n            <param name=\"address\"></param>\n            <param name=\"remote\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetRelativeEndPoint(System.Net.IPEndPoint,System.Net.IPAddress)\">\n            <summary>获取相对于指定远程地址的本地地址</summary>\n            <param name=\"local\"></param>\n            <param name=\"remote\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.CheckPort(System.Net.IPAddress,NewLife.Net.NetType,System.Int32)\">\n            <summary>指定地址的指定端口是否已被使用，似乎没办法判断IPv6地址</summary>\n            <param name=\"protocol\"></param>\n            <param name=\"address\"></param>\n            <param name=\"port\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.CheckPort(NewLife.Net.NetUri)\">\n            <summary>检查该协议的地址端口是否已经呗使用</summary>\n            <param name=\"uri\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetActiveInterfaces\">\n            <summary>获取活动的接口信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetDhcps\">\n            <summary>获取可用的DHCP地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetDns\">\n            <summary>获取可用的DNS地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetGateways\">\n            <summary>获取可用的网关地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetIPs\">\n            <summary>获取可用的IP地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetIPsWithCache\">\n            <summary>获取本机可用IP地址，缓存60秒，异步更新</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetMulticasts\">\n            <summary>获取可用的多播地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetMacs\">\n            <summary>获取以太网MAC地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.MyIP\">\n            <summary>获取本地第一个IPv4地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.MyIPv6\">\n            <summary>获取本地第一个IPv6地址</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.Wake(System.String[])\">\n            <summary>唤醒指定MAC地址的计算机</summary>\n            <param name=\"macs\"></param>\n        </member>\n        <member name=\"M:System.NetHelper.GetMac(System.Net.IPAddress)\">\n            <summary>根据IP地址获取MAC地址</summary>\n            <param name=\"ip\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.GetAddress(System.Net.IPAddress)\">\n            <summary>获取IP地址的物理地址位置</summary>\n            <param name=\"addr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.IPToAddress(System.String)\">\n            <summary>根据字符串形式IP地址转为物理地址</summary>\n            <param name=\"addr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.NetHelper.IPProvider\">\n            <summary>IP地址提供者接口</summary>\n        </member>\n        <member name=\"M:System.NetHelper.IPProvider.GetAddress(System.Net.IPAddress)\">\n            <summary>获取IP地址的物理地址位置</summary>\n            <param name=\"addr\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.CreateClient(NewLife.Net.NetUri)\">\n            <summary>根据本地网络标识创建客户端</summary>\n            <param name=\"local\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.NetHelper.CreateRemote(NewLife.Net.NetUri)\">\n            <summary>根据远程网络标识创建客户端</summary>\n            <param name=\"remote\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.AttributeX\">\n            <summary>特性辅助类</summary>\n        </member>\n        <member name=\"M:System.AttributeX.GetCustomAttributes``1(System.Reflection.Assembly)\">\n            <summary>获取自定义属性，带有缓存功能，避免因.Net内部GetCustomAttributes没有缓存而带来的损耗</summary>\n            <typeparam name=\"TAttribute\"></typeparam>\n            <param name=\"assembly\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.AttributeX.GetDisplayName(System.Reflection.MemberInfo,System.Boolean)\">\n            <summary>获取成员绑定的显示名，优先DisplayName，然后Description</summary>\n            <param name=\"member\"></param>\n            <param name=\"inherit\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.AttributeX.GetDescription(System.Reflection.MemberInfo,System.Boolean)\">\n            <summary>获取成员绑定的显示名，优先DisplayName，然后Description</summary>\n            <param name=\"member\"></param>\n            <param name=\"inherit\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.AttributeX.GetCustomAttributeValue``2(System.Reflection.Assembly)\">\n            <summary>获取自定义属性的值。可用于ReflectionOnly加载的程序集</summary>\n            <typeparam name=\"TAttribute\"></typeparam>\n            <typeparam name=\"TResult\"></typeparam>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.AttributeX.GetCustomAttributeValue``2(System.Reflection.MemberInfo,System.Boolean)\">\n            <summary>获取自定义属性的值。可用于ReflectionOnly加载的程序集</summary>\n            <typeparam name=\"TAttribute\"></typeparam>\n            <typeparam name=\"TResult\"></typeparam>\n            <param name=\"target\">目标对象</param>\n            <param name=\"inherit\">是否递归</param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.SecurityHelper\">\n            <summary>安全算法</summary>\n        </member>\n        <member name=\"M:System.SecurityHelper.MD5(System.Byte[])\">\n            <summary>MD5散列</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.MD5(System.String,System.Text.Encoding)\">\n            <summary>MD5散列</summary>\n            <param name=\"data\"></param>\n            <param name=\"encoding\">字符串编码，默认Default</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.MD5_16(System.String,System.Text.Encoding)\">\n            <summary>MD5散列</summary>\n            <param name=\"data\"></param>\n            <param name=\"encoding\">字符串编码，默认Default</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Crc(System.Byte[])\">\n            <summary>Crc散列</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Crc16(System.Byte[])\">\n            <summary>Crc16散列</summary>\n            <param name=\"data\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.SHA1(System.Byte[],System.Byte[])\">\n            <summary>SHA128</summary>\n            <param name=\"data\"></param>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.SHA256(System.Byte[],System.Byte[])\">\n            <summary>SHA256</summary>\n            <param name=\"data\"></param>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.SHA384(System.Byte[],System.Byte[])\">\n            <summary>SHA384</summary>\n            <param name=\"data\"></param>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.SHA512(System.Byte[],System.Byte[])\">\n            <summary>SHA512</summary>\n            <param name=\"data\"></param>\n            <param name=\"key\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Encrypt(System.Security.Cryptography.SymmetricAlgorithm,System.IO.Stream,System.IO.Stream)\">\n            <summary>对称加密算法扩展</summary>\n            <remarks>注意：CryptoStream会把 outstream 数据流关闭</remarks>\n            <param name=\"sa\"></param>\n            <param name=\"instream\"></param>\n            <param name=\"outstream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Encrypt(System.Security.Cryptography.SymmetricAlgorithm,System.Byte[],System.Byte[],System.Security.Cryptography.CipherMode,System.Security.Cryptography.PaddingMode)\">\n            <summary>对称加密算法扩展</summary>\n            <param name=\"sa\">算法</param>\n            <param name=\"data\">数据</param>\n            <param name=\"pass\">密码</param>\n            <param name=\"mode\">模式。.Net默认CBC，Java默认ECB</param>\n            <param name=\"padding\">填充算法。默认PKCS7，等同Java的PKCS5</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Descrypt(System.Security.Cryptography.SymmetricAlgorithm,System.IO.Stream,System.IO.Stream)\">\n            <summary>对称解密算法扩展\n            <para>注意：CryptoStream会把 instream 数据流关闭</para>\n            </summary>\n            <param name=\"sa\"></param>\n            <param name=\"instream\"></param>\n            <param name=\"outstream\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.Descrypt(System.Security.Cryptography.SymmetricAlgorithm,System.Byte[],System.Byte[],System.Security.Cryptography.CipherMode,System.Security.Cryptography.PaddingMode)\">\n            <summary>对称解密算法扩展</summary>\n            <param name=\"sa\">算法</param>\n            <param name=\"data\">数据</param>\n            <param name=\"pass\">密码</param>\n            <param name=\"mode\">模式。.Net默认CBC，Java默认ECB</param>\n            <param name=\"padding\">填充算法。默认PKCS7，等同Java的PKCS5</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.SecurityHelper.RC4(System.Byte[],System.Byte[])\">\n            <summary>RC4对称加密算法</summary>\n            <param name=\"data\"></param>\n            <param name=\"pass\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:System.Windows.Forms.ControlHelper\">\n            <summary>控件助手</summary>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Invoke(System.Windows.Forms.Control,System.Action)\">\n            <summary>执行无参委托</summary>\n            <param name=\"control\"></param>\n            <param name=\"method\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Invoke``1(System.Windows.Forms.Control,System.Action{``0},``0)\">\n            <summary>执行单一参数无返回值的委托</summary>\n            <typeparam name=\"T\"></typeparam>\n            <param name=\"control\"></param>\n            <param name=\"method\"></param>\n            <param name=\"arg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Invoke``2(System.Windows.Forms.Control,System.Action{``0,``1},``0,``1)\">\n            <summary>执行二参数无返回值的委托</summary>\n            <typeparam name=\"T\"></typeparam>\n            <typeparam name=\"T2\"></typeparam>\n            <param name=\"control\"></param>\n            <param name=\"method\"></param>\n            <param name=\"arg\"></param>\n            <param name=\"arg2\"></param>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Append(System.Windows.Forms.TextBoxBase,System.String,System.Int32)\">\n            <summary>附加文本到文本控件末尾。主要解决非UI线程以及滚动控件等问题</summary>\n            <param name=\"txt\">控件</param>\n            <param name=\"msg\">消息</param>\n            <param name=\"maxLines\">最大行数。超过该行数讲清空控件</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Scroll(System.Windows.Forms.TextBoxBase,System.Boolean)\">\n            <summary>滚动控件的滚动条</summary>\n            <param name=\"txt\">指定控件</param>\n            <param name=\"bottom\">是否底端，或者顶端</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.ProcessReturn(System.Windows.Forms.TextBoxBase,System.String@)\">\n            <summary>处理回车，移到行首</summary>\n            <param name=\"txt\"></param>\n            <param name=\"m\"></param>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.SetDefaultStyle(System.Windows.Forms.Control,System.Int32)\">\n            <summary>设置默认样式，包括字体、前景色、背景色</summary>\n            <param name=\"control\">控件</param>\n            <param name=\"size\">字体大小</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.SetFontSize(System.Windows.Forms.Control,System.Int32)\">\n            <summary>设置字体大小</summary>\n            <param name=\"control\"></param>\n            <param name=\"size\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.ColourDefault(System.Windows.Forms.RichTextBox,System.Int32)\">\n            <summary>采用默认着色方案进行着色</summary>\n            <param name=\"rtb\">文本控件</param>\n            <param name=\"start\">开始位置</param>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.ChangeCppColor(System.Windows.Forms.RichTextBox,System.Int32)\">\n            <summary>改变C++类名方法名颜色</summary>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Colour(System.Windows.Forms.RichTextBox,System.Text.RegularExpressions.Regex,System.Int32,System.Drawing.Color[])\">\n            <summary>着色文本控件的内容</summary>\n            <param name=\"rtb\">文本控件</param>\n            <param name=\"reg\">正则表达式</param>\n            <param name=\"start\">开始位置</param>\n            <param name=\"colors\">颜色数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.Colour(System.Windows.Forms.RichTextBox,System.String,System.Int32,System.Drawing.Color[])\">\n            <summary>着色文本控件的内容</summary>\n            <param name=\"rtb\">文本控件</param>\n            <param name=\"reg\">正则表达式</param>\n            <param name=\"start\">开始位置</param>\n            <param name=\"colors\">颜色数组</param>\n            <returns></returns>\n        </member>\n        <member name=\"P:System.Windows.Forms.ControlHelper.Dpi\">\n            <summary>当前Dpi</summary>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.FixDpi(System.Windows.Forms.ListView)\">\n            <summary>修正ListView的Dpi</summary>\n            <param name=\"lv\"></param>\n        </member>\n        <member name=\"M:System.Windows.Forms.ControlHelper.FixDpi(System.Windows.Forms.Form)\">\n            <summary>修正窗体的Dpi</summary>\n            <param name=\"frm\"></param>\n        </member>\n    </members>\n</doc>\n"
  },
  {
    "path": "DLL/NewLife.RocketMQ.xml",
    "content": "<?xml version=\"1.0\"?>\n<doc>\n    <assembly>\n        <name>NewLife.RocketMQ</name>\n    </assembly>\n    <members>\n        <member name=\"T:NewLife.RocketMQ.BrokerClient\">\n            <summary>代理客户端</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.BrokerClient._Servers\">\n            <summary>服务器地址</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerClient.#ctor(System.String[])\">\n            <summary>实例化代理客户端</summary>\n            <param name=\"servers\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerClient.Start\">\n            <summary>启动</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerClient.UnRegisterClient(System.String,System.String)\">\n            <summary>注销客户端</summary>\n            <param name=\"id\"></param>\n            <param name=\"group\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerClient.Ping\">\n            <summary>心跳</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerClient.GetRuntimeInfo\">\n            <summary>获取运行时信息</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.ClusterClient\">\n            <summary>集群客户端</summary>\n            <remarks>\n            维护到一个集群的客户端连接，内部采用负载均衡调度算法。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Id\">\n            <summary>编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Timeout\">\n            <summary>超时。默认3000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Servers\">\n            <summary>服务器地址集合</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Config\">\n            <summary>配置</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.Start\">\n            <summary>开始</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.EnsureCreate\">\n            <summary>确保创建连接</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.SendAsync(NewLife.RocketMQ.Protocol.Command)\">\n            <summary>发送命令</summary>\n            <param name=\"cmd\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.Invoke(NewLife.RocketMQ.Protocol.RequestCode,System.Object,System.Object,System.Boolean)\">\n            <summary>发送指定类型的命令</summary>\n            <param name=\"request\"></param>\n            <param name=\"body\"></param>\n            <param name=\"extFields\"></param>\n            <param name=\"ignoreError\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.OnBuild(NewLife.RocketMQ.Protocol.Header)\">\n            <summary>建立命令时，处理头部</summary>\n            <param name=\"header\"></param>\n        </member>\n        <member name=\"E:NewLife.RocketMQ.ClusterClient.Received\">\n            <summary>收到命令时</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.OnReceive(NewLife.RocketMQ.Protocol.Command)\">\n            <summary>收到命令</summary>\n            <param name=\"cmd\"></param>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.ClusterClient.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.ClusterClient.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Permissions\">\n            <summary>权限</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Permissions.Write\">\n            <summary>写入</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Permissions.Read\">\n            <summary>读取</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.BrokerInfo\">\n            <summary>代理信息</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.Name\">\n            <summary>名称</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.Addresses\">\n            <summary>地址集合</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.Permission\">\n            <summary>权限</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.ReadQueueNums\">\n            <summary>读队列数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.WriteQueueNums\">\n            <summary>写队列数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.BrokerInfo.TopicSynFlag\">\n            <summary>主题同步标记</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerInfo.Equals(System.Object)\">\n            <summary>相等比较</summary>\n            <param name=\"obj\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.BrokerInfo.GetHashCode\">\n            <summary>计算哈希</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Common.WeightRoundRobin\">\n            <summary>带权重负载均衡算法</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Common.WeightRoundRobin.Weights\">\n            <summary>权重集合</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Common.WeightRoundRobin.minWeight\">\n            <summary>最小权重</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Common.WeightRoundRobin._states\">\n            <summary>状态值</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Common.WeightRoundRobin._times\">\n            <summary>次数</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Common.WeightRoundRobin.#ctor(System.Int32[])\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Common.WeightRoundRobin.Get(System.Int32@)\">\n            <summary>根据权重选择，并返回该项是第几次选中</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Common.WeightRoundRobin.Get\">\n            <summary>根据权重选择</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Consumer\">\n            <summary>消费者</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.Data\">\n            <summary>数据</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.ConsumerInterval\">\n            <summary>消费间隔。默认15_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.PersistConsumerOffsetInterval\">\n            <summary>持久化消费偏移间隔。默认5_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.BatchSize\">\n            <summary>拉取的批大小。默认32</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.StartTime\">\n            <summary>启动时间</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.FromLastOffset\">\n            <summary>从最后偏移开始消费。默认true</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Consumer.OnConsume\">\n            <summary>消费委托</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.Start\">\n            <summary>启动</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.Pull(NewLife.RocketMQ.Protocol.MessageQueue,System.Int64,System.Int32,System.Int32)\">\n            <summary>从指定队列拉取消息</summary>\n            <param name=\"mq\"></param>\n            <param name=\"offset\"></param>\n            <param name=\"maxNums\"></param>\n            <param name=\"msTimeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.QueryOffset(NewLife.RocketMQ.Protocol.MessageQueue)\">\n            <summary>查询指定队列的偏移量</summary>\n            <param name=\"mq\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.QueryMaxOffset(NewLife.RocketMQ.Protocol.MessageQueue)\">\n            <summary>\n            查询“队列”最大偏移量，不是消费提交的最后偏移量\n            </summary>\n            <param name=\"mq\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.QueryMinOffset(NewLife.RocketMQ.Protocol.MessageQueue)\">\n            <summary>\n            获取最小偏移量\n            </summary>\n            <param name=\"mq\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.SearchOffset(NewLife.RocketMQ.Protocol.MessageQueue,System.Int64)\">\n            <summary>根据时间戳查询偏移</summary>\n            <param name=\"mq\"></param>\n            <param name=\"timestamp\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.UpdateOffset(NewLife.RocketMQ.Protocol.MessageQueue,System.Int64)\">\n            <summary>更新队列的偏移</summary>\n            <param name=\"mq\"></param>\n            <param name=\"commitOffset\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.GetConsumers(System.String)\">\n            <summary>获取消费者下所有消费者</summary>\n            <param name=\"group\"></param>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.AutoSchedule\">\n            <summary>启动消费者时自动开始调度。默认true</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.StartSchedule\">\n            <summary>开始调度</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.Consume(NewLife.RocketMQ.Protocol.MessageQueue,NewLife.RocketMQ.Protocol.PullResult)\">\n            <summary>拉取到一批消息</summary>\n            <param name=\"queue\"></param>\n            <param name=\"result\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Consumer.Queues\">\n            <summary>当前所需要消费的队列。由均衡算法产生</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.QueueStore.Equals(System.Object)\">\n            <summary>相等比较</summary>\n            <param name=\"obj\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.QueueStore.GetHashCode\">\n            <summary>计算哈希</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.Rebalance\">\n            <summary>重新平衡消费队列</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Consumer.OnReceive(NewLife.RocketMQ.Protocol.Command)\">\n            <summary>收到命令</summary>\n            <param name=\"cmd\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Client.MqBase\">\n            <summary>业务基类</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.NameServerAddress\">\n            <summary>名称服务器地址</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Group\">\n            <summary>消费组</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.ClientIP\">\n            <summary>本地IP地址</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.InstanceName\">\n            <summary>实例名</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.PollNameServerInterval\">\n            <summary>拉取名称服务器间隔。默认30_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.HeartbeatBrokerInterval\">\n            <summary>Broker心跳间隔。默认30_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.UnitName\">\n            <summary>单元名称</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.UnitMode\">\n            <summary>单元模式</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Active\">\n            <summary>是否可用</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Brokers\">\n            <summary>代理集合</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Client.MqBase._NameServer\">\n            <summary>名称服务器</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Server\">\n            <summary>获取名称服务器地址的http地址</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.AccessKey\">\n            <summary>访问令牌</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.SecretKey\">\n            <summary>访问密钥</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.OnsChannel\">\n            <summary>阿里云MQ通道</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.ClientId\">\n            <summary>客户端标识</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.#ctor\">\n            <summary>实例化</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.OnDispose(System.Boolean)\">\n            <summary>销毁</summary>\n            <param name=\"disposing\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.Start\">\n            <summary>开始</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.GetBroker(System.String)\">\n            <summary>获取代理客户端</summary>\n            <param name=\"name\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Clients\">\n            <summary>Broker客户端集合</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.OnReceive(NewLife.RocketMQ.Protocol.Command)\">\n            <summary>收到命令</summary>\n            <param name=\"cmd\"></param>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Client.MqBase.Log\">\n            <summary>日志</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Client.MqBase.WriteLog(System.String,System.Object[])\">\n            <summary>写日志</summary>\n            <param name=\"format\"></param>\n            <param name=\"args\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.NameClient\">\n            <summary>连接名称服务器的客户端</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.NameClient.Brokers\">\n            <summary>Broker集合</summary>\n        </member>\n        <member name=\"E:NewLife.RocketMQ.NameClient.OnBrokerChange\">\n            <summary>代理改变时触发</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.NameClient.#ctor(System.String,NewLife.RocketMQ.Client.MqBase)\">\n            <summary>实例化</summary>\n            <param name=\"id\"></param>\n            <param name=\"config\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.NameClient.Start\">\n            <summary>启动</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.NameClient.GetRouteInfo(System.String)\">\n            <summary>获取主题的路由信息，含登录验证</summary>\n            <param name=\"topic\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Producer\">\n            <summary>生产者</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Producer.Start\">\n            <summary>启动</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Producer.Publish(NewLife.RocketMQ.Protocol.Message,System.Int32)\">\n            <summary>发送消息</summary>\n            <param name=\"msg\"></param>\n            <param name=\"timeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Producer.Publish(System.Object,System.String,System.Int32)\">\n            <summary>发布消息</summary>\n            <param name=\"body\"></param>\n            <param name=\"tags\"></param>\n            <param name=\"timeout\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Producer.CreateTopic(System.String,System.Int32,System.Int32)\">\n            <summary>创建主题</summary>\n            <param name=\"topic\"></param>\n            <param name=\"queueNum\"></param>\n            <param name=\"topicSysFlag\"></param>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Producer.SelectQueue\">\n            <summary>选择队列</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.Command\">\n            <summary>命令</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Command.Header\">\n            <summary>头部</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Command.Payload\">\n            <summary>主体</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Command.Reply\">\n            <summary>是否响应</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Command.OneWay\">\n            <summary></summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Command.Error\">\n            <summary></summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.Read(System.IO.Stream,System.Object)\">\n            <summary>从数据流中读取</summary>\n            <param name=\"stream\"></param>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.ReadBodyAsJson\">\n            <summary>读取Body作为Json返回</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.Write(System.IO.Stream,System.Object)\">\n            <summary>写入命令到数据流</summary>\n            <param name=\"stream\"></param>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.ToPacket\">\n            <summary>命令转字节数组</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.CreateReply\">\n            <summary>创建响应</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Command.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.Header\">\n            <summary>头部</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Code\">\n            <summary>请求/响应码</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.ExtFields\">\n            <summary>扩展字段</summary>\n            <remarks>\n            这个字段不通的请求/响应不一样，完全自定义。数据结构上是java的hashmap。\n            在Java的每个RemotingCammand中，其实都带有一个CommandCustomHeader的属性成员，可以认为他是一个强类型的extFields，\n            再最后传输的时候，这个CommandCustomHeader会被忽略，而传输前会把其中的所有字段全部都原封不动塞到extFields中，以作传输。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Flag\">\n            <summary>标识</summary>\n            <remarks>\n            第0位标识是这次通信是request还是response，0标识request, 1 标识response。\n            第1位标识是否是oneway请求，1标识oneway。应答方在处理oneway请求的时候，不会做出响应，请求方也无需等待应答方响应。\n            </remarks>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Language\">\n            <summary>由于要支持多语言，所以这一字段可以给通信双方知道对方通信层锁使用的开发语言</summary>\n            <remarks>这里必须是JAVA，不能是CSharp，甚至Java都不行</remarks>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Opaque\">\n            <summary>请求标识码。在Java版的通信层中，这个只是一个不断自增的整形，为了收到应答方响应的的时候找到对应的请求。</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.SerializeTypeCurrentRPC\">\n            <summary>序列化类型</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Version\">\n            <summary>给通信层知道对方的版本号，响应方可以以此做兼容老版本等的特殊操作</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Header.Remark\">\n            <summary>附带的文本信息。常见的如存放一些broker/nameserver返回的一些异常信息，方便开发人员定位问题。</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Header.GetExtFields\">\n            <summary>获取扩展字段。如果为空则创建</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.HeartbeatData\">\n            <summary>心跳数据</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.HeartbeatData.ClientID\">\n            <summary>客户端编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.HeartbeatData.ConsumerDataSet\">\n            <summary>消费数据集</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.HeartbeatData.ProducerDataSet\">\n            <summary>生产者数据集</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.ProducerData\">\n            <summary>生产者数据</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ProducerData.GroupName\">\n            <summary>组名</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.ConsumerData\">\n            <summary>消费者数据</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.ConsumeFromWhere\">\n            <summary>从哪里开始消费</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.ConsumeType\">\n            <summary>消费类型</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.GroupName\">\n            <summary>组名</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.MessageModel\">\n            <summary>消息模型。广播/集群</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.SubscriptionDataSet\">\n            <summary>订阅数据集</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ConsumerData.UnitMode\">\n            <summary>单元模式</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.SubscriptionData\">\n            <summary>订阅者数据</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.ExpressionType\">\n            <summary>表达式类型</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.SubString\">\n            <summary>子字符串</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.TagsSet\">\n            <summary>标签集合</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.CodeSet\">\n            <summary>代码集合</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.ClassFilterMode\">\n            <summary>过滤模式</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.FilterClassSource\">\n            <summary>过滤源</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SubscriptionData.SubVersion\">\n            <summary>子版本</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.Message\">\n            <summary>消息</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.Tags\">\n            <summary>标签</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.Keys\">\n            <summary>键</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.Flag\">\n            <summary>标记</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.Body\">\n            <summary>消息体</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.BodyString\">\n            <summary>消息体。字符串格式</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.WaitStoreMsgOK\">\n            <summary>等待存储消息</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.Message.DelayTimeLevel\">\n            <summary>延迟时间等级</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Message.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Message.GetProperties\">\n            <summary>获取属性</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.Message.SetProperties(System.String)\">\n            <summary>设置数据</summary>\n            <param name=\"properties\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.MessageExt\">\n            <summary>消息扩展</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.QueueId\">\n            <summary>队列编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.StoreSize\">\n            <summary>存储大小</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.BodyCRC\">\n            <summary>CRC校验</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.QueueOffset\">\n            <summary>队列偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.CommitLogOffset\">\n            <summary>提交日志偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.SysFlag\">\n            <summary>系统标记</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.BornTimestamp\">\n            <summary>生产时间</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.BornHost\">\n            <summary>生产主机</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.StoreTimestamp\">\n            <summary>存储时间</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.StoreHost\">\n            <summary>存储主机</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.ReconsumeTimes\">\n            <summary>重新消费次数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.PreparedTransactionOffset\">\n            <summary>准备事务偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.Properties\">\n            <summary>属性</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageExt.MsgId\">\n            <summary>消息编号</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageExt.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageExt.Read(System.IO.Stream,System.Object)\">\n            <summary>从数据流中读取</summary>\n            <param name=\"stream\"></param>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageExt.ReadAll(NewLife.Data.Packet)\">\n            <summary>读取所有消息</summary>\n            <param name=\"body\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageExt.Write(System.IO.Stream,System.Object)\">\n            <summary>写入命令到数据流</summary>\n            <param name=\"stream\"></param>\n            <param name=\"context\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.MessageQueue\">\n            <summary>消息队列</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageQueue.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageQueue.BrokerName\">\n            <summary>代理名称</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.MessageQueue.QueueId\">\n            <summary>队列编号</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageQueue.Equals(System.Object)\">\n            <summary>相等比较</summary>\n            <param name=\"obj\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageQueue.GetHashCode\">\n            <summary>计算哈希</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MessageQueue.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.MqCodec\">\n            <summary>编码器</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.#ctor\">\n            <summary>实例化编码器</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.Encode(NewLife.Model.IHandlerContext,NewLife.RocketMQ.Protocol.Command)\">\n            <summary>编码</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.AddToQueue(NewLife.Model.IHandlerContext,NewLife.RocketMQ.Protocol.Command)\">\n            <summary>加入队列</summary>\n            <param name=\"context\"></param>\n            <param name=\"msg\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.Decode(NewLife.Model.IHandlerContext,NewLife.Data.Packet)\">\n            <summary>解码</summary>\n            <param name=\"context\"></param>\n            <param name=\"pk\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.Close(NewLife.Model.IHandlerContext,System.String)\">\n            <summary>连接关闭时，清空粘包编码器</summary>\n            <param name=\"context\"></param>\n            <param name=\"reason\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.MqCodec.IsMatch(System.Object,System.Object)\">\n            <summary>是否匹配响应</summary>\n            <param name=\"request\"></param>\n            <param name=\"response\"></param>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.PullMessageRequestHeader\">\n            <summary>拉取信息请求头</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.ConsumerGroup\">\n            <summary>消费组</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.Subscription\">\n            <summary>订阅表达式</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.SuspendTimeoutMillis\">\n            <summary>挂起超时时间。默认20_000ms</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.SubVersion\">\n            <summary>子版本</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.QueueId\">\n            <summary>队列</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.QueueOffset\">\n            <summary>队列偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.MaxMsgNums\">\n            <summary>最大消息数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.CommitOffset\">\n            <summary>提交偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.SysFlag\">\n            <summary>系统标记</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.PullMessageRequestHeader.GetProperties\">\n            <summary>获取属性字典</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.PullStatus\">\n            <summary>拉取状态</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.PullStatus.Found\">\n            <summary>已发现</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.PullStatus.NoNewMessage\">\n            <summary>没有新的消息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.PullStatus.NoMatchedMessage\">\n            <summary>没有匹配消息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.PullStatus.OffsetIllegal\">\n            <summary>偏移量非法</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.PullStatus.Unknown\">\n            <summary>未知类型</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.PullResult\">\n            <summary>拉取结果</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullResult.Status\">\n            <summary>状态</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullResult.MinOffset\">\n            <summary>最小偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullResult.MaxOffset\">\n            <summary>最大偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullResult.NextBeginOffset\">\n            <summary>下一轮拉取偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.PullResult.Messages\">\n            <summary>消息</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.PullResult.ToString\">\n            <summary>友好字符串</summary>\n            <returns></returns>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.PullResult.Read(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>读取数据</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.QueryResult\">\n            <summary>查询结果</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.QueryResult.IndexLastUpdateTimestamp\">\n            <summary>最后更新时间</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.QueryResult.MessageList\">\n            <summary>消息列表</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.RequestCode\">\n            <summary>请求代码</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.SEND_MESSAGE\">\n            <summary>发消息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.PULL_MESSAGE\">\n            <summary>收消息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.QUERY_CONSUMER_OFFSET\">\n            <summary>查询消费偏移</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ALL_TOPIC_CONFIG\">\n            <summary>用于向brokers查询所有的topic和它们的配置</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_BROKER_CONFIG\">\n            <summary>获取代理配置</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_BROKER_RUNTIME_INFO\">\n            <summary>获取代理运行时信息,包括broker版本、磁盘容量、系统负载等</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_MAX_OFFSET\">\n            <summary>获取topic/队列偏移量的最大值</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_MIN_OFFSET\">\n            <summary>获取topic/队列偏移量的最小值</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.HEART_BEAT\">\n            <summary>发送心跳</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.UNREGISTER_CLIENT\">\n            <summary>注销</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.CONSUMER_SEND_MSG_BACK\">\n            <summary>当consumer客户端无法处理消息时,将这些消息发送回brokers,以便将来将这些消息重新发送给consumers</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_CONSUMER_LIST_BY_GROUP\">\n            <summary>查询每个consumer group的存活成员</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.NOTIFY_CONSUMER_IDS_CHANGED\">\n            <summary>当broker得知一个consumer宕机时,它会通知其他工作的consumers尽快重新平衡</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ALL_CONSUMER_OFFSET\">\n            <summary>获取所有的消费的偏移量</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ALL_DELAY_OFFSET\">\n            <summary>获取延迟topic的偏移量</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ROUTEINTO_BY_TOPIC\">\n            <summary>获取topic路由信息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_BROKER_CLUSTER_INFO\">\n            <summary>获取群集信息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP\">\n            <summary>创建新的consumer group或更新现有的consumer group以更改属性</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG\">\n            <summary>查询所有已知的consumer group配置</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_TOPIC_STATS_INFO\">\n            <summary>查询topic相关的统计信息</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER\">\n            <summary>查询所有topic</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.INVOKE_BROKER_TO_RESET_OFFSET\">\n            <summary>要求broker从consumer客户端按给定的时间戳重置偏移量</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.UPDATE_NAMESRV_CONFIG\">\n            <summary>update the config of name server</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.GET_NAMESRV_CONFIG\">\n            <summary>get config from name server</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.RequestCode.SEND_BATCH_MESSAGE\">\n            <summary>批处理模式发送消息</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.ResponseCode\">\n            <summary>响应码</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.ResponseException\">\n            <summary>响应异常</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.ResponseException.Code\">\n            <summary>响应代码</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.ResponseException.#ctor(System.Int32,System.String)\">\n            <summary>实例化响应异常</summary>\n            <param name=\"code\"></param>\n            <param name=\"message\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.SendMessageRequestHeader\">\n            <summary>发送消息请求头</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.ProducerGroup\">\n            <summary>生产组</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.Topic\">\n            <summary>主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.DefaultTopic\">\n            <summary>默认主题</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.DefaultTopicQueueNums\">\n            <summary>默认主题队列数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.QueueId\">\n            <summary>队列编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.SysFlag\">\n            <summary>系统标记</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.BornTimestamp\">\n            <summary>生产时间。毫秒</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.Flag\">\n            <summary>标记</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.Properties\">\n            <summary>属性。Tags/Keys等</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.ReconsumeTimes\">\n            <summary>重新消费次数</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.UnitMode\">\n            <summary>单元模式</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.SendMessageRequestHeader.GetProperties\">\n            <summary>获取属性字典</summary>\n            <returns></returns>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.SendStatus\">\n            <summary>发送状态</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.SendStatus.SendOK\">\n            <summary>成功</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.SendStatus.FlushDiskTimeout\">\n            <summary>刷盘超时</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.SendStatus.FlushSlaveTimeout\">\n            <summary>刷从机超时</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.SendStatus.SlaveNotAvailable\">\n            <summary>从机不可用</summary>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.SendResult\">\n            <summary>发送结果</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.Status\">\n            <summary>状态</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.MsgId\">\n            <summary>消息编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.Queue\">\n            <summary>队列</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.QueueOffset\">\n            <summary>队列偏移</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.TransactionId\">\n            <summary>事务编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.OffsetMsgId\">\n            <summary>偏移消息编号</summary>\n        </member>\n        <member name=\"P:NewLife.RocketMQ.Protocol.SendResult.RegionId\">\n            <summary>区域</summary>\n        </member>\n        <member name=\"M:NewLife.RocketMQ.Protocol.SendResult.Read(System.Collections.Generic.IDictionary{System.String,System.String})\">\n            <summary>读取结果</summary>\n            <param name=\"dic\"></param>\n        </member>\n        <member name=\"T:NewLife.RocketMQ.Protocol.ServiceState\">\n            <summary>服务状态</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.ServiceState.CreateJust\">\n            <summary>刚刚建立</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.ServiceState.Running\">\n            <summary>运行中</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.ServiceState.ShutdownAlready\">\n            <summary>已经关闭</summary>\n        </member>\n        <member name=\"F:NewLife.RocketMQ.Protocol.ServiceState.StartFailed\">\n            <summary>启动失败</summary>\n        </member>\n    </members>\n</doc>\n"
  },
  {
    "path": "Doc/Changelog.md",
    "content": "# NewLife.RocketMQ 更新日志 2026\n\n## v3.0.2026.0304 (2026-03-04)\n\n### 架构优化\n* 重构 RocketMQ gRPC 协议为 SpanReader/SpanWriter 实现，提升性能\n* 重构架构文档，优化编解码器实现\n* 优化项目文件描述及标题信息\n\n### 文档完善\n* 文档体系重构：新增架构与需求文档，优化 Readme\n* 新增架构设计文档和功能分析文档\n\n### 依赖升级\n* 升级 NewLife.Core 依赖到最新版本\n\n---\n\n## v3.0.2026.0228 (2026-02-28)\n\n### 问题修复\n* 修复逻辑缺陷并补充单元测试\n* 新增客户端拉取超时机制，防止 RocketMQ 4.9.8 消费者线程卡死\n\n---\n\n## v3.0.2026.0216 (2026-02-16)\n\n### 重大更新：v3.0 云适配重构\n* 云适配重构与功能全面单元测试覆盖\n* 完善兼容性梳理与优化进展\n\n### 新增特性\n* 新增 VIP 通道支持\n* 新增批量消息确认机制\n* 新增 5.x MsgId 支持\n* 新增 gRPC Telemetry 遥测支持\n* 完整实现**事务消息**功能（PR #108）\n  - 增加 RocketMQ 事务消息发布与结束接口\n  - 完善事务消息实现并通过审查与安全扫描\n* 完整实现**请求-应答模式** (Request-Reply) 功能（PR #107）\n  - 完善 RocketMQ 5.0 特性支持\n  - 通过单元测试和文档验证\n\n### 文档完善\n* 新增功能分析与架构设计文档\n* Phase 6 文档总结：兼容性梳理与优化进展\n\n### 依赖升级\n* 升级 NuGet 依赖包\n\n---\n\n## v3.0.2026.0211 (2026-02-11)\n\n### 依赖升级\n* 升级 NewLife.Core 依赖到最新版本\n"
  },
  {
    "path": "Doc/RequestReply_Guide.md",
    "content": "# RocketMQ Request-Reply 使用指南\n\n## 概述\n\n从 RocketMQ 4.6.0 版本开始，引入了 Request-Reply 特性，该特性允许生产者在发送消息后同步或异步等待消费者消费完消息并返回响应消息，实现类似 RPC 调用的效果。\n\nNewLife.RocketMQ 现已支持此特性，并兼容 RocketMQ 5.0+。\n\n## 主要特性\n\n- **同步请求**：发送请求后阻塞等待响应\n- **异步请求**：发送请求后异步等待响应\n- **超时控制**：支持设置请求超时时间\n- **自动关联**：自动管理请求和响应的关联\n- **简单易用**：API 设计简洁，易于集成\n\n## 使用示例\n\n### 1. 生产者端 - 发送请求\n\n#### 同步请求\n\n```csharp\nusing NewLife.RocketMQ;\n\n// 创建生产者\nvar producer = new Producer\n{\n    Topic = \"request_topic\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    RequestTimeout = 3000  // 设置默认超时时间为3秒\n};\nproducer.Start();\n\ntry\n{\n    // 发送请求并同步等待响应\n    var requestBody = \"这是一个请求消息\";\n    var response = producer.Request(requestBody, timeout: 5000);\n    \n    Console.WriteLine($\"收到响应: {response.BodyString}\");\n}\nfinally\n{\n    producer.Stop();\n    producer.Dispose();\n}\n```\n\n#### 异步请求\n\n```csharp\nusing NewLife.RocketMQ;\n\n// 创建生产者\nvar producer = new Producer\n{\n    Topic = \"request_topic\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    RequestTimeout = 3000\n};\nproducer.Start();\n\ntry\n{\n    // 异步发送请求并等待响应\n    var requestBody = \"这是一个异步请求消息\";\n    var response = await producer.RequestAsync(requestBody, timeout: 5000);\n    \n    Console.WriteLine($\"收到响应: {response.BodyString}\");\n}\nfinally\n{\n    producer.Stop();\n    producer.Dispose();\n}\n```\n\n### 2. 消费者端 - 处理请求并发送回复\n\n#### 同步处理\n\n```csharp\nusing NewLife.RocketMQ;\n\n// 创建消费者\nvar consumer = new Consumer\n{\n    Topic = \"request_topic\",\n    Group = \"request_consumer_group\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    FromLastOffset = false\n};\n\n// 设置消息处理回调\nconsumer.OnConsume = (queue, messages) =>\n{\n    foreach (var message in messages)\n    {\n        Console.WriteLine($\"收到请求: {message.BodyString}\");\n        \n        // 检查是否是请求消息\n        if (!String.IsNullOrEmpty(message.CorrelationId))\n        {\n            // 处理业务逻辑\n            var result = ProcessRequest(message.BodyString);\n            \n            // 发送回复\n            consumer.SendReply(message, result);\n            \n            Console.WriteLine($\"已发送回复: {result}\");\n        }\n    }\n    return true;\n};\n\nconsumer.Start();\n\n// 保持运行\nConsole.WriteLine(\"消费者已启动，按任意键退出...\");\nConsole.ReadKey();\n\nconsumer.Stop();\nconsumer.Dispose();\n\nstring ProcessRequest(string request)\n{\n    // 实现你的业务逻辑\n    return $\"处理结果: {request}\";\n}\n```\n\n#### 异步处理\n\n```csharp\nusing NewLife.RocketMQ;\n\n// 创建消费者\nvar consumer = new Consumer\n{\n    Topic = \"request_topic\",\n    Group = \"request_consumer_group\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    FromLastOffset = false\n};\n\n// 设置异步消息处理回调\nconsumer.OnConsumeAsync = async (queue, messages, cancellationToken) =>\n{\n    foreach (var message in messages)\n    {\n        Console.WriteLine($\"收到请求: {message.BodyString}\");\n        \n        // 检查是否是请求消息\n        if (!String.IsNullOrEmpty(message.CorrelationId))\n        {\n            // 异步处理业务逻辑\n            var result = await ProcessRequestAsync(message.BodyString, cancellationToken);\n            \n            // 异步发送回复\n            await consumer.SendReplyAsync(message, result, cancellationToken);\n            \n            Console.WriteLine($\"已发送回复: {result}\");\n        }\n    }\n    return true;\n};\n\nconsumer.Start();\n\n// 保持运行\nConsole.WriteLine(\"消费者已启动，按任意键退出...\");\nConsole.ReadKey();\n\nconsumer.Stop();\nconsumer.Dispose();\n\nasync Task<string> ProcessRequestAsync(string request, CancellationToken ct)\n{\n    // 实现你的异步业务逻辑\n    await Task.Delay(100, ct);  // 模拟异步操作\n    return $\"处理结果: {request}\";\n}\n```\n\n### 3. 超时处理\n\n```csharp\nusing NewLife.RocketMQ;\n\nvar producer = new Producer\n{\n    Topic = \"request_topic\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    RequestTimeout = 1000  // 默认超时1秒\n};\nproducer.Start();\n\ntry\n{\n    var response = await producer.RequestAsync(\"请求消息\");\n    Console.WriteLine($\"收到响应: {response.BodyString}\");\n}\ncatch (TimeoutException ex)\n{\n    Console.WriteLine($\"请求超时: {ex.Message}\");\n}\nfinally\n{\n    producer.Stop();\n    producer.Dispose();\n}\n```\n\n## API 参考\n\n### Producer 类\n\n#### 属性\n\n- `RequestTimeout`：请求超时时间（毫秒），默认 3000ms\n\n#### 方法\n\n- `MessageExt Request(Message message, Int32 timeout = -1)`\n  - 发送请求消息，同步等待响应\n  - 参数：\n    - `message`：请求消息\n    - `timeout`：超时时间（毫秒），-1 表示使用默认超时时间\n  - 返回：响应消息\n  - 异常：`TimeoutException` - 请求超时\n\n- `MessageExt Request(Object body, Int32 timeout = -1)`\n  - 发送请求消息，同步等待响应（简化版本）\n  - 参数：\n    - `body`：消息体内容\n    - `timeout`：超时时间（毫秒）\n  - 返回：响应消息\n\n- `Task<MessageExt> RequestAsync(Message message, Int32 timeout = -1, CancellationToken cancellationToken = default)`\n  - 异步发送请求消息并等待响应\n  - 参数：\n    - `message`：请求消息\n    - `timeout`：超时时间（毫秒）\n    - `cancellationToken`：取消令牌\n  - 返回：响应消息\n  - 异常：`TimeoutException` - 请求超时\n\n- `Task<MessageExt> RequestAsync(Object body, Int32 timeout = -1, CancellationToken cancellationToken = default)`\n  - 异步发送请求消息并等待响应（简化版本）\n\n### Consumer 类\n\n#### 方法\n\n- `SendResult SendReply(MessageExt requestMessage, Object replyBody)`\n  - 发送回复消息\n  - 参数：\n    - `requestMessage`：原始请求消息\n    - `replyBody`：回复消息内容\n  - 返回：发送结果\n  - 异常：\n    - `ArgumentNullException` - 参数为空\n    - `InvalidOperationException` - 请求消息缺少必要属性\n\n- `Task<SendResult> SendReplyAsync(MessageExt requestMessage, Object replyBody, CancellationToken cancellationToken = default)`\n  - 异步发送回复消息\n  - 参数：\n    - `requestMessage`：原始请求消息\n    - `replyBody`：回复消息内容\n    - `cancellationToken`：取消令牌\n  - 返回：发送结果\n\n### Message 类新增属性\n\n- `ReplyToClient`：回复地址，指示回复消息应发送到的客户端ID\n- `CorrelationId`：关联ID，用于将回复消息与请求消息关联\n- `MessageType`：消息类型，用于区分普通消息和回复消息（\"REQUEST\"/\"REPLY\"）\n- `RequestTimeout`：请求超时时间（毫秒）\n\n## 注意事项\n\n1. **版本要求**：需要 RocketMQ 服务器版本 4.6.0 或更高\n2. **超时设置**：合理设置超时时间，避免长时间阻塞\n3. **异常处理**：务必捕获 `TimeoutException` 处理超时情况\n4. **资源释放**：使用完毕后及时释放 Producer 和 Consumer 资源\n5. **Topic 规划**：建议为 Request-Reply 使用独立的 Topic\n6. **消费者处理**：消费者必须检查 `CorrelationId` 属性来判断是否为请求消息\n\n## 兼容性\n\n- 支持 .NET Framework 4.5+\n- 支持 .NET Standard 2.0+\n- 支持 .NET Core 2.0+\n- 支持 .NET 5.0+\n- 兼容 RocketMQ 4.6.0 或以上版本\n- 兼容 RocketMQ 5.0 或以上版本\n\n## 性能建议\n\n1. 复用 Producer 和 Consumer 实例，避免频繁创建销毁\n2. 合理设置超时时间，避免资源浪费\n3. 对于高并发场景，建议使用异步 API\n4. 监控回复消息的处理时间，及时优化业务逻辑\n\n## 故障排查\n\n### 请求超时\n\n- 检查消费者是否正常运行\n- 检查网络连接是否正常\n- 检查消费者处理逻辑是否耗时过长\n- 适当增加超时时间\n\n### 收不到回复\n\n- 确认消费者正确调用了 `SendReply` 或 `SendReplyAsync`\n- 检查消费者日志，确认是否有异常\n- 确认消息的 `CorrelationId` 属性正确设置\n- 检查 Topic 配置是否正确\n\n## 更多示例\n\n更多使用示例请参考项目源码中的单元测试：`XUnitTestRocketMQ/RequestReplyTests.cs`\n"
  },
  {
    "path": "Doc/架构设计.md",
    "content": "# NewLife.RocketMQ 架构\n\n## 1. 架构概览\n\nNewLife.RocketMQ 采用**分层清晰、职责单一、可扩展**的架构设计，纯 C# 实现，零外部依赖。\n\n### 1.1 设计理念\n\n- **纯托管实现**：完全使用 C# 实现，无需 Java、gRPC、Protobuf 第三方库\n- **双协议支持**：同时支持 Remoting 协议（4.x）和 gRPC 协议（5.x）\n- **云厂商适配**：统一 `ICloudProvider` 接口，轻松接入各云服务商\n- **高性能优化**：连接复用、对象池、VIP 通道、消息压缩等优化手段\n- **可测试性**：30+ 测试类覆盖核心功能和边缘场景\n\n### 1.2 层次结构\n\n```\n┌──────────────────────────────────────────────────────────┐\n│                      业务层 (MqBase)                      │\n│  ┌─────────────────┐         ┌─────────────────┐         │\n│  │   Producer      │         │   Consumer      │         │\n│  │  生产者业务逻辑  │         │  消费者业务逻辑  │         │\n│  └─────────────────┘         └─────────────────┘         │\n└──────────────────────────────────────────────────────────┘\n                             │\n┌──────────────────────────────────────────────────────────┐\n│                      通信层 (Client)                      │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │  ClusterClient  │  │  GrpcClient     │               │\n│  │  TCP 长连接管理  │  │  HTTP/2 gRPC   │               │\n│  └─────────────────┘  └─────────────────┘               │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │  NameClient     │  │GrpcMessaging    │               │\n│  │  路由发现        │  │Service          │               │\n│  └─────────────────┘  └─────────────────┘               │\n│  ┌─────────────────┐                                     │\n│  │  BrokerClient   │                                     │\n│  │  心跳/注销       │                                     │\n│  └─────────────────┘                                     │\n└──────────────────────────────────────────────────────────┘\n                             │\n┌──────────────────────────────────────────────────────────┐\n│                    协议层 (Protocol)                      │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │    Command      │  │  GrpcModels     │               │\n│  │  Remoting 帧    │  │  gRPC 消息模型  │               │\n│  └─────────────────┘  └─────────────────┘               │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │   MessageExt    │  │  ProtoWriter    │               │\n│  │  消息模型        │  │  Protobuf 编码  │               │\n│  └─────────────────┘  └─────────────────┘               │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │  RequestCode    │  │  ProtoReader    │               │\n│  │  请求码枚举      │  │  Protobuf 解码  │               │\n│  └─────────────────┘  └─────────────────┘               │\n└──────────────────────────────────────────────────────────┘\n                             │\n┌──────────────────────────────────────────────────────────┐\n│                   传输层 (Transport)                      │\n│  ┌─────────────────┐  ┌─────────────────┐               │\n│  │  NewLife.Net    │  │   HttpClient    │               │\n│  │  TCP Socket     │  │   HTTP/2        │               │\n│  └─────────────────┘  └─────────────────┘               │\n└──────────────────────────────────────────────────────────┘\n                             │\n┌──────────────────────────────────────────────────────────┐\n│                 云厂商适配层 (CloudProvider)              │\n│  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐            │\n│  │Aliyun  │ │Huawei  │ │Tencent │ │  ACL   │            │\n│  │Provider│ │Provider│ │Provider│ │Provider│            │\n│  └────────┘ └────────┘ └────────┘ └────────┘            │\n└──────────────────────────────────────────────────────────┘\n```\n\n### 1.3 代码组织结构\n\n```\nMqBase (业务基类，NameServer连接/Broker管理/Topic与消费组CRUD/消息查询)\n├── Producer (生产者：普通/异步/单向/延迟/事务/批量/Request-Reply/gRPC)\n└── Consumer (消费者：Pull/调度/Rebalance/多Topic/顺序/重试/Pop/gRPC)\n\nClusterClient (集群客户端/通信层，Remoting协议)\n├── NameClient (名称服务器客户端：路由发现/定时轮询/多Topic路由/Broker主从解析)\n└── BrokerClient (Broker客户端：心跳/注销/命令收发)\n\nGrpc/ (gRPC传输层，RocketMQ 5.x Proxy协议，netstandard2.1+)\n├── GrpcClient (HTTP/2 gRPC客户端，帧编解码，Unary + Server Streaming)\n├── GrpcMessagingService (消息服务：路由/发送/接收/确认/心跳/事务/延迟/死信)\n├── ProtoWriter / ProtoReader (轻量级Protobuf编解码器，无外部依赖)\n├── GrpcModels (Resource/Endpoints/Message/SystemProperties/MessageQueue等)\n├── GrpcServiceMessages (Request/Response消息类型，约25个，含Telemetry)\n└── GrpcEnums (GrpcCode/GrpcMessageType/GrpcClientType/AddressScheme等)\n\nProtocol/\n├── Command (命令帧，Remoting协议编解码)\n├── MqCodec (网络编解码器)\n├── Message / MessageExt (消息模型，含批量解码/ZLIB解压/IPv4+IPv6/5.x MessageId)\n├── RequestCode (约60个指令码) / ResponseCode (约20个响应码)\n├── MQVersion (V3.0 ~ V5.9.9 + HIGHER_VERSION，约450个版本)\n└── 各类 Header / State / Enum\n\nCloudProvider/\n├── ICloudProvider (统一云厂商接口)\n├── AliyunProvider (阿里云适配)\n├── AclProvider (Apache ACL适配)\n├── HuaweiProvider (华为云适配)\n└── TencentProvider (腾讯云适配)\n```\n\n---\n\n## 2. 数据模型\n\n### 2.1 消息模型\n\n| 类型 | 说明 | 字段 |\n|------|------|------|\n| Message | 基础消息 | Topic, Tags, Keys, Body, Properties, DelayTimeLevel |\n| MessageExt | 扩展消息（接收端） | MsgId, QueueId, QueueOffset, BornTimestamp, StoreTimestamp, BornHost, StoreHost, SysFlag, ReconsumeTimes |\n| GrpcMessage | gRPC 消息（5.x） | Topic(GrpcResource), UserProperties, SystemProperties(GrpcSystemProperties), Body |\n\n### 2.2 路由模型\n\n| 类型 | 说明 | 字段 |\n|------|------|------|\n| BrokerInfo | Broker 信息 | BrokerName, MasterAddress, SlaveAddresses, Weight |\n| MessageQueue | 消息队列 | BrokerName, QueueId, ReadQueueNums, WriteQueueNums |\n\n### 2.3 协议帧模型\n\n**Remoting 协议帧**：\n\n```\n┌────────┬────────────┬────────────┬─────────────────┐\n│ Length │HeaderLength│   Header   │      Body       │\n│ 4 bytes│  4 bytes   │  N bytes   │    M bytes      │\n└────────┴────────────┴────────────┴─────────────────┘\nLength = 4 + N + M\nHeaderLength 高 8 位：SerializeType (0=JSON, 1=ROCKETMQ)\nHeaderLength 低 24 位：实际 Header 长度\n```\n\n**gRPC 消息帧**：\n\n```\n┌────────┬────────────┬──────────────────────────────┐\n│  Comp  │   Length   │          Body                │\n│ 1 byte │  4 bytes   │        N bytes               │\n└────────┴────────────┴──────────────────────────────┘\nComp: 0=不压缩, 1=gzip\nLength: 大端序，Protobuf 消息体长度\n```\n\n---\n\n## 3. 接口设计\n\n### 3.1 生产者 API\n\n| 接口 | 方法 | 签名 | 入参 | 出参 | 说明 |\n|------|------|------|------|------|------|\n| Producer | Publish | `SendResult Publish(Object message, String tags)` | message: 消息体, tags: 标签 | SendResult | 同步发送 |\n| Producer | PublishAsync | `Task<SendResult> PublishAsync(Object message, String tags)` | 同上 | Task\\<SendResult\\> | 异步发送 |\n| Producer | PublishOneway | `void PublishOneway(Object message, String tags)` | 同上 | 无 | 单向发送 |\n| Producer | PublishBatch | `SendResult PublishBatch(IList<Message> messages)` | messages: 消息列表 | SendResult | 批量发送 |\n| Producer | PublishDelay | `SendResult PublishDelay(Object message, DelayTimeLevels delay)` | message, delay: 延迟等级 | SendResult | 延迟消息 |\n| Producer | PublishDelayViaGrpcAsync | `Task PublishDelayViaGrpcAsync(String body, DateTime deliveryTime)` | body, deliveryTime: 投递时间 | Task | gRPC 任意延迟 |\n| Producer | PublishTransaction | `SendResult PublishTransaction(Object message)` | message: 消息体 | SendResult | 事务半消息 |\n| Producer | EndTransaction | `void EndTransaction(SendResult result, TransactionState state)` | result, state: 提交/回滚 | 无 | 结束事务 |\n| Producer | Request | `MessageExt Request(Object message, Int32 timeout)` | message, timeout: 超时 | MessageExt | Request-Reply |\n\n### 3.2 消费者 API\n\n| 接口 | 方法 | 签名 | 入参 | 出参 | 说明 |\n|------|------|------|------|------|------|\n| Consumer | OnConsume | `Func<MessageQueue, MessageExt[], Boolean>` | 委托 | — | 消费回调 |\n| Consumer | Pull | `PullResult Pull(MessageQueue queue, Int64 offset, Int32 maxNums)` | queue, offset, maxNums | PullResult | 拉取消息 |\n| Consumer | PopMessageAsync | `Task<IList<MessageExt>> PopMessageAsync(...)` | timeout 等 | IList\\<MessageExt\\> | Pop 消费 |\n| Consumer | AckMessageAsync | `Task AckMessageAsync(MessageExt msg)` | msg: 消息 | Task | 确认消费 |\n| Consumer | BatchAckMessageAsync | `Task BatchAckMessageAsync(IList<MessageExt> msgs)` | msgs: 消息列表 | Task | 批量确认 |\n\n### 3.3 云厂商适配接口\n\n| 接口 | 方法 | 签名 | 入参 | 出参 | 说明 |\n|------|------|------|------|------|------|\n| ICloudProvider | TransformTopic | `String TransformTopic(String topic)` | topic: 原始主题名 | 转换后主题名 | 主题名转换 |\n| ICloudProvider | TransformGroup | `String TransformGroup(String group)` | group: 原始组名 | 转换后组名 | 消费组名转换 |\n| ICloudProvider | GetNameServerAddress | `String GetNameServerAddress()` | 无 | NameServer 地址 | 获取 NameServer |\n\n### 3.4 核心 RequestCode\n\n| 分类 | RequestCode | 值 | 说明 |\n|------|------------|:--:|------|\n| 消息发送 | SEND_MESSAGE_V2 | 310 | 发送消息V2 |\n| | SEND_BATCH_MESSAGE | 320 | 批量发送 |\n| | SEND_REPLY_MESSAGE_V2 | 325 | 回复消息 |\n| 消息拉取 | PULL_MESSAGE | 11 | 拉取消息 |\n| | POP_MESSAGE | 200050 | Pop 消费 |\n| | ACK_MESSAGE | 200051 | Pop 确认 |\n| | BATCH_ACK_MESSAGE | 200151 | 批量 Pop 确认 |\n| 事务消息 | END_TRANSACTION | 37 | 结束事务 |\n| | CHECK_TRANSACTION_STATE | 39 | 事务回查 |\n| 偏移管理 | QUERY_CONSUMER_OFFSET | 14 | 查询偏移 |\n| | UPDATE_CONSUMER_OFFSET | 15 | 更新偏移 |\n| | SEARCH_OFFSET_BY_TIMESTAMP | 29 | 按时间戳搜索 |\n| 顺序消费 | LOCK_BATCH_MQ | 41 | 锁定队列 |\n| | UNLOCK_BATCH_MQ | 42 | 解锁队列 |\n| 路由管理 | GET_ROUTEINTO_BY_TOPIC | 105 | Topic 路由 |\n| | GET_BROKER_CLUSTER_INFO | 106 | 集群信息 |\n| 心跳 | HEART_BEAT | 34 | 心跳 |\n| | UNREGISTER_CLIENT | 35 | 注销客户端 |\n\n### 3.5 gRPC RPC 方法\n\n| 方法 | 类型 | 说明 |\n|------|------|------|\n| QueryRoute | Unary | 查询主题路由 |\n| SendMessage | Unary | 发送消息（普通/延迟/FIFO/事务） |\n| QueryAssignment | Unary | 查询队列分配 |\n| ReceiveMessage | Server Streaming | 接收消息（长轮询） |\n| AckMessage | Unary | 确认消息消费 |\n| Heartbeat | Unary | 心跳 |\n| EndTransaction | Unary | 结束事务 |\n| ForwardToDeadLetterQueue | Unary | 转发到死信队列 |\n| ChangeInvisibleDuration | Unary | 修改不可见时间 |\n| NotifyClientTermination | Unary | 通知客户端终止 |\n| Telemetry | Bidirectional Streaming | 客户端资源上报 |\n\n---\n\n## 4. 技术选型\n\n| 领域 | 选型 | 理由 |\n|------|------|------|\n| 网络通信（Remoting） | NewLife.Net | 高性能 TCP 库，单机千万级吞吐，内置连接池 |\n| 网络通信（gRPC） | System.Net.Http.HttpClient | .NET 原生 HTTP/2 支持，无需第三方库 |\n| 序列化（Remoting） | NewLife.Serialization | 内置 JSON 序列化，无外部依赖 |\n| 序列化（gRPC） | 自研 ProtoWriter/ProtoReader | 轻量级 Protobuf 编解码，零外部依赖 |\n| 加密签名 | System.Security.Cryptography | .NET 内置 HMAC-SHA1 |\n| SSL/TLS | System.Net.Security.SslStream | .NET 内置 SSL/TLS 支持 |\n| 日志 | NewLife.Log.ILog | NewLife 核心日志组件 |\n| 追踪 | NewLife.Model.ITracer | NewLife APM 追踪组件 |\n| 负载均衡 | 自研 WeightRoundRobin | 加权轮询算法，支持 Broker 权重 |\n| 压缩 | System.IO.Compression.DeflateStream | .NET 内置 ZLIB 压缩 |\n\n---\n\n## 5. 关键设计决策\n\n| 决策点 | 方案 | 备选方案 | 选择理由 |\n|--------|------|---------|---------|\n| Protobuf 编解码 | 自研 ProtoWriter/ProtoReader | Google.Protobuf / protobuf-net | 零外部依赖，减少包体积和部署复杂度 |\n| gRPC 通信 | 原生 HttpClient + HTTP/2 | Grpc.Net.Client | 零外部依赖，仅需 netstandard2.1+ |\n| 连接管理 | ConcurrentDictionary 连接池 | 第三方连接池库 | 简单高效，满足 Broker 少连接场景 |\n| 消费者负载均衡 | 客户端平均分配 | 服务端 Rebalance | 兼容 4.x，5.x 服务端 Rebalance 需 Broker 配合 |\n| 云厂商适配 | ICloudProvider 接口 + 策略模式 | 硬编码各厂商逻辑 | 易扩展，新增厂商只需实现接口 |\n| 多目标框架 | net45/net461/netstandard2.0/netstandard2.1 | 仅 netstandard2.0 | 覆盖企业遗留系统和最新平台 |\n| 消息压缩 | 发送端 ZLIB 自动压缩 | 不压缩 / 手动压缩 | 大消息体自动优化带宽 |\n| VIP 通道 | BrokerPort - 2 优先级连接 | 仅标准端口 | 提升高优先级消息性能 |\n\n---\n\n## 6. 任务分解\n\n所有任务已按迭代完成，详见需求文档「6. 功能清单与迭代计划」。\n\n### 已完成批次\n\n| 批次 | 任务范围 | 状态 |\n|------|---------|:----:|\n| 批次 1 | T001~T008：核心通信与基础消息 | ✅ |\n| 批次 2 | T009~T016：生产可靠性增强 | ✅ |\n| 批次 3 | T017~T027：功能完善 | ✅ |\n| 批次 4 | T028~T032：云厂商适配 | ✅ |\n| 批次 5 | T033~T038：gRPC 协议与 5.x 新特性 | ✅ |\n| 批次 6 | T039~T044：功能增强与优化 | ✅ |\n\n---\n\n## 7. 风险与缓解\n\n| 风险 | 影响 | 缓解措施 |\n|------|------|---------|\n| 各云厂商 5.x gRPC 接入未验证 | 阿里云 Serverless / 华为云 5.x / 腾讯云可能不兼容 | 客户端已具备完整 gRPC 能力，待实际环境逐步验证 |\n| Language 标识为 DOTNET | 部分 Broker 可能不识别 | DOTNET 为合理标识，暂无实际影响报告 |\n| 自研 Protobuf 编解码与官方不一致 | 极端数据格式可能解析失败 | 30+ 测试覆盖核心编解码场景，持续补充边界用例 |\n| RocketMQ 新版本协议变更 | 新增 RequestCode / Header 字段 | MQVersion 枚举已扩展到 V5.9.9 + HIGHER_VERSION，跟进社区更新 |\n| 阿里云公网版 BrokerName 不匹配 | 消费者偏移匹配可能失败 | 已用容错代码处理，不影响核心消费流程 |\n| 连接泄漏或超时 | 长时间运行可能资源不足 | 心跳保活 30s + 连接池管理 + Dispose 释放 |\n\n---\n\n## 附录 A：核心文件清单\n\n| 文件 | 行数 | 说明 |\n|------|:----:|------|\n| MqBase.cs | ~1500 | 业务基类 |\n| Producer.cs | ~1200 | 生产者实现 |\n| Consumer.cs | ~1800 | 消费者实现 |\n| ClusterClient.cs | ~423 | TCP 连接管理 |\n| NameClient.cs | ~243 | 路由发现 |\n| BrokerClient.cs | ~142 | 心跳/注销 |\n| Command.cs | ~333 | Remoting 帧编解码 |\n| MessageExt.cs | ~244 | 消息扩展模型 |\n| RequestCode.cs | ~277 | 60+ 请求码 |\n| MQVersion.cs | ~909 | 450+ 协议版本 |\n| GrpcClient.cs | ~310 | gRPC HTTP/2 客户端 |\n| ProtoWriter.cs | ~343 | Protobuf 编码器 |\n| ProtoReader.cs | ~308 | Protobuf 解码器 |\n| GrpcModels.cs | ~520 | gRPC 消息模型 |\n| GrpcServiceMessages.cs | ~883 | gRPC 服务消息 |\n| GrpcMessagingService.cs | ~417 | 11 个 RPC 方法 |\n\n## 附录 B：双协议特性对比\n\n| 特性 | Remoting 协议 | gRPC 协议 |\n|------|--------------|-----------|\n| 传输层 | TCP 长连接（NewLife.Net） | HTTP/2（HttpClient） |\n| 编解码 | 自定义二进制帧 + JSON | Protobuf（自研编解码器） |\n| 支持版本 | RocketMQ 4.x / 5.x Broker | RocketMQ 5.x Proxy |\n| 目标框架 | net45+ | netstandard2.1+ / net5+ |\n| 连接复用 | 单连接 Opaque 复用 | HTTP/2 多路复用 |\n| SSL/TLS | 支持 | 原生支持（HTTPS） |\n| VIP 通道 | 支持（BrokerPort - 2） | N/A |\n| 签名认证 | HMAC-SHA1 | HTTP Header |\n| 消息压缩 | ZLIB（SysFlag 标记） | gzip（HTTP Content-Encoding） |\n\n## 附录 C：各厂商 RocketMQ 产品对比\n\n| 厂商 | 产品 | 协议 | 认证方式 | NameServer 发现 | 适配器 |\n|------|------|------|---------|----------------|--------|\n| Apache | RocketMQ 4.x | Remoting (TCP) | ACL (AccessKey) | 直连/HTTP | AclProvider |\n| Apache | RocketMQ 5.x | Remoting + gRPC | ACL | 直连/HTTP/Proxy | AclProvider |\n| 阿里云 | 消息队列 RocketMQ 4.x | Remoting | AK/SK + HMAC-SHA1 | HTTP 接口 | AliyunProvider |\n| 阿里云 | 消息队列 RocketMQ 5.x | gRPC 为主 | AK/SK | SDK/HTTP | AliyunProvider |\n| 华为云 | DMS for RocketMQ | Remoting | SASL / AK/SK | 实例地址 | HuaweiProvider |\n| 腾讯云 | TDMQ RocketMQ | Remoting | HMAC-SHA1 | VPC 内网 | TencentProvider |\n\n## 附录 D：测试覆盖\n\n测试框架：xUnit，目标框架 net10.0，共 30 个测试文件。\n\n| 分类 | 测试文件 | 覆盖功能 |\n|------|---------|---------|\n| 核心功能 | ProducerTests, ConsumerTests, CommandTests, MessageTests, NameClientTests | 基础收发、协议编解码、路由发现 |\n| 高级特性 | TransactionCheckTests, BatchMessageTests, RetryTests, OrderConsumeTests, PopConsumeTests | 事务回查、批量消息、重试、顺序消费、Pop |\n| 协议兼容 | IPv6Tests, MessageId5xTests, MQVersionTests, ProtoTests | IPv6、5.x MessageId、协议版本、Protobuf |\n| 云厂商 | AliyunTests, AliyunIssuesTests, CloudProviderTests | 阿里云适配、云厂商接口 |\n| 性能优化 | CompressionTests, ConcurrentConsumeTests, VipChannelTests | 压缩、并发控制、VIP 通道 |\n| 管理功能 | ManagementTests, ConsumeStatsTests, QueryMessageTests | 管理 API、消费统计、消息查询 |\n| 扩展功能 | MultiTopicTests, RequestReplyTests, MessageTraceTests, SQL92FilterTests | 多 Topic、Request-Reply、轨迹、SQL92 |\n"
  },
  {
    "path": "Doc/需求文档.md",
    "content": "# NewLife.RocketMQ 需求\n\n## 1. 背景与目标\n\n### 1.1 背景与痛点\n\nApache RocketMQ 是国内使用最广泛的分布式消息中间件之一，广泛应用于电商交易、金融支付、物联网、大数据等领域。然而 .NET 生态中缺乏成熟、轻量、功能完整的 RocketMQ 客户端：\n\n- **官方 C# 客户端（rocketmq-client-csharp）**：仅支持 gRPC 协议（5.x），依赖 Google.Protobuf 和 Grpc.Net.Client 等第三方库，不支持 Remoting 协议（4.x），且更新缓慢（最新 commit 10个月前）\n- **社区第三方客户端**：功能不完整，多数已停止维护，缺乏云厂商适配\n- **.NET Framework 兼容**：大量企业 .NET Framework 4.5+ 项目无法使用现有客户端\n\n### 1.2 目标\n\n打造企业级**纯托管 .NET RocketMQ 客户端**，实现以下可衡量目标：\n\n- 完整支持 RocketMQ Remoting 协议（4.x/5.x Broker）和 gRPC Proxy 协议（5.x Proxy）\n- 零外部依赖（无需 Java、gRPC、Protobuf 第三方库），NuGet 一键安装\n- 覆盖生产者、消费者全部核心功能及企业级特性（重试、死信、事务、顺序消费等）\n- 统一云厂商适配接口，支持阿里云、华为云、腾讯云及 Apache ACL 认证\n- 兼容 .NET Framework 4.5+ 到 .NET 10 全版本\n\n## 2. 用户角色\n\n| 角色 | 说明 | 核心诉求 |\n|------|------|---------|\n| .NET 开发者 | 使用 .NET 平台开发业务系统的开发人员 | 简单易用、功能完整、文档清晰的 RocketMQ 客户端 |\n| 架构师 | 负责技术选型和系统架构设计 | 高性能、高可靠、可扩展、多云适配的消息中间件方案 |\n| 运维工程师 | 负责系统部署与运维 | 零外部依赖、易于部署、可观测性（日志/轨迹/监控） |\n| 云平台用户 | 使用阿里云/华为云/腾讯云消息队列服务 | 无缝接入云厂商 RocketMQ 实例，支持认证和路由转换 |\n\n## 3. 功能需求\n\n### 3.1 Remoting 协议通信层\n\n- **描述**：实现 RocketMQ 4.x/5.x Broker 的 TCP 私有协议通信\n- **用户故事**：作为 .NET 开发者，我希望通过 Remoting 协议连接 RocketMQ 4.x/5.x Broker，以便使用成熟稳定的通信方式\n- **验收条件**（AC）：\n  - [x] 支持 Remoting 二进制帧编解码（Length + HeaderLength + Header + Body）\n  - [x] 支持 JSON 和 ROCKETMQ 两种序列化格式\n  - [x] 支持 TCP 长连接管理与复用（ConcurrentDictionary 连接池）\n  - [x] 支持 Opaque 请求-响应匹配\n  - [x] 支持 HMAC-SHA1 统一签名\n  - [x] 支持 SSL/TLS 加密传输\n  - [x] 支持 VIP 通道（BrokerPort - 2）\n  - [x] 实现 60+ RequestCode 指令\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.2 gRPC Proxy 协议通信层\n\n- **描述**：实现 RocketMQ 5.x Proxy 的 gRPC 协议通信（HTTP/2 + Protobuf）\n- **用户故事**：作为 .NET 开发者，我希望通过 gRPC 协议连接 RocketMQ 5.x Proxy，以便使用 5.x 新架构的特性\n- **验收条件**（AC）：\n  - [x] 自研轻量级 Protobuf 编解码器（ProtoWriter/ProtoReader），无外部依赖\n  - [x] 支持 gRPC 帧格式（Comp + Length + Body）\n  - [x] 支持 HTTP/2 多路复用\n  - [x] 支持 Unary（请求-响应）调用\n  - [x] 支持 Server Streaming（服务端流式）调用\n  - [x] 实现 11 个核心 RPC 方法（QueryRoute/SendMessage/ReceiveMessage/AckMessage/Heartbeat/EndTransaction/ForwardToDeadLetterQueue/ChangeInvisibleDuration/NotifyClientTermination/Telemetry/QueryAssignment）\n  - [x] 仅在 netstandard2.1+ / net5+ 目标框架可用\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.3 NameServer 路由发现\n\n- **描述**：实现 Topic 路由发现与定时轮询更新\n- **用户故事**：作为 .NET 开发者，我希望客户端自动发现 Topic 路由并定时刷新，以便无需手动管理 Broker 连接\n- **验收条件**（AC）：\n  - [x] 支持 Topic 路由查询（GET_ROUTEINTO_BY_TOPIC）\n  - [x] 支持定时轮询路由更新（30s 间隔）\n  - [x] 支持多 Topic 路由管理\n  - [x] 支持 Broker 主从地址解析\n  - [x] 支持 HTTP 方式获取 NameServer 地址（阿里云）\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.4 生产者核心功能\n\n- **描述**：实现消息生产者全部核心功能\n- **用户故事**：作为 .NET 开发者，我希望使用丰富的消息发送方式，以便满足各种业务场景\n- **验收条件**（AC）：\n  - [x] 同步发送（Publish）\n  - [x] 异步发送（PublishAsync）\n  - [x] 单向发送（PublishOneway）\n  - [x] 批量消息发送（PublishBatch / SEND_BATCH_MESSAGE）\n  - [x] 延迟消息发送（PublishDelay，18 级预设延迟）\n  - [x] 任意时间延迟消息（PublishDelayViaGrpcAsync，gRPC 协议）\n  - [x] 事务消息（PublishTransaction / EndTransaction）\n  - [x] 事务回查回调（OnCheckTransaction，同步/异步委托）\n  - [x] 顺序消息（指定 MessageQueue 发送）\n  - [x] Request-Reply 模式（Request / RequestAsync）\n  - [x] 消息压缩（CompressOverBytes 阈值自动 ZLIB 压缩）\n  - [x] 发送重试（RetryTimesWhenSendFailed）\n  - [x] 发送端钩子（ISendMessageHook）\n  - [x] 消息轨迹追踪（AsyncTraceDispatcher / MessageTraceHook）\n  - [x] 加权轮询负载均衡（ILoadBalance / WeightRoundRobin）\n  - [x] gRPC 发送（SendMessageViaGrpcAsync / PublishTransactionViaGrpcAsync）\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.5 消费者核心功能\n\n- **描述**：实现消息消费者全部核心功能\n- **用户故事**：作为 .NET 开发者，我希望使用灵活的消息消费方式，以便满足不同业务消费模式需求\n- **验收条件**（AC）：\n  - [x] Pull 模式消费（长轮询拉取）\n  - [x] 消费调度（自动分配队列并启动消费线程）\n  - [x] 集群消费模式（Clustering，平均分配 Rebalance）\n  - [x] 广播消费模式（Broadcasting，本地 JSON 文件持久化偏移）\n  - [x] Tag 过滤\n  - [x] SQL92 表达式过滤\n  - [x] 多 Topic 订阅（Topics 属性，按 Topic 分别 Rebalance）\n  - [x] 消费重试（EnableRetry + MaxReconsumeTimes，自动回退到 RETRY Topic）\n  - [x] 死信队列（超过最大重试次数自动进入 %DLQ% Topic）\n  - [x] 消费回退（SendMessageBackAsync / CONSUMER_SEND_MSG_BACK）\n  - [x] 顺序消费（LockBatchMQAsync / UnlockBatchMQAsync / OrderConsume）\n  - [x] Pop 消费模式（PopMessageAsync / AckMessageAsync / ChangeInvisibleTimeAsync）\n  - [x] 批量确认 Pop 消息（BatchAckMessageAsync / BATCH_ACK_MESSAGE）\n  - [x] 按时间戳消费（SearchOffset / SEARCH_OFFSET_BY_TIMESTAMP）\n  - [x] 消费限流（MaxConcurrentConsume 信号量控制）\n  - [x] 消费者变更通知（NOTIFY_CONSUMER_IDS_CHANGED 触发重平衡）\n  - [x] Request-Reply 回复（SendReply / SendReplyAsync）\n  - [x] 消费端钩子（IConsumeMessageHook）\n  - [x] gRPC 消费（ReceiveMessageViaGrpcAsync / AckMessageViaGrpcAsync / HeartbeatViaGrpcAsync）\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.6 管理与运维功能\n\n- **描述**：实现 RocketMQ 管理 API\n- **用户故事**：作为运维工程师，我希望通过客户端管理 Topic、消费组、查询消息，以便方便运维操作\n- **验收条件**（AC）：\n  - [x] Topic 创建/更新/删除（CreateTopic / DeleteTopic）\n  - [x] 消费组创建/更新/删除（CreateSubscriptionGroup / DeleteSubscriptionGroup）\n  - [x] 消息查询（按 ID：ViewMessage / 按 Key：QueryMessageByKey）\n  - [x] 消费统计查询（GetConsumeStats / GetTopicStatsInfo）\n  - [x] 集群信息查询（GetClusterInfo）\n  - [x] 消费者连接列表查询（GetConsumerConnectionList）\n  - [x] 偏移量管理与重置（QueryOffset / UpdateOffset / ResetConsumerOffset）\n  - [x] Broker 运行信息（GetRuntimeInfo）\n  - [x] 消息过滤服务器注册（RegisterFilterServer）\n- **优先级**：Should\n- **完成状态**：✅ 已完成\n\n### 3.7 云厂商适配\n\n- **描述**：统一云厂商适配接口，支持多云平台接入\n- **用户故事**：作为云平台用户，我希望通过统一接口接入不同云厂商的 RocketMQ 实例，以便减少适配工作量\n- **验收条件**（AC）：\n  - [x] 统一 ICloudProvider 接口（SignatureTransformTopic/TransformGroup/GetNameServerAddress）\n  - [x] 阿里云适配（AliyunProvider：实例 ID 前缀路由 + HTTP NameServer 发现 + HMAC-SHA1 签名）\n  - [x] 华为云适配（HuaweiProvider：SSL/TLS + 实例 ID 路由）\n  - [x] 腾讯云适配（TencentProvider：Namespace 前缀路由）\n  - [x] Apache ACL 适配（AclProvider：HMAC-SHA1 签名，不转换 Topic/Group）\n  - [x] 旧版参数兼容（AliyunOptions / AclOptions 自动桥接到新适配器）\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.8 消息编解码\n\n- **描述**：完整实现消息二进制编解码\n- **用户故事**：作为 .NET 开发者，我希望客户端正确处理各种消息格式，以便与不同版本的 RocketMQ 兼容\n- **验收条件**（AC）：\n  - [x] 标准 4.x 消息二进制格式编解码\n  - [x] ZLIB 消息解压缩（SysFlag 第 0 位标识）\n  - [x] IPv4/IPv6 地址自动识别和解析（SysFlag 第 2 位标识）\n  - [x] 批量消息解码（DecodeBatch，SysFlag 第 4 位标识）\n  - [x] 5.x MessageId 新格式编解码（CreateMessageId5x / TryParseMessageId5x / IsMessageId5x）\n  - [x] 消息属性分隔符与 Java 官方一致（\\x01 和 \\x02）\n- **优先级**：Must\n- **完成状态**：✅ 已完成\n\n### 3.9 可观测性\n\n- **描述**：提供日志、轨迹、监控等可观测手段\n- **用户故事**：作为运维工程师，我希望能追踪消息链路和监控客户端运行状态，以便快速定位问题\n- **验收条件**（AC）：\n  - [x] 结构化日志（ILog）\n  - [x] 消息轨迹追踪（AsyncTraceDispatcher / MessageTraceHook）\n  - [x] 性能追踪集成（Tracer / NewSpan）\n  - [x] 客户端资源上报（gRPC Telemetry）\n  - [x] 消费者运行信息上报（GetConsumerRunningInfo）\n- **优先级**：Should\n- **完成状态**：✅ 已完成\n\n## 4. 非功能需求\n\n### 4.1 性能\n\n- 基于 NewLife.Net 高性能网络层，支持连接复用（单连接 Opaque 复用 / HTTP/2 多路复用）\n- 支持 VIP 通道（BrokerPort - 2 优先级连接）\n- 支持消息压缩（发送端 ZLIB，超阈值自动压缩）\n- 支持消费限流（信号量控制最大并发）\n- 对象池和内存池优化（Pool.StringBuilder、ArrayPool 等）\n\n### 4.2 安全\n\n- 支持 HMAC-SHA1 签名认证（统一由 ICloudProvider 实现）\n- 支持 SSL/TLS 加密传输（SslProtocol + Certificate 配置）\n- 支持 Apache ACL 权限控制\n- 不暴露内部实现路径和敏感凭据\n\n### 4.3 兼容性\n\n- 目标框架：net45 / net461 / netstandard2.0 / netstandard2.1（gRPC 功能 netstandard2.1+）\n- RocketMQ 服务端兼容：4.0 ~ 5.x（Remoting 协议）/ 5.x Proxy（gRPC 协议）\n- 云厂商兼容：阿里云 4.x/5.x、华为云 DMS、腾讯云 TDMQ\n- 新增 API 评估各框架降级实现\n\n## 5. 边界与约束\n\n### 5.1 不做什么（明确排除项）\n\n- **Compaction Topic**：RocketMQ 5.1+ KV 语义 Topic，属于小众特性，暂不实现\n- **服务端 Rebalance**：需 RocketMQ 5.0+ Broker 端深度配合，属于 Broker 端特性，客户端暂不实现\n- **SASL 认证**：华为云可能使用的 SASL/PLAIN 或 SASL/SCRAM 认证，待有明确需求再实现\n- **非 .NET 平台支持**：仅支持 .NET 平台\n\n### 5.2 已知限制\n\n- Controller 模式对客户端透明，无需特殊适配\n- 阿里云 Serverless 实例仅支持 gRPC 接入，客户端已具备能力但待实际环境验证\n- 华为云/腾讯云 gRPC 接入待实际环境验证\n- Language 标识为 DOTNET，不在 Java 官方枚举中，部分 Broker 可能不识别\n\n## 6. 功能清单与迭代计划\n\n### 迭代 1：核心通信与基础消息（Must）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F001 | Remoting 协议帧编解码 | 正确编解码二进制帧 | 无 | ✅ |\n| F002 | TCP 连接管理与复用 | 支持连接池、Opaque 匹配 | F001 | ✅ |\n| F003 | NameServer 路由发现 | 30s 定时轮询、多 Topic 路由 | F002 | ✅ |\n| F004 | Broker 心跳机制 | 30s 心跳、注销 | F002 | ✅ |\n| F005 | 消息发送（同步/异步/单向） | 三种发送模式均可用 | F003 | ✅ |\n| F006 | Pull 消费 | 长轮询拉取消息 | F003 | ✅ |\n| F007 | 消费调度与 Rebalance | 自动分配队列、平均分配算法 | F006 | ✅ |\n| F008 | 偏移量管理 | 查询/更新/搜索偏移 | F006 | ✅ |\n\n### 迭代 2：生产可靠性增强（Must）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F009 | 延迟消息（18 级定时） | 支持预设延迟等级 | F005 | ✅ |\n| F010 | 事务消息（半消息） | 发送/提交/回滚 | F005 | ✅ |\n| F011 | 事务回查回调 | 响应 CHECK_TRANSACTION_STATE | F010 | ✅ |\n| F012 | 批量消息发送 | SEND_BATCH_MESSAGE | F005 | ✅ |\n| F013 | 顺序消息发送 | 指定 MessageQueue | F005 | ✅ |\n| F014 | 消费重试 | RETRY Topic + 自动回退 | F007 | ✅ |\n| F015 | 死信队列 | 超过重试次数进入 %DLQ% | F014 | ✅ |\n| F016 | 顺序消费锁定 | LOCK/UNLOCK_BATCH_MQ | F007 | ✅ |\n\n### 迭代 3：功能完善（Should）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F017 | Tag 过滤 | 支持 Tag 表达式 | F006 | ✅ |\n| F018 | SQL92 过滤 | ExpressionType=SQL92 | F006 | ✅ |\n| F019 | 消息压缩 | 发送端 ZLIB 压缩 | F005 | ✅ |\n| F020 | IPv6 支持 | 自动识别 IPv4/IPv6 | F006 | ✅ |\n| F021 | 多 Topic 订阅 | Topics 属性分别 Rebalance | F007 | ✅ |\n| F022 | 广播模式本地偏移 | OffsetStorePath JSON 持久化 | F008 | ✅ |\n| F023 | Request-Reply 模式 | 同步/异步请求回复 | F005 | ✅ |\n| F024 | 消费限流 | MaxConcurrentConsume 信号量 | F007 | ✅ |\n| F025 | Pop 消费模式 | Pop/Ack/ChangeInvisibleTime | F006 | ✅ |\n| F026 | 消息轨迹 | AsyncTraceDispatcher 分发 | F005 | ✅ |\n| F027 | 消息查询 | 按 ID / 按 Key 查询 | F003 | ✅ |\n\n### 迭代 4：云厂商适配（Must）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F028 | ICloudProvider 统一接口 | 7 个方法/属性定义 | 无 | ✅ |\n| F029 | 阿里云适配 | 实例 ID 路由 + HTTP NameServer | F028 | ✅ |\n| F030 | 华为云适配 | SSL/TLS + 实例 ID 路由 | F028 | ✅ |\n| F031 | 腾讯云适配 | Namespace 前缀路由 | F028 | ✅ |\n| F032 | Apache ACL 适配 | HMAC-SHA1 签名 | F028 | ✅ |\n\n### 迭代 5：gRPC 协议与 5.x 新特性（Must）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F033 | Protobuf 编解码器 | ProtoWriter/ProtoReader 自研 | 无 | ✅ |\n| F034 | gRPC 客户端 | HTTP/2 Unary + Server Streaming | F033 | ✅ |\n| F035 | gRPC 消息服务 | 11 个 RPC 方法实现 | F034 | ✅ |\n| F036 | 任意时间延迟消息 | PublishDelayViaGrpcAsync | F035 | ✅ |\n| F037 | 5.x MessageId | 新格式编解码 | 无 | ✅ |\n| F038 | 客户端资源上报 | gRPC Telemetry | F035 | ✅ |\n\n### 迭代 6：功能增强与优化（Should）— ✅ 已完成\n\n| 编号 | 功能点 | 验收条件 | 前置依赖 | 完成状态 |\n|------|--------|---------|---------|:-------:|\n| F039 | VIP 通道 | VipChannelEnabled 属性 | F002 | ✅ |\n| F040 | 批量确认 Pop 消息 | BatchAckMessageAsync | F025 | ✅ |\n| F041 | 消费统计完整 API | GetConsumeStats / GetTopicStatsInfo | F003 | ✅ |\n| F042 | 消息过滤服务器注册 | RegisterFilterServer | F003 | ✅ |\n| F043 | Broker 主从切换 | 消费失败自动切换从节点 | F003 | ✅ |\n| F044 | 管理功能 | Topic/消费组 CRUD、偏移重置 | F003 | ✅ |\n\n### 待验证功能（各云厂商 5.x 环境）\n\n| 编号 | 功能点 | 状态 | 说明 |\n|------|--------|:----:|------|\n| F045 | 阿里云 5.x gRPC 接入 | ⚠️ 待验证 | 客户端已具备能力，待实际环境测试 |\n| F046 | 阿里云 Serverless 实例 | ⚠️ 待验证 | 仅支持 gRPC 接入 |\n| F047 | 华为云 5.x gRPC 接入 | ⚠️ 待验证 | HuaweiProvider + gRPC 理论可行 |\n| F048 | 腾讯云 TDMQ 验证 | ⚠️ 待验证 | TencentProvider 待生产环境验证 |\n\n### 暂不实现功能\n\n| 编号 | 功能点 | 原因 |\n|------|--------|------|\n| F049 | 服务端 Rebalance | Broker 端特性，客户端需深度配合 |\n| F050 | Compaction Topic | 5.1+ 小众特性，Broker 端支持 |\n| F051 | SASL 认证 | 华为云特有，待有明确需求 |\n\n## 7. 竞品分析\n\n### 7.1 .NET 生态 RocketMQ 客户端对比\n\n| 维度 | NewLife.RocketMQ | Apache rocketmq-client-csharp | 其他社区客户端 |\n|------|:----------------:|:---------------------------:|:------------:|\n| **协议支持** | Remoting + gRPC 双协议 | 仅 gRPC（5.x） | 仅 Remoting（部分） |\n| **4.x 兼容** | ✅ 完整支持 | ❌ 不支持 | ⚠️ 部分支持 |\n| **5.x 支持** | ✅ Remoting + gRPC | ✅ gRPC | ❌ 不支持 |\n| **外部依赖** | ✅ 零依赖 | ❌ Google.Protobuf / Grpc.Net.Client / NLog 等 | ⚠️ 部分依赖 |\n| **Protobuf 实现** | 自研 ProtoWriter/ProtoReader | Google.Protobuf | 无 |\n| **目标框架** | net45 ~ netstandard2.1 | .NET 5+ / .NET Core 3.1 | 不一致 |\n| **.NET Framework 支持** | ✅ 4.5+ | ❌ 不支持 | ⚠️ 部分 |\n| **多云适配** | ✅ 统一 ICloudProvider 接口 | ❌ 需自行适配 | ❌ 无 |\n| **阿里云适配** | ✅ AliyunProvider | ❌ | ❌ |\n| **华为云适配** | ✅ HuaweiProvider | ❌ | ❌ |\n| **腾讯云适配** | ✅ TencentProvider | ❌ | ❌ |\n| **事务消息** | ✅ 发送+回查 | ✅ 发送+回查 | ⚠️ 部分 |\n| **消费重试/死信** | ✅ 完整 | ✅ 内置 | ❌ 无 |\n| **顺序消费** | ✅ 锁定机制 | ✅ FIFO | ❌ 无 |\n| **批量消息** | ✅ 发送+解码 | ❌ 不支持 | ❌ 无 |\n| **Pop 消费** | ✅ Pop/Ack/BatchAck | ✅ SimpleConsumer | ❌ 无 |\n| **Request-Reply** | ✅ 同步/异步 | ❌ 不支持 | ❌ 无 |\n| **消息轨迹** | ✅ MessageTraceHook | ⚠️ OpenTelemetry 集成 | ❌ 无 |\n| **VIP 通道** | ✅ | N/A（gRPC） | ❌ 无 |\n| **消息压缩** | ✅ ZLIB 自动压缩 | ✅ ZLib | ❌ 无 |\n| **管理 API** | ✅ Topic/消费组 CRUD、消息查询、偏移重置 | ❌ 无 | ❌ 无 |\n| **测试覆盖** | 30+ 测试类 | 有单元测试 | 少量或无 |\n| **维护活跃度** | ✅ 持续维护（新生命团队） | ⚠️ 更新较慢（最新 10 个月前） | ❌ 多数已停止 |\n| **文档语言** | 中文为主 | 中英文 | 不一致 |\n| **NuGet 包名** | NewLife.RocketMQ | RocketMQ.Client | — |\n| **开源协议** | MIT | Apache 2.0 | 不一致 |\n\n### 7.2 跨语言 RocketMQ 客户端对比\n\n| 维度 | NewLife.RocketMQ (.NET) | 官方 Java 客户端 | 官方 Go 客户端 | 官方 C++ 客户端 |\n|------|:----------------------:|:----------------:|:--------------:|:--------------:|\n| **语言生态** | .NET 原生 | Java 原生 | Go 原生 | C++ 原生 |\n| **部署复杂度** | ✅ 单一 DLL，零依赖 | ⚠️ 需要 JRE | ✅ 单一二进制 | ⚠️ 需编译 |\n| **4.x Remoting** | ✅ 完整 | ✅ 完整 | ❌ 仅 5.x gRPC | ❌ 仅 5.x gRPC |\n| **5.x gRPC** | ✅ 自研编解码 | ✅ 官方实现 | ✅ 官方实现 | ✅ 官方实现 |\n| **消息类型** | Normal/Delay/FIFO/Transaction | 全部 | 全部 | 全部 |\n| **消费者类型** | Pull/Push(模拟)/Pop/Simple | 全部 | SimpleConsumer/PushConsumer | SimpleConsumer |\n| **功能完整度** | ✅ 4.x 100%，5.x ~90% | ✅ 100% | ✅ 5.x 功能完整 | ✅ 5.x 功能完整 |\n| **消息批量** | ✅ 发送+解码 | ✅ 发送+解码 | ❌ | ❌ |\n| **管理 API** | ✅ 完整 | ✅ 完整 | ❌ 无 | ❌ 无 |\n| **社区活跃度** | ⚠️ 新生命团队维护 | ✅ Apache 官方 | ✅ Apache 官方 | ✅ Apache 官方 |\n\n### 7.3 与同类消息队列 .NET 客户端对比\n\n| 维度 | NewLife.RocketMQ | Confluent.Kafka | RabbitMQ.Client | NewLife.Redis (队列) |\n|------|:----------------:|:--------------:|:--------------:|:------------------:|\n| **消息中间件** | RocketMQ | Kafka | RabbitMQ | Redis Stream/List |\n| **协议** | Remoting + gRPC | Kafka 协议 | AMQP | RESP |\n| **外部依赖** | ✅ 零依赖 | ⚠️ librdkafka | ✅ 零依赖 | ✅ 零依赖 |\n| **事务消息** | ✅ | ✅（幂等生产者） | ⚠️ 确认机制 | ❌ |\n| **延迟消息** | ✅ 18 级 + 任意时间 | ❌ 不支持 | ✅ TTL + 死信 | ✅ 延迟队列 |\n| **顺序消息** | ✅ 队列锁定 | ✅ 分区有序 | ❌ 不保证 | ❌ 不保证 |\n| **消费重试** | ✅ 内置 | ❌ 需自行实现 | ✅ 死信交换器 | ❌ 需自行实现 |\n| **消息轨迹** | ✅ 内置 | ⚠️ 需集成 | ⚠️ 需集成 | ❌ |\n| **多云适配** | ✅ 统一接口 | ⚠️ Confluent Cloud | ❌ | ❌ |\n| **消息规模** | 万亿级 | 万亿级 | 百亿级 | 十亿级 |\n| **适用场景** | 企业级业务消息 | 大数据流处理 | 微服务解耦 | 轻量级队列/缓存 |\n\n### 7.4 核心竞争优势总结\n\n1. **零外部依赖**：纯 C# 实现，无需 Java、gRPC、Protobuf 第三方库，部署运维成本最低\n2. **双协议支持**：唯一同时支持 Remoting（4.x）和 gRPC（5.x）的 .NET 客户端，向后兼容且面向未来\n3. **最广框架覆盖**：.NET Framework 4.5+ 到 .NET 10，覆盖企业遗留系统和最新平台\n4. **统一多云适配**：唯一内置阿里云/华为云/腾讯云/Apache ACL 四家适配器的客户端\n5. **生产级特性**：完整的企业级功能（事务回查、消费重试、死信队列、顺序消费、Pop 消费、消息轨迹等）\n6. **管理 API 完整**：Topic/消费组 CRUD、消息查询、消费统计、偏移重置等运维功能\n7. **持续维护**：新生命团队持续迭代更新，紧跟 RocketMQ 社区演进\n\n## 8. 验收记录\n\n### 功能验收\n\n| 编号 | 功能点 | 验收条件 | 状态 | 备注 |\n|------|--------|---------|:----:|------|\n| F001~F008 | 核心通信与基础消息 | 协议编解码、连接管理、路由发现、消费调度 | ✅ 通过 | — |\n| F009~F016 | 生产可靠性增强 | 延迟/事务/批量/顺序/重试/死信 | ✅ 通过 | — |\n| F017~F027 | 功能完善 | 过滤/压缩/IPv6/多 Topic/Pop/轨迹/查询 | ✅ 通过 | — |\n| F028~F032 | 云厂商适配 | 四家适配器全部实现 | ✅ 通过 | 云厂商 5.x 环境待验证 |\n| F033~F038 | gRPC 协议与 5.x 新特性 | Protobuf 编解码/gRPC 通信/11 个 RPC | ✅ 通过 | netstandard2.1+ |\n| F039~F044 | 功能增强与优化 | VIP 通道/批量 Ack/管理 API | ✅ 通过 | — |\n\n### 遗留问题\n\n| 问题 | 影响 | 后续计划 |\n|------|------|---------|\n| 阿里云公网版 BrokerName 不匹配 | 消费者状态中偏移匹配需容错 | 已用 `?? new OffsetWrapperModel()` 容错处理 |\n| 各云厂商 5.x gRPC 接入未验证 | 阿里云 Serverless/华为云 5.x/腾讯云 | 客户端已具备能力，待实际环境测试 |\n| Language 标识为 DOTNET | 部分 Broker 可能不识别 | 暂无影响，DOTNET 为合理标识 |\n\n### 经验总结\n\n- **做得好的**：零依赖的 Protobuf 编解码器设计、统一云厂商适配接口、50+ 测试类高覆盖率\n- **待改进的**：云厂商 5.x 环境的实际验证、性能基准测试数据补充\n\n### 单元测试覆盖统计\n\n| 测试类 | 测试数 | 覆盖目标 | 状态 |\n|--------|:------:|---------|:----:|\n| CommandTests | 16 | Command 协议帧编解码 | ✅ 通过 |\n| SpanRefactorTests | 20+ | 二进制协议序列化、MessageExt 5.x ID | ✅ 通过 |\n| ProtoTests | 30+ | Protobuf 编解码（Varint/Fixed/Map/嵌套/gRPC） | ✅ 通过 |\n| NameClientTests | 8 | 路由发现解析、缓存、主从地址 | ✅ 通过 |\n| BatchMessageTests | 10 | 批量消息编解码、SysFlag、自动展开 | ✅ 通过 |\n| MessageTests | 5 | 消息体设置、属性解析 | ✅ 通过 |\n| MessageExtendedTests | 20 | Request-Reply 属性、延迟/等待/事务/UserProperty | ✅ 通过 |\n| MessageId5xTests | 11 | 5.x 消息ID生成与解析 | ✅ 通过 |\n| IPv6Tests | 5 | IPv6 消息解码、SysFlag 位判断 | ✅ 通过 |\n| CloudProviderTests | 25 | Aliyun/ACL/Huawei/Tencent 适配器 | ✅ 通过 |\n| HeaderTests | 9 | 协议头默认值、GetExtFields、CreateException | ✅ 通过 |\n| SendResultTests | 8 | 发送结果 Read 方法、ToString、枚举值 | ✅ 通过 |\n| PullResultTests | 8 | 拉取结果 Read 方法、ToString、枚举值 | ✅ 通过 |\n| MessageQueueTests | 10 | 消息队列 Equals/GetHashCode/ToString | ✅ 通过 |\n| ResponseExceptionTests | 5 | 响应异常构造与捕获 | ✅ 通过 |\n| RequestHeaderTests | 7 | 三种请求头 GetProperties 反射映射 | ✅ 通过 |\n| WeightRoundRobinTests | 8 | 负载均衡算法（等/不等权重分配、初始化） | ✅ 通过 |\n| BrokerInfoTests | 9 | 代理信息属性、Equals/GetHashCode、Permissions | ✅ 通过 |\n| ProtocolDataTests | 12 | HeartbeatData/ProducerData/ConsumerData/SubscriptionData/QueryResult | ✅ 通过 |\n| TraceModelTests | 9 | TraceContext/TraceBean/SendMessageContext/ConsumeMessageContext | ✅ 通过 |\n| ModelTests | 11 | 枚举值验证（DelayTimeLevels/RequestCode/ResponseCode 等） | ✅ 通过 |\n| MqBasePropertyTests | 15 | MqBase 默认值、ClientId、属性设置、Active 状态 | ✅ 通过 |\n| ConsumerStatesModelTests | 7 | ConsumerStatesModel/MessageQueueModel/OffsetWrapperModel | ✅ 通过 |\n| MqSettingTests | 1 | MqSetting 配置属性 | ✅ 通过 |\n| MQVersionTests | 1 | MQVersion 版本转换 | ✅ 通过 |\n| MQVersionUpdateTests | 4 | 版本枚举 5.x、默认版本 | ✅ 通过 |\n| VipChannelTests | 6 | VIP 通道启用/禁用 | ✅ 通过 |\n| SQL92FilterTests | 5 | SQL92 过滤表达式类型 | ✅ 通过 |\n| CompressionTests | 3 | 消息压缩阈值 | ✅ 通过 |\n| BroadcastOffsetTests | 4 | 广播模式消息模型与偏移存储 | ✅ 通过 |\n| MultiTopicTests | 6 | 多主题消费 | ✅ 通过 |\n| RetryTests | 7 | 重试机制参数 | ✅ 通过 |\n| ConcurrentConsumeTests | 3 | 并发消费参数 | ✅ 通过 |\n| TransactionCheckTests | 3 | 事务回查回调 | ✅ 通过 |\n| OrderConsumeTests | 6 | 顺序消费与队列锁 | ✅ 通过 |\n| PopConsumeTests | 5 | Pop 消费异常处理与请求码 | ✅ 通过 |\n| BatchAckTests | 6 | 批量确认异常处理与请求码 | ✅ 通过 |\n| QueryMessageTests | 3 | 消息查询参数校验 | ✅ 通过 |\n| RequestReplyTests | 1 | Request-Reply 消息属性 | ✅ 通过 |\n| BrokerFailoverTests | 5 | Broker 主从切换 | ✅ 通过 |\n| ConsumeStatsTests | 2 | 消费统计请求码 | ✅ 通过 |\n| **合计** | **344** | — | **✅ 全部通过** |\n\n> 另有 30 个集成测试标记为 Skip（需要 RocketMQ 服务器支持），包括 ProducerTests、ConsumerTests、AliyunTests、ManagementTests 等。\n\n## 9. 术语表\n\n| 术语 | 定义 |\n|------|------|\n| Remoting 协议 | RocketMQ 4.x 的经典 TCP 私有协议，采用二进制帧格式 + JSON/二进制序列化 |\n| gRPC 协议 | RocketMQ 5.x 引入的新协议，基于 HTTP/2 + Protobuf |\n| Proxy | RocketMQ 5.x 架构中的代理层，接收 gRPC 请求并转发到 Broker |\n| NameServer | RocketMQ 的名称服务器，提供 Topic 路由发现 |\n| Broker | RocketMQ 的消息存储和转发服务器 |\n| Rebalance | 消费者负载均衡，将队列均匀分配给消费者 |\n| Pop 消费 | RocketMQ 4.9+ 引入的新消费模式，不自动提交偏移，需手动确认 |\n| VIP 通道 | 使用 BrokerPort - 2 的优先级端口，提供更高性能的连接 |\n| ICloudProvider | 统一云厂商适配接口，封装签名、路由转换、NameServer 发现等逻辑 |\n| HMAC-SHA1 | 基于哈希的消息认证码，用于请求签名 |\n| Opaque | 请求唯一标识符，用于 TCP 连接上的请求-响应匹配 |\n| SysFlag | 消息系统标志位，标识压缩、IPv6、批量等特征 |\n| 死信队列（DLQ） | Dead Letter Queue，超过最大重试次数后消息进入的队列 |\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 新生命开发团队\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NewLife.RocketMQ/.github/copilot-instructions.md",
    "content": "# NewLife Copilot 协作指令\n\n本说明适用于新生命团队（NewLife）及其全部开源/衍生项目，规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。\n\n> 目标：把\"每次请求必须携带的通用规则\"控制在可接受体积；组件/业务专项流程放在 `.github/instructions/`，按需读取。\n\n---\n\n## 1. 核心原则\n\n| 原则 | 说明 |\n|------|------|\n| **提效** | 减少机械样板，聚焦业务/核心算法 |\n| **一致** | 风格、结构、命名、API 行为稳定 |\n| **可控** | 限制改动影响面，可审计，兼容友好 |\n| **可靠** | 先检索再生成，不虚构，不破坏现有合约 |\n| **主动** | 发现问题主动修复，不回避合理优化 |\n\n---\n\n## 2. 适用范围\n\n- 含 NewLife 组件或衍生的全部 C#/.NET 仓库\n- 不含纯前端/非 .NET/市场文案\n- 存在本文件 → 必须遵循\n\n---\n\n## 3. 组件专用指令索引（按需加载）\n\n以下专用指令**仅在相关任务时**才需要读取，避免每次请求都携带大段流程/示例。\n\n### 3.1 XCode / Cube（数据库 & Web 快速开发）\n\n当任务涉及以下任一信号时，请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**，若存在则读取并遵循：\n\n- 需求包含：XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令\n- 解决方案/项目中出现：`NewLife.XCode` 包引用\n- 存在：`Model.xml`、`*.xcode.xml`、`*.Data.csproj`（或项目名以 `.Data` 结尾）\n- 代码出现命名空间/类型：`XCode.*`、`Entity`（XCode 实体基类）、XCode 相关特性/接口\n- **用户提到修改任意 `.xml` 文件**（如 `member.xml`、`area.xml` 等配置文件），应**主动搜索** `xcode.instructions.md` 判断是否需要引入\n\n**主动检测策略**：当用户提及 XML 文件修改时，即使未明确提到 XCode 关键字，也应先用 `file_search` 搜索 `xcode.instructions.md`，若存在则读取，以确定该 XML 文件是否属于 XCode/Cube 体系。\n\n未满足以上条件时，**不要**引入 XCode/Cube 初始化流程，避免干扰其它仓库的常规开发。\n\n---\n\n## 4. 工作流\n\n```\n需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明\n```\n\n1. **需求分类**：功能/修复/性能/重构/文档\n2. **检索**：相关类型、目录、方法、已有扩展/工具（**优先复用**）\n3. **评估**：是否公共 API？是否性能热点？**是否存在潜在问题？**\n4. **设计**：列出改动点 + 兼容/降级策略\n5. **实施**：\n   - 完成用户请求的核心任务\n   - **顺带修复**发现的明显缺陷（资源泄漏、空引用、逻辑错误）\n   - **顺带优化**可简化的重复代码\n   - 保留原注释与结构，除非注释本身有误\n6. **验证**：\n   - 代码变更：必须编译通过；运行相关单元测试（未找到需说明）\n   - 仅文档变更（未修改任何代码文件）：可跳过编译与单元测试\n7. **说明**：变更摘要/影响范围/风险点\n\n### 4.1 主动优化原则\n\n当用户请求分析或优化代码时，**应主动**：\n\n| 类型 | 行动 |\n|------|------|\n| **架构梳理** | 梳理代码架构并进行重构，让代码结构更清晰易懂 |\n| **语法现代化** | 使用最新的 C# 语法来简化代码，提升可读性 |\n| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复，让代码更健壮 |\n| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 |\n| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 |\n| **注释完善** | 补充类、接口、属性、方法头部的注释，以及方法内部重要代码的注释 |\n| **架构参考** | 参考网络上同类功能的优秀架构，给出架构调整建议 |\n\n**架构调整策略**：\n- **改动较小**：直接调整，完成后说明变更内容\n- **改动较大**：先列出调整方案，询问用户意见，待确认后再修改\n\n**不应过度保守**：\n- ❌ 仅添加注释而忽略明显的代码问题\n- ❌ 发现资源泄漏却不修复\n- ❌ 看到重复代码却不提取\n- ❌ 用户要求优化时只做表面工作\n\n**保持谨慎的场景**：\n- 公共 API 签名变更 → 需说明兼容性影响\n- 性能关键路径 → 需有依据或说明推理\n- 大范围重构 → 需先与用户确认范围\n\n### 4.2 防御性注释规则\n\n在旧有代码中，经常可以看到**被注释掉的代码**，这些注释代码前面通常带有说明文字。\n\n**这些是防御性注释**：\n- 记录了过去曾经踩过的坑\n- 目的是告诉后来人不要按照注释代码去写，否则会有问题\n- **禁止删除此类防御性注释**，用于警示后人\n\n**识别特征**：\n```csharp\n// 曾经尝试过 xxx 方案，但会导致 yyy 问题\n// var result = DoSomethingWrong();\n\n// 不要使用 xxx，否则会造成 yyy\n// await client.SendAsync(data);\n\n// 这里不能用 xxx，因为 yyy\n// stream.Flush();\n```\n\n**处理原则**：\n- ✅ 保留这类带说明的注释代码\n- ✅ 可以补充更详细的说明，解释为什么不能这样做\n- ❌ 不要删除这类防御性注释\n- ❌ 不要尝试\"恢复\"这些被注释的代码\n\n---\n\n## 5. 编码规范\n\n### 5.1 基础规范\n\n| 项目 | 规范 |\n|------|------|\n| 语言版本 | `<LangVersion>latest</LangVersion>`，所有目标框架均使用最新 C# 语法 |\n| 命名空间 | file-scoped namespace |\n| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等，避免 `string`/`int`/`bool` |\n| 兼容性 | 代码需兼容 .NET 4.5+；**禁止**使用 `ArgumentNullException.ThrowIfNull`，改用 `if (value == null) throw new ArgumentNullException(nameof(value));` |\n| 单文件 | 每文件一个主要公共类型；较大平台差异使用 `partial` |\n\n### 5.2 命名规范\n\n| 成员类型 | 命名规则 | 示例 |\n|---------|---------|------|\n| 类型/公共成员 | PascalCase | `UserService`、`GetName()` |\n| 参数/局部变量 | camelCase | `userName`、`count` |\n| 私有字段（实例/静态） | `_camelCase` | `_cache`、`_instance` |\n| 属性/方法（实例/静态） | PascalCase | `Name`、`Default`、`Create()` |\n| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` |\n\n### 5.3 代码风格\n\n```csharp\n// ✅ 单行 if：单语句且整行不过长时同行\nif (value == null) return;\nif (key == null) throw new ArgumentNullException(nameof(key));\n\n// ✅ 单行 if：语句较长时另起一行\nif (value == null)\n    throw new ArgumentNullException(nameof(value), \"Value cannot be null\");\n\n// ✅ 多分支单语句：不加花括号\nif (count > 0)\n    DoSomething();\nelse\n    DoOther();\n\n// ✅ 循环必须保留花括号（即使单语句）\nforeach (var item in list)\n{\n    Process(item);\n}\n```\n\n### 5.4 Region 组织结构\n\n较长的类使用 `#region` 分段组织，顺序为：`属性` → `静态`（如有）→ `构造` → `方法` → `辅助`（如有）→ `日志`。\n\n**日志 Region 规则**：\n- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法\n- **必须放在类代码的最后**\n- **必须用名为\"日志\"的 region 包裹**\n- 不要放在\"辅助\" region 中，应单独作为\"日志\" region\n\n### 5.5 现代 C# 语法\n\n优先使用最新语法（switch 表达式、模式匹配、目标类型 `new`、record 等），即使目标框架是 net45。\n\n### 5.6 集合表达式\n\n优先使用集合表达式 `[]` 初始化集合：`List<String> Tags { get; set; } = [];`\n\n### 5.7 Null 条件运算符\n\n优先使用 `?.` / `??` 简化空值检查：`span?.AppendTag(\"test\");` `var name = user?.Profile?.Name ?? \"\";`\n\n---\n\n## 6. 多目标框架\n\nNewLife 支持 `net45` 到 `net10`，常用条件符号：`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。\n\n新增 API 时需评估各框架兼容性，必要时提供降级实现。\n\n---\n\n## 7. 文档注释\n\n| 规则 | 说明 |\n|------|------|\n| `<summary>` | **必须同一行闭合**，简短描述方法用途 |\n| `<param>` | **必须为每个参数添加**，无论方法可见性如何 |\n| `<returns>` | 有返回值时必须添加 |\n| `<remarks>` | 复杂方法可增加详细说明（可多行） |\n| 覆盖范围 | `public`/`protected` 成员必须注释；`private`/`internal` 建议添加 |\n| `[Obsolete]` | 必须包含迁移建议 |\n\n**正确示例**：`/// <summary>获取名称</summary>` `/// <param name=\"id\">编号</param>`\n\n**禁止**：`<summary>` 拆成多行；缺少 `<param>`；有参数但无 param 标签。\n\n---\n\n## 8. 异步与性能\n\n| 规范 | 说明 |\n|------|------|\n| 方法命名 | 异步方法后缀 `Async` |\n| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` |\n| 高频路径 | 优先对象池/`ArrayPool<T>`/`Span`，避免多余分配 |\n| 反射/Linq | 仅用于非热点路径；热点使用手写循环/缓存 |\n| 池化资源 | 明确获取/归还；异常分支不遗失归还 |\n\n**内置工具优先**：`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。\n\n---\n\n## 9. 日志与追踪\n\n规则：若类包含 `ILog Log` 与 `WriteLog`，必须放在类末尾，并用名为\"日志\"的 `#region` 包裹；关键过程可使用 `Tracer?.NewSpan()` 埋点。\n\n---\n\n## 10. 错误处理\n\n- **精准异常类型**：`ArgumentNullException`/`InvalidOperationException` 等\n- **参数校验**：空/越界/格式\n- **TryXxx 模式**：不用异常作常规分支\n- **类型转换**：优先使用 `ToInt()`/`ToBoolean()` 等扩展方法\n- **对外异常**：不暴露内部实现/路径\n\n---\n\n## 11. 测试规范\n\n| 项目 | 规范 |\n|------|------|\n| 框架 | xUnit |\n| 命名 | `{ClassName}Tests` |\n| 描述 | `[DisplayName(\"中文描述意图\")]` |\n| IO | 使用临时目录；端口用 0/随机 |\n| 覆盖 | 正常/边界/异常/并发（必要时） |\n\n### 测试执行策略\n\n1. 优先检索 `{ClassName}` 引用，若落入测试项目则运行\n2. 未命中则查找 `{ClassName}Tests.cs`\n3. **未发现相关测试需明确说明**，不自动创建测试项目\n\n---\n\n## 12. NuGet 发布规范\n\n| 类型 | 命名规则 | 示例 |\n|------|---------|------|\n| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` |\n| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` |\n\n- **正式版**：每月月初发布\n- **测试版**：提交代码到 GitHub 时自动发布\n\n---\n\n## 13. Markdown 文档规范\n\n| 项目 | 规范 |\n|------|------|\n| 文件编码 | **必须** UTF-8，**禁止** GB2312/GBK/UTF-8 BOM |\n| 默认存放 | 代码库根目录下的 `Doc` 目录 |\n| 文件命名 | 优先**中文文件名**，简洁描述内容 |\n\n**注意**：已有文件**必须先读取**再增量修改，**禁止直接覆盖**。\n\n---\n\n## 14. Copilot 行为守则\n\n### 必须\n\n- 简体中文回复\n- 输出前检索现有实现，**禁止重复造轮子**\n- 先列方案再实现\n- 标记不确定上下文为\"需查看文件\"\n- **发现明显缺陷时主动修复**（资源泄漏、空引用、逻辑错误）\n- **用户要求优化时深入分析**，不做表面工作\n\n### 鼓励\n\n- 提取重复代码为公共方法\n- 简化冗余的条件判断\n- 使用现代 C# 语法改进可读性\n- 补充缺失的资源释放逻辑\n- 修正错误或过时的注释\n\n### 禁止\n\n- 虚构 API/文件/类型\n- 伪造测试结果/性能数据\n- 擅自删除公共/受保护成员\n- 擅自删除已有代码注释（除非注释本身错误）\n- **删除防御性注释**（带说明的注释代码，记录历史踩坑经验）\n- 仅删除空白行制造\"格式优化\"提交\n- 删除循环体的花括号\n- 将 `<summary>` 拆成多行\n- 将 `String`/`Int32` 改为 `string`/`int`\n- 新增外部依赖（除非说明理由并给出权衡）\n- 在热点路径添加未缓存反射/复杂 Linq\n- 输出敏感凭据/内部地址\n- **发现问题却视而不见**\n- **用户要求优化时仅做注释/测试等表面工作**\n\n---\n\n## 15. 变更说明模板\n\n提交或答复需包含：\n\n```markdown\n## 概述\n做了什么 / 为什么\n\n## 影响\n- 公共 API：是/否\n- 性能影响：无/有（说明）\n\n## 兼容性\n降级策略 / 条件编译点\n\n## 风险\n潜在回归 / 性能开销\n\n## 后续\n是否补测试 / 文档\n```\n\n---\n\n## 16. 术语说明\n\n| 术语 | 定义 |\n|------|------|\n| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 |\n| **基线** | 变更前的功能/性能参考数据 |\n| **顺带修复** | 在完成主任务过程中，修复发现的相关问题 |\n| **防御性注释** | 被注释掉的代码，前面带有说明，记录历史踩坑经验，用于警示后人 |\n\n---\n\n## 17. 代码优化检查清单\n\n当进行代码优化时，按以下清单逐项检查：\n\n### 架构与结构\n- [ ] 代码架构是否清晰？是否需要重构？\n- [ ] 类的职责是否单一？是否需要拆分？\n- [ ] 是否有重复代码可以提取为公共方法？\n- [ ] Region 组织是否符合规范（属性→静态→构造→方法→辅助→日志）？\n\n### 语法现代化\n- [ ] 是否可以使用更简洁的 C# 语法？（switch 表达式、模式匹配等）\n- [ ] 集合初始化是否使用了集合表达式 `[]`？\n- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码？\n\n### 健壮性\n- [ ] 是否存在空引用风险？\n- [ ] 资源是否正确释放？（IDisposable、流、连接等）\n- [ ] 异常处理是否完善？\n- [ ] 并发场景是否线程安全？\n\n### 性能\n- [ ] 是否存在可以缓存的重复计算？\n- [ ] 是否有不必要的对象分配？\n- [ ] 热点路径是否避免了反射和复杂 Linq？\n- [ ] 是否使用了对象池/ArrayPool 等池化技术？\n\n### 注释与文档\n- [ ] 类、接口是否有 `<summary>` 注释？\n- [ ] 公共方法是否有完整的参数和返回值注释？\n- [ ] 方法内重要逻辑是否有注释说明？\n- [ ] 防御性注释是否保留？\n\n### 日志\n- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后？\n- [ ] 是否用名为\"日志\"的 region 包裹？\n\n---\n\n（完）\n"
  },
  {
    "path": "NewLife.RocketMQ/AclOptions.cs",
    "content": "﻿namespace NewLife.RocketMQ\n{\n    /// <summary>\n    /// 支持 Apache RocketMQ ACL机制\n    /// </summary>\n    public class AclOptions\n    {\n        /// <summary>Acl访问令牌</summary>\n        public String AccessKey { get; set; }\n\n        /// <summary>Acl访问密钥</summary>\n        public String SecretKey { get; set; }\n\n        /// <summary>通道</summary>\n        public String OnsChannel { get; set; } = \"LOCAL\";\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/AclProvider.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\n/// <summary>Apache RocketMQ ACL 适配器</summary>\npublic class AclProvider : ICloudProvider\n{\n    /// <summary>提供者名称</summary>\n    public String Name => \"ACL\";\n\n    /// <summary>访问令牌</summary>\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥</summary>\n    public String SecretKey { get; set; }\n\n    /// <summary>通道标识。默认空</summary>\n    public String OnsChannel { get; set; } = \"\";\n\n    /// <summary>转换主题名。ACL模式不转换</summary>\n    public String TransformTopic(String topic) => topic;\n\n    /// <summary>转换消费组名。ACL模式不转换</summary>\n    public String TransformGroup(String group) => group;\n\n    /// <summary>获取 NameServer 地址。ACL模式不从HTTP获取</summary>\n    public String GetNameServerAddress() => null;\n\n    /// <summary>从旧版 AclOptions 创建</summary>\n    /// <param name=\"options\">旧版ACL选项</param>\n    /// <returns></returns>\n    public static AclProvider FromOptions(AclOptions options)\n    {\n        if (options == null) return null;\n\n        return new AclProvider\n        {\n            AccessKey = options.AccessKey,\n            SecretKey = options.SecretKey,\n            OnsChannel = options.OnsChannel ?? \"\",\n        };\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/AliyunOptions.cs",
    "content": "﻿using System;\n\nnamespace NewLife.RocketMQ\n{\n    /// <summary>\n    /// 阿里云选项\n    /// </summary>\n    public class AliyunOptions\n    {\n        #region 阿里云属性\n        /// <summary>获取名称服务器地址的http地址。阿里云专用</summary>\n        public String Server { get; set; } = \"http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet\";\n\n        /// <summary>访问令牌。阿里云专用</summary>\n        public String AccessKey { get; set; }\n\n        /// <summary>访问密钥。阿里云专用</summary>\n        public String SecretKey { get; set; }\n\n        /// <summary>实例ID。阿里云专用</summary>\n        public String InstanceId { get; set; }\n\n        /// <summary>阿里云MQ通道。阿里云专用</summary>\n        public String OnsChannel { get; set; } = \"ALIYUN\";\n        #endregion\n\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/AliyunProvider.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\n/// <summary>阿里云 RocketMQ 适配器</summary>\npublic class AliyunProvider : ICloudProvider\n{\n    /// <summary>提供者名称</summary>\n    public String Name => \"Aliyun\";\n\n    /// <summary>访问令牌</summary>\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥</summary>\n    public String SecretKey { get; set; }\n\n    /// <summary>通道标识。默认ALIYUN</summary>\n    public String OnsChannel { get; set; } = \"ALIYUN\";\n\n    /// <summary>实例ID。MQ_INST_xxx</summary>\n    public String InstanceId { get; set; }\n\n    /// <summary>NameServer HTTP 发现地址</summary>\n    public String Server { get; set; }\n\n    /// <summary>转换主题名。阿里云需要加实例ID前缀</summary>\n    /// <param name=\"topic\">原始主题名</param>\n    /// <returns></returns>\n    public String TransformTopic(String topic)\n    {\n        var ins = InstanceId;\n        if (!String.IsNullOrEmpty(ins) && !topic.StartsWith(ins))\n            return $\"{ins}%{topic}\";\n\n        return topic;\n    }\n\n    /// <summary>转换消费组名。阿里云需要加实例ID前缀</summary>\n    /// <param name=\"group\">原始消费组名</param>\n    /// <returns></returns>\n    public String TransformGroup(String group)\n    {\n        var ins = InstanceId;\n        if (!String.IsNullOrEmpty(ins) && !group.StartsWith(ins))\n            return $\"{ins}%{group}\";\n\n        return group;\n    }\n\n    /// <summary>获取 NameServer 地址。从阿里云 HTTP 接口获取</summary>\n    /// <returns></returns>\n    public String GetNameServerAddress()\n    {\n        var addr = Server;\n        if (String.IsNullOrEmpty(addr) || !addr.StartsWith(\"http\", StringComparison.OrdinalIgnoreCase))\n            return null;\n\n        var http = new System.Net.Http.HttpClient();\n        var html = http.GetStringAsync(addr).ConfigureAwait(false).GetAwaiter().GetResult();\n\n        return String.IsNullOrWhiteSpace(html) ? null : html.Trim();\n    }\n\n    /// <summary>从旧版 AliyunOptions 创建</summary>\n    /// <param name=\"options\">旧版阿里云选项</param>\n    /// <returns></returns>\n    public static AliyunProvider FromOptions(AliyunOptions options)\n    {\n        if (options == null) return null;\n\n        return new AliyunProvider\n        {\n            AccessKey = options.AccessKey,\n            SecretKey = options.SecretKey,\n            OnsChannel = options.OnsChannel ?? \"ALIYUN\",\n            InstanceId = options.InstanceId,\n            Server = options.Server,\n        };\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/BrokerClient.cs",
    "content": "﻿using NewLife.Log;\nusing NewLife.Net;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Threading;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>代理客户端</summary>\npublic class BrokerClient : ClusterClient\n{\n    #region 属性\n    /// <summary>服务器地址</summary>\n    private readonly String[] _Servers;\n    #endregion\n\n    #region 构造\n    /// <summary>实例化代理客户端</summary>\n    /// <param name=\"servers\"></param>\n    public BrokerClient(String[] servers) => _Servers = servers;\n    #endregion\n\n    #region 方法\n    /// <summary>启动</summary>\n    protected override void OnStart()\n    {\n        //Servers = _Servers.Select(e => new NetUri(e)).ToArray();\n        var list = new List<NetUri>();\n        foreach (var item in _Servers)\n        {\n            var uri = new NetUri(item);\n            if (uri.Type == NetType.Unknown) uri.Type = NetType.Tcp;\n\n            // VIP通道使用端口-2\n            if (Config != null && Config.VipChannelEnabled && uri.Port > 2)\n                uri.Port -= 2;\n\n            list.Add(uri);\n        }\n        Servers = list.ToArray();\n\n        base.OnStart();\n\n        // 心跳\n        StartPing();\n    }\n    #endregion\n\n    #region 注销\n    /// <summary>注销客户端</summary>\n    /// <param name=\"group\"></param>\n    public virtual Command UnRegisterClient(String group)\n    {\n        if (group.IsNullOrEmpty()) group = \"CLIENT_INNER_PRODUCER\";\n\n        return Invoke(RequestCode.UNREGISTER_CLIENT, new\n        {\n            ClientId = Id,\n            ProducerGroup = group,\n            ConsumerGroup = group,\n        });\n    }\n\n    /// <inheritdoc/>\n\n    protected override void Dispose(Boolean disposing)\n    {\n        if (disposing)\n            _timer?.Dispose();\n\n        base.Dispose(disposing);\n    }\n    #endregion\n\n    #region 心跳\n    private TimerX _timer;\n\n    private void StartPing()\n    {\n        if (_timer == null)\n        {\n            var period = Config.HeartbeatBrokerInterval;\n\n            _timer = new TimerX(OnPing, null, 100, period) { Async = true };\n        }\n    }\n\n    private void OnPing(Object state)\n    {\n        DefaultSpan.Current = null;\n\n        Ping();\n    }\n\n    /// <summary>心跳</summary>\n    public void Ping()\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Ping\");\n        try\n        {\n            var cfg = Config;\n\n            var body = new HeartbeatData { ClientID = Id };\n\n            // 生产者 和 消费者 略有不同\n            if (cfg is Producer pd)\n            {\n                body.ProducerDataSet = [\n                new ProducerData { GroupName = pd.Group },\n                new ProducerData { GroupName = \"CLIENT_INNER_PRODUCER\" },\n            ];\n                body.ConsumerDataSet = [];\n            }\n            else if (cfg is Consumer cm)\n            {\n                body.ProducerDataSet = [new ProducerData { GroupName = \"CLIENT_INNER_PRODUCER\" }];\n                body.ConsumerDataSet = cm.Data.ToArray();\n            }\n\n            span?.AppendTag(body);\n\n            // 心跳忽略错误。有时候报40错误\n            Invoke(RequestCode.HEART_BEAT, body, null, true);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            if (ex.GetTrue() is not TaskCanceledException)\n                throw;\n        }\n    }\n    #endregion\n\n    #region 运行信息\n    /// <summary>获取运行时信息</summary>\n    /// <returns></returns>\n    public IDictionary<String, Object> GetRuntimeInfo()\n    {\n        var rs = Invoke(RequestCode.GET_BROKER_RUNTIME_INFO, null);\n        if (rs == null || rs.Payload == null) return null;\n\n        var dic = rs.ReadBodyAsJson();\n\n        return dic?[\"table\"] as IDictionary<String, Object>;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/ClusterClient.cs",
    "content": "﻿using System.Net.Sockets;\nusing System.Security.Cryptography;\nusing NewLife.Data;\nusing NewLife.Log;\nusing NewLife.Net;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>集群客户端</summary>\n/// <remarks>\n/// 维护到一个集群的客户端连接，内部采用负载均衡调度算法。\n/// </remarks>\npublic abstract class ClusterClient : DisposeBase\n{\n    #region 属性\n    /// <summary>编号</summary>\n    public String Id { get; set; }\n\n    /// <summary>名称</summary>\n    public String Name { get; set; }\n\n    /// <summary>超时。默认3000ms</summary>\n    public Int32 Timeout { get; set; } = 3_000;\n\n    /// <summary>服务器地址集合</summary>\n    public NetUri[] Servers { get; set; }\n\n    /// <summary>配置</summary>\n    public MqBase Config { get; set; }\n\n    /// <summary>性能跟踪</summary>\n    public ITracer Tracer { get; set; }\n\n    private ISocketClient _Client;\n    private SerializeType _serializeType = SerializeType.JSON;\n    #endregion\n\n    #region 构造\n    /// <summary>实例化</summary>\n    public ClusterClient()\n    {\n        //_Pool = new MyPool { Client = this };\n    }\n\n    /// <summary>销毁</summary>\n    /// <param name=\"disposing\"></param>\n    protected override void Dispose(Boolean disposing)\n    {\n        base.Dispose(disposing);\n\n        //_Pool.TryDispose();\n        _Client.TryDispose();\n    }\n    #endregion\n\n    #region 方法\n    /// <summary>开始</summary>\n    public void Start()\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Start\", Servers);\n        OnStart();\n    }\n\n    /// <summary>开始</summary>\n    protected virtual void OnStart()\n    {\n        WriteLog(\"集群地址：{0}\", Servers.Join(\";\"));\n\n        if (Config != null)\n            _serializeType = Config.SerializeType;\n\n        EnsureCreate();\n    }\n\n    /// <summary>确保创建连接</summary>\n    protected void EnsureCreate()\n    {\n        var client = _Client;\n        if (client != null && client.Active && !client.Disposed) return;\n        lock (this)\n        {\n            client = _Client;\n            if (client != null && client.Active && !client.Disposed) return;\n            _Client = null;\n\n            foreach (var uri in Servers)\n            {\n                WriteLog(\"正在连接[{0}]\", uri);\n\n                if (uri.Type == NetType.Unknown) uri.Type = NetType.Tcp;\n\n                client = uri.CreateRemote();\n                client.Timeout = Timeout;\n                client.Log = Log;\n                if (Log != null && Log.Level <= LogLevel.Debug) client.Tracer = Tracer;\n                client.Add(new MqCodec { Timeout = Timeout });\n\n                // 关闭Tcp延迟以合并小包的算法，降低延迟\n                if (client is TcpSession tcp)\n                {\n                    tcp.SslProtocol = Config.SslProtocol;\n                    tcp.Certificate = Config.Certificate;\n                    tcp.NoDelay = true;\n                }\n\n                try\n                {\n                    if (client.Open())\n                    {\n                        client.Received += Client_Received;\n                        _Client = client;\n                        break;\n                    }\n                }\n                catch { }\n            }\n\n            if (_Client == null) throw new XException(\"[{0}]集群所有地址[{1}]连接失败！\", Name, Servers.Length);\n        }\n    }\n\n    private Int32 g_id;\n    /// <summary>发送命令</summary>\n    /// <param name=\"cmd\"></param>\n    /// <param name=\"waitResult\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    protected virtual async Task<Command> SendAsync(Command cmd, Boolean waitResult, CancellationToken cancellationToken = default)\n    {\n        if (cmd.Header.Opaque == 0) cmd.Header.Opaque = Interlocked.Increment(ref g_id);\n\n        if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"=> {0}\", cmd);\n\n        var code = (RequestCode)cmd.Header.Code;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:SendAsync:{code}\");\n\n        // 签名\n        SetSignature(cmd);\n\n        EnsureCreate();\n        var client = _Client;\n        try\n        {\n            if (span is DefaultSpan ds && ds.TraceFlag > 0)\n            {\n                span.AppendTag(cmd);\n                span.AppendTag(cmd.Payload?.ToStr());\n            }\n\n            if (waitResult)\n            {\n                var rs = await client.SendMessageAsync(cmd, cancellationToken).ConfigureAwait(false);\n\n                if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"<= {0}\", rs as Command);\n\n                var result = rs as Command;\n                if (rs != null && span is DefaultSpan ds2 && ds2.TraceFlag > 0)\n                {\n                    span.AppendTag(Environment.NewLine);\n                    span.AppendTag(rs);\n                    span.AppendTag(result?.Payload?.ToStr());\n                }\n\n                return result;\n            }\n            else\n            {\n                var row = client.SendMessage(cmd);\n                return new Command\n                {\n                    Reply = true,\n                    Header = new Header() { Code = (Int32)ResponseCode.SUCCESS }\n                };\n            }\n        }\n        catch (Exception ex)\n        {\n            // 拉取消息超时，不记录错误日志\n            if (code == RequestCode.PULL_MESSAGE && ex is TaskCanceledException)\n                span?.AppendTag(ex.Message);\n            else\n                span?.SetError(ex, null);\n\n            // 销毁，下次使用另一个地址\n            if (ex is SocketException or IOException)\n                client.TryDispose();\n\n            throw;\n        }\n    }\n\n    private void SetSignature(Command cmd)\n    {\n        // 各云厂商和Apache ACL统一使用HMAC-SHA1签名：\n        // 将扩展字段按ASCII排序拼接值，再加上body，计算HmacSHA1\n\n        String accessKey;\n        String secretKey;\n        String onsChannel;\n\n        // 优先使用新的 CloudProvider 接口\n        var provider = Config.CloudProvider;\n        if (provider != null && !provider.AccessKey.IsNullOrEmpty())\n        {\n            accessKey = provider.AccessKey;\n            secretKey = provider.SecretKey;\n            onsChannel = provider.OnsChannel;\n        }\n        else\n        {\n            // 兼容旧版：依次检查 Aliyun / AclOptions\n#pragma warning disable CS0618\n            var aliyun = Config.Aliyun;\n            if (aliyun != null && !aliyun.AccessKey.IsNullOrEmpty())\n            {\n                accessKey = aliyun.AccessKey;\n                secretKey = aliyun.SecretKey;\n                onsChannel = aliyun.OnsChannel;\n            }\n            else\n            {\n                var acl = Config.AclOptions;\n                if (acl == null || acl.AccessKey.IsNullOrEmpty()) return;\n\n                accessKey = acl.AccessKey;\n                secretKey = acl.SecretKey;\n                onsChannel = acl.OnsChannel;\n            }\n#pragma warning restore CS0618\n        }\n\n        var sha = new HMACSHA1(secretKey.GetBytes());\n        var ms = new MemoryStream();\n\n        var dic = cmd.Header.GetExtFields();\n        dic[\"AccessKey\"] = accessKey;\n        dic[\"OnsChannel\"] = onsChannel;\n\n        // 按照 asscii 排序已有 key\n        var comparer = Comparer<string>.Create(string.CompareOrdinal);\n        foreach (var item in dic.OrderBy(e => e.Key, comparer).ToDictionary(e => e.Key, e => e.Value))\n        {\n            if (item.Value != null)\n            {\n                ms.Write(item.Value.GetBytes());\n            }\n        }\n\n        // Body\n        cmd.Payload?.CopyTo(ms);\n\n        var sign = sha.ComputeHash(ms.ToArray());\n        dic[\"Signature\"] = sign.ToBase64();\n    }\n\n    /// <summary>发送指定类型的命令</summary>\n    /// <param name=\"request\"></param>\n    /// <param name=\"body\"></param>\n    /// <param name=\"extFields\"></param>\n    /// <param name=\"ignoreError\"></param>\n    /// <returns></returns>\n    public virtual Command Invoke(RequestCode request, Object body, Object extFields = null, Boolean ignoreError = false)\n    {\n        var cmd = CreateCommand(request, body, extFields);\n\n        // 避免UI死锁\n        var rs = SendAsync(cmd, true).ConfigureAwait(false).GetAwaiter().GetResult();\n\n        // 判断异常响应\n        if (!ignoreError && rs.Header != null && rs.Header.Code != 0) throw rs.Header.CreateException();\n\n        return rs;\n    }\n\n    /// <summary>发送指定类型的命令</summary>\n    public virtual async Task<Command> InvokeAsync(RequestCode request, Object body, Object extFields = null,\n        Boolean ignoreError = false, CancellationToken cancellationToken = default)\n    {\n        var cmd = CreateCommand(request, body, extFields);\n\n        var rs = await SendAsync(cmd, true, cancellationToken).ConfigureAwait(false);\n\n        // 判断异常响应\n        if (!ignoreError && rs.Header != null && rs.Header.Code != 0)\n        {\n            throw rs.Header.CreateException();\n        }\n\n        return rs;\n    }\n\n    /// <summary>发送指定类型的命令</summary>\n    /// <param name=\"request\"></param>\n    /// <param name=\"body\"></param>\n    /// <param name=\"extFields\"></param>\n    /// <returns></returns>\n    public virtual Command InvokeOneway(RequestCode request, Object body, Object extFields = null)\n    {\n        var cmd = CreateCommand(request, body, extFields);\n        cmd.OneWay = true;\n\n        // 避免UI死锁\n        var rs = Task.Run(() => SendAsync(cmd, false)).Result;\n\n        return rs;\n    }\n\n    /// <summary>创建命令</summary>\n    /// <param name=\"request\"></param>\n    /// <param name=\"body\"></param>\n    /// <param name=\"extFields\"></param>\n    /// <returns></returns>\n    public virtual Command CreateCommand(RequestCode request, Object body, Object extFields)\n    {\n        var header = new Header\n        {\n            Code = (Int32)request,\n            SerializeTypeCurrentRPC = _serializeType + \"\",\n            Remark = request + \"\",\n        };\n        if (Config != null && Config.Version > 0) header.Version = Config.Version;\n\n        var cmd = new Command\n        {\n            Header = header,\n        };\n\n        // 主体\n        if (body is IPacket pk)\n            cmd.Payload = pk;\n        else if (body is Byte[] buf)\n            cmd.Payload = (ArrayPacket)buf;\n        else if (body != null)\n            cmd.Payload = (ArrayPacket)Config.JsonHost.Write(body, false, false, false).GetBytes();\n\n        if (extFields != null)\n        {\n            var dic = header.GetExtFields();\n            foreach (var item in extFields.ToDictionary())\n            {\n                if (item.Value != null && item.Value.GetType() == typeof(Boolean))\n                    dic[item.Key] = item.Value.ToBoolean() ? \"true\" : \"false\";\n                else\n                    dic[item.Key] = item.Value + \"\";\n            }\n        }\n\n        OnBuild(header);\n\n        return cmd;\n    }\n\n    /// <summary>建立命令时，处理头部</summary>\n    /// <param name=\"header\"></param>\n    protected virtual void OnBuild(Header header)\n    {\n        header.Language = \"DOTNET\";\n    }\n    #endregion\n\n    #region 接收数据\n    private void Client_Received(Object sender, ReceivedEventArgs e)\n    {\n        if (e.Message is not Command cmd) return;\n\n        if (cmd.Reply)\n        {\n            //if (cmd.Header != null) _serializeType = cmd.Header.SerializeTypeCurrentRPC.ToEnum(SerializeType.JSON);\n\n            return;\n        }\n\n        var rs = OnReceive(cmd);\n        if (rs != null)\n        {\n            var ss = sender as ISocketRemote;\n            ss.SendMessage(rs);\n        }\n    }\n\n    /// <summary>收到命令时</summary>\n    public event EventHandler<EventArgs<Command>> Received;\n\n    /// <summary>收到命令</summary>\n    /// <param name=\"cmd\"></param>\n    protected virtual Command OnReceive(Command cmd)\n    {\n        var code = !cmd.Reply ? (RequestCode)cmd.Header.Code + \"\" : (ResponseCode)cmd.Header.Code + \"\";\n        WriteLog(\"收到：Code={0} {1}\", code, cmd.Header.ToJson());\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Receive:{code}\", cmd);\n        span?.AppendTag(cmd.Payload?.ToStr());\n        try\n        {\n            if (Received == null) return null;\n\n            var e = new EventArgs<Command>(cmd);\n            Received.Invoke(this, e);\n\n            return e.Arg;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            throw;\n        }\n    }\n    #endregion\n\n    #region 日志\n    /// <summary>日志</summary>\n    public ILog Log { get; set; }\n\n    /// <summary>写日志</summary>\n    /// <param name=\"format\"></param>\n    /// <param name=\"args\"></param>\n    public void WriteLog(String format, params Object[] args) => Log?.Info($\"[{Name}]{format}\", args);\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Common/BrokerInfo.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\n/// <summary>权限</summary>\n[Flags]\npublic enum Permissions\n{\n    /// <summary>写入</summary>\n    Write = 2,\n\n    /// <summary>读取</summary>\n    Read = 4,\n}\n\n/// <summary>代理信息</summary>\npublic class BrokerInfo\n{\n    #region 属性\n    /// <summary>名称</summary>\n    public String Name { get; set; }\n\n    /// <summary>集群</summary>\n    public String Cluster { get; set; }\n\n    /// <summary>地址集合</summary>\n    public String[] Addresses { get; set; }\n\n    /// <summary>主节点地址（BrokerId=0）</summary>\n    public String MasterAddress { get; set; }\n\n    /// <summary>从节点地址集合（BrokerId>0）</summary>\n    public String[] SlaveAddresses { get; set; }\n\n    /// <summary>权限</summary>\n    public Permissions Permission { get; set; }\n\n    /// <summary>读队列数</summary>\n    public Int32 ReadQueueNums { get; set; }\n\n    /// <summary>写队列数</summary>\n    public Int32 WriteQueueNums { get; set; }\n\n    /// <summary>主题同步标记</summary>\n    public Int32 TopicSynFlag { get; set; }\n\n    /// <summary>是否主节点</summary>\n    public Boolean IsMaster { get; set; }\n    #endregion\n\n    #region 相等\n    /// <summary>相等比较</summary>\n    /// <param name=\"obj\"></param>\n    /// <returns></returns>\n    public override Boolean Equals(Object obj)\n    {\n        var x = this;\n        if (obj is not BrokerInfo y) return false;\n\n        return x.Name == y.Name && (x.Addresses == y.Addresses || x.Addresses != null && y.Addresses != null && x.Addresses.SequenceEqual(y.Addresses))\n            && x.Permission == y.Permission && x.TopicSynFlag == y.TopicSynFlag\n            && x.ReadQueueNums == y.ReadQueueNums && x.WriteQueueNums == y.WriteQueueNums;\n    }\n\n    /// <summary>计算哈希</summary>\n    /// <returns></returns>\n    public override Int32 GetHashCode()\n    {\n        var obj = this;\n        return obj.Name.GetHashCode() ^ obj.Addresses.GetHashCode()\n            ^ obj.Permission.GetHashCode() ^ obj.TopicSynFlag\n            ^ obj.ReadQueueNums ^ obj.WriteQueueNums;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Common/ILoadBalance.cs",
    "content": "﻿using System;\n\nnamespace NewLife.RocketMQ.Common\n{\n    /// <summary>负载均衡接口</summary>\n    public interface ILoadBalance\n    {\n        /// <summary>已就绪</summary>\n        Boolean Ready { get; set; }\n\n        /// <summary>设置每个选项的权重数据</summary>\n        /// <param name=\"weights\"></param>\n        void Set(Int32[] weights);\n\n        /// <summary>根据权重选择，并返回该项是第几次选中</summary>\n        /// <param name=\"times\">第几次选中</param>\n        /// <returns></returns>\n        Int32 Get(out Int32 times);\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Common/WeightRoundRobin.cs",
    "content": "﻿namespace NewLife.RocketMQ.Common;\n\n/// <summary>带权重负载均衡算法</summary>\npublic class WeightRoundRobin : ILoadBalance\n{\n    #region 属性\n    /// <summary>已就绪</summary>\n    public Boolean Ready { get; set; }\n\n    /// <summary>权重集合</summary>\n    public Int32[] Weights { get; private set; }\n\n    /// <summary>最小权重</summary>\n    private Int32 minWeight;\n\n    /// <summary>状态值</summary>\n    private Int32[] _states;\n\n    /// <summary>次数</summary>\n    private Int32[] _times;\n    #endregion\n\n    #region 方法\n    /// <summary>设置每个选项的权重数据</summary>\n    /// <param name=\"weights\"></param>\n    public void Set(Int32[] weights)\n    {\n        if (weights == null) throw new ArgumentNullException(nameof(weights));\n        if (Weights != null && Weights.SequenceEqual(weights)) return;\n\n        Weights = weights;\n\n        minWeight = weights.Min();\n\n        _states = new Int32[weights.Length];\n        _times = new Int32[weights.Length];\n\n        Ready = true;\n    }\n\n    /// <summary>根据权重选择，并返回该项是第几次选中</summary>\n    /// <param name=\"times\">第几次选中</param>\n    /// <returns></returns>\n    public Int32 Get(out Int32 times)\n    {\n        times = 1;\n        var ts = _states;\n        if (ts == null) return 0;\n\n        // 选择状态最大值\n        var cur = GetMax(ts, out var idx);\n\n        // 如果所有状态都不达标，则集体加盐\n        if (cur < minWeight)\n        {\n            for (var i = 0; i < Weights.Length; i++)\n            {\n                ts[i] += Weights[i];\n            }\n\n            // 重新选择状态最大值\n            cur = GetMax(ts, out idx);\n        }\n\n        // 已选择，减状态\n        ts[idx] -= minWeight;\n\n        times = ++_times[idx];\n\n        return idx;\n    }\n\n    /// <summary>根据权重选择</summary>\n    /// <returns></returns>\n    public Int32 Get() => Get(out var n);\n\n    private Int32 GetMax(Int32[] ds, out Int32 idx)\n    {\n        var n = Int32.MinValue;\n        idx = 0;\n        for (var i = 0; i < ds.Length; i++)\n        {\n            if (ds[i] > n)\n            {\n                n = ds[i];\n                idx = i;\n            }\n        }\n\n        return n;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Consumer.cs",
    "content": "﻿using System.Reflection;\nusing System.Text;\nusing System.Xml.Serialization;\nusing NewLife.Data;\nusing NewLife.Log;\nusing NewLife.Reflection;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Models;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.RocketMQ.Protocol.ConsumerStates;\nusing NewLife.Serialization;\nusing NewLife.Threading;\n\nusing NewLife.RocketMQ.MessageTrace;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>消费者</summary>\npublic class Consumer : MqBase\n{\n    #region 属性\n    /// <summary>数据</summary>\n    public IList<ConsumerData> Data { get; set; }\n\n    /// <summary>标签集合</summary>\n    public String[] Tags { get; set; }\n\n    /// <summary>多主题订阅列表。设置后一个Consumer可同时消费多个Topic，每个Topic使用相同的Tags和ExpressionType</summary>\n    /// <remarks>\n    /// 设置 Topics 后，原 Topic 属性作为默认主题，Topics 中的所有主题都会被订阅。\n    /// 如果 Topics 未设置，则保持原有单 Topic 行为不变。\n    /// </remarks>\n    public String[] Topics { get; set; }\n\n    /// <summary>消费挂起超时。每次拉取消息，服务端如果没有消息时的挂起时间，默认15_000ms</summary>\n    public Int32 SuspendTimeout { get; set; } = 15_000;\n\n    /// <summary>客户端拉取消息超时。默认0表示自动使用SuspendTimeout加10_000ms，防止服务端超时后无响应导致消费线程永久阻塞</summary>\n    public Int32 PullTimeout { get; set; } = 0;\n\n    /// <summary>拉取的批大小。默认32</summary>\n    public Int32 BatchSize { get; set; } = 32;\n\n    /// <summary>启动时间</summary>\n    private DateTime StartTime { get; set; } = DateTime.Now;\n\n    /// <summary>首次消费时的消费策略，默认值false，表示从头开始收，等同于Java版的COMSUME_FROM_FIRST_OFFSET</summary>\n    public Boolean FromLastOffset { get; set; } = false;\n\n    /// <summary>\n    /// 订阅表达式 TAG\n    /// </summary>\n    public String Subscription { get; set; } = \"*\";\n\n    /// <summary>表达式类型。TAG或SQL92，默认TAG。使用SQL92时Subscription填写SQL表达式</summary>\n    public String ExpressionType { get; set; } = \"TAG\";\n\n    /// <summary>启动消费者时自动开始调度。默认true</summary>\n    public Boolean AutoSchedule { get; set; } = true;\n\n    /// <summary>消息模型。广播/集群</summary>\n    public MessageModels MessageModel { get; set; } = MessageModels.Clustering;\n\n    /// <summary>消费类型。CONSUME_PASSIVELY/CONSUME_ACTIVELY</summary>\n    public String ConsumeType { get; set; } = \"CONSUME_PASSIVELY\";\n\n    /// <summary>最大重试次数。默认16次，超过后进入死信队列</summary>\n    public Int32 MaxReconsumeTimes { get; set; } = 16;\n\n    /// <summary>是否启用消费重试。默认true，消费失败时自动将消息发回Broker的RETRY Topic</summary>\n    public Boolean EnableRetry { get; set; } = true;\n\n    /// <summary>重试延迟等级。默认0表示由Broker根据重试次数决定延迟，大于0时使用指定等级</summary>\n    public Int32 RetryDelayLevel { get; set; }\n\n    /// <summary>是否顺序消费。启用后消费前自动锁定队列，确保同一时刻只有一个消费者</summary>\n    public Boolean OrderConsume { get; set; }\n\n    /// <summary>最大并发消费数。0表示不限制，每个队列一个消费线程。大于0时使用信号量控制所有队列的总并发</summary>\n    public Int32 MaxConcurrentConsume { get; set; }\n\n    /// <summary>消费委托</summary>\n    public Func<MessageQueue, MessageExt[], Boolean> OnConsume;\n\n    /// <summary>异步消费委托</summary>\n    public Func<MessageQueue, MessageExt[], CancellationToken, Task<Boolean>> OnConsumeAsync;\n\n    /// <summary>消费事件</summary>\n    public event EventHandler<ConsumeEventArgs> Consumed;\n\n    private readonly IList<IConsumeMessageHook> _consumeMessageHooks = new List<IConsumeMessageHook>();\n    private AsyncTraceDispatcher _traceDispatcher;\n\n    /// <summary>本地偏移存储路径。广播模式时使用，默认当前目录下的 .offsets/{Group}.json</summary>\n    public String OffsetStorePath { get; set; }\n\n    private SemaphoreSlim _concurrentSemaphore;\n\n    /// <summary>获取所有有效的订阅主题</summary>\n    private String[] GetEffectiveTopics()\n    {\n        var topics = Topics;\n        if (topics != null && topics.Length > 0) return topics;\n        return [Topic];\n    }\n    #endregion\n\n    #region 构造\n\n    /// <summary>销毁</summary>\n    /// <param name=\"disposing\"></param>\n    protected override void Dispose(Boolean disposing)\n    {\n        // 停止并保存偏移\n        Stop();\n\n        base.Dispose(disposing);\n\n        _source.TryDispose();\n        _source = null;\n\n        _timer.TryDispose();\n        _timer = null;\n    }\n\n    #endregion\n\n    #region 方法\n\n    /// <summary>启动</summary>\n    /// <returns></returns>\n    protected override void OnStart()\n    {\n        var allTopics = GetEffectiveTopics();\n        WriteLog(\"正在准备消费 {0}\", allTopics.Join(\",\"));\n\n        if (EnableMessageTrace)\n        {\n            _traceDispatcher = new AsyncTraceDispatcher();\n            _traceDispatcher.Start(NameServerAddress);\n            _consumeMessageHooks.Add(new MessageTraceHook(_traceDispatcher));\n        }\n\n        var list = Data;\n        if (list == null)\n        {\n            // 为每个Topic建立订阅数据\n            var sds = allTopics.Select(t => new SubscriptionData\n            {\n                Topic = t,\n                TagsSet = Tags\n            }).ToArray();\n\n            var cd = new ConsumerData\n            {\n                GroupName = Group,\n                ConsumeFromWhere = FromLastOffset ? \"CONSUME_FROM_LAST_OFFSET\" : \"CONSUME_FROM_FIRST_OFFSET\",\n                MessageModel = MessageModel.ToString().ToUpper(),\n                SubscriptionDataSet = sds,\n                ConsumeType = ConsumeType,\n            };\n\n            list = new[] { cd };\n\n            Data = list;\n        }\n\n        base.OnStart();\n\n        // 多Topic时，通知NameClient轮询额外主题的路由\n        if (allTopics.Length > 1 && _NameServer != null)\n        {\n            var extras = allTopics.Where(t => t != Topic).ToArray();\n            _NameServer.ExtraTopics = extras;\n\n            // 首次获取额外主题的路由\n            foreach (var topic in extras)\n            {\n                try\n                {\n                    _NameServer.GetRouteInfo(topic);\n                }\n                catch (Exception ex)\n                {\n                    WriteLog(\"获取主题[{0}]路由失败：{1}\", topic, ex.Message);\n                }\n            }\n        }\n\n        // 默认自动开始调度\n        if (AutoSchedule) StartSchedule();\n    }\n\n    /// <summary>\n    /// 停止\n    /// </summary>\n    protected override void OnStop()\n    {\n        // 停止并保存偏移\n        StopSchedule();\n        PersistAll(_Queues).Wait();\n\n        base.OnStop();\n    }\n\n    /// <summary>创建Broker客户端，已重载，设置更大的超时时间</summary>\n    /// <param name=\"name\"></param>\n    /// <param name=\"addrs\"></param>\n    /// <returns></returns>\n    protected override BrokerClient CreateBroker(String name, String[] addrs)\n    {\n        var client = base.CreateBroker(name, addrs);\n        if (client.Timeout < SuspendTimeout) client.Timeout = SuspendTimeout;\n\n        return client;\n    }\n    #endregion\n\n    #region 拉取消息\n\n    /// <summary>从指定队列拉取消息</summary>\n    /// <param name=\"mq\"></param>\n    /// <param name=\"offset\"></param>\n    /// <param name=\"maxNums\"></param>\n    /// <param name=\"msTimeout\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<PullResult> Pull(MessageQueue mq, Int64 offset, Int32 maxNums, Int32 msTimeout = -1, CancellationToken cancellationToken = default)\n    {\n        var header = new PullMessageRequestHeader\n        {\n            ConsumerGroup = Group,\n            Topic = mq.Topic ?? Topic,\n            Subscription = Subscription,\n            ExpressionType = ExpressionType,\n            QueueId = mq.QueueId,\n            QueueOffset = offset,\n            MaxMsgNums = maxNums,\n            SysFlag = 6,\n            SubVersion = StartTime.ToLong(),\n        };\n        if (msTimeout >= 0) header.SuspendTimeoutMillis = msTimeout;\n\n        var st = _Queues.FirstOrDefault(e => e.Queue == mq);\n        if (st != null) header.CommitOffset = st.CommitOffset;\n\n        var dic = header.GetProperties();\n        var bk = GetBroker(mq.BrokerName);\n\n        var rs = await bk.InvokeAsync(RequestCode.PULL_MESSAGE, null, dic, true, cancellationToken).ConfigureAwait(false);\n        if (rs?.Header == null) return null;\n\n        var pr = new PullResult();\n\n        if (rs.Header.Code == 0)\n            pr.Status = PullStatus.Found;\n        else if (rs.Header.Code == (Int32)ResponseCode.PULL_NOT_FOUND)\n            pr.Status = PullStatus.NoNewMessage;\n        else if (rs.Header.Code == (Int32)ResponseCode.PULL_OFFSET_MOVED || rs.Header.Code == (Int32)ResponseCode.PULL_RETRY_IMMEDIATELY)\n            pr.Status = PullStatus.OffsetIllegal;\n        else\n        {\n            pr.Status = PullStatus.Unknown;\n            Log.Warn(\"[{0}]{1} 序列编号：{2} 序列偏移量：{3}\", (ResponseCode)rs.Header.Code, rs.Header.Remark, mq.QueueId, offset);\n        }\n\n        pr.Read(rs.Header?.ExtFields);\n\n        // 读取内容\n        var pk = rs.Payload;\n        if (pk != null) pr.Messages = MessageExt.ReadAll(pk).ToArray();\n\n        return pr;\n    }\n\n    #endregion\n\n    #region 业务方法\n\n    /// <summary>查询指定队列的偏移量</summary>\n    /// <param name=\"mq\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Int64> QueryOffset(MessageQueue mq, CancellationToken cancellationToken = default)\n    {\n        var bk = GetBroker(mq.BrokerName);\n        var rs = await bk.InvokeAsync(RequestCode.QUERY_CONSUMER_OFFSET, null, new\n        {\n            consumerGroup = Group,\n            topic = mq.Topic ?? Topic,\n            queueId = mq.QueueId,\n        }, true, cancellationToken).ConfigureAwait(false);\n\n        var dic = rs.Header?.ExtFields;\n        if (dic == null) return -1;\n\n        return dic.TryGetValue(\"offset\", out var str) ? str.ToLong() : -1;\n    }\n\n    /// <summary>\n    /// 查询“队列”最大偏移量，不是消费提交的最后偏移量\n    /// </summary>\n    /// <param name=\"mq\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Int64> QueryMaxOffset(MessageQueue mq, CancellationToken cancellationToken = default)\n    {\n        var bk = GetBroker(mq.BrokerName);\n        var rs = await bk.InvokeAsync(RequestCode.GET_MAX_OFFSET, null, new\n        {\n            consumerGroup = Group,\n            topic = mq.Topic ?? Topic,\n            queueId = mq.QueueId,\n        }, true, cancellationToken).ConfigureAwait(false);\n\n        var dic = rs.Header?.ExtFields;\n        if (dic == null) return -1;\n\n        return dic.TryGetValue(\"offset\", out var str) ? str.ToLong() : -1;\n    }\n\n    /// <summary>\n    /// 获取最小偏移量\n    /// </summary>\n    /// <param name=\"mq\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Int64> QueryMinOffset(MessageQueue mq, CancellationToken cancellationToken = default)\n    {\n        var bk = GetBroker(mq.BrokerName);\n        var rs = await bk.InvokeAsync(RequestCode.GET_MIN_OFFSET, null, new\n        {\n            consumerGroup = Group,\n            topic = mq.Topic ?? Topic,\n            queueId = mq.QueueId,\n        }, true, cancellationToken).ConfigureAwait(false);\n\n        var dic = rs.Header?.ExtFields;\n        if (dic == null) return -1;\n\n        return dic.TryGetValue(\"offset\", out var str) ? str.ToLong() : -1;\n    }\n\n    /// <summary>根据时间戳查询偏移</summary>\n    /// <param name=\"mq\">队列</param>\n    /// <param name=\"timestamp\">时间戳（毫秒）</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Int64> SearchOffset(MessageQueue mq, Int64 timestamp, CancellationToken cancellationToken = default)\n    {\n        var bk = GetBroker(mq.BrokerName);\n        var rs = await bk.InvokeAsync(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, null, new\n        {\n            topic = mq.Topic ?? Topic,\n            queueId = mq.QueueId,\n            timestamp,\n        }, true, cancellationToken).ConfigureAwait(false);\n\n        var dic = rs.Header?.ExtFields;\n        if (dic == null) return -1;\n\n        return dic.TryGetValue(\"offset\", out var str) ? str.ToLong() : -1;\n    }\n\n    /// <summary>更新队列的偏移</summary>\n    /// <param name=\"mq\"></param>\n    /// <param name=\"commitOffset\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Boolean> UpdateOffset(MessageQueue mq, Int64 commitOffset, CancellationToken cancellationToken = default)\n    {\n        var bk = GetBroker(mq.BrokerName);\n        var rs = await bk.InvokeAsync(RequestCode.UPDATE_CONSUMER_OFFSET, null, new\n        {\n            commitOffset,\n            consumerGroup = Group,\n            queueId = mq.QueueId,\n            topic = mq.Topic ?? Topic,\n        }, false, cancellationToken).ConfigureAwait(false);\n\n        //var dic = rs?.Header?.ExtFields;\n        //if (dic == null) return false;\n\n        return true;\n    }\n\n    /// <summary>获取消费者下所有消费者</summary>\n    /// <param name=\"group\"></param>\n    public async Task<ICollection<String>> GetConsumers(String group = null)\n    {\n        if (group.IsNullOrEmpty()) group = Group;\n\n        var header = new { consumerGroup = group, };\n\n        var cs = new HashSet<String>();\n\n        // 在所有Broker上查询\n        foreach (var item in Brokers)\n        {\n            using var span = Tracer?.NewSpan($\"mq:{Name}:GetConsumers\", item.Name);\n            try\n            {\n                var bk = GetBroker(item.Name);\n                //bk.Ping();\n                var rs = await bk.InvokeAsync(RequestCode.GET_CONSUMER_LIST_BY_GROUP, null, header).ConfigureAwait(false);\n                span?.AppendTag(rs.Payload?.ToStr());\n                //WriteLog(rs.Header.ExtFields?.ToJson());\n                var js = rs.ReadBodyAsJson();\n                if (js != null && js[\"consumerIdList\"] is IList<Object> list)\n                {\n                    foreach (String clientId in list)\n                    {\n                        if (!cs.Contains(clientId)) cs.Add(clientId);\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                if (ex is not ResponseException)\n                    span?.SetError(ex, null);\n\n                //XTrace.WriteException(ex);\n                WriteLog(ex.GetTrue().Message);\n            }\n        }\n\n        return cs;\n    }\n\n    /// <summary>获取消费者连接列表</summary>\n    /// <param name=\"group\">消费组名</param>\n    /// <returns></returns>\n    public async Task<IDictionary<String, Object>> GetConsumerConnectionList(String group = null)\n    {\n        if (group.IsNullOrEmpty()) group = Group;\n\n        foreach (var item in Brokers)\n        {\n            using var span = Tracer?.NewSpan($\"mq:{Name}:GetConsumerConnectionList\", item.Name);\n            try\n            {\n                var bk = GetBroker(item.Name);\n                var rs = await bk.InvokeAsync(RequestCode.GET_CONSUMER_CONNECTION_LIST, null, new { consumerGroup = group }).ConfigureAwait(false);\n                if (rs?.Payload != null) return rs.ReadBodyAsJson();\n            }\n            catch (Exception ex)\n            {\n                span?.SetError(ex, null);\n            }\n        }\n\n        return null;\n    }\n\n    /// <summary>重置消费偏移</summary>\n    /// <param name=\"timestamp\">时间戳（毫秒），将偏移重置到该时间点</param>\n    /// <param name=\"group\">消费组名</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Boolean> ResetConsumerOffset(Int64 timestamp, String group = null, CancellationToken cancellationToken = default)\n    {\n        if (group.IsNullOrEmpty()) group = Group;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:ResetConsumerOffset\", timestamp);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                var bk = GetBroker(item.Name);\n                await bk.InvokeAsync(RequestCode.INVOKE_BROKER_TO_RESET_OFFSET, null, new\n                {\n                    topic = Topic,\n                    group,\n                    timestamp,\n                    isForce = false,\n                }, true, cancellationToken).ConfigureAwait(false);\n            }\n\n            return true;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"重置消费偏移失败：{0}\", ex.Message);\n            return false;\n        }\n    }\n\n    #endregion\n\n    #region 顺序消费锁定\n    /// <summary>批量锁定队列。顺序消费时确保同一队列同一时刻只有一个消费者</summary>\n    /// <param name=\"mqs\">待锁定的队列集合</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>成功锁定的队列集合</returns>\n    public async Task<IList<MessageQueue>> LockBatchMQAsync(IList<MessageQueue> mqs, CancellationToken cancellationToken = default)\n    {\n        if (mqs == null || mqs.Count == 0) return [];\n\n        var locked = new List<MessageQueue>();\n        var groups = mqs.GroupBy(e => e.BrokerName);\n\n        foreach (var g in groups)\n        {\n            using var span = Tracer?.NewSpan($\"mq:{Name}:LockBatchMQ\", g.Key);\n            try\n            {\n                var bk = GetBroker(g.Key);\n                if (bk == null) continue;\n\n                var body = new\n                {\n                    consumerGroup = Group,\n                    clientId = ClientId,\n                    mqSet = g.Select(e => new { topic = e.Topic, brokerName = e.BrokerName, queueId = e.QueueId }).ToArray(),\n                };\n\n                var rs = await bk.InvokeAsync(RequestCode.LOCK_BATCH_MQ, body, null, true, cancellationToken).ConfigureAwait(false);\n                if (rs?.Payload != null)\n                {\n                    var js = rs.ReadBodyAsJson();\n                    if (js != null && js[\"lockOKMQSet\"] is IList<Object> list)\n                    {\n                        foreach (IDictionary<String, Object> item in list)\n                        {\n                            locked.Add(new MessageQueue\n                            {\n                                Topic = item[\"topic\"] + \"\",\n                                BrokerName = item[\"brokerName\"] + \"\",\n                                QueueId = item[\"queueId\"].ToInt(),\n                            });\n                        }\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                span?.SetError(ex, null);\n                WriteLog(\"锁定队列失败[{0}]：{1}\", g.Key, ex.Message);\n            }\n        }\n\n        return locked;\n    }\n\n    /// <summary>批量解锁队列</summary>\n    /// <param name=\"mqs\">待解锁的队列集合</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task UnlockBatchMQAsync(IList<MessageQueue> mqs, CancellationToken cancellationToken = default)\n    {\n        if (mqs == null || mqs.Count == 0) return;\n\n        var groups = mqs.GroupBy(e => e.BrokerName);\n\n        foreach (var g in groups)\n        {\n            using var span = Tracer?.NewSpan($\"mq:{Name}:UnlockBatchMQ\", g.Key);\n            try\n            {\n                var bk = GetBroker(g.Key);\n                if (bk == null) continue;\n\n                var body = new\n                {\n                    consumerGroup = Group,\n                    clientId = ClientId,\n                    mqSet = g.Select(e => new { topic = e.Topic, brokerName = e.BrokerName, queueId = e.QueueId }).ToArray(),\n                };\n\n                await bk.InvokeAsync(RequestCode.UNLOCK_BATCH_MQ, body, null, false, cancellationToken).ConfigureAwait(false);\n            }\n            catch (Exception ex)\n            {\n                span?.SetError(ex, null);\n                WriteLog(\"解锁队列失败[{0}]：{1}\", g.Key, ex.Message);\n            }\n        }\n    }\n    #endregion\n\n    #region 消费调度\n\n    private Task[] _tasks;\n    private volatile Int32 _version;\n    private CancellationTokenSource _source;\n\n    /// <summary>开始调度</summary>\n    public void StartSchedule()\n    {\n        if (_timer != null) return;\n        lock (this)\n        {\n            if (_timer != null) return;\n\n            // 快速检查消费组，均衡成功后改为30秒一次\n            _timer = new TimerX(CheckGroup, null, 100, 1_000) { Async = true };\n        }\n    }\n\n    private void DoSchedule()\n    {\n        var qs = _Queues;\n        if (qs == null || qs.Length == 0) return;\n\n        Interlocked.Increment(ref _version);\n\n        // 关线程\n        //Stop();\n        StopSchedule();\n\n        // 如果有多个消费者，则等一段时间让大家停止消费，尽量避免重复消费\n        //if (_Consumers != null && _Consumers.Length > 1) Thread.Sleep(10_000);\n\n        // 释放资源\n        if (_source != null)\n        {\n            _source.Cancel();\n            _source.TryDispose();\n        }\n\n        var source = new CancellationTokenSource();\n        WriteLog(\"正在创建[{0}]个消费线程，Group={1}，Topics={2}\", qs.Length, Group, GetEffectiveTopics().Join(\",\"));\n\n        // 初始化并发限流信号量\n        if (MaxConcurrentConsume > 0)\n        {\n            _concurrentSemaphore = new SemaphoreSlim(MaxConcurrentConsume, MaxConcurrentConsume);\n            WriteLog(\"消费限流已启用，最大并发数={0}\", MaxConcurrentConsume);\n        }\n        else\n        {\n            _concurrentSemaphore = null;\n        }\n\n        // 开线程\n        var tasks = new Task[qs.Length];\n        for (var i = 0; i < qs.Length; i++)\n        {\n            var queueStore = qs[i];\n            tasks[i] = Task.Factory.StartNew(async () =>\n            {\n                await DoPull(queueStore, source.Token).ConfigureAwait(false);\n            }, TaskCreationOptions.LongRunning);\n        }\n        _tasks = tasks;\n\n        _source = source;\n    }\n\n    /// <summary>停止</summary>\n    public void StopSchedule()\n    {\n        var ts = _tasks;\n        if (ts != null && ts.Length > 0)\n        {\n            WriteLog(\"停止调度线程[{0}]\", ts.Length);\n\n            // 预留一点退出时间\n            Interlocked.Increment(ref _version);\n\n            // 释放资源\n            if (_source != null)\n            {\n                _source.Cancel();\n                _source.TryDispose();\n                _source = null;\n            }\n\n            var timeout = TimeSpan.FromSeconds(3 * _tasks.Length);\n            try\n            {\n                Task.WaitAll(_tasks, timeout);\n            }\n            catch\n            {\n                // 理论上不会遇到异常\n                // 但等待过程可能会遇到积压的 Task 异常，统统吃掉，从业务上也没有需要捕获的需要\n            }\n\n            _tasks = null;\n        }\n    }\n\n    private async Task DoPull(QueueStore st, CancellationToken cancellationToken)\n    {\n        var mq = st.Queue;\n        WriteLog(\"开始消费[{0}]，Group={1}，{2}，Offset={3}，CommitOffset={4}\", mq.Topic ?? Topic, Group, mq, st.Offset, st.CommitOffset);\n\n        // 顺序消费时先锁定队列\n        if (OrderConsume)\n        {\n            var locked = await LockBatchMQAsync([mq], cancellationToken).ConfigureAwait(false);\n            if (locked.Count == 0)\n            {\n                WriteLog(\"顺序消费锁定队列失败[{0}]，跳过消费\", mq);\n                return;\n            }\n        }\n\n        var currentVersion = _version;\n        while (currentVersion == _version && !cancellationToken.IsCancellationRequested)\n        {\n            DefaultSpan.Current = null;\n            try\n            {\n                var offset = st.Offset;\n                // 客户端超时保护：防止服务端在SuspendTimeout后无响应导致消费线程永久阻塞\n                var pullTimeout = PullTimeout > 0 ? PullTimeout : SuspendTimeout + 10_000;\n                using var pullCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);\n                pullCts.CancelAfter(pullTimeout);\n                var pr = await Pull(mq, offset, BatchSize, SuspendTimeout, pullCts.Token).ConfigureAwait(false);\n                if (pr != null)\n                {\n                    switch (pr.Status)\n                    {\n                        case PullStatus.Found:\n                            if (pr.Messages != null && pr.Messages.Length > 0)\n                            {\n                                DefaultSpan.Current = null;\n\n                                // 限流：等待信号量\n                                if (_concurrentSemaphore != null)\n                                    await _concurrentSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);\n\n                                // 性能埋点\n                                using var span = Tracer?.NewSpan($\"mq:{Name}:Consume\", pr.Messages);\n                                try\n                                {\n                                    // 触发消费\n                                    var rs = await Consume(mq, pr, cancellationToken).ConfigureAwait(false);\n\n                                    // 更新偏移\n                                    if (rs)\n                                    {\n                                        st.Offset = pr.NextBeginOffset;\n                                        // 提交消费进度\n                                        await UpdateOffset(mq, st.Offset, cancellationToken).ConfigureAwait(false);\n                                    }\n                                }\n                                catch (Exception ex)\n                                {\n                                    span?.SetError(ex, mq);\n\n                                    throw;\n                                }\n                                finally\n                                {\n                                    _concurrentSemaphore?.Release();\n                                }\n                            }\n\n                            break;\n                        case PullStatus.NoNewMessage:\n                            break;\n                        case PullStatus.NoMatchedMessage:\n                            break;\n                        case PullStatus.OffsetIllegal:\n                            if (pr.NextBeginOffset >= 0)\n                            {\n                                WriteLog(\"无效的offset，可能历史消息已过期 [{0}@{1}] Offset={2:n0}, NextOffset={3:n0}\", mq.BrokerName, mq.QueueId, st.Offset, pr.NextBeginOffset);\n                                st.Offset = pr.NextBeginOffset;\n                            }\n\n                            break;\n                        case PullStatus.Unknown:\n                            Log.Error(\"未知响应类型消息，序列[{1}]偏移量{0}\", st.Offset, st.Queue.QueueId);\n                            break;\n                        default:\n                            break;\n                    }\n                }\n            }\n            catch (ThreadAbortException) { break; }\n            catch (ThreadInterruptedException) { break; }\n            catch (TaskCanceledException) { }\n            catch (AggregateException) { }\n            catch (Exception ex)\n            {\n                Log?.Error(ex.GetMessage());\n                // 出现其他异常的情况下，等待一会，防止出现大量异常\n                await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);\n            }\n        }\n\n        // 保存消费进度\n        if (st.Offset >= 0 && st.Offset != st.CommitOffset)\n        {\n            var rs = await UpdateOffset(mq, st.Offset, cancellationToken).ConfigureAwait(false);\n            st.CommitOffset = st.Offset;\n        }\n\n        // 顺序消费结束时解锁队列\n        if (OrderConsume)\n        {\n            await UnlockBatchMQAsync([mq], cancellationToken).ConfigureAwait(false);\n        }\n\n        WriteLog(\"消费[{0}]结束\", mq.Topic ?? Topic);\n    }\n\n    /// <summary>拉取到一批消息</summary>\n    /// <param name=\"queue\"></param>\n    /// <param name=\"result\"></param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    protected virtual async Task<Boolean> Consume(MessageQueue queue, PullResult result, CancellationToken cancellationToken)\n    {\n        if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"{0}\", result);\n\n        var context = new ConsumeMessageContext\n        {\n            ConsumerGroup = Group,\n            Mq = queue,\n            MsgList = result.Messages.ToList(),\n        };\n\n        foreach (var hook in _consumeMessageHooks)\n        {\n            try\n            {\n                hook.ExecuteHookBefore(context);\n            }\n            catch (Exception e)\n            {\n                if (Log.Enable) Log.Error(e.Message);\n            }\n        }\n\n        Consumed?.Invoke(this, new ConsumeEventArgs { Queue = queue, Messages = result.Messages, Result = result });\n\n        var success = false;\n        if (OnConsume != null) success = OnConsume(queue, result.Messages);\n        if (OnConsumeAsync != null) success = await OnConsumeAsync(queue, result.Messages, cancellationToken).ConfigureAwait(false);\n\n        // 消费失败且启用了重试时，将消息回退到RETRY Topic\n        if (!success && EnableRetry && result.Messages != null)\n        {\n            foreach (var msg in result.Messages)\n            {\n                if (msg.ReconsumeTimes < MaxReconsumeTimes)\n                {\n                    try\n                    {\n                        await SendMessageBackAsync(msg, RetryDelayLevel, MaxReconsumeTimes, cancellationToken).ConfigureAwait(false);\n                    }\n                    catch (Exception ex)\n                    {\n                        WriteLog(\"消息回退失败[{0}]：{1}\", msg.MsgId, ex.Message);\n                    }\n                }\n                else\n                {\n                    // 超过最大重试次数，消息将进入死信队列 %DLQ%{ConsumerGroup}\n                    WriteLog(\"消息[{0}]超过最大重试次数[{1}]，将进入死信队列\", msg.MsgId, MaxReconsumeTimes);\n                }\n            }\n        }\n\n        context.Success = success;\n\n        foreach (var hook in _consumeMessageHooks)\n        {\n            try\n            {\n                hook.ExecuteHookAfter(context);\n            }\n            catch (Exception e)\n            {\n                if (Log.Enable) Log.Error(e.Message);\n            }\n        }\n\n        return success;\n    }\n\n    private async Task PersistAll(IEnumerable<QueueStore> stores)\n    {\n        if (stores == null) return;\n\n        // 广播模式保存到本地文件\n        if (MessageModel == MessageModels.Broadcasting)\n        {\n            SaveLocalOffsets(stores.ToArray());\n            return;\n        }\n\n        var ts = new List<Task>();\n        using var source = new CancellationTokenSource(5_000);\n\n        foreach (var item in stores)\n        {\n            if (item.Offset >= 0 && item.Offset != item.CommitOffset)\n            {\n                var mq = item.Queue;\n                WriteLog(\"队列[{0}@{1}]更新偏移[{2:n0}]\", mq.BrokerName, mq.QueueId, item.Offset);\n\n                ts.Add(Task.Run(() => UpdateOffset(item.Queue, item.Offset, source.Token)));\n\n                item.CommitOffset = item.Offset;\n            }\n        }\n\n        await Task.WhenAll(ts).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 消费端负载均衡\n\n    /// <summary>当前所需要消费的队列。由均衡算法产生</summary>\n    public MessageQueue[] Queues => _Queues?.Select(e => e.Queue).ToArray();\n\n    private QueueStore[] _Queues;\n    //private String[] _Consumers;\n\n    class QueueStore\n    {\n        [XmlIgnore]\n        public MessageQueue Queue { get; set; }\n        public Int64 Offset { get; set; } = -1;\n        public Int64 CommitOffset { get; set; } = -1;\n\n        #region 相等\n\n        /// <summary>相等比较</summary>\n        /// <param name=\"obj\"></param>\n        /// <returns></returns>\n        public override Boolean Equals(Object obj) => obj is QueueStore y && Equals(Queue, y.Queue) && Offset == y.Offset;\n\n        /// <summary>计算哈希</summary>\n        /// <returns></returns>\n        public override Int32 GetHashCode() => (Queue == null ? 0 : Queue.GetHashCode()) ^ Offset.GetHashCode();\n\n        public override String ToString() => Queue?.ToString();\n        #endregion\n    }\n\n    /// <summary>重新平衡消费队列</summary>\n    /// <returns></returns>\n    public async Task<Boolean> Rebalance()\n    {\n        /*\n         * 1，获取消费组下所有消费组，排序\n         * 2，获取主题下所有队列，排序\n         * 3，各消费者平均分配队列，不采用环形，减少消费者到Broker连接数\n         */\n\n        if (_Queues == null) WriteLog(\"准备从所有Broker服务器上获取消费者列表，以确定当前消费者应该负责消费的queue分片\");\n\n        var cs = await GetConsumers(Group).ConfigureAwait(false);\n        if (cs.Count == 0) return false;\n\n        // 为所有订阅主题构建队列列表，按Topic分别分配\n        var allTopics = GetEffectiveTopics();\n        var qs = new List<MessageQueue>();\n        foreach (var topic in allTopics)\n        {\n            // 获取该Topic对应的Broker信息\n            var topicBrokers = _NameServer?.GetTopicBrokers(topic) ?? Brokers;\n            if (topicBrokers == null) continue;\n\n            foreach (var br in topicBrokers)\n            {\n                if (br.Permission.HasFlag(Permissions.Read))\n                {\n                    for (var i = 0; i < br.ReadQueueNums; i++)\n                    {\n                        qs.Add(new MessageQueue { Topic = topic, BrokerName = br.Name, QueueId = i, });\n                    }\n                }\n            }\n        }\n\n        var cs2 = cs.OrderBy(e => e).ToList();\n\n        if (_Queues == null) WriteLog(\"消费者列表[{0}]：{1}\", cs2.Count, cs2.Join());\n\n        // 集群模式需要分配Queue，而广播模式不需要\n        if (MessageModel == MessageModels.Clustering)\n        {\n            // 排序，计算索引。如果当前节点不在消费者列表里，则跳过\n            var cid = ClientId;\n            var idx = cs2.IndexOf(cid);\n            if (idx < 0 || idx >= cs2.Count) return false;\n\n            // 先分糖，每人多少个。你一个我一个，一圈又一圈的分。\n            // 如果无法均分，前面的消费者会比后面的消费者多拿一个，并且最多只会多一个\n            var ds = new Int32[cs2.Count];\n            for (Int32 i = 0, k = 0; i < qs.Count; i++)\n            {\n                ds[k++]++;\n\n                if (k >= ds.Length) k = 0;\n            }\n\n            // 我的前面分了多少，就是前面的桶内数字之和\n            var start = ds.Take(idx).Sum();\n            // 跳过前面，取我的糖\n            qs = qs.Skip(start).Take(ds[idx]).ToList();\n        }\n\n        var rs = new List<QueueStore>();\n        foreach (var item in qs)\n        {\n            rs.Add(new QueueStore { Queue = item });\n        }\n\n        // 如果序列相等则返回false\n        var ori = _Queues;\n        if (ori != null)\n        {\n            var q1 = ori.Select(e => e.Queue).ToArray();\n            var q2 = rs.Select(e => e.Queue).ToArray();\n\n            if (q1.SequenceEqual(q2)) return false;\n\n            await PersistAll(ori).ConfigureAwait(false);\n        }\n\n        var dic = qs.GroupBy(e => e.BrokerName).ToDictionary(e => e.Key, e => e.Join(\",\", x => x.QueueId));\n        var str = dic.Join(\";\", e => $\"{e.Key}[{e.Value}]\");\n        WriteLog(\"消费重新平衡，当前消费者负责queue分片：{0}\", str);\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Rebalance\", str);\n\n        _Queues = rs.ToArray();\n        await InitOffsetAsync().ConfigureAwait(false);\n        //_Consumers = cs2.ToArray();\n\n        return true;\n    }\n\n    private TimerX _timer;\n    private DateTime _nextCheck;\n    private Boolean _checking;\n\n    /// <summary>检查消费组，如果消费者有变化，则需要重新平衡，重新分配各消费者所处理的队列</summary>\n    /// <param name=\"state\"></param>\n    private async Task CheckGroup(Object state = null)\n    {\n        if (_checking) return;\n\n        // 避免多次平衡同时进行\n        var now = TimerX.Now;\n        if (now < _nextCheck) return;\n\n        //lock (this)\n        //{\n        if (_checking) return;\n        _checking = true;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:CheckGroup\");\n        try\n        {\n            var rs = await Rebalance().ConfigureAwait(false);\n            if (!rs) return;\n\n            if (AutoSchedule) DoSchedule();\n\n            if (_timer != null) _timer.Period = 60_000;\n            _nextCheck = now.AddSeconds(3);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            XTrace.WriteException(ex);\n        }\n        finally\n        {\n            _checking = false;\n        }\n        //}\n    }\n\n    private async Task InitOffsetAsync(CancellationToken cancellationToken = default)\n    {\n        var qs = _Queues;\n        if (qs == null || qs.Length == 0) return;\n\n        // 广播模式优先从本地加载偏移\n        if (MessageModel == MessageModels.Broadcasting)\n        {\n            var localOffsets = LoadLocalOffsets();\n            foreach (var store in qs)\n            {\n                if (store.Offset >= 0) continue;\n\n                var key = $\"{store.Queue.Topic ?? Topic}@{store.Queue.BrokerName}@{store.Queue.QueueId}\";\n                // 兼容旧格式（不含Topic前缀）\n                if (!localOffsets.TryGetValue(key, out var offset) || offset < 0)\n                {\n                    var oldKey = $\"{store.Queue.BrokerName}@{store.Queue.QueueId}\";\n                    localOffsets.TryGetValue(oldKey, out offset);\n                }\n                if (offset >= 0)\n                {\n                    store.Offset = store.CommitOffset = offset;\n                    WriteLog(\"从本地加载offset[{0}@{1}] Offset={2:n0}\", store.Queue.BrokerName, store.Queue.QueueId, store.Offset);\n                }\n                else\n                {\n                    // 本地没有，从服务端查询\n                    var off = FromLastOffset\n                        ? await QueryMaxOffset(store.Queue, cancellationToken).ConfigureAwait(false)\n                        : await QueryMinOffset(store.Queue, cancellationToken).ConfigureAwait(false);\n\n                    store.Offset = store.CommitOffset = off;\n                    WriteLog(\"初始化offset[{0}@{1}] Offset={2:n0}（广播模式）\", store.Queue.BrokerName, store.Queue.QueueId, store.Offset);\n                }\n            }\n\n            return;\n        }\n\n        var offsetTables = new Dictionary<MessageQueueModel, OffsetWrapperModel>();\n        // 获取当前消费者分配到的服务器及服务器队列，按Topic+Broker分组查询\n        var topicBrokerGroups = qs.Select(t => new { t.Queue.Topic, t.Queue.BrokerName })\n            .Distinct()\n            .GroupBy(t => t.Topic);\n        foreach (var topicGroup in topicBrokerGroups)\n        {\n            var queryTopic = topicGroup.Key ?? Topic;\n            foreach (var tb in topicGroup)\n            {\n                var broker = GetBroker(tb.BrokerName);\n                var command = await broker.InvokeAsync(RequestCode.GET_CONSUME_STATS, null, new\n                {\n                    consumerGroup = Group,\n                    topic = queryTopic\n                }, true, cancellationToken).ConfigureAwait(false);\n                var consumerStates = ConsumerStatesSpecialJsonHandler(command.Payload);\n                //foreach (var (key, value) in consumerStates.OffsetTable) offsetTables.Add(key, value);\n                foreach (var item in consumerStates.OffsetTable)\n                {\n                    // 避免重复添加（同一个Broker上不同Topic的key可能相同）\n                    if (!offsetTables.ContainsKey(item.Key))\n                        offsetTables.Add(item.Key, item.Value);\n                }\n            }\n        }\n\n        // 表示没消费过\n        var neverConsumed = offsetTables.All(t => t.Value.ConsumerOffset == 0);\n\n        foreach (var store in qs)\n        {\n            if (store.Offset >= 0) continue;\n\n            // 先按BrokerName精确匹配，如果找不到（阿里云公网版BrokerName可能不一致），则按QueueId模糊匹配\n            var item = offsetTables.FirstOrDefault(t => t.Key.BrokerName == store.Queue.BrokerName && t.Key.QueueId == store.Queue.QueueId);\n            if (item.Value == null)\n            {\n                // 阿里云公网版RocketMQ，消费者状态返回的是真正brokerName，而前面Broker得到的是网关名\n                item = offsetTables.FirstOrDefault(t => t.Key.QueueId == store.Queue.QueueId);\n            }\n            var offsetTable = item.Value ?? new OffsetWrapperModel();\n            if (neverConsumed)\n            {\n                var offset = 0L;\n                if (FromLastOffset)\n                {\n                    offset = offsetTable.BrokerOffset;\n                    if (offset <= 0) offset = await QueryMaxOffset(store.Queue, cancellationToken).ConfigureAwait(false);\n                }\n                else\n                {\n                    offset = await QueryMinOffset(store.Queue, cancellationToken).ConfigureAwait(false);\n                }\n\n                store.Offset = store.CommitOffset = offset;\n                await UpdateOffset(store.Queue, offset, cancellationToken).ConfigureAwait(false);\n            }\n            else\n            {\n                var offset = offsetTable.ConsumerOffset;\n                if (offset <= 0) offset = await QueryOffset(store.Queue, cancellationToken).ConfigureAwait(false);\n\n                store.Offset = store.CommitOffset = offset;\n            }\n\n            WriteLog(\"初始化offset[{0}@{1}] Offset={2:n0}\", store.Queue.BrokerName, store.Queue.QueueId, store.Offset);\n        }\n    }\n\n    /// <summary>\n    /// 消费者状态信息特殊Json处理\n    /// </summary>\n    /// <param name=\"payload\">负载数据</param>\n    /// <returns></returns>\n    private ConsumerStatesModel ConsumerStatesSpecialJsonHandler(IPacket payload)\n    {\n        #region <cmd formate/>\n\n        // Apache RocketMQ 获取Consumer_States返回结果\n        // 返回消息格式不是正常的JSON需要特殊处理\n        // {\n        //     \"consumeTps\":0.0,\n        //     \"offsetTable\":{\n        //         {\"brokerName\":\"wh-sr-11-26\",\"queueId\":0,\"topic\":\"mip_topic_0\"}:{\"brokerOffset\":5,\"consumerOffset\":35,\"lastTimestamp\":0},\n        //         {\"brokerName\":\"wh-sr-11-26\",\"queueId\":1,\"topic\":\"mip_topic_0\"}:{\"brokerOffset\":5,\"consumerOffset\":35,\"lastTimestamp\":0}\n        //     }\n        // }\n\n        // 阿里版本 RocketMQ 获取Consumer_States返回结果\n        // 返回消息格式不是正常的JSON需要特殊处理\n        // {\n        //     \"consumeTps\":0.0,\n        //     \"offsetTable\":{\n        //         { \"brokerName\":\"cn-qingdao-public-share-05-2\",\"mainQueue\":false,\"queueGroupId\":-1,\"queueId\":5,\"topic\":\"mip_topic_0\"}:\n        //         { \"brokerOffset\":4,\"consumerOffset\":4,\"earliestUnPulledTimestamp\":0,\"earliestUnconsumedTimestamp\":0,\n        //         \"inFlightMsgCountEstimatedAccumulation\":0,\"lagEstimatedAccumulation\":0,\"lastTimestamp\":1647746454641,\"pullOffset\":4}\n        //     }\n        // }\n\n        #endregion\n\n        var cmdStr = payload.ToStr();\n        cmdStr = cmdStr[1..^1];\n\n        var indexOf = cmdStr.IndexOf('{') + 1;\n        var lastIndexOf = cmdStr.LastIndexOf('}');\n        cmdStr = cmdStr[indexOf..lastIndexOf];\n\n        var offsetArr = cmdStr.Split('}');\n        var offsetNew = (from offset in offsetArr\n                         where !String.IsNullOrWhiteSpace(offset)\n                         select String.Concat(offset.Trim(',').Trim(':'), \"}\")).ToList();\n\n        var consumerStatesModel = new ConsumerStatesModel() { OffsetTable = [] };\n\n        for (var i = 0; i < offsetNew.Count / 2; i++)\n        {\n            var list = offsetNew.Skip(i * 2).Take(2).ToList();\n\n            var messageQueue = list[0].ToJsonEntity<MessageQueueModel>();\n            var offsetWrapper = list[1].ToJsonEntity<OffsetWrapperModel>();\n            consumerStatesModel.OffsetTable.Add(messageQueue, offsetWrapper);\n        }\n\n        if (consumerStatesModel.OffsetTable.Count == 0)\n        {\n            WriteLog(\"无法解析消费者状态，可能是服务端版本不兼容，响应如下：\");\n            WriteLog(cmdStr);\n        }\n\n        return consumerStatesModel;\n    }\n\n    /// <summary>获取本地偏移存储路径</summary>\n    private String GetOffsetFilePath()\n    {\n        var path = OffsetStorePath;\n        if (String.IsNullOrEmpty(path))\n            path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, \".offsets\", $\"{Group}.json\");\n\n        return path;\n    }\n\n    /// <summary>从本地文件加载偏移。广播模式使用</summary>\n    private Dictionary<String, Int64> LoadLocalOffsets()\n    {\n        var file = GetOffsetFilePath();\n        if (!File.Exists(file)) return [];\n\n        try\n        {\n            var json = File.ReadAllText(file);\n            if (String.IsNullOrWhiteSpace(json)) return [];\n\n            return JsonHelper.Default.Read<Dictionary<String, Int64>>(json) ?? [];\n        }\n        catch\n        {\n            return [];\n        }\n    }\n\n    /// <summary>保存偏移到本地文件。广播模式使用</summary>\n    private void SaveLocalOffsets(QueueStore[] stores)\n    {\n        if (stores == null || stores.Length == 0) return;\n\n        var dic = new Dictionary<String, Int64>();\n        foreach (var st in stores)\n        {\n            if (st.Offset >= 0)\n            {\n                var key = $\"{st.Queue.Topic ?? Topic}@{st.Queue.BrokerName}@{st.Queue.QueueId}\";\n                dic[key] = st.Offset;\n            }\n        }\n\n        var file = GetOffsetFilePath();\n        var dir = Path.GetDirectoryName(file);\n        if (!String.IsNullOrEmpty(dir) && !Directory.Exists(dir))\n            Directory.CreateDirectory(dir);\n\n        File.WriteAllText(file, JsonHelper.Default.Write(dic));\n    }\n\n    #endregion\n\n    #region 下行指令\n\n    /// <summary>收到命令</summary>\n    /// <param name=\"cmd\"></param>\n    protected override Command OnReceive(Command cmd)\n    {\n        if (!cmd.Reply)\n        {\n            var code = (RequestCode)cmd.Header.Code;\n            switch (code)\n            {\n                case RequestCode.NOTIFY_CONSUMER_IDS_CHANGED:\n                    NotifyConsumerIdsChanged(cmd);\n                    break;\n                case RequestCode.RESET_CONSUMER_CLIENT_OFFSET:\n                    ResetOffset(cmd);\n                    break;\n                case RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT:\n                    GetConsumeStatus(cmd);\n                    break;\n                case RequestCode.GET_CONSUMER_RUNNING_INFO:\n                    return GetConsumerRunningInfo(cmd);\n                default:\n                    break;\n            }\n        }\n\n        return null;\n    }\n\n    private void NotifyConsumerIdsChanged(Command cmd) => _timer?.SetNext(-1);\n\n    private void ResetOffset(Command cmd)\n    {\n        var js = cmd.Payload?.ToStr();\n        if (js.IsNullOrEmpty()) return;\n\n        // 请求内容是一个奇怪的Json，Key是MessageQueue对象，Value是偏移量\n        var ss = js.Split(\",{\");\n        foreach (var item in ss)\n        {\n            var name = item.Substring(\"\\\"brokerName\\\":\", \",\").Trim('\\\"');\n            var qid = item.Substring(\"\\\"queueId\\\":\", \",\").ToInt();\n            var offset = item.TrimEnd('}').Substring(\"}:\", null).ToLong();\n\n            var mq = _Queues.FirstOrDefault(e => e.Queue.BrokerName == name & e.Queue.QueueId == qid);\n            if (mq != null) mq.Offset = offset;\n        }\n    }\n\n    private void GetConsumeStatus(Command cmd) { }\n\n    private Command GetConsumerRunningInfo(Command cmd)\n    {\n        var ci = new ConsumerRunningInfo();\n        var dic = new Dictionary<String, String>();\n        foreach (var pi in GetType().GetProperties())\n        {\n            if (pi.DeclaringType == typeof(DisposeBase)) continue;\n            if (pi.PropertyType.GetTypeCode() == TypeCode.Object) continue;\n\n            var val = pi.GetValue(this, null);\n            if (val != null) dic[pi.Name] = val + \"\";\n        }\n\n        var asm = Assembly.GetExecutingAssembly();\n        dic[\"PROP_CLIENT_VERSION\"] = asm.GetName().Version + \"\";\n        dic[\"PROP_CONSUMEORDERLY\"] = OrderConsume ? \"true\" : \"false\";\n        dic[\"PROP_CONSUMER_START_TIMESTAMP\"] = StartTime.ToInt() + \"\";\n        dic[\"PROP_CONSUME_TYPE\"] = \"CONSUME_PASSIVELY\";\n        dic[\"PROP_NAMESERVER_ADDR\"] = NameServerAddress;\n        dic[\"PROP_THREADPOOL_CORE_SIZE\"] = (_tasks?.Length ?? 1).ToString();\n        dic[\"messageModel\"] = \"CLUSTERING\";\n        ci.Properties = dic;\n\n        var sd = new SubscriptionData { Topic = Topic, };\n        ci.SubscriptionSet = GetEffectiveTopics().Select(t => new SubscriptionData { Topic = t }).ToArray();\n\n        var sb = new StringBuilder();\n        sb.Append('{');\n        if (_Queues != null)\n        {\n            sb.Append(\"\\\"mqTable\\\":{\");\n            for (var i = 0; i < _Queues.Length; i++)\n            {\n                if (i > 0) sb.Append(',');\n\n                var item = _Queues[i];\n\n                sb.Append(JsonHost.Write(item.Queue, false, false, true));\n                sb.Append(':');\n                sb.Append(JsonHost.Write(item, false, false, true));\n            }\n\n            sb.Append('}');\n        }\n        {\n            sb.Append(',');\n            sb.Append(\"\\\"properties\\\":\");\n            sb.Append(JsonHost.Write(ci.Properties));\n        }\n        {\n            sb.Append(',');\n            sb.Append(\"\\\"subscriptionSet\\\":\");\n            sb.Append(JsonHost.Write(ci.SubscriptionSet, false, false, true));\n        }\n        sb.Append('}');\n\n        var rs = cmd.CreateReply() as Command;\n        rs.Header.Language = \"DOTNET\";\n        rs.Payload = (ArrayPacket)sb.ToString().GetBytes();\n\n        return rs;\n    }\n\n    #endregion\n\n    #region 消费重试\n    /// <summary>将消费失败的消息发回Broker，进入RETRY Topic延迟后重新消费</summary>\n    /// <param name=\"msg\">消费失败的消息</param>\n    /// <param name=\"delayLevel\">延迟等级。0=由Broker决定，大于0指定等级（1~18）</param>\n    /// <param name=\"maxReconsumeTimes\">最大重试次数。-1表示使用默认值</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>是否成功</returns>\n    public async Task<Boolean> SendMessageBackAsync(MessageExt msg, Int32 delayLevel = 0, Int32 maxReconsumeTimes = -1, CancellationToken cancellationToken = default)\n    {\n        if (msg == null) throw new ArgumentNullException(nameof(msg));\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:SendMessageBack\", msg.MsgId);\n        try\n        {\n            var bk = GetBroker(msg.StoreHost?.Split(':').FirstOrDefault() ?? Brokers?.FirstOrDefault()?.Name);\n            if (bk == null)\n            {\n                // 尝试在所有Broker上发送\n                bk = Clients?.FirstOrDefault();\n            }\n            if (bk == null) return false;\n\n            var header = new\n            {\n                group = Group,\n                offset = msg.CommitLogOffset,\n                delayLevel = delayLevel > 0 ? delayLevel : RetryDelayLevel,\n                originMsgId = msg.MsgId ?? \"\",\n                originTopic = msg.Topic ?? Topic,\n                unitMode = UnitMode,\n                maxReconsumeTimes = maxReconsumeTimes >= 0 ? maxReconsumeTimes : MaxReconsumeTimes,\n            };\n\n            await bk.InvokeAsync(RequestCode.CONSUMER_SEND_MSG_BACK, null, header, true, cancellationToken).ConfigureAwait(false);\n            return true;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"消息回退失败：{0}\", ex.Message);\n            return false;\n        }\n    }\n\n    /// <summary>将消费失败的消息发回Broker，进入RETRY Topic延迟后重新消费</summary>\n    /// <param name=\"msg\">消费失败的消息</param>\n    /// <param name=\"delayLevel\">延迟等级</param>\n    /// <param name=\"maxReconsumeTimes\">最大重试次数</param>\n    /// <returns>是否成功</returns>\n    public Boolean SendMessageBack(MessageExt msg, Int32 delayLevel = 0, Int32 maxReconsumeTimes = -1)\n        => SendMessageBackAsync(msg, delayLevel, maxReconsumeTimes).ConfigureAwait(false).GetAwaiter().GetResult();\n    #endregion\n\n    #region Pop消费模式\n    /// <summary>Pop方式拉取消息。5.0新增的轻量消费模式，无需客户端Rebalance</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"queueId\">队列编号。-1表示由Broker自动分配</param>\n    /// <param name=\"maxNums\">最大拉取数</param>\n    /// <param name=\"invisibleTime\">不可见时间（毫秒），消息被拉取后在此时间内不会被其他消费者看到</param>\n    /// <param name=\"pollTime\">长轮询等待时间（毫秒）</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>拉取结果</returns>\n    public async Task<PullResult> PopMessageAsync(String brokerName, Int32 queueId = -1, Int32 maxNums = 32, Int64 invisibleTime = 60_000, Int32 pollTime = 15_000, CancellationToken cancellationToken = default)\n    {\n        if (String.IsNullOrEmpty(brokerName)) throw new ArgumentNullException(nameof(brokerName));\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:PopMessage\", brokerName);\n        try\n        {\n            var bk = GetBroker(brokerName);\n            if (bk == null) return null;\n\n            var header = new\n            {\n                consumerGroup = Group,\n                topic = Topic,\n                queueId,\n                maxMsgNums = maxNums,\n                invisibleTime,\n                pollTime,\n                bornTime = DateTime.Now.ToLong(),\n                initMode = FromLastOffset ? 1 : 0,\n                expType = ExpressionType,\n                exp = Subscription,\n            };\n\n            var rs = await bk.InvokeAsync(RequestCode.POP_MESSAGE, null, header, true, cancellationToken).ConfigureAwait(false);\n            if (rs?.Header == null) return null;\n\n            var pr = new PullResult();\n            if (rs.Header.Code == 0)\n                pr.Status = PullStatus.Found;\n            else if (rs.Header.Code == (Int32)ResponseCode.PULL_NOT_FOUND)\n                pr.Status = PullStatus.NoNewMessage;\n            else\n                pr.Status = PullStatus.Unknown;\n\n            pr.Read(rs.Header?.ExtFields);\n\n            if (rs.Payload != null) pr.Messages = MessageExt.ReadAll(rs.Payload).ToArray();\n\n            return pr;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>确认Pop消息消费完成</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"extraInfo\">Pop检查点信息，即消息属性中的POP_CK字段值</param>\n    /// <param name=\"offset\">消息在Queue中的偏移量</param>\n    /// <param name=\"queueId\">队列编号</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Boolean> AckMessageAsync(String brokerName, String extraInfo, Int64 offset, Int32 queueId = -1, CancellationToken cancellationToken = default)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:AckMessage\", offset);\n        try\n        {\n            var bk = GetBroker(brokerName);\n            if (bk == null) return false;\n\n            var header = new\n            {\n                consumerGroup = Group,\n                topic = Topic,\n                extraInfo,\n                offset,\n                queueId,\n            };\n\n            await bk.InvokeAsync(RequestCode.ACK_MESSAGE, null, header, true, cancellationToken).ConfigureAwait(false);\n            return true;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"确认Pop消息失败：{0}\", ex.Message);\n            return false;\n        }\n    }\n\n    /// <summary>确认Pop消息消费完成。自动从消息属性中提取Pop检查点信息（POP_CK）</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"msg\">通过Pop方式拉取的消息</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public Task<Boolean> AckMessageAsync(String brokerName, MessageExt msg, CancellationToken cancellationToken = default)\n    {\n        if (msg == null) throw new ArgumentNullException(nameof(msg));\n        if (String.IsNullOrEmpty(msg.PopCheckPoint)) throw new ArgumentException(\"消息不含Pop检查点信息（POP_CK属性缺失），请确认该消息是通过Pop方式拉取的。\", nameof(msg));\n\n        return AckMessageAsync(brokerName, msg.PopCheckPoint, msg.QueueOffset, msg.QueueId, cancellationToken);\n    }\n\n    /// <summary>修改Pop消息不可见时间，延长消费处理窗口</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"extraInfo\">Pop检查点信息，即消息属性中的POP_CK字段值</param>\n    /// <param name=\"offset\">消息在Queue中的偏移量</param>\n    /// <param name=\"invisibleTime\">新的不可见时间（毫秒）</param>\n    /// <param name=\"queueId\">队列编号</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Boolean> ChangeInvisibleTimeAsync(String brokerName, String extraInfo, Int64 offset, Int64 invisibleTime, Int32 queueId = -1, CancellationToken cancellationToken = default)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:ChangeInvisibleTime\", offset);\n        try\n        {\n            var bk = GetBroker(brokerName);\n            if (bk == null) return false;\n\n            var header = new\n            {\n                consumerGroup = Group,\n                topic = Topic,\n                extraInfo,\n                offset,\n                invisibleTime,\n                queueId,\n            };\n\n            await bk.InvokeAsync(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, null, header, true, cancellationToken).ConfigureAwait(false);\n            return true;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"修改不可见时间失败：{0}\", ex.Message);\n            return false;\n        }\n    }\n\n    /// <summary>修改Pop消息不可见时间，延长消费处理窗口。自动从消息属性中提取Pop检查点信息（POP_CK）</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"msg\">通过Pop方式拉取的消息</param>\n    /// <param name=\"invisibleTime\">新的不可见时间（毫秒）</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public Task<Boolean> ChangeInvisibleTimeAsync(String brokerName, MessageExt msg, Int64 invisibleTime, CancellationToken cancellationToken = default)\n    {\n        if (msg == null) throw new ArgumentNullException(nameof(msg));\n        if (String.IsNullOrEmpty(msg.PopCheckPoint)) throw new ArgumentException(\"消息不含Pop检查点信息（POP_CK属性缺失），请确认该消息是通过Pop方式拉取的。\", nameof(msg));\n\n        return ChangeInvisibleTimeAsync(brokerName, msg.PopCheckPoint, msg.QueueOffset, invisibleTime, msg.QueueId, cancellationToken);\n    }\n\n    /// <summary>批量确认Pop消息消费完成</summary>\n    /// <param name=\"brokerName\">Broker名称</param>\n    /// <param name=\"ackEntries\">批量确认条目列表，每个条目包含extraInfo和offset</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>成功确认的数量</returns>\n    public async Task<Int32> BatchAckMessageAsync(String brokerName, IList<(String extraInfo, Int64 offset)> ackEntries, CancellationToken cancellationToken = default)\n    {\n        if (String.IsNullOrEmpty(brokerName)) throw new ArgumentNullException(nameof(brokerName));\n        if (ackEntries == null || ackEntries.Count == 0) return 0;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:BatchAckMessage\", ackEntries.Count);\n        try\n        {\n            var bk = GetBroker(brokerName);\n            if (bk == null) return 0;\n\n            // 构造批量确认请求体：JSON数组格式\n            var entries = ackEntries.Select(e => new { extraInfo = e.extraInfo, offset = e.offset }).ToArray();\n            var body = JsonHost.Write(entries, false, false, false).GetBytes();\n\n            var header = new\n            {\n                consumerGroup = Group,\n                topic = Topic,\n            };\n\n            await bk.InvokeAsync(RequestCode.BATCH_ACK_MESSAGE, body, header, true, cancellationToken).ConfigureAwait(false);\n            return ackEntries.Count;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"批量确认Pop消息失败：{0}\", ex.Message);\n            return 0;\n        }\n    }\n    #endregion\n\n#if NETSTANDARD2_1_OR_GREATER\n    #region gRPC消费模式\n    /// <summary>通过gRPC协议接收消息。需要先设置GrpcProxyAddress</summary>\n    /// <param name=\"queue\">gRPC消息队列</param>\n    /// <param name=\"filterExpression\">过滤表达式。默认Tag=*</param>\n    /// <param name=\"batchSize\">批量大小。默认32</param>\n    /// <param name=\"invisibleDuration\">不可见时间。默认30秒</param>\n    /// <param name=\"longPollingTimeout\">长轮询超时。默认20秒</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>gRPC消息列表</returns>\n    public async Task<IList<Grpc.GrpcMessage>> ReceiveMessageViaGrpcAsync(\n        Grpc.GrpcMessageQueue queue,\n        Grpc.GrpcFilterExpression filterExpression = null,\n        Int32 batchSize = 32,\n        TimeSpan? invisibleDuration = null,\n        TimeSpan? longPollingTimeout = null,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:ReceiveMessage:grpc\");\n        try\n        {\n            return await _GrpcService.ReceiveMessageAsync(\n                Group,\n                queue,\n                filterExpression,\n                batchSize,\n                invisibleDuration,\n                longPollingTimeout,\n                cancellationToken\n            ).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>通过gRPC协议确认消息</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"entries\">确认条目列表</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>确认结果</returns>\n    public async Task<Grpc.AckMessageResponse> AckMessageViaGrpcAsync(\n        String topic,\n        IList<Grpc.AckMessageEntry> entries,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:AckMessage:grpc\");\n        try\n        {\n            return await _GrpcService.AckMessageAsync(topic, Group, entries, cancellationToken).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>通过gRPC协议查询队列分配</summary>\n    /// <param name=\"topic\">主题。默认使用当前Topic</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>分配结果</returns>\n    public async Task<Grpc.QueryAssignmentResponse> QueryAssignmentViaGrpcAsync(String topic = null, CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.QueryAssignmentAsync(topic ?? Topic, Group, cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>通过gRPC协议修改消息不可见时间</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"receiptHandle\">收据句柄</param>\n    /// <param name=\"messageId\">消息ID</param>\n    /// <param name=\"invisibleDuration\">新的不可见时间</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Grpc.ChangeInvisibleDurationResponse> ChangeInvisibleDurationViaGrpcAsync(\n        String topic,\n        String receiptHandle,\n        String messageId,\n        TimeSpan invisibleDuration,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.ChangeInvisibleDurationAsync(topic, Group, receiptHandle, messageId, invisibleDuration, cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>通过gRPC协议发送心跳</summary>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Grpc.HeartbeatResponse> HeartbeatViaGrpcAsync(CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.HeartbeatAsync(Group, Grpc.GrpcClientType.SIMPLE_CONSUMER, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n#endif\n\n    #region Request-Reply 请求响应模式\n    /// <summary>发送回复消息</summary>\n    /// <param name=\"requestMessage\">原始请求消息</param>\n    /// <param name=\"replyBody\">回复消息体</param>\n    /// <returns>发送结果</returns>\n    public virtual SendResult SendReply(MessageExt requestMessage, Object replyBody)\n    {\n        if (requestMessage == null) throw new ArgumentNullException(nameof(requestMessage));\n        if (replyBody == null) throw new ArgumentNullException(nameof(replyBody));\n\n        // 检查是否有回复地址\n        var replyToClient = requestMessage.ReplyToClient;\n        if (String.IsNullOrEmpty(replyToClient))\n        {\n            throw new InvalidOperationException(\"Request message does not have ReplyToClient property\");\n        }\n\n        var correlationId = requestMessage.CorrelationId;\n        if (String.IsNullOrEmpty(correlationId))\n        {\n            throw new InvalidOperationException(\"Request message does not have CorrelationId property\");\n        }\n\n        // 创建回复消息\n        var replyMessage = new Message\n        {\n            Topic = requestMessage.Topic,\n            CorrelationId = correlationId,\n            MessageType = \"REPLY\"\n        };\n        replyMessage.SetBody(replyBody);\n\n        // 使用临时Producer发送回复\n        using var producer = new Producer\n        {\n            NameServerAddress = NameServerAddress,\n            Topic = requestMessage.Topic,\n            Group = Group + \"_REPLY\",\n            ClientIP = ClientIP,\n            InstanceName = InstanceName,\n            Log = Log,\n            Tracer = Tracer\n        };\n        producer.Start();\n\n        return producer.Publish(replyMessage);\n    }\n\n    /// <summary>发送回复消息(异步)</summary>\n    /// <param name=\"requestMessage\">原始请求消息</param>\n    /// <param name=\"replyBody\">回复消息体</param>\n    /// <param name=\"cancellationToken\">取消令牌</param>\n    /// <returns>发送结果</returns>\n    public virtual async Task<SendResult> SendReplyAsync(MessageExt requestMessage, Object replyBody, CancellationToken cancellationToken = default)\n    {\n        if (requestMessage == null) throw new ArgumentNullException(nameof(requestMessage));\n        if (replyBody == null) throw new ArgumentNullException(nameof(replyBody));\n\n        // 检查是否有回复地址\n        var replyToClient = requestMessage.ReplyToClient;\n        if (String.IsNullOrEmpty(replyToClient))\n        {\n            throw new InvalidOperationException(\"Request message does not have ReplyToClient property\");\n        }\n\n        var correlationId = requestMessage.CorrelationId;\n        if (String.IsNullOrEmpty(correlationId))\n        {\n            throw new InvalidOperationException(\"Request message does not have CorrelationId property\");\n        }\n\n        // 创建回复消息\n        var replyMessage = new Message\n        {\n            Topic = requestMessage.Topic,\n            CorrelationId = correlationId,\n            MessageType = \"REPLY\"\n        };\n        replyMessage.SetBody(replyBody);\n\n        // 使用临时Producer发送回复\n        using var producer = new Producer\n        {\n            NameServerAddress = NameServerAddress,\n            Topic = requestMessage.Topic,\n            Group = Group + \"_REPLY\",\n            ClientIP = ClientIP,\n            InstanceName = InstanceName,\n            Log = Log,\n            Tracer = Tracer\n        };\n        producer.Start();\n\n        return await producer.PublishAsync(replyMessage, null, cancellationToken).ConfigureAwait(false);\n    }\n\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/GrpcClient.cs",
    "content": "﻿#if NETSTANDARD2_1_OR_GREATER\nusing System.Net;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing NewLife.Log;\n\nnamespace NewLife.RocketMQ.Grpc;\n\n/// <summary>轻量级gRPC客户端。基于HttpClient HTTP/2实现gRPC协议</summary>\n/// <remarks>\n/// gRPC协议格式：\n/// - 传输层: HTTP/2\n/// - 请求: POST /package.Service/Method\n/// - 内容类型: application/grpc\n/// - 消息帧: [1字节压缩标志][4字节大端序长度][Protobuf消息体]\n/// \n/// 不依赖任何gRPC包，完全使用 HttpClient + 手工帧编码实现。\n/// </remarks>\npublic class GrpcClient : IDisposable\n{\n    #region 属性\n    /// <summary>目标地址。格式如 http://host:port</summary>\n    public String Address { get; set; }\n\n    /// <summary>超时时间（毫秒）。默认30000</summary>\n    public Int32 Timeout { get; set; } = 30_000;\n\n    /// <summary>访问令牌。用于认证</summary>\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥</summary>\n    public String SecretKey { get; set; }\n\n    /// <summary>客户端标识</summary>\n    public String ClientId { get; set; }\n\n    /// <summary>命名空间</summary>\n    public String Namespace { get; set; }\n\n    /// <summary>日志</summary>\n    public ILog Log { get; set; } = Logger.Null;\n\n    /// <summary>性能追踪</summary>\n    public ITracer Tracer { get; set; }\n\n    private HttpClient _client;\n    #endregion\n\n    #region 构造\n    /// <summary>实例化gRPC客户端</summary>\n    public GrpcClient() { }\n\n    /// <summary>实例化gRPC客户端</summary>\n    /// <param name=\"address\">目标地址</param>\n    public GrpcClient(String address) => Address = address;\n\n    /// <summary>释放</summary>\n    public void Dispose()\n    {\n        _client?.Dispose();\n        _client = null;\n    }\n    #endregion\n\n    #region 方法\n    /// <summary>确保HttpClient已创建</summary>\n    private HttpClient EnsureClient()\n    {\n        if (_client != null) return _client;\n        lock (this)\n        {\n            if (_client != null) return _client;\n\n            var handler = new HttpClientHandler\n            {\n                // gRPC通常不需要验证服务端证书（内网）\n                ServerCertificateCustomValidationCallback = (_, _, _, _) => true,\n            };\n\n            var client = new HttpClient(handler)\n            {\n                BaseAddress = new Uri(Address),\n                Timeout = TimeSpan.FromMilliseconds(Timeout),\n            };\n\n            _client = client;\n            return client;\n        }\n    }\n\n    /// <summary>Unary调用。发送一个请求，接收一个响应</summary>\n    /// <param name=\"service\">服务名。如 apache.rocketmq.v2.MessagingService</param>\n    /// <param name=\"method\">方法名。如 SendMessage</param>\n    /// <param name=\"request\">请求消息</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>响应数据</returns>\n    public async Task<Byte[]> UnaryCallAsync(String service, String method, Byte[] request, CancellationToken cancellationToken = default)\n    {\n        var client = EnsureClient();\n        var path = $\"/{service}/{method}\";\n\n        using var span = Tracer?.NewSpan($\"grpc:{method}\", path);\n        try\n        {\n            // 构造gRPC帧：[1字节压缩标志=0][4字节大端序长度][数据]\n            var frame = FrameEncode(request);\n\n            var httpRequest = new HttpRequestMessage(HttpMethod.Post, path);\n            httpRequest.Version = new Version(2, 0);\n\n            // gRPC 必需的 headers\n            httpRequest.Content = new ByteArrayContent(frame);\n            httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(\"application/grpc\");\n            httpRequest.Headers.Add(\"te\", \"trailers\");\n\n            // 认证和元数据\n            SetMetadata(httpRequest);\n\n            var response = await client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);\n\n            // 读取响应\n            var responseData = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);\n\n            // 检查 grpc-status\n            CheckGrpcStatus(response);\n\n            // 解码gRPC帧\n            return FrameDecode(responseData);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>Server Streaming调用。发送一个请求，流式接收多个响应</summary>\n    /// <param name=\"service\">服务名</param>\n    /// <param name=\"method\">方法名</param>\n    /// <param name=\"request\">请求消息</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>响应数据流</returns>\n    public async IAsyncEnumerable<Byte[]> ServerStreamingCallAsync(String service, String method, Byte[] request, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)\n    {\n        var client = EnsureClient();\n        var path = $\"/{service}/{method}\";\n\n        using var span = Tracer?.NewSpan($\"grpc:{method}:stream\", path);\n        try\n        {\n            var frame = FrameEncode(request);\n\n            var httpRequest = new HttpRequestMessage(HttpMethod.Post, path);\n            httpRequest.Version = new Version(2, 0);\n            httpRequest.Content = new ByteArrayContent(frame);\n            httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue(\"application/grpc\");\n            httpRequest.Headers.Add(\"te\", \"trailers\");\n\n            SetMetadata(httpRequest);\n\n            var response = await client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);\n            var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);\n\n            // 流式读取多个gRPC帧\n            var headerBuf = new Byte[5];\n            while (!cancellationToken.IsCancellationRequested)\n            {\n                // 读取5字节帧头\n                var read = await ReadFullAsync(stream, headerBuf, 0, 5, cancellationToken).ConfigureAwait(false);\n                if (read < 5) break;\n\n                // 解析帧头\n                var compressed = headerBuf[0] != 0;\n                var length = (Int32)((headerBuf[1] << 24) | (headerBuf[2] << 16) | (headerBuf[3] << 8) | headerBuf[4]);\n                if (length <= 0) continue;\n\n                // 读取消息体\n                var body = new Byte[length];\n                read = await ReadFullAsync(stream, body, 0, length, cancellationToken).ConfigureAwait(false);\n                if (read < length) break;\n\n                yield return body;\n            }\n        }\n        finally\n        {\n            span?.Dispose();\n        }\n    }\n    #endregion\n\n    #region 辅助\n    /// <summary>设置gRPC元数据（认证等）</summary>\n    /// <param name=\"request\">请求</param>\n    private void SetMetadata(HttpRequestMessage request)\n    {\n        if (!String.IsNullOrEmpty(ClientId))\n            request.Headers.TryAddWithoutValidation(\"x-mq-client-id\", ClientId);\n\n        if (!String.IsNullOrEmpty(Namespace))\n            request.Headers.TryAddWithoutValidation(\"x-mq-namespace\", Namespace);\n\n        // RocketMQ 5.x gRPC 使用 authorization header\n        if (!String.IsNullOrEmpty(AccessKey) && !String.IsNullOrEmpty(SecretKey))\n        {\n            var timestamp = DateTime.UtcNow.ToString(\"yyyyMMddTHHmmssZ\");\n            request.Headers.TryAddWithoutValidation(\"x-mq-date-time\", timestamp);\n            request.Headers.TryAddWithoutValidation(\"authorization\",\n                $\"MQv2-HMAC-SHA1 Credential={AccessKey}, SignedHeaders=x-mq-date-time, Signature={ComputeSignature(timestamp)}\");\n        }\n\n        request.Headers.TryAddWithoutValidation(\"x-mq-language\", \"DOTNET\");\n        request.Headers.TryAddWithoutValidation(\"x-mq-protocol\", \"grpc\");\n    }\n\n    /// <summary>计算签名</summary>\n    /// <param name=\"dateTime\">时间戳</param>\n    /// <returns></returns>\n    private String ComputeSignature(String dateTime)\n    {\n        using var hmac = new System.Security.Cryptography.HMACSHA1(System.Text.Encoding.UTF8.GetBytes(SecretKey));\n        var data = System.Text.Encoding.UTF8.GetBytes(dateTime);\n        return Convert.ToBase64String(hmac.ComputeHash(data));\n    }\n\n    /// <summary>gRPC帧编码。[1字节压缩标志][4字节大端序长度][数据]</summary>\n    /// <param name=\"data\">数据</param>\n    /// <returns></returns>\n    public static Byte[] FrameEncode(Byte[] data)\n    {\n        var len = data?.Length ?? 0;\n        var frame = new Byte[5 + len];\n        frame[0] = 0; // 不压缩\n        frame[1] = (Byte)(len >> 24);\n        frame[2] = (Byte)(len >> 16);\n        frame[3] = (Byte)(len >> 8);\n        frame[4] = (Byte)len;\n        if (len > 0) Buffer.BlockCopy(data, 0, frame, 5, len);\n        return frame;\n    }\n\n    /// <summary>gRPC帧解码。跳过5字节帧头返回消息体</summary>\n    /// <param name=\"frame\">帧数据</param>\n    /// <returns></returns>\n    public static Byte[] FrameDecode(Byte[] frame)\n    {\n        if (frame == null || frame.Length < 5) return [];\n\n        var length = (frame[1] << 24) | (frame[2] << 16) | (frame[3] << 8) | frame[4];\n        if (length <= 0 || frame.Length < 5 + length) return [];\n\n        var data = new Byte[length];\n        Buffer.BlockCopy(frame, 5, data, 0, length);\n        return data;\n    }\n\n    /// <summary>检查gRPC响应状态</summary>\n    /// <param name=\"response\">HTTP响应</param>\n    private static void CheckGrpcStatus(HttpResponseMessage response)\n    {\n        // grpc-status 可能在 trailer 或 header 中\n        var grpcStatus = -1;\n        String grpcMessage = null;\n\n        // TrailingHeaders 在 .NET 5+ 可用，低版本 try-catch 处理\n        try\n        {\n#if NET5_0_OR_GREATER\n            if (response.TrailingHeaders.TryGetValues(\"grpc-status\", out var statusValues))\n            {\n                var statusStr = statusValues.FirstOrDefault();\n                if (statusStr != null) Int32.TryParse(statusStr, out grpcStatus);\n            }\n            if (response.TrailingHeaders.TryGetValues(\"grpc-message\", out var msgValues))\n            {\n                grpcMessage = msgValues.FirstOrDefault();\n            }\n#endif\n        }\n        catch { }\n\n        // 也检查普通 header（有些服务器在 header 中返回）\n        if (grpcStatus < 0 && response.Headers.TryGetValues(\"grpc-status\", out var headerStatusValues))\n        {\n            var statusStr = headerStatusValues.FirstOrDefault();\n            if (statusStr != null) Int32.TryParse(statusStr, out grpcStatus);\n        }\n\n        // grpc-status: 0 = OK\n        if (grpcStatus > 0)\n        {\n            throw new InvalidOperationException($\"gRPC error {grpcStatus}: {grpcMessage ?? \"Unknown error\"}\");\n        }\n    }\n\n    /// <summary>确保从流中读取指定长度的数据</summary>\n    private static async Task<Int32> ReadFullAsync(Stream stream, Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)\n    {\n        var totalRead = 0;\n        while (totalRead < count)\n        {\n            var read = await stream.ReadAsync(buffer, offset + totalRead, count - totalRead, cancellationToken).ConfigureAwait(false);\n            if (read == 0) break;\n            totalRead += read;\n        }\n        return totalRead;\n    }\n    #endregion\n}\n#endif\n"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/GrpcEnums.cs",
    "content": "﻿namespace NewLife.RocketMQ.Grpc;\n\n/// <summary>gRPC响应状态码</summary>\n/// <remarks>参考 apache.rocketmq.v2.Code</remarks>\npublic enum GrpcCode\n{\n    /// <summary>成功</summary>\n    OK = 20000,\n\n    /// <summary>多个结果</summary>\n    MULTIPLE_RESULTS = 30000,\n\n    /// <summary>错误请求</summary>\n    BAD_REQUEST = 40000,\n\n    /// <summary>非法访问点</summary>\n    ILLEGAL_ACCESS_POINT = 40001,\n\n    /// <summary>非法主题</summary>\n    ILLEGAL_TOPIC = 40002,\n\n    /// <summary>非法消费组</summary>\n    ILLEGAL_CONSUMER_GROUP = 40003,\n\n    /// <summary>非法消息标签</summary>\n    ILLEGAL_MESSAGE_TAG = 40004,\n\n    /// <summary>非法消息Key</summary>\n    ILLEGAL_MESSAGE_KEY = 40005,\n\n    /// <summary>非法消息分组</summary>\n    ILLEGAL_MESSAGE_GROUP = 40006,\n\n    /// <summary>非法消息属性Key</summary>\n    ILLEGAL_MESSAGE_PROPERTY_KEY = 40007,\n\n    /// <summary>无效事务ID</summary>\n    INVALID_TRANSACTION_ID = 40008,\n\n    /// <summary>非法消息ID</summary>\n    ILLEGAL_MESSAGE_ID = 40009,\n\n    /// <summary>非法过滤表达式</summary>\n    ILLEGAL_FILTER_EXPRESSION = 40010,\n\n    /// <summary>非法不可见时间</summary>\n    ILLEGAL_INVISIBLE_TIME = 40011,\n\n    /// <summary>非法投递时间</summary>\n    ILLEGAL_DELIVERY_TIME = 40012,\n\n    /// <summary>无效收据句柄</summary>\n    INVALID_RECEIPT_HANDLE = 40013,\n\n    /// <summary>消息属性与类型冲突</summary>\n    MESSAGE_PROPERTY_CONFLICT_WITH_TYPE = 40014,\n\n    /// <summary>未认证</summary>\n    UNAUTHORIZED = 40100,\n\n    /// <summary>禁止</summary>\n    FORBIDDEN = 40300,\n\n    /// <summary>未找到</summary>\n    NOT_FOUND = 40400,\n\n    /// <summary>消息未找到</summary>\n    MESSAGE_NOT_FOUND = 40401,\n\n    /// <summary>主题未找到</summary>\n    TOPIC_NOT_FOUND = 40402,\n\n    /// <summary>消费组未找到</summary>\n    CONSUMER_GROUP_NOT_FOUND = 40403,\n\n    /// <summary>请求超时</summary>\n    REQUEST_TIMEOUT = 40800,\n\n    /// <summary>消息体过大</summary>\n    PAYLOAD_TOO_LARGE = 41300,\n\n    /// <summary>前置条件失败</summary>\n    PRECONDITION_FAILED = 42800,\n\n    /// <summary>请求过于频繁</summary>\n    TOO_MANY_REQUESTS = 42900,\n\n    /// <summary>内部错误</summary>\n    INTERNAL_ERROR = 50000,\n\n    /// <summary>内部服务器错误</summary>\n    INTERNAL_SERVER_ERROR = 50001,\n\n    /// <summary>不支持</summary>\n    UNSUPPORTED = 50100,\n\n    /// <summary>消费消息失败</summary>\n    FAILED_TO_CONSUME_MESSAGE = 60000,\n}\n\n/// <summary>消息类型</summary>\npublic enum GrpcMessageType\n{\n    /// <summary>普通消息</summary>\n    NORMAL = 0,\n\n    /// <summary>顺序消息（FIFO）</summary>\n    FIFO = 1,\n\n    /// <summary>延迟消息</summary>\n    DELAY = 2,\n\n    /// <summary>事务消息</summary>\n    TRANSACTION = 3,\n}\n\n/// <summary>客户端类型</summary>\npublic enum GrpcClientType\n{\n    /// <summary>生产者</summary>\n    PRODUCER = 1,\n\n    /// <summary>推送消费者</summary>\n    PUSH_CONSUMER = 2,\n\n    /// <summary>简单消费者</summary>\n    SIMPLE_CONSUMER = 3,\n\n    /// <summary>Pull消费者</summary>\n    PULL_CONSUMER = 4,\n}\n\n/// <summary>地址类型</summary>\npublic enum AddressScheme\n{\n    /// <summary>IPv4</summary>\n    IPv4 = 0,\n\n    /// <summary>IPv6</summary>\n    IPv6 = 1,\n\n    /// <summary>域名</summary>\n    DOMAIN_NAME = 2,\n}\n\n/// <summary>队列权限</summary>\npublic enum GrpcPermission\n{\n    /// <summary>无权限</summary>\n    NONE = 0,\n\n    /// <summary>只读</summary>\n    READ = 1,\n\n    /// <summary>只写</summary>\n    WRITE = 2,\n\n    /// <summary>读写</summary>\n    READ_WRITE = 3,\n}\n\n/// <summary>过滤类型</summary>\npublic enum GrpcFilterType\n{\n    /// <summary>Tag过滤</summary>\n    TAG = 1,\n\n    /// <summary>SQL过滤</summary>\n    SQL = 2,\n}\n\n/// <summary>摘要类型</summary>\npublic enum GrpcDigestType\n{\n    /// <summary>CRC32</summary>\n    CRC32 = 0,\n\n    /// <summary>MD5</summary>\n    MD5 = 1,\n\n    /// <summary>SHA1</summary>\n    SHA1 = 2,\n}\n\n/// <summary>消息编码</summary>\npublic enum GrpcEncoding\n{\n    /// <summary>不压缩</summary>\n    IDENTITY = 0,\n\n    /// <summary>GZIP压缩</summary>\n    GZIP = 1,\n}\n\n/// <summary>事务来源</summary>\npublic enum GrpcTransactionSource\n{\n    /// <summary>服务端回查</summary>\n    SOURCE_SERVER_CHECK = 0,\n\n    /// <summary>客户端提交</summary>\n    SOURCE_CLIENT = 1,\n}\n\n/// <summary>事务决议</summary>\npublic enum GrpcTransactionResolution\n{\n    /// <summary>提交</summary>\n    COMMIT = 0,\n\n    /// <summary>回滚</summary>\n    ROLLBACK = 1,\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/GrpcMessagingService.cs",
    "content": "﻿#if NETSTANDARD2_1_OR_GREATER\nusing NewLife.Buffers;\nusing NewLife.Log;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Grpc;\n\n/// <summary>RocketMQ 5.x gRPC 消息服务客户端</summary>\n/// <remarks>\n/// 封装 apache.rocketmq.v2.MessagingService 的 gRPC 调用，\n/// 提供路由查询、消息发送/接收、确认、事务、心跳等功能。\n/// \n/// 对应 RocketMQ 5.x Proxy 的 gRPC API。\n/// </remarks>\npublic class GrpcMessagingService : IDisposable\n{\n    #region 常量\n    private const String ServiceName = \"apache.rocketmq.v2.MessagingService\";\n    #endregion\n\n    #region 属性\n    /// <summary>gRPC客户端</summary>\n    public GrpcClient Client { get; set; }\n\n    /// <summary>命名空间</summary>\n    public String Namespace { get; set; }\n\n    /// <summary>日志</summary>\n    public ILog Log { get; set; } = Logger.Null;\n\n    /// <summary>性能追踪</summary>\n    public ITracer Tracer { get; set; }\n    #endregion\n\n    #region 构造\n    /// <summary>实例化gRPC消息服务客户端</summary>\n    public GrpcMessagingService() { }\n\n    /// <summary>实例化gRPC消息服务客户端</summary>\n    /// <param name=\"address\">Proxy地址。如 http://host:8081</param>\n    public GrpcMessagingService(String address)\n    {\n        Client = new GrpcClient(address) { Log = Log, Tracer = Tracer };\n    }\n\n    /// <summary>释放</summary>\n    public void Dispose() => Client?.Dispose();\n    #endregion\n\n    #region 路由\n    /// <summary>查询主题路由</summary>\n    /// <param name=\"topic\">主题名</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>路由信息（消息队列列表）</returns>\n    public async Task<QueryRouteResponse> QueryRouteAsync(String topic, CancellationToken cancellationToken = default)\n    {\n        var request = new QueryRouteRequest\n        {\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n        };\n\n        return await InvokeAsync<QueryRouteRequest, QueryRouteResponse>(\"QueryRoute\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 发送消息\n    /// <summary>发送消息</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"tag\">标签</param>\n    /// <param name=\"keys\">消息Key列表</param>\n    /// <param name=\"properties\">用户属性</param>\n    /// <param name=\"messageGroup\">消息分组（FIFO消息）</param>\n    /// <param name=\"deliveryTimestamp\">定时投递时间（延迟消息）</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>发送结果</returns>\n    public async Task<SendMessageResponse> SendMessageAsync(\n        String topic,\n        Byte[] body,\n        String tag = null,\n        IList<String> keys = null,\n        IDictionary<String, String> properties = null,\n        String messageGroup = null,\n        DateTime? deliveryTimestamp = null,\n        CancellationToken cancellationToken = default)\n    {\n        var sysProps = new GrpcSystemProperties\n        {\n            Tag = tag,\n            MessageType = GrpcMessageType.NORMAL,\n            BornTimestamp = DateTime.UtcNow,\n            BornHost = Environment.MachineName,\n        };\n\n        if (keys != null && keys.Count > 0)\n            sysProps.Keys = new List<String>(keys);\n\n        // 根据参数判断消息类型\n        if (!String.IsNullOrEmpty(messageGroup))\n        {\n            sysProps.MessageType = GrpcMessageType.FIFO;\n            sysProps.MessageGroup = messageGroup;\n        }\n        else if (deliveryTimestamp != null)\n        {\n            sysProps.MessageType = GrpcMessageType.DELAY;\n            sysProps.DeliveryTimestamp = deliveryTimestamp;\n        }\n\n        var msg = new GrpcMessage\n        {\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            SystemProperties = sysProps,\n            Body = body,\n        };\n\n        if (properties != null)\n        {\n            foreach (var kv in properties)\n            {\n                msg.UserProperties[kv.Key] = kv.Value;\n            }\n        }\n\n        var request = new SendMessageRequest();\n        request.Messages.Add(msg);\n\n        return await InvokeAsync<SendMessageRequest, SendMessageResponse>(\"SendMessage\", request, cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>发送事务消息（半消息）</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"tag\">标签</param>\n    /// <param name=\"keys\">消息Key列表</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>发送结果</returns>\n    public async Task<SendMessageResponse> SendTransactionMessageAsync(\n        String topic,\n        Byte[] body,\n        String tag = null,\n        IList<String> keys = null,\n        CancellationToken cancellationToken = default)\n    {\n        var sysProps = new GrpcSystemProperties\n        {\n            Tag = tag,\n            MessageType = GrpcMessageType.TRANSACTION,\n            BornTimestamp = DateTime.UtcNow,\n            BornHost = Environment.MachineName,\n        };\n\n        if (keys != null && keys.Count > 0)\n            sysProps.Keys = new List<String>(keys);\n\n        var msg = new GrpcMessage\n        {\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            SystemProperties = sysProps,\n            Body = body,\n        };\n\n        var request = new SendMessageRequest();\n        request.Messages.Add(msg);\n\n        return await InvokeAsync<SendMessageRequest, SendMessageResponse>(\"SendMessage\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 接收消息\n    /// <summary>查询队列分配</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>分配结果</returns>\n    public async Task<QueryAssignmentResponse> QueryAssignmentAsync(String topic, String group, CancellationToken cancellationToken = default)\n    {\n        var request = new QueryAssignmentRequest\n        {\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n        };\n\n        return await InvokeAsync<QueryAssignmentRequest, QueryAssignmentResponse>(\"QueryAssignment\", request, cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>接收消息（Server Streaming）</summary>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"queue\">消息队列</param>\n    /// <param name=\"filterExpression\">过滤表达式</param>\n    /// <param name=\"batchSize\">批量大小</param>\n    /// <param name=\"invisibleDuration\">不可见时间</param>\n    /// <param name=\"longPollingTimeout\">长轮询超时</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>消息列表</returns>\n    public async Task<IList<GrpcMessage>> ReceiveMessageAsync(\n        String group,\n        GrpcMessageQueue queue,\n        GrpcFilterExpression filterExpression = null,\n        Int32 batchSize = 32,\n        TimeSpan? invisibleDuration = null,\n        TimeSpan? longPollingTimeout = null,\n        CancellationToken cancellationToken = default)\n    {\n        var request = new ReceiveMessageRequest\n        {\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n            MessageQueue = queue,\n            FilterExpression = filterExpression ?? new GrpcFilterExpression { Type = GrpcFilterType.TAG, Expression = \"*\" },\n            BatchSize = batchSize,\n            InvisibleDuration = invisibleDuration ?? TimeSpan.FromSeconds(30),\n            LongPollingTimeout = longPollingTimeout ?? TimeSpan.FromSeconds(20),\n        };\n\n        var requestData = ProtoExtensions.Serialize(request);\n\n        var messages = new List<GrpcMessage>();\n\n        await foreach (var data in Client.ServerStreamingCallAsync(ServiceName, \"ReceiveMessage\", requestData, cancellationToken).ConfigureAwait(false))\n        {\n            var response = new ReceiveMessageResponse();\n            var reader = new SpanReader(data);\n            response.Read(ref reader);\n\n            // 检查状态\n            if (response.Status != null && response.Status.Code != GrpcCode.OK)\n            {\n                // 消息未找到不算错误\n                if (response.Status.Code == GrpcCode.MESSAGE_NOT_FOUND) break;\n                throw new InvalidOperationException($\"ReceiveMessage error: {response.Status}\");\n            }\n\n            if (response.Message != null)\n                messages.Add(response.Message);\n        }\n\n        return messages;\n    }\n    #endregion\n\n    #region 确认消息\n    /// <summary>确认消息</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"entries\">确认条目列表</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>确认结果</returns>\n    public async Task<AckMessageResponse> AckMessageAsync(\n        String topic,\n        String group,\n        IList<AckMessageEntry> entries,\n        CancellationToken cancellationToken = default)\n    {\n        var request = new AckMessageRequest\n        {\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            Entries = new List<AckMessageEntry>(entries),\n        };\n\n        return await InvokeAsync<AckMessageRequest, AckMessageResponse>(\"AckMessage\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 心跳\n    /// <summary>发送心跳</summary>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"clientType\">客户端类型</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>心跳结果</returns>\n    public async Task<HeartbeatResponse> HeartbeatAsync(String group, GrpcClientType clientType, CancellationToken cancellationToken = default)\n    {\n        var request = new HeartbeatRequest\n        {\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n            ClientType = clientType,\n        };\n\n        return await InvokeAsync<HeartbeatRequest, HeartbeatResponse>(\"Heartbeat\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 事务\n    /// <summary>结束事务</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"messageId\">消息ID</param>\n    /// <param name=\"transactionId\">事务ID</param>\n    /// <param name=\"resolution\">事务决议</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>结束事务结果</returns>\n    public async Task<GrpcEndTransactionResponse> EndTransactionAsync(\n        String topic,\n        String messageId,\n        String transactionId,\n        GrpcTransactionResolution resolution,\n        CancellationToken cancellationToken = default)\n    {\n        var request = new GrpcEndTransactionRequest\n        {\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            MessageId = messageId,\n            TransactionId = transactionId,\n            Source = GrpcTransactionSource.SOURCE_CLIENT,\n            Resolution = resolution,\n        };\n\n        return await InvokeAsync<GrpcEndTransactionRequest, GrpcEndTransactionResponse>(\"EndTransaction\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 死信队列\n    /// <summary>转发消息到死信队列</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"receiptHandle\">收据句柄</param>\n    /// <param name=\"messageId\">消息ID</param>\n    /// <param name=\"deliveryAttempt\">当前投递尝试次数</param>\n    /// <param name=\"maxDeliveryAttempts\">最大投递尝试次数</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<ForwardMessageToDeadLetterQueueResponse> ForwardToDeadLetterQueueAsync(\n        String topic,\n        String group,\n        String receiptHandle,\n        String messageId,\n        Int32 deliveryAttempt,\n        Int32 maxDeliveryAttempts,\n        CancellationToken cancellationToken = default)\n    {\n        var request = new ForwardMessageToDeadLetterQueueRequest\n        {\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            ReceiptHandle = receiptHandle,\n            MessageId = messageId,\n            DeliveryAttempt = deliveryAttempt,\n            MaxDeliveryAttempts = maxDeliveryAttempts,\n        };\n\n        return await InvokeAsync<ForwardMessageToDeadLetterQueueRequest, ForwardMessageToDeadLetterQueueResponse>(\n            \"ForwardMessageToDeadLetterQueue\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 修改不可见时间\n    /// <summary>修改消息不可见时间</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"receiptHandle\">收据句柄</param>\n    /// <param name=\"messageId\">消息ID</param>\n    /// <param name=\"invisibleDuration\">新的不可见时间</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<ChangeInvisibleDurationResponse> ChangeInvisibleDurationAsync(\n        String topic,\n        String group,\n        String receiptHandle,\n        String messageId,\n        TimeSpan invisibleDuration,\n        CancellationToken cancellationToken = default)\n    {\n        var request = new ChangeInvisibleDurationRequest\n        {\n            Group = new GrpcResource { ResourceNamespace = Namespace, Name = group },\n            Topic = new GrpcResource { ResourceNamespace = Namespace, Name = topic },\n            ReceiptHandle = receiptHandle,\n            MessageId = messageId,\n            InvisibleDuration = invisibleDuration,\n        };\n\n        return await InvokeAsync<ChangeInvisibleDurationRequest, ChangeInvisibleDurationResponse>(\n            \"ChangeInvisibleDuration\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 通知终止\n    /// <summary>通知服务端客户端即将终止</summary>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<NotifyClientTerminationResponse> NotifyClientTerminationAsync(String group = null, CancellationToken cancellationToken = default)\n    {\n        var request = new NotifyClientTerminationRequest();\n        if (!String.IsNullOrEmpty(group))\n            request.Group = new GrpcResource { ResourceNamespace = Namespace, Name = group };\n\n        return await InvokeAsync<NotifyClientTerminationRequest, NotifyClientTerminationResponse>(\n            \"NotifyClientTermination\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 客户端资源上报\n    /// <summary>上报客户端设置（Telemetry）。向Proxy上报客户端资源信息，包括设置、主题订阅等</summary>\n    /// <param name=\"settings\">客户端设置</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>服务端返回的Telemetry命令</returns>\n    public async Task<TelemetryCommand> TelemetryAsync(GrpcSettings settings, CancellationToken cancellationToken = default)\n    {\n        var request = new TelemetryCommand { Settings = settings };\n\n        return await InvokeAsync<TelemetryCommand, TelemetryCommand>(\"Telemetry\", request, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 辅助\n    /// <summary>通用Unary调用封装</summary>\n    /// <typeparam name=\"TRequest\">请求类型</typeparam>\n    /// <typeparam name=\"TResponse\">响应类型</typeparam>\n    /// <param name=\"method\">方法名</param>\n    /// <param name=\"request\">请求消息</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>响应消息</returns>\n    private async Task<TResponse> InvokeAsync<TRequest, TResponse>(String method, TRequest request, CancellationToken cancellationToken)\n        where TRequest : ISpanSerializable\n        where TResponse : ISpanSerializable, new()\n    {\n        var requestData = ProtoExtensions.Serialize(request);\n\n        var responseData = await Client.UnaryCallAsync(ServiceName, method, requestData, cancellationToken).ConfigureAwait(false);\n\n        var response = new TResponse();\n        if (responseData != null && responseData.Length > 0)\n        {\n            var reader = new SpanReader(responseData);\n            response.Read(ref reader);\n        }\n\n        return response;\n    }\n    #endregion\n}\n#endif\n"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/GrpcModels.cs",
    "content": "﻿using NewLife.Buffers;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Grpc;\n\n/// <summary>gRPC状态</summary>\npublic class GrpcStatus : ISpanSerializable\n{\n    /// <summary>状态码</summary>\n    public GrpcCode Code { get; set; }\n\n    /// <summary>状态消息</summary>\n    public String Message { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteEnum(1, (Int32)Code);\n        writer.WriteString(2, Message);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Code = (GrpcCode)reader.ReadEnum(); break;\n                case 2: Message = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n\n    /// <summary>已重载</summary>\n    public override String ToString() => $\"{Code} {Message}\";\n}\n\n/// <summary>资源描述（Topic/Group）</summary>\npublic class GrpcResource : ISpanSerializable\n{\n    /// <summary>命名空间</summary>\n    public String ResourceNamespace { get; set; }\n\n    /// <summary>名称</summary>\n    public String Name { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, ResourceNamespace);\n        writer.WriteString(2, Name);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: ResourceNamespace = reader.ReadProtoString(); break;\n                case 2: Name = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n\n    /// <summary>已重载</summary>\n    public override String ToString() => String.IsNullOrEmpty(ResourceNamespace) ? Name : $\"{ResourceNamespace}%{Name}\";\n}\n\n/// <summary>地址</summary>\npublic class GrpcAddress : ISpanSerializable\n{\n    /// <summary>主机</summary>\n    public String Host { get; set; }\n\n    /// <summary>端口</summary>\n    public Int32 Port { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, Host);\n        writer.WriteInt32(2, Port);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Host = reader.ReadProtoString(); break;\n                case 2: Port = reader.ReadProtoInt32(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n\n    /// <summary>已重载</summary>\n    public override String ToString() => $\"{Host}:{Port}\";\n}\n\n/// <summary>端点集合</summary>\npublic class GrpcEndpoints : ISpanSerializable\n{\n    /// <summary>地址类型</summary>\n    public AddressScheme Scheme { get; set; }\n\n    /// <summary>地址列表</summary>\n    public List<GrpcAddress> Addresses { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteEnum(1, (Int32)Scheme);\n        writer.WriteRepeatedMessage(2, Addresses);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Scheme = (AddressScheme)reader.ReadEnum(); break;\n                case 2: Addresses.Add(reader.ReadProtoMessage<GrpcAddress>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>Broker信息</summary>\npublic class GrpcBroker : ISpanSerializable\n{\n    /// <summary>Broker名称</summary>\n    public String Name { get; set; }\n\n    /// <summary>Broker ID</summary>\n    public Int32 Id { get; set; }\n\n    /// <summary>端点</summary>\n    public GrpcEndpoints Endpoints { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, Name);\n        writer.WriteInt32(2, Id);\n        writer.WriteMessage(3, Endpoints);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Name = reader.ReadProtoString(); break;\n                case 2: Id = reader.ReadProtoInt32(); break;\n                case 3: Endpoints = reader.ReadProtoMessage<GrpcEndpoints>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n\n    /// <summary>已重载</summary>\n    public override String ToString() => $\"{Name}#{Id}\";\n}\n\n/// <summary>gRPC消息队列</summary>\npublic class GrpcMessageQueue : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>队列ID</summary>\n    public Int32 Id { get; set; }\n\n    /// <summary>权限</summary>\n    public GrpcPermission Permission { get; set; }\n\n    /// <summary>Broker</summary>\n    public GrpcBroker Broker { get; set; }\n\n    /// <summary>接受的消息类型</summary>\n    public List<GrpcMessageType> AcceptMessageTypes { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteInt32(2, Id);\n        writer.WriteEnum(3, (Int32)Permission);\n        writer.WriteMessage(4, Broker);\n        if (AcceptMessageTypes.Count > 0)\n        {\n            var enums = new List<Int32>(AcceptMessageTypes.Count);\n            foreach (var t in AcceptMessageTypes)\n            {\n                enums.Add((Int32)t);\n            }\n            writer.WritePackedEnum(5, enums);\n        }\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Id = reader.ReadProtoInt32(); break;\n                case 3: Permission = (GrpcPermission)reader.ReadEnum(); break;\n                case 4: Broker = reader.ReadProtoMessage<GrpcBroker>(); break;\n                case 5:\n                    if (wt == 0) // 非packed\n                        AcceptMessageTypes.Add((GrpcMessageType)reader.ReadEnum());\n                    else if (wt == 2) // packed\n                    {\n                        var data = reader.ReadProtoBytes();\n                        var sub = new SpanReader(data);\n                        while (sub.Available > 0) AcceptMessageTypes.Add((GrpcMessageType)sub.ReadEnum());\n                    }\n                    break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>过滤表达式</summary>\npublic class GrpcFilterExpression : ISpanSerializable\n{\n    /// <summary>过滤类型</summary>\n    public GrpcFilterType Type { get; set; }\n\n    /// <summary>表达式</summary>\n    public String Expression { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteEnum(1, (Int32)Type);\n        writer.WriteString(2, Expression);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Type = (GrpcFilterType)reader.ReadEnum(); break;\n                case 2: Expression = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>摘要</summary>\npublic class GrpcDigest : ISpanSerializable\n{\n    /// <summary>摘要类型</summary>\n    public GrpcDigestType Type { get; set; }\n\n    /// <summary>校验值</summary>\n    public String Checksum { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteEnum(1, (Int32)Type);\n        writer.WriteString(2, Checksum);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Type = (GrpcDigestType)reader.ReadEnum(); break;\n                case 2: Checksum = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>消息系统属性</summary>\npublic class GrpcSystemProperties : ISpanSerializable\n{\n    /// <summary>标签</summary>\n    public String Tag { get; set; }\n\n    /// <summary>Keys</summary>\n    public List<String> Keys { get; set; } = [];\n\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>消息体摘要</summary>\n    public GrpcDigest BodyDigest { get; set; }\n\n    /// <summary>消息体编码</summary>\n    public GrpcEncoding BodyEncoding { get; set; }\n\n    /// <summary>消息类型</summary>\n    public GrpcMessageType MessageType { get; set; }\n\n    /// <summary>消息出生时间</summary>\n    public DateTime? BornTimestamp { get; set; }\n\n    /// <summary>消息出生主机</summary>\n    public String BornHost { get; set; }\n\n    /// <summary>消息存储时间</summary>\n    public DateTime? StoreTimestamp { get; set; }\n\n    /// <summary>消息存储主机</summary>\n    public String StoreHost { get; set; }\n\n    /// <summary>投递时间（延迟消息）</summary>\n    public DateTime? DeliveryTimestamp { get; set; }\n\n    /// <summary>收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>队列ID</summary>\n    public Int32 QueueId { get; set; }\n\n    /// <summary>队列偏移</summary>\n    public Int64 QueueOffset { get; set; }\n\n    /// <summary>不可见时间</summary>\n    public TimeSpan? InvisibleDuration { get; set; }\n\n    /// <summary>投递尝试次数</summary>\n    public Int32 DeliveryAttempt { get; set; }\n\n    /// <summary>消息分组（FIFO）</summary>\n    public String MessageGroup { get; set; }\n\n    /// <summary>追踪上下文</summary>\n    public String TraceContext { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, Tag);\n        writer.WriteRepeatedString(2, Keys);\n        writer.WriteString(3, MessageId);\n        writer.WriteMessage(4, BodyDigest);\n        writer.WriteEnum(5, (Int32)BodyEncoding);\n        writer.WriteEnum(6, (Int32)MessageType);\n        writer.WriteTimestamp(7, BornTimestamp);\n        writer.WriteString(8, BornHost);\n        writer.WriteTimestamp(9, StoreTimestamp);\n        writer.WriteString(10, StoreHost);\n        writer.WriteTimestamp(11, DeliveryTimestamp);\n        writer.WriteString(12, ReceiptHandle);\n        writer.WriteInt32(13, QueueId);\n        writer.WriteInt64(14, QueueOffset);\n        writer.WriteDuration(15, InvisibleDuration);\n        writer.WriteInt32(16, DeliveryAttempt);\n        writer.WriteString(17, MessageGroup);\n        writer.WriteString(18, TraceContext);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Tag = reader.ReadProtoString(); break;\n                case 2: Keys.Add(reader.ReadProtoString()); break;\n                case 3: MessageId = reader.ReadProtoString(); break;\n                case 4: BodyDigest = reader.ReadProtoMessage<GrpcDigest>(); break;\n                case 5: BodyEncoding = (GrpcEncoding)reader.ReadEnum(); break;\n                case 6: MessageType = (GrpcMessageType)reader.ReadEnum(); break;\n                case 7: BornTimestamp = reader.ReadTimestamp(); break;\n                case 8: BornHost = reader.ReadProtoString(); break;\n                case 9: StoreTimestamp = reader.ReadTimestamp(); break;\n                case 10: StoreHost = reader.ReadProtoString(); break;\n                case 11: DeliveryTimestamp = reader.ReadTimestamp(); break;\n                case 12: ReceiptHandle = reader.ReadProtoString(); break;\n                case 13: QueueId = reader.ReadProtoInt32(); break;\n                case 14: QueueOffset = reader.ReadProtoInt64(); break;\n                case 15: InvisibleDuration = reader.ReadDuration(); break;\n                case 16: DeliveryAttempt = reader.ReadProtoInt32(); break;\n                case 17: MessageGroup = reader.ReadProtoString(); break;\n                case 18: TraceContext = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>gRPC消息</summary>\npublic class GrpcMessage : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>用户属性</summary>\n    public Dictionary<String, String> UserProperties { get; set; } = [];\n\n    /// <summary>系统属性</summary>\n    public GrpcSystemProperties SystemProperties { get; set; }\n\n    /// <summary>消息体</summary>\n    public Byte[] Body { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteMap(2, UserProperties);\n        writer.WriteMessage(3, SystemProperties);\n        writer.WriteBytes(4, Body);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2:\n                    var (k, v) = reader.ReadMapEntry();\n                    if (k != null) UserProperties[k] = v;\n                    break;\n                case 3: SystemProperties = reader.ReadProtoMessage<GrpcSystemProperties>(); break;\n                case 4: Body = reader.ReadProtoBytes(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>队列分配</summary>\npublic class GrpcAssignment : ISpanSerializable\n{\n    /// <summary>消息队列</summary>\n    public GrpcMessageQueue MessageQueue { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, MessageQueue);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: MessageQueue = reader.ReadProtoMessage<GrpcMessageQueue>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/GrpcServiceMessages.cs",
    "content": "﻿using NewLife.Buffers;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Grpc;\n\n#region 路由查询\n/// <summary>查询路由请求</summary>\npublic class QueryRouteRequest : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>客户端端点</summary>\n    public GrpcEndpoints Endpoints { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteMessage(2, Endpoints);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Endpoints = reader.ReadProtoMessage<GrpcEndpoints>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>查询路由响应</summary>\npublic class QueryRouteResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>消息队列列表</summary>\n    public List<GrpcMessageQueue> MessageQueues { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteRepeatedMessage(2, MessageQueues);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: MessageQueues.Add(reader.ReadProtoMessage<GrpcMessageQueue>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 发送消息\n/// <summary>发送消息请求</summary>\npublic class SendMessageRequest : ISpanSerializable\n{\n    /// <summary>消息列表</summary>\n    public List<GrpcMessage> Messages { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteRepeatedMessage(1, Messages);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Messages.Add(reader.ReadProtoMessage<GrpcMessage>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>发送结果条目</summary>\npublic class SendResultEntry : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>事务ID</summary>\n    public String TransactionId { get; set; }\n\n    /// <summary>偏移量</summary>\n    public Int64 Offset { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteString(2, MessageId);\n        writer.WriteString(3, TransactionId);\n        writer.WriteInt64(4, Offset);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: MessageId = reader.ReadProtoString(); break;\n                case 3: TransactionId = reader.ReadProtoString(); break;\n                case 4: Offset = reader.ReadProtoInt64(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>发送消息响应</summary>\npublic class SendMessageResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>结果条目</summary>\n    public List<SendResultEntry> Entries { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteRepeatedMessage(2, Entries);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: Entries.Add(reader.ReadProtoMessage<SendResultEntry>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 队列分配\n/// <summary>查询队列分配请求</summary>\npublic class QueryAssignmentRequest : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>客户端端点</summary>\n    public GrpcEndpoints Endpoints { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteMessage(2, Group);\n        writer.WriteMessage(3, Endpoints);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 3: Endpoints = reader.ReadProtoMessage<GrpcEndpoints>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>查询队列分配响应</summary>\npublic class QueryAssignmentResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>分配结果</summary>\n    public List<GrpcAssignment> Assignments { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteRepeatedMessage(2, Assignments);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: Assignments.Add(reader.ReadProtoMessage<GrpcAssignment>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 接收消息\n/// <summary>接收消息请求（Server Streaming）</summary>\npublic class ReceiveMessageRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>消息队列</summary>\n    public GrpcMessageQueue MessageQueue { get; set; }\n\n    /// <summary>过滤表达式</summary>\n    public GrpcFilterExpression FilterExpression { get; set; }\n\n    /// <summary>批量大小</summary>\n    public Int32 BatchSize { get; set; }\n\n    /// <summary>不可见时间</summary>\n    public TimeSpan? InvisibleDuration { get; set; }\n\n    /// <summary>自动续租</summary>\n    public Boolean AutoRenew { get; set; }\n\n    /// <summary>长轮询超时</summary>\n    public TimeSpan? LongPollingTimeout { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteMessage(2, MessageQueue);\n        writer.WriteMessage(3, FilterExpression);\n        writer.WriteInt32(4, BatchSize);\n        writer.WriteDuration(5, InvisibleDuration);\n        writer.WriteBool(6, AutoRenew);\n        writer.WriteDuration(7, LongPollingTimeout);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: MessageQueue = reader.ReadProtoMessage<GrpcMessageQueue>(); break;\n                case 3: FilterExpression = reader.ReadProtoMessage<GrpcFilterExpression>(); break;\n                case 4: BatchSize = reader.ReadProtoInt32(); break;\n                case 5: InvisibleDuration = reader.ReadDuration(); break;\n                case 6: AutoRenew = reader.ReadBool(); break;\n                case 7: LongPollingTimeout = reader.ReadDuration(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>接收消息响应（oneof: status/message/delivery_timestamp）</summary>\npublic class ReceiveMessageResponse : ISpanSerializable\n{\n    /// <summary>状态（oneof content = 1）</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>消息（oneof content = 2）</summary>\n    public GrpcMessage Message { get; set; }\n\n    /// <summary>投递时间戳（oneof content = 3）</summary>\n    public DateTime? DeliveryTimestamp { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteMessage(2, Message);\n        writer.WriteTimestamp(3, DeliveryTimestamp);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: Message = reader.ReadProtoMessage<GrpcMessage>(); break;\n                case 3: DeliveryTimestamp = reader.ReadTimestamp(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 确认消息\n/// <summary>确认消息条目</summary>\npublic class AckMessageEntry : ISpanSerializable\n{\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, MessageId);\n        writer.WriteString(2, ReceiptHandle);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: MessageId = reader.ReadProtoString(); break;\n                case 2: ReceiptHandle = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>确认消息请求</summary>\npublic class AckMessageRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>确认条目</summary>\n    public List<AckMessageEntry> Entries { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteMessage(2, Topic);\n        writer.WriteRepeatedMessage(3, Entries);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 3: Entries.Add(reader.ReadProtoMessage<AckMessageEntry>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>确认消息结果条目</summary>\npublic class AckMessageResultEntry : ISpanSerializable\n{\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteString(1, MessageId);\n        writer.WriteString(2, ReceiptHandle);\n        writer.WriteMessage(3, Status);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: MessageId = reader.ReadProtoString(); break;\n                case 2: ReceiptHandle = reader.ReadProtoString(); break;\n                case 3: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>确认消息响应</summary>\npublic class AckMessageResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>结果条目</summary>\n    public List<AckMessageResultEntry> Entries { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteRepeatedMessage(2, Entries);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: Entries.Add(reader.ReadProtoMessage<AckMessageResultEntry>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 心跳\n/// <summary>心跳请求</summary>\npublic class HeartbeatRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>客户端类型</summary>\n    public GrpcClientType ClientType { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteEnum(2, (Int32)ClientType);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: ClientType = (GrpcClientType)reader.ReadEnum(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>心跳响应</summary>\npublic class HeartbeatResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, Status);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 结束事务\n/// <summary>结束事务请求</summary>\npublic class GrpcEndTransactionRequest : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>事务ID</summary>\n    public String TransactionId { get; set; }\n\n    /// <summary>事务来源</summary>\n    public GrpcTransactionSource Source { get; set; }\n\n    /// <summary>事务决议</summary>\n    public GrpcTransactionResolution Resolution { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteString(2, MessageId);\n        writer.WriteString(3, TransactionId);\n        writer.WriteEnum(4, (Int32)Source);\n        writer.WriteEnum(5, (Int32)Resolution);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: MessageId = reader.ReadProtoString(); break;\n                case 3: TransactionId = reader.ReadProtoString(); break;\n                case 4: Source = (GrpcTransactionSource)reader.ReadEnum(); break;\n                case 5: Resolution = (GrpcTransactionResolution)reader.ReadEnum(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>结束事务响应</summary>\npublic class GrpcEndTransactionResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, Status);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 死信队列\n/// <summary>转发消息到死信队列请求</summary>\npublic class ForwardMessageToDeadLetterQueueRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>投递尝试次数</summary>\n    public Int32 DeliveryAttempt { get; set; }\n\n    /// <summary>最大投递尝试次数</summary>\n    public Int32 MaxDeliveryAttempts { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteMessage(2, Topic);\n        writer.WriteString(3, ReceiptHandle);\n        writer.WriteString(4, MessageId);\n        writer.WriteInt32(5, DeliveryAttempt);\n        writer.WriteInt32(6, MaxDeliveryAttempts);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 3: ReceiptHandle = reader.ReadProtoString(); break;\n                case 4: MessageId = reader.ReadProtoString(); break;\n                case 5: DeliveryAttempt = reader.ReadProtoInt32(); break;\n                case 6: MaxDeliveryAttempts = reader.ReadProtoInt32(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>转发消息到死信队列响应</summary>\npublic class ForwardMessageToDeadLetterQueueResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, Status);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 修改不可见时间\n/// <summary>修改不可见时间请求</summary>\npublic class ChangeInvisibleDurationRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>不可见时间</summary>\n    public TimeSpan? InvisibleDuration { get; set; }\n\n    /// <summary>消息ID</summary>\n    public String MessageId { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteMessage(2, Topic);\n        writer.WriteString(3, ReceiptHandle);\n        writer.WriteDuration(4, InvisibleDuration);\n        writer.WriteString(5, MessageId);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 3: ReceiptHandle = reader.ReadProtoString(); break;\n                case 4: InvisibleDuration = reader.ReadDuration(); break;\n                case 5: MessageId = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>修改不可见时间响应</summary>\npublic class ChangeInvisibleDurationResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>新收据句柄</summary>\n    public String ReceiptHandle { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteString(2, ReceiptHandle);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: ReceiptHandle = reader.ReadProtoString(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 通知终止\n/// <summary>通知客户端终止请求</summary>\npublic class NotifyClientTerminationRequest : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, Group);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>通知客户端终止响应</summary>\npublic class NotifyClientTerminationResponse : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteMessage(1, Status);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n\n#region 客户端资源上报（Telemetry）\n/// <summary>Telemetry命令。客户端向Proxy上报资源信息（设置、主题订阅等）</summary>\npublic class TelemetryCommand : ISpanSerializable\n{\n    /// <summary>状态</summary>\n    public GrpcStatus Status { get; set; }\n\n    /// <summary>客户端设置</summary>\n    public GrpcSettings Settings { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Status);\n        writer.WriteMessage(2, Settings);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Status = reader.ReadProtoMessage<GrpcStatus>(); break;\n                case 2: Settings = reader.ReadProtoMessage<GrpcSettings>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>gRPC客户端设置。用于Telemetry上报客户端配置信息</summary>\npublic class GrpcSettings : ISpanSerializable\n{\n    /// <summary>客户端类型</summary>\n    public GrpcClientType ClientType { get; set; }\n\n    /// <summary>访问点</summary>\n    public GrpcEndpoints AccessPoint { get; set; }\n\n    /// <summary>请求超时（Duration，秒）</summary>\n    public TimeSpan? RequestTimeout { get; set; }\n\n    /// <summary>发布设置（生产者）</summary>\n    public GrpcPublishingSettings Publishing { get; set; }\n\n    /// <summary>订阅设置（消费者）</summary>\n    public GrpcSubscriptionSettings Subscription { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteEnum(1, (Int32)ClientType);\n        writer.WriteMessage(2, AccessPoint);\n        if (RequestTimeout != null) writer.WriteDuration(3, RequestTimeout.Value);\n        writer.WriteMessage(4, Publishing);\n        writer.WriteMessage(5, Subscription);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: ClientType = (GrpcClientType)reader.ReadEnum(); break;\n                case 2: AccessPoint = reader.ReadProtoMessage<GrpcEndpoints>(); break;\n                case 3: RequestTimeout = reader.ReadDuration(); break;\n                case 4: Publishing = reader.ReadProtoMessage<GrpcPublishingSettings>(); break;\n                case 5: Subscription = reader.ReadProtoMessage<GrpcSubscriptionSettings>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>发布设置</summary>\npublic class GrpcPublishingSettings : ISpanSerializable\n{\n    /// <summary>发布主题列表</summary>\n    public List<GrpcResource> Topics { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer) => writer.WriteRepeatedMessage(1, Topics);\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topics.Add(reader.ReadProtoMessage<GrpcResource>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>订阅设置</summary>\npublic class GrpcSubscriptionSettings : ISpanSerializable\n{\n    /// <summary>消费组</summary>\n    public GrpcResource Group { get; set; }\n\n    /// <summary>订阅列表</summary>\n    public List<GrpcSubscriptionEntry> Subscriptions { get; set; } = [];\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Group);\n        writer.WriteRepeatedMessage(2, Subscriptions);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Group = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Subscriptions.Add(reader.ReadProtoMessage<GrpcSubscriptionEntry>()); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n\n/// <summary>订阅条目</summary>\npublic class GrpcSubscriptionEntry : ISpanSerializable\n{\n    /// <summary>主题</summary>\n    public GrpcResource Topic { get; set; }\n\n    /// <summary>过滤表达式</summary>\n    public GrpcFilterExpression Expression { get; set; }\n\n    /// <summary>写入</summary>\n    /// <param name=\"writer\">编码器</param>\n    public void Write(ref SpanWriter writer)\n    {\n        writer.WriteMessage(1, Topic);\n        writer.WriteMessage(2, Expression);\n    }\n\n    /// <summary>读取</summary>\n    /// <param name=\"reader\">解码器</param>\n    public void Read(ref SpanReader reader)\n    {\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: Topic = reader.ReadProtoMessage<GrpcResource>(); break;\n                case 2: Expression = reader.ReadProtoMessage<GrpcFilterExpression>(); break;\n                default: reader.SkipField(wt); break;\n            }\n        }\n    }\n}\n#endregion\n"
  },
  {
    "path": "NewLife.RocketMQ/Grpc/ProtoExtensions.cs",
    "content": "﻿using System.Text;\nusing NewLife.Buffers;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Grpc;\n\n/// <summary>Protobuf编解码扩展方法。为 SpanWriter/SpanReader 提供 Protocol Buffers 二进制格式的读写支持</summary>\n/// <remarks>\n/// 实现 Protocol Buffers 编码规范，支持 varint/fixed/length-delimited 三种线路类型。\n/// 参考：https://protobuf.dev/programming-guides/encoding/\n/// </remarks>\npublic static class ProtoExtensions\n{\n    #region SpanWriter 写入扩展\n\n    /// <summary>写入varint编码的无符号64位整数</summary>\n    public static void WriteRawVarint(ref this SpanWriter writer, UInt64 value)\n    {\n        while (value > 0x7F)\n        {\n            writer.Write((Byte)(value | 0x80));\n            value >>= 7;\n        }\n        writer.Write((Byte)value);\n    }\n\n    /// <summary>写入field tag（字段编号 + 线路类型）</summary>\n    /// <param name=\"writer\">写入器</param>\n    /// <param name=\"fieldNumber\">字段编号</param>\n    /// <param name=\"wireType\">线路类型。0=varint, 1=64bit, 2=length-delimited, 5=32bit</param>\n    public static void WriteTag(ref this SpanWriter writer, Int32 fieldNumber, Int32 wireType)\n        => WriteRawVarint(ref writer, (UInt64)((fieldNumber << 3) | wireType));\n\n    /// <summary>写入固定4字节（小端序）</summary>\n    public static void WriteRawFixed32(ref this SpanWriter writer, UInt32 value)\n    {\n        writer.Write((Byte)(value & 0xFF));\n        writer.Write((Byte)((value >> 8) & 0xFF));\n        writer.Write((Byte)((value >> 16) & 0xFF));\n        writer.Write((Byte)((value >> 24) & 0xFF));\n    }\n\n    /// <summary>写入固定8字节（小端序）</summary>\n    public static void WriteRawFixed64(ref this SpanWriter writer, UInt64 value)\n    {\n        writer.Write((Byte)(value & 0xFF));\n        writer.Write((Byte)((value >> 8) & 0xFF));\n        writer.Write((Byte)((value >> 16) & 0xFF));\n        writer.Write((Byte)((value >> 24) & 0xFF));\n        writer.Write((Byte)((value >> 32) & 0xFF));\n        writer.Write((Byte)((value >> 40) & 0xFF));\n        writer.Write((Byte)((value >> 48) & 0xFF));\n        writer.Write((Byte)((value >> 56) & 0xFF));\n    }\n\n    /// <summary>写入int32字段（varint编码）</summary>\n    public static void WriteInt32(ref this SpanWriter writer, Int32 fieldNumber, Int32 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        if (value >= 0)\n            WriteRawVarint(ref writer, (UInt64)value);\n        else\n            WriteRawVarint(ref writer, (UInt64)(Int64)value); // 负数按10字节varint编码\n    }\n\n    /// <summary>写入int64字段（varint编码）</summary>\n    public static void WriteInt64(ref this SpanWriter writer, Int32 fieldNumber, Int64 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, (UInt64)value);\n    }\n\n    /// <summary>写入uint32字段（varint编码）</summary>\n    public static void WriteUInt32(ref this SpanWriter writer, Int32 fieldNumber, UInt32 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, value);\n    }\n\n    /// <summary>写入uint64字段（varint编码）</summary>\n    public static void WriteUInt64(ref this SpanWriter writer, Int32 fieldNumber, UInt64 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, value);\n    }\n\n    /// <summary>写入sint32字段（ZigZag + varint编码）</summary>\n    public static void WriteSInt32(ref this SpanWriter writer, Int32 fieldNumber, Int32 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, (UInt32)((value << 1) ^ (value >> 31)));\n    }\n\n    /// <summary>写入sint64字段（ZigZag + varint编码）</summary>\n    public static void WriteSInt64(ref this SpanWriter writer, Int32 fieldNumber, Int64 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, (UInt64)((value << 1) ^ (value >> 63)));\n    }\n\n    /// <summary>写入bool字段</summary>\n    public static void WriteBool(ref this SpanWriter writer, Int32 fieldNumber, Boolean value)\n    {\n        if (!value) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        writer.Write((Byte)1);\n    }\n\n    /// <summary>写入enum字段（varint编码）</summary>\n    public static void WriteEnum(ref this SpanWriter writer, Int32 fieldNumber, Int32 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 0);\n        WriteRawVarint(ref writer, (UInt64)(UInt32)value);\n    }\n\n    /// <summary>写入string字段（length-delimited UTF-8）</summary>\n    public static void WriteString(ref this SpanWriter writer, Int32 fieldNumber, String value)\n    {\n        if (String.IsNullOrEmpty(value)) return;\n        WriteTag(ref writer, fieldNumber, 2);\n        var bytes = Encoding.UTF8.GetBytes(value);\n        WriteRawVarint(ref writer, (UInt64)bytes.Length);\n        writer.Write(bytes);\n    }\n\n    /// <summary>写入bytes字段（length-delimited）</summary>\n    public static void WriteBytes(ref this SpanWriter writer, Int32 fieldNumber, Byte[] value)\n    {\n        if (value == null || value.Length == 0) return;\n        WriteTag(ref writer, fieldNumber, 2);\n        WriteRawVarint(ref writer, (UInt64)value.Length);\n        writer.Write(value);\n    }\n\n    /// <summary>写入嵌套消息字段（length-delimited）。使用重试缓冲区处理未知大小的子消息</summary>\n    public static void WriteMessage(ref this SpanWriter writer, Int32 fieldNumber, ISpanSerializable message)\n    {\n        if (message == null) return;\n\n        // 使用重试缓冲区计算子消息长度\n        var size = 4096;\n        while (true)\n        {\n            var temp = new Byte[size];\n            var sub = new SpanWriter(temp);\n            try\n            {\n                message.Write(ref sub);\n                var len = sub.WrittenCount;\n                WriteTag(ref writer, fieldNumber, 2);\n                WriteRawVarint(ref writer, (UInt64)len);\n                writer.Write(new ReadOnlySpan<Byte>(temp, 0, len));\n                return;\n            }\n            catch (InvalidOperationException)\n            {\n                size = checked(size * 2);\n            }\n        }\n    }\n\n    /// <summary>写入fixed32字段（4字节小端序）</summary>\n    public static void WriteFixed32(ref this SpanWriter writer, Int32 fieldNumber, UInt32 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 5);\n        WriteRawFixed32(ref writer, value);\n    }\n\n    /// <summary>写入fixed64字段（8字节小端序）</summary>\n    public static void WriteFixed64(ref this SpanWriter writer, Int32 fieldNumber, UInt64 value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 1);\n        WriteRawFixed64(ref writer, value);\n    }\n\n    /// <summary>写入float字段（fixed32小端序）</summary>\n    public static void WriteFloat(ref this SpanWriter writer, Int32 fieldNumber, Single value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 5);\n        WriteRawFixed32(ref writer, BitConverter.ToUInt32(BitConverter.GetBytes(value), 0));\n    }\n\n    /// <summary>写入double字段（fixed64小端序）</summary>\n    public static void WriteDouble(ref this SpanWriter writer, Int32 fieldNumber, Double value)\n    {\n        if (value == 0) return;\n        WriteTag(ref writer, fieldNumber, 1);\n        WriteRawFixed64(ref writer, (UInt64)BitConverter.DoubleToInt64Bits(value));\n    }\n\n    /// <summary>写入map字段。map编码为 repeated message { key=1, value=2 }</summary>\n    public static void WriteMap(ref this SpanWriter writer, Int32 fieldNumber, IDictionary<String, String> map)\n    {\n        if (map == null || map.Count == 0) return;\n        foreach (var kv in map)\n        {\n            var entryBuf = new Byte[512];\n            var entry = new SpanWriter(entryBuf);\n            WriteString(ref entry, 1, kv.Key);\n            WriteString(ref entry, 2, kv.Value);\n            var len = entry.WrittenCount;\n\n            WriteTag(ref writer, fieldNumber, 2);\n            WriteRawVarint(ref writer, (UInt64)len);\n            writer.Write(new ReadOnlySpan<Byte>(entryBuf, 0, len));\n        }\n    }\n\n    /// <summary>写入repeated string字段</summary>\n    public static void WriteRepeatedString(ref this SpanWriter writer, Int32 fieldNumber, IList<String> values)\n    {\n        if (values == null || values.Count == 0) return;\n        foreach (var value in values)\n            WriteString(ref writer, fieldNumber, value);\n    }\n\n    /// <summary>写入repeated enum字段（packed编码）</summary>\n    public static void WritePackedEnum(ref this SpanWriter writer, Int32 fieldNumber, IList<Int32> values)\n    {\n        if (values == null || values.Count == 0) return;\n\n        var subBuf = new Byte[values.Count * 5]; // varint最多5字节\n        var sub = new SpanWriter(subBuf);\n        foreach (var v in values)\n            WriteRawVarint(ref sub, (UInt64)(UInt32)v);\n\n        var len = sub.WrittenCount;\n        WriteTag(ref writer, fieldNumber, 2);\n        WriteRawVarint(ref writer, (UInt64)len);\n        writer.Write(new ReadOnlySpan<Byte>(subBuf, 0, len));\n    }\n\n    /// <summary>写入repeated message字段</summary>\n    public static void WriteRepeatedMessage<T>(ref this SpanWriter writer, Int32 fieldNumber, IList<T> messages)\n        where T : ISpanSerializable\n    {\n        if (messages == null || messages.Count == 0) return;\n        foreach (var msg in messages)\n            WriteMessage(ref writer, fieldNumber, msg);\n    }\n\n    /// <summary>写入google.protobuf.Timestamp（seconds=1, nanos=2）</summary>\n    public static void WriteTimestamp(ref this SpanWriter writer, Int32 fieldNumber, DateTime? value)\n    {\n        if (value == null || value.Value == DateTime.MinValue) return;\n\n        var utc = value.Value.Kind == DateTimeKind.Utc ? value.Value : value.Value.ToUniversalTime();\n        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n        var ts = utc - epoch;\n\n        var subBuf = new Byte[32];\n        var sub = new SpanWriter(subBuf);\n        WriteInt64(ref sub, 1, (Int64)ts.TotalSeconds);\n        var nanos = (Int32)((ts.Ticks % TimeSpan.TicksPerSecond) * 100);\n        WriteInt32(ref sub, 2, nanos);\n\n        var len = sub.WrittenCount;\n        WriteTag(ref writer, fieldNumber, 2);\n        WriteRawVarint(ref writer, (UInt64)len);\n        writer.Write(new ReadOnlySpan<Byte>(subBuf, 0, len));\n    }\n\n    /// <summary>写入google.protobuf.Duration（seconds=1, nanos=2）</summary>\n    public static void WriteDuration(ref this SpanWriter writer, Int32 fieldNumber, TimeSpan? value)\n    {\n        if (value == null || value.Value == TimeSpan.Zero) return;\n\n        var subBuf = new Byte[32];\n        var sub = new SpanWriter(subBuf);\n        WriteInt64(ref sub, 1, (Int64)value.Value.TotalSeconds);\n        var nanos = (Int32)((value.Value.Ticks % TimeSpan.TicksPerSecond) * 100);\n        WriteInt32(ref sub, 2, nanos);\n\n        var len = sub.WrittenCount;\n        WriteTag(ref writer, fieldNumber, 2);\n        WriteRawVarint(ref writer, (UInt64)len);\n        writer.Write(new ReadOnlySpan<Byte>(subBuf, 0, len));\n    }\n\n    #endregion\n\n    #region SpanReader 读取扩展\n\n    /// <summary>读取varint编码的无符号64位整数</summary>\n    public static UInt64 ReadRawVarint(ref this SpanReader reader)\n    {\n        var result = 0UL;\n        var shift = 0;\n        while (shift < 64)\n        {\n            if (reader.Available <= 0) throw new EndOfStreamException(\"读取varint时到达数据末尾\");\n            var b = reader.ReadByte();\n            result |= (UInt64)(b & 0x7F) << shift;\n            if ((b & 0x80) == 0) return result;\n            shift += 7;\n        }\n        throw new InvalidDataException(\"Varint编码超长\");\n    }\n\n    /// <summary>读取field tag，返回字段编号和线路类型</summary>\n    public static (Int32 FieldNumber, Int32 WireType) ReadTag(ref this SpanReader reader)\n    {\n        if (reader.Available <= 0) return (0, 0);\n        var tag = (UInt32)ReadRawVarint(ref reader);\n        return ((Int32)(tag >> 3), (Int32)(tag & 0x07));\n    }\n\n    /// <summary>读取int32值（varint解码）</summary>\n    public static Int32 ReadProtoInt32(ref this SpanReader reader) => (Int32)ReadRawVarint(ref reader);\n\n    /// <summary>读取int64值（varint解码）</summary>\n    public static Int64 ReadProtoInt64(ref this SpanReader reader) => (Int64)ReadRawVarint(ref reader);\n\n    /// <summary>读取uint32值（varint解码）</summary>\n    public static UInt32 ReadProtoUInt32(ref this SpanReader reader) => (UInt32)ReadRawVarint(ref reader);\n\n    /// <summary>读取sint32值（ZigZag解码）</summary>\n    public static Int32 ReadSInt32(ref this SpanReader reader)\n    {\n        var n = (UInt32)ReadRawVarint(ref reader);\n        return (Int32)((n >> 1) ^ -(Int32)(n & 1));\n    }\n\n    /// <summary>读取sint64值（ZigZag解码）</summary>\n    public static Int64 ReadSInt64(ref this SpanReader reader)\n    {\n        var n = ReadRawVarint(ref reader);\n        return (Int64)(n >> 1) ^ -((Int64)(n & 1));\n    }\n\n    /// <summary>读取bool值</summary>\n    public static Boolean ReadBool(ref this SpanReader reader) => ReadRawVarint(ref reader) != 0;\n\n    /// <summary>读取enum值（varint解码）</summary>\n    public static Int32 ReadEnum(ref this SpanReader reader) => (Int32)ReadRawVarint(ref reader);\n\n    /// <summary>读取string值（length-delimited UTF-8）</summary>\n    public static String ReadProtoString(ref this SpanReader reader)\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        if (len == 0) return \"\";\n        var buf = reader.ReadBytes(len).ToArray();\n        return Encoding.UTF8.GetString(buf);\n    }\n\n    /// <summary>读取bytes值（length-delimited）</summary>\n    public static Byte[] ReadProtoBytes(ref this SpanReader reader)\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        if (len == 0) return [];\n        return reader.ReadBytes(len).ToArray();\n    }\n\n    /// <summary>读取fixed32值（小端序4字节）</summary>\n    public static UInt32 ReadFixed32(ref this SpanReader reader)\n    {\n        var span = reader.ReadBytes(4);\n        return (UInt32)(span[0] | (span[1] << 8) | (span[2] << 16) | (span[3] << 24));\n    }\n\n    /// <summary>读取fixed64值（小端序8字节）</summary>\n    public static UInt64 ReadFixed64(ref this SpanReader reader)\n    {\n        var span = reader.ReadBytes(8);\n        return (UInt64)span[0] | ((UInt64)span[1] << 8) | ((UInt64)span[2] << 16) | ((UInt64)span[3] << 24)\n             | ((UInt64)span[4] << 32) | ((UInt64)span[5] << 40) | ((UInt64)span[6] << 48) | ((UInt64)span[7] << 56);\n    }\n\n    /// <summary>读取float值（小端序4字节）</summary>\n    public static Single ReadFloat(ref this SpanReader reader)\n    {\n        var bytes = reader.ReadBytes(4).ToArray();\n        return BitConverter.ToSingle(bytes, 0);\n    }\n\n    /// <summary>读取double值（小端序8字节）</summary>\n    public static Double ReadProtoDouble(ref this SpanReader reader)\n    {\n        var bytes = reader.ReadBytes(8).ToArray();\n        return BitConverter.ToDouble(bytes, 0);\n    }\n\n    /// <summary>读取嵌套消息</summary>\n    public static T ReadProtoMessage<T>(ref this SpanReader reader) where T : ISpanSerializable, new()\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        if (len == 0) return new T();\n\n        var subData = reader.ReadBytes(len).ToArray();\n        var sub = new SpanReader(subData);\n        var msg = new T();\n        msg.Read(ref sub);\n        return msg;\n    }\n\n    /// <summary>读取map字段中的一个entry（key=1 string, value=2 string）</summary>\n    public static (String Key, String Value) ReadMapEntry(ref this SpanReader reader)\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        var subData = reader.ReadBytes(len).ToArray();\n        var sub = new SpanReader(subData);\n\n        String key = null;\n        String value = null;\n        while (sub.Available > 0)\n        {\n            var (fn, wt) = ReadTag(ref sub);\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: key = ReadProtoString(ref sub); break;\n                case 2: value = ReadProtoString(ref sub); break;\n                default: SkipField(ref sub, wt); break;\n            }\n        }\n        return (key, value);\n    }\n\n    /// <summary>读取google.protobuf.Timestamp</summary>\n    public static DateTime ReadTimestamp(ref this SpanReader reader)\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        if (len == 0) return DateTime.MinValue;\n\n        var subData = reader.ReadBytes(len).ToArray();\n        var sub = new SpanReader(subData);\n        var seconds = 0L;\n        var nanos = 0;\n\n        while (sub.Available > 0)\n        {\n            var (fn, wt) = ReadTag(ref sub);\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: seconds = ReadProtoInt64(ref sub); break;\n                case 2: nanos = ReadProtoInt32(ref sub); break;\n                default: SkipField(ref sub, wt); break;\n            }\n        }\n\n        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);\n        return epoch.AddSeconds(seconds).AddTicks(nanos / 100);\n    }\n\n    /// <summary>读取google.protobuf.Duration</summary>\n    public static TimeSpan ReadDuration(ref this SpanReader reader)\n    {\n        var len = (Int32)ReadRawVarint(ref reader);\n        if (len == 0) return TimeSpan.Zero;\n\n        var subData = reader.ReadBytes(len).ToArray();\n        var sub = new SpanReader(subData);\n        var seconds = 0L;\n        var nanos = 0;\n\n        while (sub.Available > 0)\n        {\n            var (fn, wt) = ReadTag(ref sub);\n            if (fn == 0) break;\n            switch (fn)\n            {\n                case 1: seconds = ReadProtoInt64(ref sub); break;\n                case 2: nanos = ReadProtoInt32(ref sub); break;\n                default: SkipField(ref sub, wt); break;\n            }\n        }\n\n        return TimeSpan.FromSeconds(seconds) + TimeSpan.FromTicks(nanos / 100);\n    }\n\n    /// <summary>跳过当前字段的值</summary>\n    public static void SkipField(ref this SpanReader reader, Int32 wireType)\n    {\n        switch (wireType)\n        {\n            case 0: // varint\n                ReadRawVarint(ref reader);\n                break;\n            case 1: // 64-bit\n                reader.ReadBytes(8);\n                break;\n            case 2: // length-delimited\n                var len = (Int32)ReadRawVarint(ref reader);\n                reader.ReadBytes(len);\n                break;\n            case 5: // 32-bit\n                reader.ReadBytes(4);\n                break;\n            default:\n                throw new InvalidDataException($\"未知的线路类型: {wireType}\");\n        }\n    }\n\n    #endregion\n\n    #region 序列化辅助\n\n    /// <summary>序列化 ISpanSerializable 消息为 Protobuf 字节数组。使用重试缓冲区处理未知大小的消息</summary>\n    /// <param name=\"message\">消息</param>\n    /// <param name=\"initialCapacity\">初始缓冲区大小</param>\n    /// <returns>Protobuf 编码的字节数组</returns>\n    public static Byte[] Serialize(ISpanSerializable message, Int32 initialCapacity = 4096)\n    {\n        if (message == null) return [];\n        var capacity = initialCapacity;\n        while (true)\n        {\n            var buf = new Byte[capacity];\n            var writer = new SpanWriter(buf);\n            try\n            {\n                message.Write(ref writer);\n                return writer.WrittenSpan.ToArray();\n            }\n            catch (InvalidOperationException)\n            {\n                capacity = checked(capacity * 2);\n            }\n        }\n    }\n\n    #endregion\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Helper.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\nstatic class Helper\n{\n    public static TEnum ToEnum<TEnum>(this String value, TEnum defaultValue = default) where TEnum : struct => Enum.TryParse<TEnum>(value, out var v) ? v : defaultValue;\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/HuaweiProvider.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\n/// <summary>华为云 DMS RocketMQ 适配器</summary>\npublic class HuaweiProvider : ICloudProvider\n{\n    /// <summary>提供者名称</summary>\n    public String Name => \"Huawei\";\n\n    /// <summary>访问令牌</summary>\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥</summary>\n    public String SecretKey { get; set; }\n\n    /// <summary>通道标识。默认HUAWEI</summary>\n    public String OnsChannel { get; set; } = \"HUAWEI\";\n\n    /// <summary>实例ID</summary>\n    public String InstanceId { get; set; }\n\n    /// <summary>是否启用SSL</summary>\n    public Boolean EnableSsl { get; set; }\n\n    /// <summary>转换主题名。华为云不转换</summary>\n    /// <param name=\"topic\">原始主题名</param>\n    /// <returns></returns>\n    public String TransformTopic(String topic) => topic;\n\n    /// <summary>转换消费组名。华为云不转换</summary>\n    /// <param name=\"group\">原始消费组名</param>\n    /// <returns></returns>\n    public String TransformGroup(String group) => group;\n\n    /// <summary>获取 NameServer 地址。华为云不从HTTP获取</summary>\n    /// <returns></returns>\n    public String GetNameServerAddress() => null;\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/ICloudProvider.cs",
    "content": "﻿using NewLife.RocketMQ.Protocol;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>云厂商适配器接口。统一各云厂商的签名认证和实例路由逻辑</summary>\npublic interface ICloudProvider\n{\n    /// <summary>提供者名称</summary>\n    String Name { get; }\n\n    /// <summary>访问令牌</summary>\n    String AccessKey { get; }\n\n    /// <summary>访问密钥</summary>\n    String SecretKey { get; }\n\n    /// <summary>通道标识</summary>\n    String OnsChannel { get; }\n\n    /// <summary>转换主题名。用于阿里云等需要加实例ID前缀的场景</summary>\n    /// <param name=\"topic\">原始主题名</param>\n    /// <returns>转换后的主题名</returns>\n    String TransformTopic(String topic);\n\n    /// <summary>转换消费组名</summary>\n    /// <param name=\"group\">原始消费组名</param>\n    /// <returns>转换后的消费组名</returns>\n    String TransformGroup(String group);\n\n    /// <summary>获取 NameServer 地址。用于从 HTTP 接口获取地址的场景</summary>\n    /// <returns>NameServer 地址，null 表示无需特殊处理</returns>\n    String GetNameServerAddress();\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/MessageTrace/AsyncTraceDispatcher.cs",
    "content": "using System;\nusing System.Collections.Concurrent;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing NewLife.Log;\nusing NewLife.RocketMQ.Protocol;\n\nnamespace NewLife.RocketMQ.MessageTrace\n{\n    /// <summary>\n    /// 异步轨迹分发器\n    /// </summary>\n    internal class AsyncTraceDispatcher : IDisposable\n    {\n        private readonly Producer _traceProducer;\n        private readonly BlockingCollection<TraceContext> _traceQueue;\n        private readonly CancellationTokenSource _cancellationTokenSource;\n        private readonly Task _dispatchTask;\n\n        /// <summary>轨迹主题</summary>\n        public const String TraceTopic = \"RMQ_SYS_TRACE_TOPIC\";\n\n        internal AsyncTraceDispatcher()\n        {\n            // 初始化内部生产者\n            _traceProducer = new Producer\n            {\n                Topic = TraceTopic,\n                // 使用一个独特的生产者组\n                Group = \"T_P_G_RMQ_SYS_TRACE_TOPIC\",\n                Log = XTrace.Log,\n            };\n\n            _traceQueue = new BlockingCollection<TraceContext>();\n            _cancellationTokenSource = new CancellationTokenSource();\n\n            // 启动后台任务来处理轨迹消息\n            _dispatchTask = Task.Factory.StartNew(Dispatch, TaskCreationOptions.LongRunning);\n        }\n\n        public void Start(String nameServerAddress)\n        {\n            if (nameServerAddress.IsNullOrEmpty()) throw new ArgumentNullException(nameof(nameServerAddress));\n\n            _traceProducer.NameServerAddress = nameServerAddress;\n            _traceProducer.Start();\n        }\n\n        /// <summary>\n        /// 添加轨迹上下文到队列\n        /// </summary>\n        /// <param name=\"context\"></param>\n        public void AddTrace(TraceContext context)\n        {\n            if (!_traceProducer.Active) return;\n\n            try\n            {\n                _traceQueue.Add(context, _cancellationTokenSource.Token);\n            }\n            catch (OperationCanceledException)\n            {\n                // 忽略异常，因为这意味着分发器正在关闭\n            }\n            catch (Exception ex)\n            {\n                XTrace.WriteException(ex);\n            }\n        }\n\n        private void Dispatch()\n        {\n            while (!_cancellationTokenSource.IsCancellationRequested)\n            {\n                try\n                {\n                    var context = _traceQueue.Take(_cancellationTokenSource.Token);\n                    if (context != null)\n                    {\n                        ProcessTrace(context);\n                    }\n                }\n                catch (OperationCanceledException)\n                {\n                    break; // 退出循环\n                }\n                catch (Exception ex)\n                {\n                    XTrace.WriteException(ex);\n                }\n            }\n        }\n\n        private void ProcessTrace(TraceContext context)\n        {\n            var sb = new StringBuilder();\n            foreach (var bean in context.TraceBeans)\n            {\n                sb.Append(bean.Topic).Append(\"\\x01\");\n                sb.Append(bean.MsgId).Append(\"\\x01\");\n                sb.Append(bean.Tags).Append(\"\\x01\");\n                sb.Append(bean.Keys).Append(\"\\x01\");\n                sb.Append(bean.StoreHost).Append(\"\\x01\");\n                sb.Append(bean.BodyLength).Append(\"\\x01\");\n                sb.Append(context.CostTime).Append(\"\\x01\");\n                sb.Append(context.TraceType).Append(\"\\x02\");\n            }\n\n            var body = sb.ToString().TrimEnd('\\x02');\n            var keys = context.TraceBeans.Count > 0 ? context.TraceBeans[0].MsgId : \"\";\n\n            var msg = new Message\n            {\n                Topic = TraceTopic,\n                Tags = context.TraceType.ToString(),\n                Keys = keys,\n                Body = Encoding.UTF8.GetBytes(body)\n            };\n\n            try\n            {\n                _traceProducer.Publish(msg,null,3000);\n            }\n            catch (Exception ex)\n            { \n                XTrace.WriteException(ex);\n            }\n        }\n\n        public void Dispose()\n        {\n            _cancellationTokenSource.Cancel();\n            _dispatchTask.Wait(1000);\n            _traceProducer.Stop();\n        }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/MessageTrace/MessageTraceHook.cs",
    "content": "using System;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Remoting;\nusing NewLife.Net;\nusing NewLife.RocketMQ.MessageTrace;\nusing NewLife.Log;\nusing System.Linq;\nusing NewLife.Data;\n\nnamespace NewLife.RocketMQ.MessageTrace\n{\n    /// <summary>\n    /// 消息轨迹钩子实现\n    /// </summary>\n    internal class MessageTraceHook : ISendMessageHook, IConsumeMessageHook\n    {\n        private readonly AsyncTraceDispatcher _dispatcher;\n        public MessageTraceHook(AsyncTraceDispatcher dispatcher)\n        {\n            _dispatcher = dispatcher;\n        }\n\n        public String HookName => \"MessageTraceHook\";\n\n        public void ExecuteHookBefore(SendMessageContext context)\n        {\n            if (context == null) return;\n            context.TraceContext = new TraceContext\n            {\n                TraceType = TraceType.Pub,\n                GroupName = context.ProducerGroup,\n                RequestId = Guid.NewGuid().ToString(\"N\")\n            };\n        }\n\n        public void ExecuteHookAfter(SendMessageContext context)\n        {\n\n            if (context.Message?.Topic?.Equals(\"RMQ_SYS_TRACE_TOPIC\") == true)\n            {\n                return;\n            }\n            \n            if (context?.SendResult == null || context.SendResult.Status != SendStatus.SendOK) return;\n\n            var traceContext = new TraceContext\n            {\n                TraceType = TraceType.Pub,\n                GroupName = context.ProducerGroup\n            };\n\n            var traceBean = new TraceBean\n            {\n                Topic = context.Message.Topic,\n                MsgId = context.SendResult.MsgId,\n                Tags = context.Message.Tags,\n                Keys = context.Message.Keys,\n                StoreHost = context.BrokerAddr,\n                BodyLength = context.Message.Body.Length,\n                ClientHost = context.Mq.BrokerName,\n                MsgType = context.MsgType\n            };\n\n            traceContext.TraceBeans.Add(traceBean);\n            traceContext.CostTime = (Int32)(DateTime.Now - context.BornHost).TotalMilliseconds;\n            _dispatcher.AddTrace(traceContext);\n        }\n\n        public void ExecuteHookBefore(ConsumeMessageContext context)\n        {\n            if (context?.MsgList == null || context.MsgList.Count == 0) return;\n            var traceContext = new TraceContext\n            {\n                TraceType = TraceType.SubBefore,\n                GroupName = context.ConsumerGroup,\n                RequestId = Guid.NewGuid().ToString(\"N\")\n            };\n            \n            var traceBeans = context.MsgList.Select(msg => new TraceBean\n            {\n                Topic = msg.Topic,\n                MsgId = msg.MsgId,\n                Tags = msg.Tags,\n                Keys = msg.Keys,\n                StoreHost = msg.StoreHost + \"\",\n                BodyLength = msg.Body.Length,\n                ClientHost = context.Mq.BrokerName,\n                MsgType = context.MsgType\n            }).ToList();\n\n            foreach (var traceBean in traceBeans) traceContext.TraceBeans.Add(traceBean);\n            _dispatcher.AddTrace(traceContext);\n        }\n\n        public void ExecuteHookAfter(ConsumeMessageContext context)\n        {\n            if (context?.MsgList == null || context.MsgList.Count == 0) return;\n            \n            var subBeforeTraceContext = context.TraceContext;\n            \n            var subAfterTraceContext = new TraceContext\n            {\n                TraceType = TraceType.SubAfter,\n                GroupName = context.ConsumerGroup,\n                RequestId = subBeforeTraceContext.RequestId,\n                Success = context.Success,\n            };\n\n            var traceBeans = context.MsgList.Select(msg => new TraceBean\n            {\n                Topic = msg.Topic,\n                MsgId = msg.MsgId,\n                Tags = msg.Tags,\n                Keys = msg.Keys,\n                StoreHost = msg.StoreHost + \"\",\n                BodyLength = msg.Body.Length,\n                ClientHost = context.Mq.BrokerName,\n                MsgType = context.MsgType\n            }).ToList();\n\n            foreach (var traceBean in traceBeans) subAfterTraceContext.TraceBeans.Add(traceBean);\n            subAfterTraceContext.CostTime = (Int32)(DateTime.Now - subBeforeTraceContext.TimeStamp).TotalMilliseconds;\n            _dispatcher.AddTrace(subAfterTraceContext);\n        }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/MessageTrace/TraceModel.cs",
    "content": "using System;\nusing System.Collections.Generic;\nusing NewLife.RocketMQ.Protocol;\n\nnamespace NewLife.RocketMQ.MessageTrace\n{\n    /// <summary>\n    /// \n    /// </summary>\n    public interface ISendMessageHook\n    {\n\n        /// <summary>\n        /// \n        /// </summary>\n        /// <param name=\"context\"></param>\n        void ExecuteHookBefore(SendMessageContext context);\n\n        /// <summary>\n        /// \n        /// </summary>\n        /// <param name=\"context\"></param>\n        void ExecuteHookAfter(SendMessageContext context);\n    }\n\n    /// <summary>\n    /// \n    /// </summary>\n    public interface IConsumeMessageHook\n    {\n        /// <summary>\n        /// \n        /// </summary>\n        /// <param name=\"context\"></param>\n        void ExecuteHookBefore(ConsumeMessageContext context);\n\n        /// <summary>\n        /// \n        /// </summary>\n        /// <param name=\"context\"></param>\n        void ExecuteHookAfter(ConsumeMessageContext context);\n    }\n\n    /// <summary>\n    /// \n    /// </summary>\n    public class SendMessageContext\n    {\n        /// <summary>\n        /// \n        /// </summary>\n        public String ProducerGroup;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public Message Message;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public MessageQueue Mq;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public String BrokerAddr;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public SendResult SendResult;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public Exception E;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public Object MqTraceContext;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public IDictionary<String, String> Props;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public TraceContext TraceContext;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public String MsgType;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public DateTime BornHost;\n    }\n\n    /// <summary>\n    /// \n    /// </summary>\n    public class ConsumeMessageContext\n    {\n        /// <summary>\n        /// \n        /// </summary>\n        public String ConsumerGroup;\n\n        /// <summary>\n\n        public List<MessageExt> MsgList;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public MessageQueue Mq;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public Boolean Success;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public IDictionary<String, String> Props;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public TraceContext TraceContext;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public String MsgType;\n\n        /// <summary>\n        /// \n        /// </summary>\n        public DateTime BornHost;\n    }\n\n    /// <summary>\n    /// 轨迹类型\n    /// </summary>\n    public enum TraceType\n    {\n        Pub,\n        SubBefore,\n        SubAfter,\n    }\n\n    /// <summary>\n    /// 轨迹上下文\n    /// </summary>\n    public class TraceContext\n    {\n        /// <summary>轨迹类型</summary>\n        public TraceType TraceType { get; set; }\n\n        /// <summary>时间戳</summary>\n        public DateTime TimeStamp { get; set; }\n\n        /// <summary>区域ID</summary>\n        public String RegionId { get; set; }\n\n        /// <summary>分组名</summary>\n        public String GroupName { get; set; }\n\n        /// <summary>耗时</summary>\n        public int CostTime { get; set; }\n\n        /// <summary>是否成功</summary>\n        public bool Success { get; set; }\n\n        /// <summary>请求ID，例如消息ID</summary>\n        public String RequestId { get; set; }\n\n        /// <summary>轨迹豆</summary>\n        public IList<TraceBean> TraceBeans { get; set; } = new List<TraceBean>();\n    }\n\n    /// <summary>\n    /// 轨迹豆，包含轨迹上下文信息\n    /// </summary>\n    public class TraceBean\n    {\n        /// <summary>主题</summary>\n        public String Topic { get; set; }\n\n        /// <summary>消息ID</summary>\n        public String MsgId { get; set; }\n\n        /// <summary>偏移消息ID</summary>\n        public String OffsetMsgId { get; set; }\n\n        /// <summary>标签</summary>\n        public String Tags { get; set; }\n\n        /// <summary>键</summary>\n        public String Keys { get; set; }\n\n        /// <summary>存储主机</summary>\n        public String StoreHost { get; set; }\n\n        /// <summary>消息体长度</summary>\n        public int BodyLength { get; set; }\n\n        /// <summary>客户端主机</summary>\n        public String ClientHost { get; set; }\n\n        /// <summary>消息类型</summary>\n        public String MsgType { get; set; }\n\n        /// <summary>存储时间</summary>\n        public long StoreTime { get; set; }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Models/ConsumeEventArgs.cs",
    "content": "﻿using NewLife.RocketMQ.Protocol;\n\nnamespace NewLife.RocketMQ.Models\n{\n    /// <summary>消费事件参数</summary>\n    public class ConsumeEventArgs : EventArgs\n    {\n        /// <summary>队列</summary>\n        public MessageQueue Queue { get; set; }\n\n        /// <summary>消息集合</summary>\n        public MessageExt[] Messages { get; set; }\n\n        /// <summary>结果</summary>\n        public PullResult Result { get; set; }\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Models/ConsumeFromWheres.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace NewLife.RocketMQ.Models\n{\n    public enum ConsumeFromWheres\n    {\n        CONSUME_FROM_LAST_OFFSET = 0,\n        CONSUME_FROM_FIRST_OFFSET = 1,\n        CONSUME_FROM_TIMESTAMP = 2,\n        CONSUME_FROM_MIN_OFFSET = 3,\n        CONSUME_FROM_MAX_OFFSET = 4,\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Models/ConsumeTypes.cs",
    "content": "﻿namespace NewLife.RocketMQ.Models;\n\n/// <summary>消费类型</summary>\npublic enum ConsumeTypes\n{\n    /// <summary>拉取。</summary>\n    Pull,\n\n    /// <summary>推送。</summary>\n    Push,\n\n    /// <summary>弹出。</summary>\n    Pop,\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Models/DelayTimeLevels.cs",
    "content": "﻿namespace NewLife.RocketMQ.Models;\n\n#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释\n/// <summary>延迟消息的18个等级</summary>\npublic enum DelayTimeLevels\n{\n    S1 =1,\n    S5 =2,\n    S10=3,\n    S30=4,\n    Min1=5,\n    Min2=6,\n    Min3=7,\n    Min4=8,\n    Min5=9,\n    Min6=10,\n    Min7=11,\n    Min8=12,\n    Min9=13,\n    Min10=14,\n    Min20=15,\n    Min30=16,\n    Hour1=17,\n    Hour2=18,\n}\n#pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释\n"
  },
  {
    "path": "NewLife.RocketMQ/Models/MessageModels.cs",
    "content": "﻿namespace NewLife.RocketMQ.Models;\n\n/// <summary>消息模型。广播/集群</summary>\npublic enum MessageModels\n{\n    /// <summary>集群。消费组内各消费者分享数据</summary>\n    Clustering,\n\n    /// <summary>广播。消费组内各消费者各自消费全部</summary>\n    Broadcasting,\n}"
  },
  {
    "path": "NewLife.RocketMQ/MqBase.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Diagnostics;\nusing System.Reflection;\nusing System.Security.Authentication;\nusing System.Security.Cryptography.X509Certificates;\nusing System.Xml.Serialization;\nusing NewLife.Log;\nusing NewLife.Net;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Client;\n\n/// <summary>业务基类</summary>\npublic abstract class MqBase : DisposeBase\n{\n    #region 属性\n    /// <summary>名称</summary>\n    public String Name { get; set; }\n\n    /// <summary>名称服务器地址</summary>\n    public String NameServerAddress { get; set; }\n\n    /// <summary>消费组</summary>\n    /// <remarks>阿里云目前需要在Group前面带上实例ID并用【%】连接,组成路由Group[用来路由到实例Group]</remarks>\n    public String Group { get; set; } = \"DEFAULT_PRODUCER\";\n\n    /// <summary>rocketmq 默认主题</summary>\n    public static String DefaultTopic { get; } = \"TBW102\";\n\n    /// <summary>主题</summary>\n    /// <remarks>阿里云目前需要在Topic前面带上实例ID并用【%】连接,组成路由Topic[用来路由到实例Topic]</remarks>\n    public String Topic { get; set; } = DefaultTopic;\n\n    /// <summary>默认的主题队列数量</summary>\n    public Int32 DefaultTopicQueueNums { get; set; } = 4;\n\n    /// <summary>本地IP地址</summary>\n    public String ClientIP { get; set; } = NetHelper.MyIP() + \"\";\n\n    ///// <summary>本地端口</summary>\n    //public Int32 ClientPort { get; set; }\n\n    /// <summary>实例名</summary>\n    public String InstanceName { get; set; } = \"DEFAULT\";\n\n    ///// <summary>客户端回调执行线程数。默认CPU数</summary>\n    //public Int32 ClientCallbackExecutorThreads { get; set; } = Environment.ProcessorCount;\n\n    /// <summary>拉取名称服务器间隔。默认30_000ms</summary>\n    public Int32 PollNameServerInterval { get; set; } = 30_000;\n\n    /// <summary>Broker心跳间隔。默认30_000ms</summary>\n    public Int32 HeartbeatBrokerInterval { get; set; } = 30_000;\n\n    /// <summary>单元名称</summary>\n    public String UnitName { get; set; }\n\n    /// <summary>单元模式</summary>\n    public Boolean UnitMode { get; set; }\n\n    /// <summary>序列化类型。默认Json，支持RocketMQ二进制</summary>\n    public SerializeType SerializeType { get; set; } = SerializeType.JSON;\n\n    /// <summary>让通信层知道对方的版本号，响应方可以以此做兼容老版本等的特殊操作</summary>\n    public MQVersion Version { get; set; } = MQVersion.V4_9_7;\n\n    /// <summary>SSL协议。默认None</summary>\n    public SslProtocols SslProtocol { get; set; } = SslProtocols.None;\n\n    /// <summary>X509证书。用于SSL连接时验证证书指纹，可以直接加载pem证书文件，未指定时不验证证书</summary>\n    /// <remarks>\n    /// 可以使用pfx证书文件，也可以使用pem证书文件。\n    /// 服务端必须指定证书。\n    /// </remarks>\n    /// <example>\n    /// var cert = new X509Certificate2(\"file\", \"pass\");\n    /// </example>\n    public X509Certificate? Certificate { get; set; }\n\n    /// <summary>是否使用外部代理。有些RocketMQ的Broker部署在网关外部，需要使用映射地址，默认false</summary>\n    public Boolean ExternalBroker { get; set; }\n\n    /// <summary>是否启用VIP通道。启用后使用Broker端口-2作为VIP通道连接，获得更高优先级，默认false</summary>\n    /// <remarks>\n    /// RocketMQ的VIP通道使用Broker监听端口减2的端口，例如Broker端口为10911时VIP端口为10909。\n    /// VIP通道在高负载场景下可获得更高的处理优先级。\n    /// </remarks>\n    public Boolean VipChannelEnabled { get; set; }\n\n    /// <summary>是否可用</summary>\n    public Boolean Active { get; private set; }\n\n    /// <summary>代理集合</summary>\n    public IList<BrokerInfo> Brokers => _NameServer?.Brokers.OrderBy(t => t.Name).ToList();\n\n    /// <summary>云厂商适配器。用于统一签名认证和实例路由逻辑</summary>\n    /// <remarks>\n    /// 支持阿里云（AliyunProvider）、腾讯云（TencentProvider）、Apache ACL（AclProvider）等。\n    /// 设置后自动处理签名、实例ID路由等厂商特有逻辑。\n    /// </remarks>\n    public ICloudProvider CloudProvider { get; set; }\n\n    /// <summary>阿里云选项。使用阿里云RocketMQ的参数有些不一样</summary>\n    [Obsolete(\"请使用 CloudProvider = new AliyunProvider { ... } 替代\")]\n    public AliyunOptions Aliyun\n    {\n        get => _aliyunOptions;\n        set\n        {\n            _aliyunOptions = value;\n            // 自动同步到 CloudProvider\n            if (value != null && !value.AccessKey.IsNullOrEmpty())\n                CloudProvider ??= AliyunProvider.FromOptions(value);\n        }\n    }\n    private AliyunOptions _aliyunOptions;\n\n    /// <summary>Apache RocketMQ ACL 客户端配置。在Borker服务器配置设置为AclEnable = true 时配置生效。</summary>\n    [Obsolete(\"请使用 CloudProvider = new AclProvider { ... } 替代\")]\n    public AclOptions AclOptions\n    {\n        get => _aclOptions;\n        set\n        {\n            _aclOptions = value;\n            if (value != null && !value.AccessKey.IsNullOrEmpty())\n                CloudProvider ??= AclProvider.FromOptions(value);\n        }\n    }\n    private AclOptions _aclOptions;\n\n    /// <summary>Json序列化主机</summary>\n    public IJsonHost JsonHost { get; set; } = JsonHelper.Default;\n\n    /// <summary>性能追踪器</summary>\n    public ITracer Tracer { get; set; } = DefaultTracer.Instance;\n\n    /// <summary>是否启用消息轨迹</summary>\n    public Boolean EnableMessageTrace { get; set; }\n\n#if NETSTANDARD2_1_OR_GREATER\n    /// <summary>gRPC Proxy地址。设置后使用gRPC协议连接RocketMQ 5.x</summary>\n    /// <remarks>\n    /// 格式如 http://host:8081 或 https://host:8081。\n    /// 设置此属性后，将使用gRPC协议替代Remoting协议，支持RocketMQ 5.x新特性。\n    /// </remarks>\n    public String GrpcProxyAddress { get; set; }\n\n    /// <summary>gRPC消息服务客户端</summary>\n    protected Grpc.GrpcMessagingService _GrpcService;\n#endif\n\n    private String _group;\n    private String _topic;\n\n    /// <summary>名称服务器</summary>\n    protected NameClient _NameServer;\n    #endregion\n\n    #region 扩展属性\n    /// <summary>客户端标识</summary>\n    public String ClientId\n    {\n        get\n        {\n            var str = $\"{ClientIP}@{InstanceName}\";\n            if (!UnitName.IsNullOrEmpty()) str += \"@\" + UnitName;\n            return str;\n        }\n    }\n    #endregion\n\n    #region 构造\n    static MqBase()\n    {\n        // 输出当前版本\n        Assembly.GetExecutingAssembly().WriteVersion();\n\n        XTrace.WriteLine(\"RocketMQ文档：https://newlifex.com/core/rocketmq\");\n    }\n\n    /// <summary>实例化</summary>\n    public MqBase()\n    {\n        InstanceName = Process.GetCurrentProcess().Id + \"\";\n\n        // 设置UnitName，避免一个进程多实例时冲突\n        //UnitName = Rand.Next() + \"\";\n    }\n\n    /// <summary>销毁</summary>\n    /// <param name=\"disposing\"></param>\n    protected override void Dispose(Boolean disposing)\n    {\n        base.Dispose(disposing);\n\n#if NETSTANDARD2_1_OR_GREATER\n        _GrpcService.TryDispose();\n#endif\n        _NameServer.TryDispose();\n\n        //foreach (var item in _Brokers)\n        //{\n        //    item.Value.TryDispose();\n        //}\n        Stop();\n    }\n\n    /// <summary>友好字符串</summary>\n    /// <returns></returns>\n    public override String ToString() => _group;\n    #endregion\n\n    #region 基础方法\n    /// <summary>应用配置</summary>\n    /// <param name=\"setting\"></param>\n    public virtual void Configure(MqSetting setting)\n    {\n        if (!setting.NameServer.IsNullOrEmpty()) NameServerAddress = setting.NameServer;\n        if (!setting.Topic.IsNullOrEmpty()) Topic = setting.Topic;\n        if (!setting.Group.IsNullOrEmpty()) Group = setting.Group;\n\n        // 兼容旧版配置方式\n#pragma warning disable CS0618\n        Aliyun ??= new AliyunOptions();\n        if (!setting.Server.IsNullOrEmpty()) Aliyun.Server = setting.Server;\n        if (!setting.AccessKey.IsNullOrEmpty()) Aliyun.AccessKey = setting.AccessKey;\n        if (!setting.SecretKey.IsNullOrEmpty()) Aliyun.SecretKey = setting.SecretKey;\n#pragma warning restore CS0618\n    }\n\n    /// <summary>开始</summary>\n    /// <returns></returns>\n    public Boolean Start()\n    {\n        if (Active) return true;\n\n        _group = Group;\n        _topic = Topic;\n        if (Name.IsNullOrEmpty()) Name = Topic;\n\n        // 解析阿里云实例ID（兼容旧版 AliyunOptions）\n        if (CloudProvider is AliyunProvider ap)\n        {\n            var ns = NameServerAddress;\n            if (ap.InstanceId.IsNullOrEmpty() && !ns.IsNullOrEmpty() && ns.Contains(\"MQ_INST_\"))\n            {\n                ap.InstanceId = ns.Substring(\"://\", \".\");\n            }\n        }\n#pragma warning disable CS0618\n        else if (_aliyunOptions != null && !_aliyunOptions.AccessKey.IsNullOrEmpty())\n        {\n            var ns = NameServerAddress;\n            if (_aliyunOptions.InstanceId.IsNullOrEmpty() && !ns.IsNullOrEmpty() && ns.Contains(\"MQ_INST_\"))\n            {\n                _aliyunOptions.InstanceId = ns.Substring(\"://\", \".\");\n            }\n        }\n#pragma warning restore CS0618\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Start\");\n        try\n        {\n            // 通过 CloudProvider 转换 Topic/Group\n            var provider = CloudProvider;\n            if (provider != null)\n            {\n                Topic = provider.TransformTopic(Topic);\n                Group = provider.TransformGroup(Group);\n            }\n#pragma warning disable CS0618\n            else\n            {\n                // 兼容旧版：阿里云实例ID前缀\n                var ins = _aliyunOptions?.InstanceId;\n                if (!ins.IsNullOrEmpty())\n                {\n                    if (!Topic.StartsWith(ins)) Topic = $\"{ins}%{Topic}\";\n                    if (!Group.StartsWith(ins)) Group = $\"{ins}%{Group}\";\n                }\n            }\n#pragma warning restore CS0618\n\n            OnStart();\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            throw;\n        }\n\n        return Active = true;\n    }\n\n    /// <summary>开始</summary>\n    protected virtual void OnStart()\n    {\n#if NETSTANDARD2_1_OR_GREATER\n        // 使用gRPC协议时，初始化gRPC客户端\n        if (!GrpcProxyAddress.IsNullOrEmpty())\n        {\n            WriteLog(\"使用gRPC协议连接Proxy[{0}]\", GrpcProxyAddress);\n\n            var svc = new Grpc.GrpcMessagingService(GrpcProxyAddress)\n            {\n                Namespace = CloudProvider is AliyunProvider ap ? ap.InstanceId : null,\n                Log = Log,\n                Tracer = Tracer,\n            };\n\n            // 设置认证信息\n            if (CloudProvider != null && !CloudProvider.AccessKey.IsNullOrEmpty())\n            {\n                svc.Client.AccessKey = CloudProvider.AccessKey;\n                svc.Client.SecretKey = CloudProvider.SecretKey;\n            }\n            svc.Client.ClientId = ClientId;\n\n            // 查询路由验证连通性\n            var route = svc.QueryRouteAsync(Topic).ConfigureAwait(false).GetAwaiter().GetResult();\n            if (route.Status?.Code != Grpc.GrpcCode.OK)\n                throw new InvalidOperationException($\"gRPC QueryRoute failed: {route.Status}\");\n\n            WriteLog(\"gRPC路由查询成功，发现[{0}]个队列\", route.MessageQueues.Count);\n            _GrpcService = svc;\n            return;\n        }\n#endif\n\n        if (NameServerAddress.IsNullOrEmpty())\n        {\n            // 通过 CloudProvider 获取 NameServer 地址\n            var addr = CloudProvider?.GetNameServerAddress();\n            if (!addr.IsNullOrEmpty())\n            {\n                NameServerAddress = addr;\n            }\n#pragma warning disable CS0618\n            else\n            {\n                // 兼容旧版：从阿里云 HTTP 接口获取\n                var server = _aliyunOptions?.Server;\n                if (!server.IsNullOrEmpty() && server.StartsWithIgnoreCase(\"http\"))\n                {\n                    var http = new System.Net.Http.HttpClient();\n                    var html = http.GetStringAsync(server).ConfigureAwait(false).GetAwaiter().GetResult();\n\n                    if (!html.IsNullOrWhiteSpace()) NameServerAddress = html.Trim();\n                }\n            }\n#pragma warning restore CS0618\n        }\n\n        WriteLog(\"正在从名称服务器[{0}]查找该Topic所在Broker服务器地址列表\", NameServerAddress);\n\n        var client = new NameClient(ClientId, this)\n        {\n            Name = Name,\n            Tracer = Tracer,\n            Log = Log\n        };\n        client.Start();\n\n        // 阻塞获取Broker地址，确保首次使用之前已经获取到Broker地址\n        var rs = client.GetRouteInfo(Topic);\n        DefaultTopicQueueNums = Math.Min(DefaultTopicQueueNums, rs.Where(e => e.Permission.HasFlag(Permissions.Write) && e.WriteQueueNums > 0).Select(e => e.WriteQueueNums).First());\n\n        foreach (var item in rs)\n        {\n            WriteLog(\"发现Broker[{0}]: {1}, reads={2}, writes={3}\", item.Name, item.Addresses.Join(), item.ReadQueueNums, item.WriteQueueNums);\n        }\n\n        _NameServer = client;\n    }\n\n    /// <summary>停止</summary>\n    /// <returns></returns>\n    public void Stop()\n    {\n        if (!Active) return;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Stop\");\n        try\n        {\n            OnStop();\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            throw;\n        }\n\n        Active = false;\n    }\n\n    /// <summary>停止</summary>\n    protected virtual void OnStop()\n    {\n        foreach (var item in _Brokers)\n        {\n            try\n            {\n                item.Value.UnRegisterClient(Group);\n                item.Value.TryDispose();\n            }\n            catch (Exception ex)\n            {\n                XTrace.WriteException(ex);\n            }\n        }\n        _Brokers.Clear();\n    }\n    #endregion\n\n    #region 收发信息\n    private readonly ConcurrentDictionary<String, BrokerClient> _Brokers = new();\n    /// <summary>获取代理客户端</summary>\n    /// <param name=\"name\"></param>\n    /// <returns></returns>\n    protected BrokerClient GetBroker(String name)\n    {\n        if (String.IsNullOrEmpty(name)) throw new ArgumentException($\"“{nameof(name)}”不能为 null 或空。\", nameof(name));\n\n        if (_Brokers.TryGetValue(name, out var client)) return client;\n\n        var bk = Brokers?.FirstOrDefault(e => name == null || e.Name == name);\n        if (bk == null) return null;\n\n        lock (_Brokers)\n        {\n            if (_Brokers.TryGetValue(name, out client)) return client;\n\n            var addrs = bk.Addresses.ToArray();\n            if (ExternalBroker)\n            {\n                // broker可能在内网，转为公网地址\n                var uri = new NetUri(NameServerAddress.Split(\";\").FirstOrDefault());\n                var ext = uri.Host;\n                if (ext.IsNullOrEmpty()) ext = uri.Address.ToString();\n\n                for (var i = 0; i < addrs.Length; i++)\n                {\n                    var addr = addrs[i];\n                    if (addr.StartsWithIgnoreCase(\"127.\", \"10.\", \"192.\", \"172.\") && !ext.IsNullOrEmpty())\n                    {\n                        var p = addr.IndexOf(':');\n                        addrs[i] = p > 0 ? ext + addr[p..] : ext;\n                    }\n                }\n            }\n\n            // 实例化客户端\n            client = CreateBroker(bk.Name, addrs);\n\n            client.Start();\n\n            // 尝试添加\n            _Brokers.TryAdd(name, client);\n\n            return client;\n        }\n    }\n\n    /// <summary>创建Broker客户端通信</summary>\n    /// <param name=\"name\"></param>\n    /// <param name=\"addrs\"></param>\n    /// <returns></returns>\n    protected virtual BrokerClient CreateBroker(String name, String[] addrs)\n    {\n        var client = new BrokerClient(addrs)\n        {\n            Id = ClientId,\n            Name = name,\n            Config = this,\n\n            Tracer = Tracer,\n            Log = ClientLog,\n        };\n\n        client.Received += (s, e) =>\n        {\n            e.Arg = OnReceive(e.Arg);\n        };\n\n        return client;\n    }\n\n    /// <summary>Broker客户端集合</summary>\n    public ICollection<BrokerClient> Clients => _Brokers.Values;\n\n    /// <summary>收到命令</summary>\n    /// <param name=\"cmd\"></param>\n    protected virtual Command OnReceive(Command cmd) => null;\n    #endregion\n\n    #region 业务方法\n    /// <summary>更新或创建主题。重复执行时为更新</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"queueNum\">队列数</param>\n    /// <param name=\"topicSysFlag\"></param>\n    public virtual Int32 CreateTopic(String topic, Int32 queueNum, Int32 topicSysFlag = 0)\n    {\n        var header = new\n        {\n            topic,\n            defaultTopic = Topic,\n            readQueueNums = queueNum,\n            writeQueueNums = queueNum,\n            perm = 7,\n            topicFilterType = \"SINGLE_TAG\",\n            topicSysFlag,\n            order = false,\n        };\n\n        var count = 0;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:CreateTopic\", header);\n        try\n        {\n            // 在所有Broker上创建Topic\n            foreach (var item in Brokers)\n            {\n                WriteLog(\"在Broker[{0}]上创建主题：{1}\", item.Name, topic);\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.UPDATE_AND_CREATE_TOPIC, null, header);\n                    if (rs != null && rs.Header.Code == (Int32)ResponseCode.SUCCESS) count++;\n                }\n                catch (Exception ex)\n                {\n                    XTrace.WriteException(ex);\n                }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n\n            throw;\n        }\n\n        return count;\n    }\n\n    /// <summary>删除主题</summary>\n    /// <param name=\"topic\">主题</param>\n    public virtual Int32 DeleteTopic(String topic)\n    {\n        var count = 0;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:DeleteTopic\", topic);\n        try\n        {\n            // 从所有Broker上删除\n            foreach (var item in Brokers)\n            {\n                WriteLog(\"在Broker[{0}]上删除主题：{1}\", item.Name, topic);\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.DELETE_TOPIC_IN_BROKER, null, new { topic });\n                    if (rs != null && rs.Header.Code == (Int32)ResponseCode.SUCCESS) count++;\n                }\n                catch (Exception ex)\n                {\n                    XTrace.WriteException(ex);\n                }\n            }\n\n            // 从NameServer上删除\n            try\n            {\n                _NameServer?.Invoke(RequestCode.DELETE_TOPIC_IN_NAMESRV, null, new { topic });\n            }\n            catch (Exception ex)\n            {\n                XTrace.WriteException(ex);\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return count;\n    }\n\n    /// <summary>创建或更新消费组</summary>\n    /// <param name=\"groupName\">消费组名</param>\n    /// <param name=\"consumeBroadcastEnable\">是否允许广播消费</param>\n    /// <param name=\"retryMaxTimes\">最大重试次数</param>\n    /// <param name=\"retryQueueNums\">重试队列数</param>\n    public virtual Int32 CreateSubscriptionGroup(String groupName, Boolean consumeBroadcastEnable = true, Int32 retryMaxTimes = 16, Int32 retryQueueNums = 1)\n    {\n        var count = 0;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:CreateSubscriptionGroup\", groupName);\n        try\n        {\n            var header = new\n            {\n                groupName,\n                consumeBroadcastEnable,\n                consumeEnable = true,\n                retryMaxTimes,\n                retryQueueNums,\n            };\n\n            foreach (var item in Brokers)\n            {\n                WriteLog(\"在Broker[{0}]上创建消费组：{1}\", item.Name, groupName);\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null, header);\n                    if (rs != null && rs.Header.Code == (Int32)ResponseCode.SUCCESS) count++;\n                }\n                catch (Exception ex)\n                {\n                    XTrace.WriteException(ex);\n                }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return count;\n    }\n\n    /// <summary>删除消费组</summary>\n    /// <param name=\"groupName\">消费组名</param>\n    public virtual Int32 DeleteSubscriptionGroup(String groupName)\n    {\n        var count = 0;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:DeleteSubscriptionGroup\", groupName);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                WriteLog(\"在Broker[{0}]上删除消费组：{1}\", item.Name, groupName);\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.DELETE_SUBSCRIPTIONGROUP, null, new { groupName });\n                    if (rs != null && rs.Header.Code == (Int32)ResponseCode.SUCCESS) count++;\n                }\n                catch (Exception ex)\n                {\n                    XTrace.WriteException(ex);\n                }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return count;\n    }\n\n    /// <summary>按消息ID查看消息</summary>\n    /// <param name=\"msgId\">消息编号</param>\n    /// <returns></returns>\n    public virtual MessageExt ViewMessage(String msgId)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:ViewMessage\", msgId);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.VIEW_MESSAGE_BY_ID, null, new { offset = msgId }, true);\n                    if (rs?.Payload != null)\n                    {\n                        var msgs = MessageExt.ReadAll(rs.Payload);\n                        if (msgs?.Count > 0) return msgs[0];\n                    }\n                }\n                catch { }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return null;\n    }\n\n    /// <summary>获取集群信息</summary>\n    /// <returns></returns>\n    public virtual IDictionary<String, Object> GetClusterInfo()\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:GetClusterInfo\");\n        try\n        {\n            var rs = _NameServer?.Invoke(RequestCode.GET_BROKER_CLUSTER_INFO, null);\n            if (rs?.Payload != null) return rs.ReadBodyAsJson();\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return null;\n    }\n\n    /// <summary>获取消费统计信息</summary>\n    /// <param name=\"group\">消费组名</param>\n    /// <param name=\"topic\">主题。默认使用当前Topic</param>\n    /// <returns>消费统计数据的JSON字典</returns>\n    public virtual IDictionary<String, Object> GetConsumeStats(String group, String topic = null)\n    {\n        if (String.IsNullOrEmpty(group)) group = Group;\n        if (String.IsNullOrEmpty(topic)) topic = Topic;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:GetConsumeStats\", group);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.GET_CONSUME_STATS, null, new\n                    {\n                        consumerGroup = group,\n                        topic,\n                    }, true);\n                    if (rs?.Payload != null) return rs.ReadBodyAsJson();\n                }\n                catch { }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return null;\n    }\n\n    /// <summary>获取Topic统计信息</summary>\n    /// <param name=\"topic\">主题。默认使用当前Topic</param>\n    /// <returns>主题统计数据的JSON字典</returns>\n    public virtual IDictionary<String, Object> GetTopicStatsInfo(String topic = null)\n    {\n        if (String.IsNullOrEmpty(topic)) topic = Topic;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:GetTopicStatsInfo\", topic);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.GET_TOPIC_STATS_INFO, null, new { topic }, true);\n                    if (rs?.Payload != null) return rs.ReadBodyAsJson();\n                }\n                catch { }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return null;\n    }\n\n    /// <summary>按Key查询消息</summary>\n    /// <param name=\"topic\">主题</param>\n    /// <param name=\"key\">消息Key</param>\n    /// <param name=\"maxNum\">最大返回数量</param>\n    /// <param name=\"beginTimestamp\">起始时间戳（毫秒）</param>\n    /// <param name=\"endTimestamp\">结束时间戳（毫秒）</param>\n    /// <returns>匹配的消息列表</returns>\n    public virtual IList<MessageExt> QueryMessageByKey(String topic, String key, Int32 maxNum = 32, Int64 beginTimestamp = 0, Int64 endTimestamp = 0)\n    {\n        if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));\n        if (String.IsNullOrEmpty(topic)) topic = Topic;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:QueryMessageByKey\", key);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.QUERY_MESSAGE, null, new\n                    {\n                        topic,\n                        key,\n                        maxNum,\n                        beginTimestamp,\n                        endTimestamp,\n                    }, true);\n                    if (rs?.Payload != null)\n                    {\n                        var msgs = MessageExt.ReadAll(rs.Payload);\n                        if (msgs?.Count > 0) return msgs;\n                    }\n                }\n                catch { }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return [];\n    }\n\n    /// <summary>注册消息过滤服务器。将一个外部过滤服务器注册到Broker上，用于服务端消息过滤</summary>\n    /// <param name=\"filterServerAddr\">过滤服务器地址，格式如 ip:port</param>\n    /// <returns>注册成功的Broker数量</returns>\n    public virtual Int32 RegisterFilterServer(String filterServerAddr)\n    {\n        if (String.IsNullOrEmpty(filterServerAddr)) throw new ArgumentNullException(nameof(filterServerAddr));\n\n        var count = 0;\n        using var span = Tracer?.NewSpan($\"mq:{Name}:RegisterFilterServer\", filterServerAddr);\n        try\n        {\n            foreach (var item in Brokers)\n            {\n                WriteLog(\"在Broker[{0}]上注册过滤服务器：{1}\", item.Name, filterServerAddr);\n                try\n                {\n                    var bk = GetBroker(item.Name);\n                    var rs = bk.Invoke(RequestCode.REGISTER_FILTER_SERVER, null, new { filterServerAddr }, true);\n                    if (rs != null) count++;\n                }\n                catch (Exception ex)\n                {\n                    WriteLog(\"注册过滤服务器失败[{0}]：{1}\", item.Name, ex.Message);\n                }\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n\n        return count;\n    }\n    #endregion\n\n#if NETSTANDARD2_1_OR_GREATER\n    #region gRPC公共方法\n    /// <summary>通过gRPC协议查询主题路由</summary>\n    /// <param name=\"topic\">主题。默认使用当前Topic</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>路由查询结果</returns>\n    public async Task<Grpc.QueryRouteResponse> QueryRouteViaGrpcAsync(String topic = null, CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.QueryRouteAsync(topic ?? Topic, cancellationToken).ConfigureAwait(false);\n    }\n\n    /// <summary>通过gRPC协议上报客户端资源信息（Telemetry）</summary>\n    /// <param name=\"settings\">客户端设置</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>服务端返回的Telemetry命令</returns>\n    public async Task<Grpc.TelemetryCommand> TelemetryViaGrpcAsync(Grpc.GrpcSettings settings, CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:Telemetry:grpc\");\n        try\n        {\n            return await _GrpcService.TelemetryAsync(settings, cancellationToken).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>通过gRPC协议通知客户端终止</summary>\n    /// <param name=\"group\">消费组</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Grpc.NotifyClientTerminationResponse> NotifyClientTerminationViaGrpcAsync(String group = null, CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:NotifyTermination:grpc\");\n        try\n        {\n            return await _GrpcService.NotifyClientTerminationAsync(group ?? Group, cancellationToken).ConfigureAwait(false);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n    #endregion\n#endif\n\n    #region 日志\n    /// <summary>日志</summary>\n    public ILog Log { get; set; } = Logger.Null;\n\n    /// <summary>客户端日志。详细的指令收发日志，仅用于调试</summary>\n    public ILog ClientLog { get; set; }\n\n    /// <summary>写日志</summary>\n    /// <param name=\"format\"></param>\n    /// <param name=\"args\"></param>\n    public void WriteLog(String format, params Object[] args) => Log?.Info($\"[{this}]\" + format, args);\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/MqSetting.cs",
    "content": "﻿using System.ComponentModel;\nusing NewLife.Configuration;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>RocketMQ配置</summary>\n[Config(\"RocketMQ\")]\npublic class MqSetting : Config<MqSetting>\n{\n    /// <summary>名称服务器。将从该地址获取Broker</summary>\n    [Description(\"名称服务器。将从该地址获取Broker\")]\n    public String NameServer { get; set; }\n\n    /// <summary>主题</summary>\n    [Description(\"主题\")]\n    public String Topic { get; set; }\n\n    /// <summary>消费组</summary>\n    [Description(\"消费组\")]\n    public String Group { get; set; }\n\n    /// <summary>获取名称服务器地址的http地址，阿里云专用 http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet</summary>\n    [Description(\"获取名称服务器地址的http地址，阿里云专用 http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet\")]\n    public String Server { get; set; }\n\n    /// <summary>访问令牌，阿里云专用</summary>\n    [Description(\"访问令牌，阿里云专用\")]\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥，阿里云专用</summary>\n    [Description(\"访问密钥，阿里云专用\")]\n    public String SecretKey { get; set; }\n}"
  },
  {
    "path": "NewLife.RocketMQ/NameClient.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing NewLife.Data;\nusing NewLife.Log;\nusing NewLife.Net;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Threading;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>连接名称服务器的客户端</summary>\npublic class NameClient : ClusterClient\n{\n    #region 属性\n\n    /// <summary>Broker集合</summary>\n    public IList<BrokerInfo> Brokers { get; private set; } = [];\n\n    /// <summary>代理改变时触发</summary>\n    public event EventHandler OnBrokerChange;\n\n    /// <summary>额外需要轮询路由的主题列表。多Topic订阅时使用</summary>\n    public String[] ExtraTopics { get; set; }\n\n    private readonly ConcurrentDictionary<String, IList<BrokerInfo>> _topicBrokers = new();\n\n    #endregion\n\n    #region 构造\n\n    /// <summary>实例化</summary>\n    /// <param name=\"id\"></param>\n    /// <param name=\"config\"></param>\n    public NameClient(String id, MqBase config)\n    {\n        Id = id;\n        Config = config;\n    }\n    #endregion\n\n    #region 方法\n\n    /// <inheritdoc/>\n    protected override void Dispose(Boolean disposing)\n    {\n        if (disposing)\n            _timer?.Dispose();\n\n        base.Dispose(disposing);\n    }\n\n    /// <summary>启动</summary>\n    protected override void OnStart()\n    {\n        var cfg = Config;\n        if (cfg.NameServerAddress.IsNullOrEmpty())\n            throw new ArgumentNullException(nameof(cfg.NameServerAddress), \"未指定NameServer地址\");\n\n        var ss = cfg.NameServerAddress.Split(\";\");\n\n        var list = new List<NetUri>();\n        foreach (var item in ss)\n        {\n            var uri = new NetUri(item);\n            if (uri.Type == NetType.Unknown) uri.Type = NetType.Tcp;\n            list.Add(uri);\n        }\n\n        Servers = list.ToArray();\n\n        base.OnStart();\n\n        _timer ??= new TimerX(DoWork, null, cfg.PollNameServerInterval, cfg.PollNameServerInterval) { Async = true };\n    }\n\n    #endregion\n\n    #region 命令\n\n    private TimerX _timer;\n    private String _lastBrokers;\n    private void DoWork(Object state)\n    {\n        var rs = GetRouteInfo(Config.Topic);\n        var str = rs?.Join(\",\", e => $\"{e.Name}={e.Addresses.Join()}\");\n        if (str != _lastBrokers)\n        {\n            _lastBrokers = str;\n            foreach (var item in rs)\n            {\n                WriteLog(\"发现Broker[{0}]: {1}, reads={2}, writes={3}\", item.Name, item.Addresses.Join(), item.ReadQueueNums, item.WriteQueueNums);\n            }\n        }\n\n        // 轮询额外主题的路由\n        var extras = ExtraTopics;\n        if (extras != null && extras.Length > 0)\n        {\n            foreach (var topic in extras)\n            {\n                if (!String.IsNullOrEmpty(topic) && topic != Config.Topic)\n                {\n                    try\n                    {\n                        GetRouteInfo(topic);\n                    }\n                    catch (Exception ex)\n                    {\n                        WriteLog(\"获取主题[{0}]路由失败：{1}\", topic, ex.Message);\n                    }\n                }\n            }\n        }\n    }\n\n    /// <summary>获取指定主题的Broker信息（从缓存）</summary>\n    /// <param name=\"topic\">主题名</param>\n    /// <returns></returns>\n    public IList<BrokerInfo> GetTopicBrokers(String topic)\n    {\n        if (_topicBrokers.TryGetValue(topic, out var list)) return list;\n\n        return Brokers;\n    }\n\n    /// <summary>获取主题的路由信息，含登录验证</summary>\n    /// <param name=\"topic\"></param>\n    /// <returns></returns>\n    public IList<BrokerInfo> GetRouteInfo(String topic)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{topic}:GetRouteInfo\", topic);\n        try\n        {\n            // 发送命令\n            var rs = Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, new { topic });\n            span?.AppendTag(rs.Payload?.ToStr());\n            var js = rs.ReadBodyAsJson();\n\n            var list = new List<BrokerInfo>();\n            // 解析broker集群地址\n            if (js[\"brokerDatas\"] is IList<Object> bs)\n            {\n                foreach (IDictionary<String, Object> item in bs)\n                {\n                    var name = item[\"brokerName\"] + \"\";\n                    var cluster = item[\"cluster\"] + \"\";\n                    if (item[\"brokerAddrs\"] is IDictionary<String, Object> addrs)\n                    {\n                        // key==0为Master，key>0为Slave\n                        var addresses = addrs.Select(e => e.Value + \"\").ToArray();\n                        var isMaster = addrs.ContainsKey(\"0\");\n                        var masterAddr = addrs.TryGetValue(\"0\", out var ma) ? ma + \"\" : null;\n                        var slaveAddrs = addrs.Where(e => e.Key != \"0\").Select(e => e.Value + \"\").ToArray();\n\n                        // 优先 Master 在前\n                        var ordered = new List<String>();\n                        if (!String.IsNullOrEmpty(masterAddr)) ordered.Add(masterAddr);\n                        ordered.AddRange(slaveAddrs);\n\n                        list.Add(new BrokerInfo\n                        {\n                            Name = name,\n                            Cluster = cluster,\n                            Addresses = ordered.ToArray(),\n                            MasterAddress = masterAddr,\n                            SlaveAddresses = slaveAddrs,\n                            IsMaster = isMaster\n                        });\n                    }\n                }\n            }\n\n            // 解析队列集合\n            if (js[\"queueDatas\"] is IList<Object> bs2)\n            {\n                foreach (IDictionary<String, Object> item in bs2)\n                {\n                    var name = item[\"brokerName\"] + \"\";\n\n                    var bk = list.FirstOrDefault(e => e.Name == name);\n                    if (bk == null) list.Add(bk = new BrokerInfo { Name = name });\n\n                    bk.Permission = (Permissions)item[\"perm\"].ToInt();\n                    bk.ReadQueueNums = item[\"readQueueNums\"].ToInt();\n                    bk.WriteQueueNums = item[\"writeQueueNums\"].ToInt();\n                    bk.TopicSynFlag = item[\"topicSysFlag\"].ToInt();\n                }\n            }\n\n            // 如果完全相等，则直接返回。否则重新平衡队列\n            if (Brokers.SequenceEqual(list)) return list.OrderBy(t => t.Name).ToList();\n\n            Brokers = list;\n\n            // 缓存每个主题的Broker信息\n            if (!String.IsNullOrEmpty(topic))\n                _topicBrokers[topic] = list;\n\n            // 结果检查\n            if (list.Count == 0)\n            {\n                WriteLog(\"未能找到主题[{0}]的任何Broker信息，可能是Topic或NameServer错误，也可能是不支持的服务端版本。服务端返回如下：\", topic);\n                WriteLog(rs.Payload.ToStr());\n            }\n\n            // 有改变，重新平衡队列\n            OnBrokerChange?.Invoke(this, EventArgs.Empty);\n\n            return list.OrderBy(t => t.Name).ToList();\n        }\n        catch (ResponseException ex)\n        {\n            if (!MqBase.DefaultTopic.Equals(topic) && ResponseCode.TOPIC_NOT_EXIST.Equals(ex.Code))\n            {\n                WriteLog(\"未能找到主题[{0}]，将读取默认主题[TBW102]的替代。\", topic);\n                var rs = GetRouteInfo(MqBase.DefaultTopic);\n                if (rs != null && rs.Count() > 0)\n                {\n                    if (rs[0].ReadQueueNums > Config.DefaultTopicQueueNums)\n                    {\n                        foreach (var item in rs)\n                        {\n                            item.WriteQueueNums = Config.DefaultTopicQueueNums;\n                            item.ReadQueueNums = Config.DefaultTopicQueueNums;\n                        }\n                    }\n                }\n                return rs;\n            }\n            else\n            {\n                span?.SetError(ex, null);\n                throw;\n            }\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/NewLife.RocketMQ.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFrameworks>net45;net461;netstandard2.0;netstandard2.1</TargetFrameworks>\n    <AssemblyTitle>RocketMQ纯托管客户端</AssemblyTitle>\n    <Description>企业级纯托管 RocketMQ 客户端，双协议支持 Remoting 4.x/5.x 和 gRPC 5.x Proxy，零外部依赖，无需 Java、gRPC、Protobuf 第三方库，统一适配阿里云、华为云、腾讯云及 Apache ACL，完整企业级特性（消费重试、死信队列、事务消息、顺序消费、Pop 消费），十亿级项目验证</Description>\n    <Company>新生命开发团队</Company>\n    <Copyright>©2002-2026 新生命开发团队</Copyright>\n    <VersionPrefix>3.0</VersionPrefix>\n    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>\n    <Version>$(VersionPrefix).$(VersionSuffix)</Version>\n    <FileVersion>$(Version)</FileVersion>\n    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>\n    <Deterministic>false</Deterministic>\n    <OutputPath>..\\Bin</OutputPath>\n    <GenerateDocumentationFile>True</GenerateDocumentationFile>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <LangVersion>latest</LangVersion>\n    <SignAssembly>True</SignAssembly>\n    <AssemblyOriginatorKeyFile>..\\Doc\\newlife.snk</AssemblyOriginatorKeyFile>\n    <AnalysisLevel>latest</AnalysisLevel>\n    <WarningsAsErrors>CA2007</WarningsAsErrors>\n  </PropertyGroup>\n\n  <PropertyGroup>\n    <PackageId>$(AssemblyName)</PackageId>\n    <Authors>$(Company)</Authors>\n    <PackageProjectUrl>https://newlifex.com/core/rocketmq</PackageProjectUrl>\n    <PackageIcon>leaf.png</PackageIcon>\n    <RepositoryUrl>https://github.com/NewLifeX/NewLife.RocketMQ</RepositoryUrl>\n    <RepositoryType>git</RepositoryType>\n    <PackageTags>新生命团队;X组件;NewLife;RocketMQ;纯托管;零依赖;Remoting;gRPC;阿里云;华为云;腾讯云;消息队列;MQ;分布式;事务消息;延迟消息;顺序消息;死信队列;$(AssemblyName)</PackageTags>\n    <PackageReleaseNotes>修复Pop/Ack/ChangeInvisibleTime操作缺少queueId参数导致服务端异常；新增MessageExt便利方法简化消息属性访问；升级基础组件</PackageReleaseNotes>\n    <PackageLicenseExpression>MIT</PackageLicenseExpression>\n    <PublishRepositoryUrl>true</PublishRepositoryUrl>\n    <EmbedUntrackedSources>true</EmbedUntrackedSources>\n    <IncludeSymbols>true</IncludeSymbols>\n    <SymbolPackageFormat>snupkg</SymbolPackageFormat>\n    <PackageReadmeFile>Readme.MD</PackageReadmeFile>\n  </PropertyGroup>\n  \n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.SourceLink.GitHub\" Version=\"10.0.203\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Remove=\"Models\\ConsumeFromWheres.cs\" />\n    <Compile Remove=\"Protocol\\SendStatus.cs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Include=\"..\\Doc\\leaf.png\" Link=\"leaf.png\" PackagePath=\"\\\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"..\\Readme.MD\">\n      <Pack>True</Pack>\n      <PackagePath>\\</PackagePath>\n    </None>\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"NewLife.Core\" Version=\"11.15.2026.501\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NewLife.RocketMQ/Producer.cs",
    "content": "﻿using System.Collections.Concurrent;\nusing System.Globalization;\nusing NewLife.Log;\nusing NewLife.Reflection;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Common;\nusing NewLife.RocketMQ.MessageTrace;\nusing NewLife.RocketMQ.Models;\nusing NewLife.RocketMQ.Protocol;\n\nnamespace NewLife.RocketMQ;\n\n/// <summary>生产者</summary>\npublic class Producer : MqBase\n{\n    private const Int32 CommitLogOffsetHexLength = 16;\n\n    #region 属性\n    /// <summary>负载均衡。发布消息时，分发到各个队列的负载均衡算法，默认使用带权重的轮询</summary>\n    public ILoadBalance LoadBalance { get; set; }\n\n    //public Int32 DefaultTopicQueueNums { get; set; } = 4;\n\n    //public Int32 SendMsgTimeout { get; set; } = 3_000;\n\n    //public Int32 CompressMsgBodyOverHowmuch { get; set; } = 4096;\n\n    /// <summary>发送消息失败时的重试次数。默认3次</summary>\n    public Int32 RetryTimesWhenSendFailed { get; set; } = 3;\n\n    //public Int32 RetryTimesWhenSendAsyncFailed { get; set; } = 2;\n\n    //public Boolean RetryAnotherBrokerWhenNotStoreOK { get; set; }\n\n    /// <summary>最大消息大小。默认4*1024*1024</summary>\n    public Int32 MaxMessageSize { get; set; } = 4 * 1024 * 1024;\n\n    /// <summary>消息体压缩阈值（字节）。超过该大小自动ZLIB压缩，默认4096。设为0则禁用压缩</summary>\n    public Int32 CompressOverBytes { get; set; } = 4096;\n\n    private readonly IList<ISendMessageHook> _sendMessageHooks = new List<ISendMessageHook>();\n    private AsyncTraceDispatcher _traceDispatcher;\n\n    /// <summary>请求超时时间。默认3000ms</summary>\n    public Int32 RequestTimeout { get; set; } = 3_000;\n\n    /// <summary>事务回查委托。Broker发起事务回查时调用，参数为消息和事务ID，返回事务状态</summary>\n    public Func<MessageExt, String, TransactionState> OnCheckTransaction;\n\n    /// <summary>异步事务回查委托。Broker发起事务回查时调用，参数为消息、事务ID和取消令牌，返回事务状态</summary>\n    public Func<MessageExt, String, CancellationToken, Task<TransactionState>> OnCheckTransactionAsync;\n\n    private readonly ConcurrentDictionary<String, TaskCompletionSource<MessageExt>> _requestCallbacks = new();\n    private Consumer _replyConsumer;\n    private String _replyTopic;\n    #endregion\n\n    #region 基础方法\n    /// <summary>启动</summary>\n    /// <returns></returns>\n    protected override void OnStart()\n    {\n        base.OnStart();\n\n        if (EnableMessageTrace)\n        {\n            _traceDispatcher = new AsyncTraceDispatcher();\n            _traceDispatcher.Start(NameServerAddress);\n            _sendMessageHooks.Add(new MessageTraceHook(_traceDispatcher));\n        }\n\n        LoadBalance ??= new WeightRoundRobin();\n\n        if (_NameServer != null)\n        {\n            _NameServer.OnBrokerChange += (s, e) =>\n            {\n                _brokers = null;\n                //_robin = null;\n                LoadBalance.Ready = false;\n            };\n        }\n\n        // 初始化回复消息消费者\n        _replyTopic = $\"{Topic}_REPLY_{ClientId}\";\n    }\n\n    /// <summary>停止</summary>\n    protected override void OnStop()\n    {\n        // 停止回复消息消费者\n        if (_replyConsumer != null)\n        {\n            _replyConsumer.Stop();\n            _replyConsumer.Dispose();\n            _replyConsumer = null;\n        }\n\n        base.OnStop();\n    }\n    #endregion\n\n    #region 发布消息（普通/顺序）\n    /// <summary>发送消息</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列。指定时可实现顺序发布（通过SelectQueue获取），默认未指定并自动选择队列</param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult Publish(Message message, MessageQueue queue, Int32 timeout = -1)\n    {\n        // 构造请求头\n        var header = CreateHeader(message);\n\n        for (var i = 0; i <= RetryTimesWhenSendFailed; i++)\n        {\n            // 选择队列分片\n            var mq = queue ?? SelectQueue();\n            mq.Topic = Topic;\n            header.QueueId = mq.QueueId;\n            header.BrokerName = mq.BrokerName;\n\n            // 性能埋点\n            using var span = Tracer?.NewSpan($\"mq:{Name}:Publish\", message.BodyString);\n            span?.AppendTag($\"queue={mq}\");\n\n            SendMessageContext context = null;\n            try\n            {\n                // 根据队列获取Broker客户端\n                var bk = GetBroker(mq.BrokerName);\n\n                context = new SendMessageContext\n                {\n                    ProducerGroup = Group,\n                    Message = message,\n                    Mq = mq,\n                    BrokerAddr = bk.Name,\n                };\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try { hook.ExecuteHookBefore(context); }\n                    catch (Exception e) \n                    { \n                        if (Log.Enable) Log.Error(e.Message); \n                    }\n                }\n\n                var rs = bk.Invoke(RequestCode.SEND_MESSAGE_V2, message.Body, header.GetProperties(), true);\n\n                // 包装结果\n                var result = new SendResult\n                {\n                    Queue = mq,\n                    Header = rs.Header,\n                    Status = (ResponseCode)rs.Header.Code switch\n                    {\n                        ResponseCode.SUCCESS => SendStatus.SendOK,\n                        ResponseCode.FLUSH_DISK_TIMEOUT => SendStatus.FlushDiskTimeout,\n                        ResponseCode.FLUSH_SLAVE_TIMEOUT => SendStatus.FlushSlaveTimeout,\n                        ResponseCode.SLAVE_NOT_AVAILABLE => SendStatus.SlaveNotAvailable,\n                        _ => throw rs.Header.CreateException(),\n                    }\n                };\n                result.Read(rs.Header?.ExtFields);\n\n                context.SendResult = result;\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try { hook.ExecuteHookAfter(context); }\n                    catch (Exception e) { if (Log.Enable) Log.Error(e.Message); }\n                }\n\n                span?.AppendTag($\"Status={result.Status}\");\n                span?.AppendTag($\"MsgId={result.MsgId}\");\n                span?.AppendTag($\"OffsetMsgId={result.OffsetMsgId}\");\n                span?.AppendTag($\"QueueOffset={result.QueueOffset}\");\n                span?.AppendTag($\"TransactionId={result.TransactionId}\");\n\n                if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"{0}\", result);\n\n                return result;\n            }\n            catch (Exception ex)\n            {\n                if (context != null)\n                {\n                    context.E = ex;\n                    foreach (var hook in _sendMessageHooks)\n                    {\n                        try { hook.ExecuteHookAfter(context); }\n                        catch (Exception e) { if (Log.Enable) Log.Error(e.Message); }\n                    }\n                }\n\n                // 如果网络异常，则延迟重发\n                if (i < RetryTimesWhenSendFailed)\n                {\n                    Thread.Sleep(1000);\n                    continue;\n                }\n\n                span?.SetError(ex, message);\n\n                throw;\n            }\n        }\n\n        return null;\n    }\n\n    /// <summary>发布消息</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult Publish(Object body, Int32 timeout = -1) => Publish(CreateMessage(body), null, timeout);\n\n    /// <summary>发布消息</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"tags\"></param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult Publish(Object body, String tags, Int32 timeout = -1)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n\n        return Publish(message, null, timeout);\n    }\n\n    /// <summary>发布消息</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"tags\">传null则为空</param>\n    /// <param name=\"keys\">传null则为空</param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult Publish(Object body, String tags, String keys, Int32 timeout = -1)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n        message.Keys = keys;\n\n        return Publish(message, null, timeout);\n    }\n\n    /// <summary>发布事务消息（半消息）</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列</param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult PublishTransaction(Message message, MessageQueue queue = null, Int32 timeout = -1)\n    {\n        if (message is null) throw new ArgumentNullException(nameof(message));\n\n        message.Properties[\"TRAN_MSG\"] = \"true\";\n        message.Properties[\"PGROUP\"] = Group;\n\n        return Publish(message, queue, timeout);\n    }\n\n    /// <summary>发布事务消息（半消息）</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"tags\"></param>\n    /// <param name=\"keys\"></param>\n    /// <param name=\"timeout\"></param>\n    /// <returns></returns>\n    public virtual SendResult PublishTransaction(Object body, String tags = null, String keys = null, Int32 timeout = -1)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n        message.Keys = keys;\n\n        return PublishTransaction(message, null, timeout);\n    }\n    #endregion\n\n    #region 异步发布消息\n    /// <summary>发布消息</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列。指定时可实现顺序发布（通过SelectQueue获取），默认未指定并自动选择队列</param>\n    /// <param name=\"cancellationToken\"></param>\n    public virtual async Task<SendResult> PublishAsync(Message message, MessageQueue queue, CancellationToken cancellationToken = default)\n    {\n        if (message is null) throw new ArgumentNullException(nameof(message));\n\n#if NETSTANDARD2_1_OR_GREATER\n        // gRPC模式\n        if (_GrpcService != null)\n            return await PublishViaGrpcAsync(message, cancellationToken).ConfigureAwait(false);\n#endif\n\n        // 构造请求头\n        var header = CreateHeader(message);\n\n        for (var i = 0; i <= RetryTimesWhenSendFailed; i++)\n        {\n            // 选择队列分片\n            var mq = queue ?? SelectQueue();\n            mq.Topic = Topic;\n            header.QueueId = mq.QueueId;\n\n            // 性能埋点\n            using var span = Tracer?.NewSpan($\"mq:{Name}:PublishAsync\", message.BodyString);\n            try\n            {\n                var bk = GetBroker(mq.BrokerName);\n\n                var context = new SendMessageContext();\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookBefore(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                var rs = await bk.InvokeAsync(RequestCode.SEND_MESSAGE_V2, message.Body, header.GetProperties(), true, cancellationToken).ConfigureAwait(false);\n\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookAfter(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                // 包装结果\n                var sendResult = new SendResult\n                {\n                    Queue = mq,\n                    Header = rs.Header,\n                    Status = (ResponseCode)rs.Header.Code switch\n                    {\n                        ResponseCode.SUCCESS => SendStatus.SendOK,\n                        ResponseCode.FLUSH_DISK_TIMEOUT => SendStatus.FlushDiskTimeout,\n                        ResponseCode.FLUSH_SLAVE_TIMEOUT => SendStatus.FlushSlaveTimeout,\n                        ResponseCode.SLAVE_NOT_AVAILABLE => SendStatus.SlaveNotAvailable,\n                        _ => throw rs.Header.CreateException(),\n                    }\n                };\n                sendResult.Read(rs.Header?.ExtFields);\n\n                return sendResult;\n            }\n            catch (Exception ex)\n            {\n                // 如果网络异常，则延迟重发\n                if (i < RetryTimesWhenSendFailed)\n                {\n                    await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);\n                    continue;\n                }\n\n                span?.SetError(ex, message);\n\n                throw;\n            }\n        }\n\n        return null;\n    }\n\n    /// <summary>异步发布事务消息（半消息）</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列</param>\n    /// <param name=\"cancellationToken\">取消令牌</param>\n    /// <returns></returns>\n    public virtual Task<SendResult> PublishTransactionAsync(Message message, MessageQueue queue = null, CancellationToken cancellationToken = default)\n    {\n        if (message is null) throw new ArgumentNullException(nameof(message));\n\n        message.Properties[\"TRAN_MSG\"] = \"true\";\n        message.Properties[\"PGROUP\"] = Group;\n\n        return PublishAsync(message, queue, cancellationToken);\n    }\n\n    /// <summary>发布消息</summary>\n    /// <param name=\"body\"></param>\n    /// <returns></returns>\n    public virtual Task<SendResult> PublishAsync(Object body) => PublishAsync(CreateMessage(body), null);\n\n    /// <summary>发布消息</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"tags\">传null则为空</param>\n    /// <param name=\"keys\">传null则为空</param>\n    /// <returns></returns>\n    public virtual Task<SendResult> PublishAsync(Object body, String tags, String keys)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n        message.Keys = keys;\n\n        return PublishAsync(message, null);\n    }\n\n#if NETSTANDARD2_1_OR_GREATER\n    /// <summary>通过gRPC协议发送消息</summary>\n    /// <param name=\"message\">消息</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    private async Task<SendResult> PublishViaGrpcAsync(Message message, CancellationToken cancellationToken)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:PublishAsync:grpc\", message.BodyString);\n        try\n        {\n            var keys = message.Keys?.Split(',').Where(k => !String.IsNullOrEmpty(k)).ToList();\n            var rs = await _GrpcService.SendMessageAsync(\n                Topic,\n                message.Body,\n                tag: message.Tags,\n                keys: keys,\n                properties: message.Properties.Count > 0 ? message.Properties : null,\n                cancellationToken: cancellationToken\n            ).ConfigureAwait(false);\n\n            if (rs.Status?.Code != Grpc.GrpcCode.OK)\n                throw new InvalidOperationException($\"gRPC SendMessage failed: {rs.Status}\");\n\n            var entry = rs.Entries.FirstOrDefault();\n            var result = new SendResult\n            {\n                Status = SendStatus.SendOK,\n                MsgId = entry?.MessageId,\n                TransactionId = entry?.TransactionId,\n                QueueOffset = entry?.Offset ?? 0,\n            };\n\n            return result;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, message);\n            throw;\n        }\n    }\n#endif\n    #endregion\n\n    #region 发布单向消息\n    /// <summary>发送消息，不等结果</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列。指定时可实现顺序发布（通过SelectQueue获取），默认未指定并自动选择队列</param>\n    /// <returns></returns>\n    public virtual SendResult PublishOneway(Message message, MessageQueue queue)\n    {\n        // 构造请求头\n        var header = CreateHeader(message);\n\n        for (var i = 0; i <= RetryTimesWhenSendFailed; i++)\n        {\n            // 选择队列分片\n            var mq = queue ?? SelectQueue();\n            mq.Topic = Topic;\n            header.QueueId = mq.QueueId;\n\n            // 性能埋点\n            using var span = Tracer?.NewSpan($\"mq:{Name}:PublishOneway\", message.BodyString);\n            try\n            {\n                // 根据队列获取Broker客户端\n                var bk = GetBroker(mq.BrokerName);\n\n                var context = new SendMessageContext\n                {\n                    ProducerGroup = Group,\n                    Message = message,\n                    Mq = mq,\n                    BrokerAddr = bk.Name,\n                };\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookBefore(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                var rs = bk.InvokeOneway(RequestCode.SEND_MESSAGE_V2, message.Body, header.GetProperties());\n\n                // 包装结果\n                var sendResult = new SendResult\n                {\n                    Queue = mq,\n                    Header = rs.Header,\n                    Status = rs.Header.Code switch\n                    {\n                        -1 => SendStatus.SendError,\n\n                        _ => SendStatus.SendOK,\n                    }\n                };\n\n                context.SendResult = sendResult;\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookAfter(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"{0}\", sendResult);\n\n                return sendResult;\n\n            }\n            catch (Exception ex)\n            {\n                // 如果网络异常，则延迟重发\n                if (i < RetryTimesWhenSendFailed)\n                {\n                    Thread.Sleep(1000);\n                    continue;\n                }\n\n                span?.SetError(ex, message);\n\n                throw;\n            }\n        }\n        return null;\n    }\n\n    /// <summary>发布消息，不等结果</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"tags\"></param>\n    /// <returns></returns>\n    public virtual void PublishOneway(Object body, String tags = null)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n\n        PublishOneway(message, null);\n    }\n    #endregion\n\n    #region 批量发布消息\n    /// <summary>批量发布消息</summary>\n    /// <param name=\"messages\">消息集合</param>\n    /// <param name=\"timeout\">超时时间</param>\n    /// <returns></returns>\n    public virtual SendResult PublishBatch(IList<Message> messages, Int32 timeout = -1)\n    {\n        if (messages == null || messages.Count == 0) throw new ArgumentException(\"消息集合不能为空\", nameof(messages));\n\n        // 编码批量消息体\n        var ms = new MemoryStream();\n        var bn = new NewLife.Serialization.Binary { Stream = ms, IsLittleEndian = false };\n        foreach (var msg in messages)\n        {\n            msg.Topic ??= Topic;\n            var body = msg.Body ?? new Byte[0];\n            var props = msg.GetProperties() ?? \"\";\n            var propsBytes = props.GetBytes();\n\n            // 按照 RocketMQ 批量消息编码格式\n            // TotalSize(4) + MagicCode(4) + BodyCRC(4) + Flag(4) + BodyLen(4) + Body + PropsLen(2) + Props\n            var totalSize = 4 + 4 + 4 + 4 + 4 + body.Length + 2 + propsBytes.Length;\n            bn.Write(totalSize);\n            bn.Write(0); // MagicCode\n            bn.Write(0); // BodyCRC\n            bn.Write(msg.Flag);\n            bn.Write(body.Length);\n            ms.Write(body, 0, body.Length);\n            bn.Write((Int16)propsBytes.Length);\n            ms.Write(propsBytes, 0, propsBytes.Length);\n        }\n\n        var batchBody = ms.ToArray();\n\n        // 使用第一条消息的属性作为批量消息头\n        var firstMsg = messages[0];\n        var header = new SendMessageRequestHeader\n        {\n            ProducerGroup = Group,\n            Topic = Topic,\n            SysFlag = 0,\n            BornTimestamp = DateTime.UtcNow.ToLong(),\n            Flag = firstMsg.Flag,\n            Properties = firstMsg.GetProperties(),\n            ReconsumeTimes = 0,\n            UnitMode = UnitMode,\n            Batch = true,\n            DefaultTopic = DefaultTopic,\n            DefaultTopicQueueNums = DefaultTopicQueueNums\n        };\n\n        // 选择队列分片\n        var mq = SelectQueue();\n        if (mq == null) return null;\n\n        mq.Topic = Topic;\n        header.QueueId = mq.QueueId;\n        header.BrokerName = mq.BrokerName;\n\n        using var span = Tracer?.NewSpan($\"mq:{Name}:PublishBatch\", messages.Count);\n        try\n        {\n            var bk = GetBroker(mq.BrokerName);\n            var rs = bk.Invoke(RequestCode.SEND_BATCH_MESSAGE, batchBody, header.GetProperties(), true);\n\n            var result = new SendResult\n            {\n                Queue = mq,\n                Header = rs.Header,\n                Status = (ResponseCode)rs.Header.Code switch\n                {\n                    ResponseCode.SUCCESS => SendStatus.SendOK,\n                    ResponseCode.FLUSH_DISK_TIMEOUT => SendStatus.FlushDiskTimeout,\n                    ResponseCode.FLUSH_SLAVE_TIMEOUT => SendStatus.FlushSlaveTimeout,\n                    ResponseCode.SLAVE_NOT_AVAILABLE => SendStatus.SlaveNotAvailable,\n                    _ => throw rs.Header.CreateException(),\n                }\n            };\n            result.Read(rs.Header?.ExtFields);\n\n            return result;\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>批量发布字符串消息</summary>\n    /// <param name=\"bodies\">消息体集合</param>\n    /// <param name=\"tags\">标签</param>\n    /// <returns></returns>\n    public virtual SendResult PublishBatch(IList<String> bodies, String tags = null)\n    {\n        var messages = new List<Message>();\n        foreach (var body in bodies)\n        {\n            var msg = new Message();\n            msg.SetBody(body);\n            if (tags != null) msg.Tags = tags;\n            messages.Add(msg);\n        }\n\n        return PublishBatch(messages);\n    }\n    #endregion\n\n    #region 发布延迟消息\n    /// <summary>发布延迟消息</summary>\n    /// <param name=\"message\">消息体</param>\n    /// <param name=\"queue\">目标队列。指定时可实现顺序发布（通过SelectQueue获取），默认未指定并自动选择队列</param>\n    /// <param name=\"level\">延迟时间等级。18级</param>\n    /// <returns></returns>\n    public virtual SendResult PublishDelay(Message message, MessageQueue queue, DelayTimeLevels level)\n    {\n        // 构造请求头\n        message.DelayTimeLevel = (Int32)level;\n        var header = CreateHeader(message);\n\n        for (var i = 0; i <= RetryTimesWhenSendFailed; i++)\n        {\n            // 选择队列分片\n            var mq = queue ?? SelectQueue();\n            mq.Topic = Topic;\n            header.QueueId = mq.QueueId;\n\n            // 性能埋点\n            using var span = Tracer?.NewSpan($\"mq:{Name}:PublishDelay\", new { level, message.BodyString });\n            try\n            {\n                // 根据队列获取Broker客户端\n                var bk = GetBroker(mq.BrokerName);\n\n                var context = new SendMessageContext\n                {\n                    ProducerGroup = Group,\n                    Message = message,\n                    Mq = mq,\n                    BrokerAddr = bk.Name,\n                };\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookBefore(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                var rs = bk.InvokeOneway(RequestCode.SEND_MESSAGE_V2, message.Body, header.GetProperties());\n\n                // 包装结果\n                var sendResult = new SendResult\n                {\n                    Queue = mq,\n                    Header = rs.Header,\n                    Status = rs.Header.Code switch\n                    {\n                        -1 => SendStatus.SendError,\n\n                        _ => SendStatus.SendOK,\n                    }\n                };\n\n                context.SendResult = sendResult;\n                foreach (var hook in _sendMessageHooks)\n                {\n                    try\n                    {\n                        hook.ExecuteHookAfter(context);\n                    }\n                    catch (Exception e)\n                    {\n                        if (Log.Enable) Log.Error(e.Message);\n                    }\n                }\n\n                if (Log != null && Log.Level <= LogLevel.Debug) WriteLog(\"{0}\", sendResult);\n\n                return sendResult;\n\n            }\n            catch (Exception ex)\n            {\n                // 如果网络异常，则延迟重发\n                if (i < RetryTimesWhenSendFailed)\n                {\n                    Thread.Sleep(1000);\n                    continue;\n                }\n\n                span?.SetError(ex, message);\n\n                throw;\n            }\n        }\n        return null;\n    }\n\n    /// <summary>发布延迟消息</summary>\n    /// <param name=\"body\"></param>\n    /// <param name=\"level\">延迟时间等级。18级</param>\n    /// <param name=\"tags\"></param>\n    /// <returns></returns>\n    public virtual void PublishDelay(Object body, DelayTimeLevels level, String tags = null)\n    {\n        var message = CreateMessage(body);\n        message.Tags = tags;\n\n        PublishDelay(message, null, level);\n    }\n    #endregion\n\n    #region 结束事务消息\n    /// <summary>结束事务消息。提交或回滚事务</summary>\n    /// <param name=\"result\">发布事务消息返回结果</param>\n    /// <param name=\"state\">事务状态</param>\n    /// <param name=\"fromTransactionCheck\">是否来自事务回查</param>\n    public virtual void EndTransaction(SendResult result, TransactionState state, Boolean fromTransactionCheck = false)\n    {\n        if (result is null) throw new ArgumentNullException(nameof(result));\n        if (result.Queue == null) throw new ArgumentNullException(nameof(result), \"缺少队列信息\");\n        if (result.Queue.BrokerName.IsNullOrEmpty()) throw new ArgumentNullException(nameof(result), \"缺少BrokerName\");\n\n        var header = new EndTransactionRequestHeader\n        {\n            ProducerGroup = Group,\n            TranStateTableOffset = result.QueueOffset,\n            CommitLogOffset = GetCommitLogOffset(result.OffsetMsgId),\n            CommitOrRollback = (Int32)state,\n            FromTransactionCheck = fromTransactionCheck,\n            MsgId = result.MsgId,\n            TransactionId = result.TransactionId,\n        };\n\n        var bk = GetBroker(result.Queue.BrokerName);\n        bk.Invoke(RequestCode.END_TRANSACTION, null, header.GetProperties());\n    }\n\n    /// <summary>异步结束事务消息。提交或回滚事务</summary>\n    /// <param name=\"result\">发布事务消息返回结果</param>\n    /// <param name=\"state\">事务状态</param>\n    /// <param name=\"fromTransactionCheck\">是否来自事务回查</param>\n    /// <param name=\"cancellationToken\">取消令牌</param>\n    /// <returns></returns>\n    public virtual async Task EndTransactionAsync(SendResult result, TransactionState state, Boolean fromTransactionCheck = false, CancellationToken cancellationToken = default)\n    {\n        if (result is null) throw new ArgumentNullException(nameof(result));\n        if (result.Queue == null) throw new ArgumentNullException(nameof(result), \"缺少队列信息\");\n        if (result.Queue.BrokerName.IsNullOrEmpty()) throw new ArgumentNullException(nameof(result), \"缺少BrokerName\");\n\n        var header = new EndTransactionRequestHeader\n        {\n            ProducerGroup = Group,\n            TranStateTableOffset = result.QueueOffset,\n            CommitLogOffset = GetCommitLogOffset(result.OffsetMsgId),\n            CommitOrRollback = (Int32)state,\n            FromTransactionCheck = fromTransactionCheck,\n            MsgId = result.MsgId,\n            TransactionId = result.TransactionId,\n        };\n\n        var bk = GetBroker(result.Queue.BrokerName);\n        await bk.InvokeAsync(RequestCode.END_TRANSACTION, null, header.GetProperties(), false, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n\n    #region 辅助\n    /// <summary>\n    /// 创建消息，设计于支持用户重载以改变消息序列化行为\n    /// </summary>\n    /// <param name=\"body\"></param>\n    /// <returns></returns>\n    protected virtual Message CreateMessage(Object body)\n    {\n        if (body is null) throw new ArgumentNullException(nameof(body));\n        if (body is Message) throw new ArgumentOutOfRangeException(nameof(body), \"body不能是Message类型\");\n\n        if (!body.GetType().IsBaseType()) body = JsonHost.Write(body);\n\n        var msg = new Message();\n        msg.SetBody(body);\n\n        return msg;\n    }\n\n    private SendMessageRequestHeader CreateHeader(Message message)\n    {\n        var max = MaxMessageSize;\n        if (max > 0 && message.Body.Length > max) throw new InvalidOperationException($\"主题[{Topic}]的数据包大小[{message.Body.Length}]超过最大限制[{max}]，大key会拖累整个队列，可通过MaxMessageSize调节。\");\n\n        // 消息压缩\n        var sysFlag = 0;\n        var compressOver = CompressOverBytes;\n        if (compressOver > 0 && message.Body != null && message.Body.Length > compressOver)\n        {\n            message.Body = message.Body.Compress();\n            sysFlag |= 1; // 第0位表示压缩\n        }\n\n        // 构造请求头\n        var smrh = new SendMessageRequestHeader\n        {\n            ProducerGroup = Group,\n            Topic = Topic,\n            //QueueId = mq.QueueId,\n            SysFlag = sysFlag,\n            BornTimestamp = DateTime.UtcNow.ToLong(),\n            Flag = message.Flag,\n            Properties = message.GetProperties(),\n            ReconsumeTimes = 0,\n            UnitMode = UnitMode,\n            DefaultTopic = DefaultTopic,\n            DefaultTopicQueueNums = DefaultTopicQueueNums\n        };\n\n        if (message.Properties.TryGetValue(\"TRAN_MSG\", out var str) && str.ToBoolean()) smrh.SysFlag = (Int32)TransactionState.Prepared;\n\n        return smrh;\n    }\n\n    private static Int64 GetCommitLogOffset(String offsetMsgId)\n    {\n        if (offsetMsgId.IsNullOrEmpty() || offsetMsgId.Length < CommitLogOffsetHexLength) return 0;\n\n        // OffsetMsgId尾部16位是8字节（Int64）的CommitLogOffset十六进制表示\n        return Int64.TryParse(offsetMsgId.Substring(offsetMsgId.Length - CommitLogOffsetHexLength), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var rs) ? rs : 0;\n    }\n    #endregion\n\n    #region 选择Broker队列\n    private IList<BrokerInfo> _brokers;\n    //private WeightRoundRobin _robin;\n    /// <summary>选择队列</summary>\n    /// <returns></returns>\n    public virtual MessageQueue SelectQueue()\n    {\n        var lb = LoadBalance;\n        if (!lb.Ready)\n        {\n            // 只选择主节点且可写\n            var list = Brokers.Where(e => e.IsMaster && e.Permission.HasFlag(Permissions.Write) && e.WriteQueueNums > 0).ToList();\n            if (list.Count == 0) return null;\n\n            var total = list.Sum(e => e.WriteQueueNums);\n            if (total <= 0) return null;\n\n            _brokers = list;\n            //lb = new WeightRoundRobin();\n            lb.Set(list.Select(e => e.WriteQueueNums).ToArray());\n        }\n\n        // 解锁解决冲突，让消息发送更均匀\n        lock (lb)\n        {\n            // 构造排序列表。希望能够均摊到各Broker\n            var idx = lb.Get(out var times);\n            var bk = _brokers[idx];\n            return new MessageQueue { BrokerName = bk.Name, QueueId = (times - 1) % bk.WriteQueueNums };\n        }\n    }\n    #endregion\n\n    #region Request-Reply 请求响应模式\n    /// <summary>发送请求消息，同步等待响应</summary>\n    /// <param name=\"message\">请求消息</param>\n    /// <param name=\"timeout\">超时时间(毫秒)，默认使用RequestTimeout</param>\n    /// <returns>响应消息</returns>\n    public virtual MessageExt Request(Message message, Int32 timeout = -1)\n    {\n        if (message == null) throw new ArgumentNullException(nameof(message));\n\n        if (timeout <= 0) timeout = RequestTimeout;\n\n        // 生成关联ID\n        var correlationId = Guid.NewGuid().ToString(\"N\");\n        message.CorrelationId = correlationId;\n        message.ReplyToClient = ClientId;\n        message.MessageType = \"REQUEST\";\n        message.RequestTimeout = timeout;\n\n        // 注册回调\n        var tcs = new TaskCompletionSource<MessageExt>();\n        _requestCallbacks[correlationId] = tcs;\n\n        try\n        {\n            // 发送请求消息\n            var result = Publish(message, null, timeout);\n\n            // 等待响应\n            if (tcs.Task.Wait(timeout))\n            {\n                return tcs.Task.Result;\n            }\n            else\n            {\n                throw new TimeoutException($\"Request timeout after {timeout}ms, correlationId={correlationId}\");\n            }\n        }\n        finally\n        {\n            // 清理回调\n            _requestCallbacks.TryRemove(correlationId, out _);\n        }\n    }\n\n    /// <summary>发送请求消息，同步等待响应</summary>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"timeout\">超时时间(毫秒)</param>\n    /// <returns>响应消息</returns>\n    public virtual MessageExt Request(Object body, Int32 timeout = -1) => Request(CreateMessage(body), timeout);\n\n    /// <summary>发送请求消息，异步等待响应</summary>\n    /// <param name=\"message\">请求消息</param>\n    /// <param name=\"timeout\">超时时间(毫秒)，默认使用RequestTimeout</param>\n    /// <param name=\"cancellationToken\">取消令牌</param>\n    /// <returns>响应消息</returns>\n    public virtual async Task<MessageExt> RequestAsync(Message message, Int32 timeout = -1, CancellationToken cancellationToken = default)\n    {\n        if (message == null) throw new ArgumentNullException(nameof(message));\n\n        if (timeout <= 0) timeout = RequestTimeout;\n\n        // 生成关联ID\n        var correlationId = Guid.NewGuid().ToString(\"N\");\n        message.CorrelationId = correlationId;\n        message.ReplyToClient = ClientId;\n        message.MessageType = \"REQUEST\";\n        message.RequestTimeout = timeout;\n\n        // 注册回调\n        var tcs = new TaskCompletionSource<MessageExt>();\n        _requestCallbacks[correlationId] = tcs;\n\n        try\n        {\n            // 发送请求消息\n            await PublishAsync(message, null, cancellationToken).ConfigureAwait(false);\n\n            // 等待响应，使用兼容的方式\n            var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false);\n            \n            if (completedTask == tcs.Task)\n            {\n                return await tcs.Task.ConfigureAwait(false);\n            }\n            else\n            {\n                throw new TimeoutException($\"Request timeout after {timeout}ms, correlationId={correlationId}\");\n            }\n        }\n        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)\n        {\n            throw new TimeoutException($\"Request timeout after {timeout}ms, correlationId={correlationId}\");\n        }\n        finally\n        {\n            // 清理回调\n            _requestCallbacks.TryRemove(correlationId, out _);\n        }\n    }\n\n    /// <summary>发送请求消息，异步等待响应</summary>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"timeout\">超时时间(毫秒)</param>\n    /// <param name=\"cancellationToken\">取消令牌</param>\n    /// <returns>响应消息</returns>\n    public virtual Task<MessageExt> RequestAsync(Object body, Int32 timeout = -1, CancellationToken cancellationToken = default)\n        => RequestAsync(CreateMessage(body), timeout, cancellationToken);\n\n    /// <summary>处理回复消息</summary>\n    /// <param name=\"message\">回复消息</param>\n    internal void HandleReplyMessage(MessageExt message)\n    {\n        var correlationId = message.CorrelationId;\n        if (String.IsNullOrEmpty(correlationId)) return;\n\n        if (_requestCallbacks.TryGetValue(correlationId, out var tcs))\n        {\n            tcs.TrySetResult(message);\n        }\n    }\n    #endregion\n\n    #region 事务回查\n    /// <summary>收到命令</summary>\n    /// <param name=\"cmd\"></param>\n    protected override Command OnReceive(Command cmd)\n    {\n        if (!cmd.Reply)\n        {\n            var code = (RequestCode)cmd.Header.Code;\n            if (code == RequestCode.CHECK_TRANSACTION_STATE)\n                return HandleCheckTransaction(cmd);\n        }\n\n        return null;\n    }\n\n    private Command HandleCheckTransaction(Command cmd)\n    {\n        using var span = Tracer?.NewSpan($\"mq:{Name}:CheckTransaction\");\n        try\n        {\n            var dic = cmd.Header?.ExtFields;\n            var transactionId = dic != null && dic.TryGetValue(\"transactionId\", out var tid) ? tid : null;\n            var commitLogOffset = dic != null && dic.TryGetValue(\"commitLogOffset\", out var clo) ? clo.ToLong() : 0L;\n            var tranStateTableOffset = dic != null && dic.TryGetValue(\"tranStateTableOffset\", out var tso) ? tso.ToLong() : 0L;\n            var msgId = dic != null && dic.TryGetValue(\"msgId\", out var mid) ? mid : null;\n\n            // 从消息体中尝试解析消息\n            MessageExt msgExt = null;\n            if (cmd.Payload != null)\n            {\n                var msgs = MessageExt.ReadAll(cmd.Payload);\n                msgExt = msgs?.FirstOrDefault();\n            }\n            msgExt ??= new MessageExt { MsgId = msgId, TransactionId = transactionId };\n\n            // 调用回查委托\n            var state = TransactionState.Rollback;\n            if (OnCheckTransactionAsync != null)\n                state = OnCheckTransactionAsync(msgExt, transactionId, default).ConfigureAwait(false).GetAwaiter().GetResult();\n            else if (OnCheckTransaction != null)\n                state = OnCheckTransaction(msgExt, transactionId);\n            else\n            {\n                WriteLog(\"收到事务回查但未设置OnCheckTransaction委托，事务ID={0}，将默认回滚\", transactionId);\n            }\n\n            // 构造EndTransaction请求\n            var header = new EndTransactionRequestHeader\n            {\n                ProducerGroup = Group,\n                TranStateTableOffset = tranStateTableOffset,\n                CommitLogOffset = commitLogOffset,\n                CommitOrRollback = (Int32)state,\n                FromTransactionCheck = true,\n                MsgId = msgId,\n                TransactionId = transactionId,\n            };\n\n            var bk = Clients?.FirstOrDefault();\n            bk?.Invoke(RequestCode.END_TRANSACTION, null, header.GetProperties());\n\n        WriteLog(\"事务回查完成，事务ID={0}，状态={1}\", transactionId, state);\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            WriteLog(\"事务回查处理异常：{0}\", ex.Message);\n        }\n\n        return null;\n    }\n    #endregion\n\n#if NETSTANDARD2_1_OR_GREATER\n    #region gRPC扩展方法\n    /// <summary>通过gRPC协议发送延迟消息（任意时间）。RocketMQ 5.x新特性</summary>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"deliveryTimestamp\">投递时间（UTC或本地时间）</param>\n    /// <param name=\"tag\">标签</param>\n    /// <param name=\"keys\">消息Key列表</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>发送结果</returns>\n    public async Task<SendResult> PublishDelayViaGrpcAsync(\n        Object body,\n        DateTime deliveryTimestamp,\n        String tag = null,\n        IList<String> keys = null,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        var message = CreateMessage(body);\n        using var span = Tracer?.NewSpan($\"mq:{Name}:PublishDelay:grpc\", new { deliveryTimestamp, body = message.BodyString });\n        try\n        {\n            var rs = await _GrpcService.SendMessageAsync(\n                Topic,\n                message.Body,\n                tag: tag,\n                keys: keys,\n                deliveryTimestamp: deliveryTimestamp,\n                cancellationToken: cancellationToken\n            ).ConfigureAwait(false);\n\n            if (rs.Status?.Code != Grpc.GrpcCode.OK)\n                throw new InvalidOperationException($\"gRPC SendMessage (delay) failed: {rs.Status}\");\n\n            var entry = rs.Entries.FirstOrDefault();\n            return new SendResult\n            {\n                Status = SendStatus.SendOK,\n                MsgId = entry?.MessageId,\n                TransactionId = entry?.TransactionId,\n                QueueOffset = entry?.Offset ?? 0,\n            };\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>通过gRPC协议发送事务消息（半消息）。RocketMQ 5.x gRPC API</summary>\n    /// <param name=\"body\">消息体</param>\n    /// <param name=\"tag\">标签</param>\n    /// <param name=\"keys\">消息Key列表</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>发送结果（包含TransactionId）</returns>\n    public async Task<SendResult> PublishTransactionViaGrpcAsync(\n        Object body,\n        String tag = null,\n        IList<String> keys = null,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        var message = CreateMessage(body);\n        using var span = Tracer?.NewSpan($\"mq:{Name}:PublishTransaction:grpc\", message.BodyString);\n        try\n        {\n            var rs = await _GrpcService.SendTransactionMessageAsync(\n                Topic,\n                message.Body,\n                tag: tag,\n                keys: keys,\n                cancellationToken: cancellationToken\n            ).ConfigureAwait(false);\n\n            if (rs.Status?.Code != Grpc.GrpcCode.OK)\n                throw new InvalidOperationException($\"gRPC SendTransactionMessage failed: {rs.Status}\");\n\n            var entry = rs.Entries.FirstOrDefault();\n            return new SendResult\n            {\n                Status = SendStatus.SendOK,\n                MsgId = entry?.MessageId,\n                TransactionId = entry?.TransactionId,\n                QueueOffset = entry?.Offset ?? 0,\n            };\n        }\n        catch (Exception ex)\n        {\n            span?.SetError(ex, null);\n            throw;\n        }\n    }\n\n    /// <summary>通过gRPC协议结束事务</summary>\n    /// <param name=\"messageId\">消息ID</param>\n    /// <param name=\"transactionId\">事务ID</param>\n    /// <param name=\"commit\">是否提交。true提交，false回滚</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns></returns>\n    public async Task<Grpc.GrpcEndTransactionResponse> EndTransactionViaGrpcAsync(\n        String messageId,\n        String transactionId,\n        Boolean commit,\n        CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.EndTransactionAsync(\n            Topic,\n            messageId,\n            transactionId,\n            commit ? Grpc.GrpcTransactionResolution.COMMIT : Grpc.GrpcTransactionResolution.ROLLBACK,\n            cancellationToken\n        ).ConfigureAwait(false);\n    }\n\n    /// <summary>通过gRPC协议查询主题路由</summary>\n    /// <param name=\"topic\">主题名。默认使用当前Topic</param>\n    /// <param name=\"cancellationToken\">取消通知</param>\n    /// <returns>路由信息</returns>\n    public async Task<Grpc.QueryRouteResponse> QueryRouteViaGrpcAsync(String topic = null, CancellationToken cancellationToken = default)\n    {\n        if (_GrpcService == null) throw new InvalidOperationException(\"gRPC service not initialized. Set GrpcProxyAddress first.\");\n\n        return await _GrpcService.QueryRouteAsync(topic ?? Topic, cancellationToken).ConfigureAwait(false);\n    }\n    #endregion\n#endif\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Properties/PublishProfiles/FolderProfile.pubxml",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121. \n-->\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <PublishProtocol>FileSystem</PublishProtocol>\n    <Configuration>Release</Configuration>\n    <Platform>Any CPU</Platform>\n    <TargetFramework>netstandard2.0</TargetFramework>\n    <PublishDir>..\\Bin\\</PublishDir>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/Command.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Xml.Serialization;\nusing NewLife.Buffers;\nusing NewLife.Collections;\nusing NewLife.Data;\nusing NewLife.Log;\nusing NewLife.Messaging;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>命令</summary>\n/// <remarks>\n/// Remoting 协议帧格式（大端序）：\n/// [4字节 TotalLength] [4字节 OriHeaderLength] [HeaderData] [BodyData]\n/// 其中 OriHeaderLength 高8位为序列化类型，低24位为头部数据长度。\n/// 使用 SpanReader/SpanWriter 进行高性能零拷贝编解码。\n/// </remarks>\npublic class Command : IAccessor, IMessage\n{\n    #region 属性\n    /// <summary>头部</summary>\n    public Header Header { get; set; }\n\n    /// <summary>主体</summary>\n    [XmlIgnore, IgnoreDataMember]\n    public IPacket Payload { get; set; }\n\n    /// <summary>原始Json</summary>\n    [XmlIgnore, IgnoreDataMember]\n    public String RawJson { get; private set; }\n    #endregion\n\n    #region 扩展属性\n    /// <summary>是否响应</summary>\n    public Boolean Reply { get; set; }\n\n    /// <summary>是否单向</summary>\n    public Boolean OneWay { get; set; }\n\n    /// <summary>是否异常</summary>\n    Boolean IMessage.Error { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }\n    #endregion\n\n    #region 构造\n\n    /// <summary>销毁。回收内存</summary>\n    public void Dispose() => Payload.TryDispose();\n\n    #endregion\n\n    #region 读写\n    /// <summary>从数据流中读取</summary>\n    /// <param name=\"stream\">数据流</param>\n    /// <param name=\"context\">上下文</param>\n    /// <returns></returns>\n    public Boolean Read(Stream stream, Object context = null)\n    {\n        try\n        {\n            // 先读取8字节帧头（TotalLength + OriHeaderLength）\n            var headBuf = new Byte[8];\n            if (stream.Read(headBuf, 0, 8) < 8) return false;\n\n            var reader = new SpanReader(headBuf) { IsLittleEndian = false };\n            var len = reader.ReadInt32();\n            if (len < 4 || len > 4 * 1024 * 1024) return false;\n\n            var oriHeaderLen = reader.ReadInt32();\n            var headerLen = oriHeaderLen & 0xFFFFFF;\n            if (headerLen <= 0 || headerLen > 8 * 1024) return false;\n\n            // 读取剩余数据（头部 + 主体）\n            var bodyLen = len - 4 - headerLen;\n            var dataBuf = new Byte[headerLen + (bodyLen > 0 ? bodyLen : 0)];\n            if (stream.Read(dataBuf, 0, dataBuf.Length) < dataBuf.Length) return false;\n\n            // 读取序列化类型\n            var type = (SerializeType)((oriHeaderLen >> 24) & 0xFF);\n            if (type == SerializeType.JSON)\n            {\n                var json = dataBuf.ToStr(null, 0, headerLen);\n                RawJson = json;\n\n                var header = json.ToJsonEntity<Header>();\n                if (header.SerializeTypeCurrentRPC.IsNullOrEmpty()) header.SerializeTypeCurrentRPC = type + \"\";\n\n                Header = header;\n                Reply = (header.Flag & 0b01) == 0b01;\n                OneWay = (header.Flag & 0b10) == 0b10;\n\n                // 读取主体\n                if (bodyLen > 0)\n                    Payload = new ArrayPacket(dataBuf, headerLen, bodyLen);\n            }\n            else if (type == SerializeType.ROCKETMQ)\n            {\n                var rd = new SpanReader(dataBuf, 0, headerLen) { IsLittleEndian = false };\n\n                var header = new Header\n                {\n                    SerializeTypeCurrentRPC = type + \"\",\n                    Code = rd.ReadUInt16(),\n                    Language = ((LanguageCode)rd.ReadByte()) + \"\",\n                    Version = (MQVersion)rd.ReadUInt16(),\n                    Opaque = rd.ReadInt32(),\n                    Flag = rd.ReadInt32(),\n                    Remark = ReadStr(ref rd, false, headerLen),\n                };\n\n                Reply = (header.Flag & 0b01) == 0b01;\n                OneWay = (header.Flag & 0b10) == 0b10;\n\n                // 读取扩展字段\n                var extFieldsLength = rd.ReadInt32();\n                if (extFieldsLength > 0)\n                {\n                    if (extFieldsLength > headerLen) throw new Exception($\"扩展字段长度[{extFieldsLength}]超过头部长度[{headerLen}]\");\n\n                    var extFields = header.GetExtFields();\n                    var endPos = rd.Position + extFieldsLength;\n                    while (rd.Position < endPos)\n                    {\n                        var k = ReadStr(ref rd, true, extFieldsLength);\n                        var v = ReadStr(ref rd, false, extFieldsLength);\n                        extFields[k + \"\"] = v;\n                    }\n                }\n\n                Header = header;\n\n                // 读取主体\n                if (bodyLen > 0)\n                    Payload = new ArrayPacket(dataBuf, headerLen, bodyLen);\n            }\n            else\n                throw new NotSupportedException($\"不支持[{type}]序列化\");\n        }\n        catch (Exception ex)\n        {\n            XTrace.WriteLine(\"序列化错误！{0}\", ex.Message);\n            return false;\n        }\n\n        return true;\n    }\n\n    /// <summary>从SpanReader中读取字符串</summary>\n    /// <param name=\"reader\">读取器</param>\n    /// <param name=\"useShortLength\">是否使用2字节长度前缀</param>\n    /// <param name=\"limit\">长度上限</param>\n    /// <returns></returns>\n    private static String ReadStr(ref SpanReader reader, Boolean useShortLength, Int32 limit)\n    {\n        var len = useShortLength ? reader.ReadInt16() : reader.ReadInt32();\n        if (len == 0) return null;\n        if (len > limit) throw new Exception($\"字符串长度[{len}]超过限制[{limit}]\");\n\n        return reader.ReadBytes(len).ToArray().ToStr();\n    }\n\n    /// <summary>向SpanWriter写入字符串</summary>\n    /// <param name=\"writer\">写入器</param>\n    /// <param name=\"useShortLength\">是否使用2字节长度前缀</param>\n    /// <param name=\"value\">字符串值</param>\n    private static void WriteStr(ref SpanWriter writer, Boolean useShortLength, String value)\n    {\n        var buf = value?.GetBytes();\n        var len = buf?.Length ?? 0;\n\n        if (useShortLength)\n            writer.Write((Int16)len);\n        else\n            writer.Write(len);\n\n        if (len > 0) writer.Write(buf);\n    }\n\n    /// <summary>读取Body作为Json返回</summary>\n    /// <returns></returns>\n    public IDictionary<String, Object> ReadBodyAsJson()\n    {\n        var pk = Payload;\n        if (pk == null || pk.Total == 0) return null;\n\n        return new JsonParser(pk.ToStr()).Decode() as IDictionary<String, Object>;\n    }\n\n    /// <summary>写入命令到数据流</summary>\n    /// <param name=\"stream\">数据流</param>\n    /// <param name=\"context\">上下文</param>\n    /// <returns></returns>\n    public Boolean Write(Stream stream, Object context = null)\n    {\n        var header = Header;\n        if (Reply) header.Flag |= 0b01;\n        if (OneWay) header.Flag |= 0b10;\n        var pk = Payload;\n\n        // 区分不同的序列化类型\n        var type = header.SerializeTypeCurrentRPC.ToEnum(SerializeType.JSON);\n        if (type == SerializeType.JSON)\n        {\n            // 计算头部\n            //var json = Header.ToJson();\n            var json = Header.ToJson(false, false, false);\n            RawJson = json;\n            var hs = json.GetBytes();\n\n            // 计算长度\n            var len = 4 + hs.Length;\n            if (pk != null) len += pk.Total;\n\n            // 使用SpanWriter写入8字节帧头（TotalLength + HeaderLength）\n            var prefix = new Byte[8];\n            var writer = new SpanWriter(prefix) { IsLittleEndian = false };\n            writer.Write(len);\n            writer.Write(hs.Length);\n            stream.Write(prefix, 0, 8);\n\n            // 写入头部JSON\n            stream.Write(hs, 0, hs.Length);\n        }\n        else if (type == SerializeType.ROCKETMQ)\n        {\n            // 使用SpanWriter编码ROCKETMQ二进制头部\n            var hsBuf = new Byte[8 * 1024];\n            var writer = new SpanWriter(hsBuf) { IsLittleEndian = false };\n\n            writer.Write((UInt16)header.Code);\n            writer.Write((Byte)header.Language.ToEnum(LanguageCode.JAVA));\n            writer.Write((UInt16)header.Version);\n            writer.Write(header.Opaque);\n            writer.Write(header.Flag);\n\n            WriteStr(ref writer, false, header.Remark);\n\n            if (header.ExtFields != null && header.ExtFields.Count > 0)\n            {\n                // 先编码扩展字段到临时缓冲区\n                var extBuf = new Byte[4 * 1024];\n                var extWriter = new SpanWriter(extBuf) { IsLittleEndian = false };\n                foreach (var item in header.ExtFields)\n                {\n                    WriteStr(ref extWriter, true, item.Key);\n                    WriteStr(ref extWriter, false, item.Value);\n                }\n\n                var extLen = extWriter.Position;\n                writer.Write(extLen);\n                if (extLen > 0) writer.Write(new ReadOnlySpan<Byte>(extBuf, 0, extLen));\n            }\n            else\n            {\n                writer.Write(0);\n            }\n\n            // 计算长度\n            var hsLen = writer.Position;\n            var oriHeaderLen = (hsLen & 0xFFFFFF) | ((Byte)type << 24);\n\n            var len = 4 + hsLen;\n            if (pk != null) len += pk.Total;\n\n            // 使用SpanWriter写入帧头\n            var prefix = new Byte[8];\n            var prefixWriter = new SpanWriter(prefix) { IsLittleEndian = false };\n            prefixWriter.Write(len);\n            prefixWriter.Write(oriHeaderLen);\n            stream.Write(prefix, 0, 8);\n\n            // 写入头部\n            stream.Write(hsBuf, 0, hsLen);\n        }\n        else\n            throw new NotSupportedException($\"不支持[{type}]序列化\");\n\n        // 写入主体\n        if (pk != null && pk.Total > 0) pk.CopyTo(stream);\n\n        return true;\n    }\n\n    /// <summary>命令转字节数组</summary>\n    /// <returns></returns>\n    public IPacket ToPacket()\n    {\n        var ms = new MemoryStream();\n        Write(ms, null);\n\n        ms.Position = 0;\n        return new ArrayPacket(ms);\n    }\n\n    /// <summary>创建响应</summary>\n    /// <returns></returns>\n    public IMessage CreateReply()\n    {\n        if (Header == null || Reply || OneWay) throw new Exception(\"不能创建响应命令\");\n\n        var head = new Header\n        {\n            Opaque = Header.Opaque,\n            SerializeTypeCurrentRPC = Header.SerializeTypeCurrentRPC,\n            Version = Header.Version,\n        };\n\n        var cmd = new Command\n        {\n            Reply = true,\n            Header = head,\n        };\n\n        return cmd;\n    }\n\n    Boolean IMessage.Read(IPacket pk) => Read(pk.GetStream());\n    #endregion\n\n    #region 辅助\n    /// <summary>友好字符串</summary>\n    /// <returns></returns>\n    public override String ToString()\n    {\n        var h = Header;\n        if (h == null) return base.ToString();\n\n        var sb = Pool.StringBuilder.Get();\n        // 请求与响应\n        if (Reply)\n        {\n            sb.Append('#');\n            sb.Append((ResponseCode)h.Code);\n        }\n        else\n        {\n            sb.Append((RequestCode)h.Code);\n        }\n\n        var pk = Payload;\n        sb.AppendFormat(\"({0})\", h.Opaque);\n\n        var ext = h.ExtFields;\n        if (ext != null && ext.Count > 0) sb.AppendFormat(\"<{0}>\", ext.ToJson());\n\n        if (pk != null && pk.Total > 0)\n        {\n            sb.AppendFormat(\"[{0}]\", pk.Total);\n            sb.Append(pk.ToStr(null, 0, 256));\n        }\n\n        return sb.Return(true);\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ConsumerData.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>消费者数据</summary>\npublic class ConsumerData\n{\n    #region 属性\n    /// <summary>从哪里开始消费</summary>\n    public String ConsumeFromWhere { get; set; } = \"CONSUME_FROM_LAST_OFFSET\";\n\n    /// <summary>消费类型</summary>\n    public String ConsumeType { get; set; } = \"CONSUME_ACTIVELY\";\n\n    /// <summary>组名</summary>\n    public String GroupName { get; set; }\n\n    /// <summary>消息模型。广播/集群</summary>\n    public String MessageModel { get; set; } = \"CLUSTERING\";\n\n    /// <summary>订阅数据集</summary>\n    public SubscriptionData[] SubscriptionDataSet { get; set; }\n\n    /// <summary>单元模式</summary>\n    public Boolean UnitMode { get; set; }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ConsumerRunningInfo.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace NewLife.RocketMQ.Protocol\n{\n    class ConsumerRunningInfo\n    {\n        #region 属性\n        public IDictionary<String,String> Properties { get; set; }\n\n        public SubscriptionData[] SubscriptionSet { get; set; }\n\n        public String[] MqTable { get; set; }\n        #endregion\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ConsumerStates/ConsumerStatesModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace NewLife.RocketMQ.Protocol.ConsumerStates\n{\n    /// <summary>\n    /// 消费者状态信息模型\n    /// </summary>\n    public class ConsumerStatesModel\n    {\n        /// <summary>\n        /// 消费Tps\n        /// </summary>\n        public double ConsumeTps { get; set; }\n\n        /// <summary>\n        /// 消费Offset信息Table\n        /// </summary>\n        public Dictionary<MessageQueueModel,OffsetWrapperModel> OffsetTable { get; set; }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ConsumerStates/MessageQueueModel.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol.ConsumerStates;\n\n/// <summary>\n/// 消息队列信息模型\n/// </summary>\npublic class MessageQueueModel\n{\n    /// <summary>\n    /// Broker服务器名称\n    /// </summary>\n    public String BrokerName { get; set; }\n\n    /// <summary>\n    /// 队列编码\n    /// </summary>\n    public Int32 QueueId { get; set; }\n\n    /// <summary>\n    /// 主题\n    /// </summary>\n    public String Topic { get; set; }\n\n    /// <summary>\n    /// 阿里版本返回字段\n    /// </summary>\n    public Boolean MainQueue { get; set; }\n\n    /// <summary>\n    /// 阿里版本返回字段\n    /// </summary>\n    public Int32 QueueGroupId { get; set; }\n\n    /// <summary>已重载。</summary>\n    /// <returns></returns>\n    public override String ToString() => $\"{BrokerName}[{QueueId}]\";   \n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ConsumerStates/OffsetWrapperModel.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace NewLife.RocketMQ.Protocol.ConsumerStates\n{\n    /// <summary>\n    /// 消费点位信息模型\n    /// </summary>\n    public class OffsetWrapperModel\n    {\n        /// <summary>\n        /// 代理者位点\n        /// </summary>\n        public Int64 BrokerOffset { get; set; }\n\n        /// <summary>\n        /// 消费者点位\n        /// </summary>\n        public Int64 ConsumerOffset { get; set; }\n\n        /// <summary>\n        /// 上次时间\n        /// </summary>\n        public Int64 LastTimestamp { get; set; }\n\n        /// <summary>\n        /// 阿里版拉取点位\n        /// </summary>\n        public Int64 PullOffset { get; set; }\n    }\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/EndTransactionRequestHeader.cs",
    "content": "﻿using System.Reflection;\nusing NewLife.Reflection;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>结束事务请求头</summary>\npublic class EndTransactionRequestHeader\n{\n    #region 属性\n    /// <summary>生产组</summary>\n    public String ProducerGroup { get; set; }\n\n    /// <summary>事务状态表偏移</summary>\n    public Int64 TranStateTableOffset { get; set; }\n\n    /// <summary>提交日志偏移</summary>\n    public Int64 CommitLogOffset { get; set; }\n\n    /// <summary>提交或回滚标记</summary>\n    public Int32 CommitOrRollback { get; set; }\n\n    /// <summary>是否来自事务回查</summary>\n    public Boolean FromTransactionCheck { get; set; }\n\n    /// <summary>消息编号</summary>\n    public String MsgId { get; set; }\n\n    /// <summary>事务编号</summary>\n    public String TransactionId { get; set; }\n    #endregion\n\n    #region 方法\n    /// <summary>获取属性字典</summary>\n    /// <returns></returns>\n    public IDictionary<String, Object> GetProperties()\n    {\n        var dic = new Dictionary<String, Object>();\n        foreach (var pi in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))\n        {\n            if (pi.GetIndexParameters().Length > 0) continue;\n            var name = pi.Name;\n            if (!name.IsNullOrEmpty()) name = Char.ToLowerInvariant(name[0]) + name.Substring(1);\n\n            dic[name] = this.GetValue(pi);\n        }\n\n        return dic;\n    }\n    #endregion\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/Header.cs",
    "content": "﻿using System.Xml.Serialization;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>头部</summary>\npublic class Header\n{\n    #region 属性\n    /// <summary>请求/响应码</summary>\n    [XmlElement(\"code\")]\n    public Int32 Code { get; set; }\n\n    /// <summary>扩展字段</summary>\n    /// <remarks>\n    /// 这个字段在不同的请求/响应不一样，完全自定义。数据结构上是java的hashmap。\n    /// 在Java的每个RemotingCammand中，其实都带有一个CommandCustomHeader的属性成员，可以认为他是一个强类型的extFields，\n    /// 在最后传输的时候，这个CommandCustomHeader会被忽略，而传输前会把其中的所有字段全部都原封不动塞到extFields中，以作传输。\n    /// </remarks>\n    [XmlElement(\"extFields\")]\n    public IDictionary<String, String> ExtFields { get; set; }\n\n    /// <summary>标识</summary>\n    /// <remarks>\n    /// 第0位标识是这次通信是request还是response，0标识request, 1 标识response。\n    /// 第1位标识是否是oneway请求，1标识oneway。应答方在处理oneway请求的时候，不会做出响应，请求方也无需等待应答方响应。\n    /// </remarks>\n    [XmlElement(\"flag\")]\n    public Int32 Flag { get; set; }\n\n    /// <summary>由于要支持多语言，所以这一字段可以给通信双方知道对方通信层所使用的开发语言。</summary>\n    /// <remarks>LanguageCode，JAVA/CPP/DOTNET/PYTHON/DELPHI/ERLANG/RUBY/OTHER/HTTP/GO/PHP/OMS</remarks>\n    [XmlElement(\"language\")]\n    public String Language { get; set; } = \"CPP\";\n\n    /// <summary>请求标识码。在Java版的通信层中，这个只是一个不断自增的整型，为了收到应答方响应的的时候找到对应的请求。</summary>\n    [XmlElement(\"opaque\")]\n    public Int32 Opaque { get; set; }\n\n    /// <summary>序列化类型</summary>\n    [XmlElement(\"serializeTypeCurrentRPC\")]\n    public String SerializeTypeCurrentRPC { get; set; } = \"JSON\";\n\n    /// <summary>让通信层知道对方的版本号，响应方可以以此做兼容老版本等的特殊操作</summary>\n    [XmlElement(\"version\")]\n    public MQVersion Version { get; set; } = MQVersion.V4_8_0;\n\n    /// <summary>附带的文本信息。常见的如存放一些broker/nameserver返回的一些异常信息，方便开发人员定位问题。</summary>\n    [XmlElement(\"remark\")]\n    public String Remark { get; set; }\n    #endregion\n\n    #region 方法\n    /// <summary>获取扩展字段。如果为空则创建</summary>\n    /// <returns></returns>\n    public IDictionary<String, String> GetExtFields()\n    {\n        ExtFields ??= new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);\n\n        return ExtFields;\n    }\n\n    /// <summary>创建异常</summary>\n    /// <returns></returns>\n    public ResponseException CreateException()\n    {\n        var err = Remark;\n        if (!err.IsNullOrEmpty())\n        {\n            var p = err.IndexOf(\"Exception: \");\n            if (p >= 0) err = err.Substring(p + \"Exception: \".Length);\n            p = err.IndexOf(\", \");\n            if (p > 0) err = err.Substring(0, p);\n        }\n\n        return new ResponseException((ResponseCode)Code, err);\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/HeartbeatData.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>心跳数据</summary>\n    public class HeartbeatData\n    {\n        #region 属性\n        /// <summary>客户端编号</summary>\n        public String ClientID { get; set; }\n\n        /// <summary>消费数据集</summary>\n        public ConsumerData[] ConsumerDataSet { get; set; }\n\n        /// <summary>生产者数据集</summary>\n        public ProducerData[] ProducerDataSet { get; set; }\n        #endregion\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/LanguageCode.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>语言类型</summary>\npublic enum LanguageCode : Byte\n{\n    /// <summary></summary>\n    JAVA = 0,\n\n    /// <summary></summary>\n    CPP = 1,\n\n    /// <summary></summary>\n    DOTNET = 2,\n\n    /// <summary></summary>\n    PYTHON = 3,\n\n    /// <summary></summary>\n    DELPHI = 4,\n\n    /// <summary></summary>\n    ERLANG = 5,\n\n    /// <summary></summary>\n    RUBY = 6,\n\n    /// <summary></summary>\n    OTHER = 7,\n\n    /// <summary></summary>\n    HTTP = 8,\n\n    /// <summary></summary>\n    GO = 9,\n\n    /// <summary></summary>\n    PHP = 10,\n\n    /// <summary></summary>\n    OMS = 11,\n\n    /// <summary></summary>\n    RUST = 12,\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/MQVersion.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释\npublic enum MQVersion : Int32\n{\n    V3_0_0_SNAPSHOT,\n    V3_0_0_ALPHA1,\n    V3_0_0_BETA1,\n    V3_0_0_BETA2,\n    V3_0_0_BETA3,\n    V3_0_0_BETA4,\n    V3_0_0_BETA5,\n    V3_0_0_BETA6_SNAPSHOT,\n    V3_0_0_BETA6,\n    V3_0_0_BETA7_SNAPSHOT,\n    V3_0_0_BETA7,\n    V3_0_0_BETA8_SNAPSHOT,\n    V3_0_0_BETA8,\n    V3_0_0_BETA9_SNAPSHOT,\n    V3_0_0_BETA9,\n    V3_0_0_FINAL,\n    V3_0_1_SNAPSHOT,\n    V3_0_1,\n    V3_0_2_SNAPSHOT,\n    V3_0_2,\n    V3_0_3_SNAPSHOT,\n    V3_0_3,\n    V3_0_4_SNAPSHOT,\n    V3_0_4,\n    V3_0_5_SNAPSHOT,\n    V3_0_5,\n    V3_0_6_SNAPSHOT,\n    V3_0_6,\n    V3_0_7_SNAPSHOT,\n    V3_0_7,\n    V3_0_8_SNAPSHOT,\n    V3_0_8,\n    V3_0_9_SNAPSHOT,\n    V3_0_9,\n\n    V3_0_10_SNAPSHOT,\n    V3_0_10,\n\n    V3_0_11_SNAPSHOT,\n    V3_0_11,\n\n    V3_0_12_SNAPSHOT,\n    V3_0_12,\n\n    V3_0_13_SNAPSHOT,\n    V3_0_13,\n\n    V3_0_14_SNAPSHOT,\n    V3_0_14,\n\n    V3_0_15_SNAPSHOT,\n    V3_0_15,\n\n    V3_1_0_SNAPSHOT,\n    V3_1_0,\n\n    V3_1_1_SNAPSHOT,\n    V3_1_1,\n\n    V3_1_2_SNAPSHOT,\n    V3_1_2,\n\n    V3_1_3_SNAPSHOT,\n    V3_1_3,\n\n    V3_1_4_SNAPSHOT,\n    V3_1_4,\n\n    V3_1_5_SNAPSHOT,\n    V3_1_5,\n\n    V3_1_6_SNAPSHOT,\n    V3_1_6,\n\n    V3_1_7_SNAPSHOT,\n    V3_1_7,\n\n    V3_1_8_SNAPSHOT,\n    V3_1_8,\n\n    V3_1_9_SNAPSHOT,\n    V3_1_9,\n\n    V3_2_0_SNAPSHOT,\n    V3_2_0,\n\n    V3_2_1_SNAPSHOT,\n    V3_2_1,\n\n    V3_2_2_SNAPSHOT,\n    V3_2_2,\n\n    V3_2_3_SNAPSHOT,\n    V3_2_3,\n\n    V3_2_4_SNAPSHOT,\n    V3_2_4,\n\n    V3_2_5_SNAPSHOT,\n    V3_2_5,\n\n    V3_2_6_SNAPSHOT,\n    V3_2_6,\n\n    V3_2_7_SNAPSHOT,\n    V3_2_7,\n\n    V3_2_8_SNAPSHOT,\n    V3_2_8,\n\n    V3_2_9_SNAPSHOT,\n    V3_2_9,\n\n    V3_3_1_SNAPSHOT,\n    V3_3_1,\n\n    V3_3_2_SNAPSHOT,\n    V3_3_2,\n\n    V3_3_3_SNAPSHOT,\n    V3_3_3,\n\n    V3_3_4_SNAPSHOT,\n    V3_3_4,\n\n    V3_3_5_SNAPSHOT,\n    V3_3_5,\n\n    V3_3_6_SNAPSHOT,\n    V3_3_6,\n\n    V3_3_7_SNAPSHOT,\n    V3_3_7,\n\n    V3_3_8_SNAPSHOT,\n    V3_3_8,\n\n    V3_3_9_SNAPSHOT,\n    V3_3_9,\n\n    V3_4_1_SNAPSHOT,\n    V3_4_1,\n\n    V3_4_2_SNAPSHOT,\n    V3_4_2,\n\n    V3_4_3_SNAPSHOT,\n    V3_4_3,\n\n    V3_4_4_SNAPSHOT,\n    V3_4_4,\n\n    V3_4_5_SNAPSHOT,\n    V3_4_5,\n\n    V3_4_6_SNAPSHOT,\n    V3_4_6,\n\n    V3_4_7_SNAPSHOT,\n    V3_4_7,\n\n    V3_4_8_SNAPSHOT,\n    V3_4_8,\n\n    V3_4_9_SNAPSHOT,\n    V3_4_9,\n    V3_5_1_SNAPSHOT,\n    V3_5_1,\n\n    V3_5_2_SNAPSHOT,\n    V3_5_2,\n\n    V3_5_3_SNAPSHOT,\n    V3_5_3,\n\n    V3_5_4_SNAPSHOT,\n    V3_5_4,\n\n    V3_5_5_SNAPSHOT,\n    V3_5_5,\n\n    V3_5_6_SNAPSHOT,\n    V3_5_6,\n\n    V3_5_7_SNAPSHOT,\n    V3_5_7,\n\n    V3_5_8_SNAPSHOT,\n    V3_5_8,\n\n    V3_5_9_SNAPSHOT,\n    V3_5_9,\n\n    V3_6_1_SNAPSHOT,\n    V3_6_1,\n\n    V3_6_2_SNAPSHOT,\n    V3_6_2,\n\n    V3_6_3_SNAPSHOT,\n    V3_6_3,\n\n    V3_6_4_SNAPSHOT,\n    V3_6_4,\n\n    V3_6_5_SNAPSHOT,\n    V3_6_5,\n\n    V3_6_6_SNAPSHOT,\n    V3_6_6,\n\n    V3_6_7_SNAPSHOT,\n    V3_6_7,\n\n    V3_6_8_SNAPSHOT,\n    V3_6_8,\n\n    V3_6_9_SNAPSHOT,\n    V3_6_9,\n\n    V3_7_1_SNAPSHOT,\n    V3_7_1,\n\n    V3_7_2_SNAPSHOT,\n    V3_7_2,\n\n    V3_7_3_SNAPSHOT,\n    V3_7_3,\n\n    V3_7_4_SNAPSHOT,\n    V3_7_4,\n\n    V3_7_5_SNAPSHOT,\n    V3_7_5,\n\n    V3_7_6_SNAPSHOT,\n    V3_7_6,\n\n    V3_7_7_SNAPSHOT,\n    V3_7_7,\n\n    V3_7_8_SNAPSHOT,\n    V3_7_8,\n\n    V3_7_9_SNAPSHOT,\n    V3_7_9,\n\n    V3_8_1_SNAPSHOT,\n    V3_8_1,\n\n    V3_8_2_SNAPSHOT,\n    V3_8_2,\n\n    V3_8_3_SNAPSHOT,\n    V3_8_3,\n\n    V3_8_4_SNAPSHOT,\n    V3_8_4,\n\n    V3_8_5_SNAPSHOT,\n    V3_8_5,\n\n    V3_8_6_SNAPSHOT,\n    V3_8_6,\n\n    V3_8_7_SNAPSHOT,\n    V3_8_7,\n\n    V3_8_8_SNAPSHOT,\n    V3_8_8,\n\n    V3_8_9_SNAPSHOT,\n    V3_8_9,\n\n    V3_9_1_SNAPSHOT,\n    V3_9_1,\n\n    V3_9_2_SNAPSHOT,\n    V3_9_2,\n\n    V3_9_3_SNAPSHOT,\n    V3_9_3,\n\n    V3_9_4_SNAPSHOT,\n    V3_9_4,\n\n    V3_9_5_SNAPSHOT,\n    V3_9_5,\n\n    V3_9_6_SNAPSHOT,\n    V3_9_6,\n\n    V3_9_7_SNAPSHOT,\n    V3_9_7,\n\n    V3_9_8_SNAPSHOT,\n    V3_9_8,\n\n    V3_9_9_SNAPSHOT,\n    V3_9_9,\n\n    V4_0_0_SNAPSHOT,\n    V4_0_0,\n\n    V4_0_1_SNAPSHOT,\n    V4_0_1,\n\n    V4_0_2_SNAPSHOT,\n    V4_0_2,\n\n    V4_0_3_SNAPSHOT,\n    V4_0_3,\n\n    V4_0_4_SNAPSHOT,\n    V4_0_4,\n\n    V4_0_5_SNAPSHOT,\n    V4_0_5,\n\n    V4_0_6_SNAPSHOT,\n    V4_0_6,\n\n    V4_0_7_SNAPSHOT,\n    V4_0_7,\n\n    V4_0_8_SNAPSHOT,\n    V4_0_8,\n\n    V4_0_9_SNAPSHOT,\n    V4_0_9,\n\n    V4_1_0_SNAPSHOT,\n    V4_1_0,\n\n    V4_1_1_SNAPSHOT,\n    V4_1_1,\n\n    V4_1_2_SNAPSHOT,\n    V4_1_2,\n\n    V4_1_3_SNAPSHOT,\n    V4_1_3,\n\n    V4_1_4_SNAPSHOT,\n    V4_1_4,\n\n    V4_1_5_SNAPSHOT,\n    V4_1_5,\n\n    V4_1_6_SNAPSHOT,\n    V4_1_6,\n\n    V4_1_7_SNAPSHOT,\n    V4_1_7,\n\n    V4_1_8_SNAPSHOT,\n    V4_1_8,\n\n    V4_1_9_SNAPSHOT,\n    V4_1_9,\n\n    V4_2_0_SNAPSHOT,\n    V4_2_0,\n\n    V4_2_1_SNAPSHOT,\n    V4_2_1,\n\n    V4_2_2_SNAPSHOT,\n    V4_2_2,\n\n    V4_2_3_SNAPSHOT,\n    V4_2_3,\n\n    V4_2_4_SNAPSHOT,\n    V4_2_4,\n\n    V4_2_5_SNAPSHOT,\n    V4_2_5,\n\n    V4_2_6_SNAPSHOT,\n    V4_2_6,\n\n    V4_2_7_SNAPSHOT,\n    V4_2_7,\n\n    V4_2_8_SNAPSHOT,\n    V4_2_8,\n\n    V4_2_9_SNAPSHOT,\n    V4_2_9,\n\n    V4_3_0_SNAPSHOT,\n    V4_3_0,\n\n    V4_3_1_SNAPSHOT,\n    V4_3_1,\n\n    V4_3_2_SNAPSHOT,\n    V4_3_2,\n\n    V4_3_3_SNAPSHOT,\n    V4_3_3,\n\n    V4_3_4_SNAPSHOT,\n    V4_3_4,\n\n    V4_3_5_SNAPSHOT,\n    V4_3_5,\n\n    V4_3_6_SNAPSHOT,\n    V4_3_6,\n\n    V4_3_7_SNAPSHOT,\n    V4_3_7,\n\n    V4_3_8_SNAPSHOT,\n    V4_3_8,\n\n    V4_3_9_SNAPSHOT,\n    V4_3_9,\n\n    V4_4_0_SNAPSHOT,\n    V4_4_0,\n\n    V4_4_1_SNAPSHOT,\n    V4_4_1,\n\n    V4_4_2_SNAPSHOT,\n    V4_4_2,\n\n    V4_4_3_SNAPSHOT,\n    V4_4_3,\n\n    V4_4_4_SNAPSHOT,\n    V4_4_4,\n\n    V4_4_5_SNAPSHOT,\n    V4_4_5,\n\n    V4_4_6_SNAPSHOT,\n    V4_4_6,\n\n    V4_4_7_SNAPSHOT,\n    V4_4_7,\n\n    V4_4_8_SNAPSHOT,\n    V4_4_8,\n\n    V4_4_9_SNAPSHOT,\n    V4_4_9,\n\n    V4_5_0_SNAPSHOT,\n    V4_5_0,\n\n    V4_5_1_SNAPSHOT,\n    V4_5_1,\n\n    V4_5_2_SNAPSHOT,\n    V4_5_2,\n\n    V4_5_3_SNAPSHOT,\n    V4_5_3,\n\n    V4_5_4_SNAPSHOT,\n    V4_5_4,\n\n    V4_5_5_SNAPSHOT,\n    V4_5_5,\n\n    V4_5_6_SNAPSHOT,\n    V4_5_6,\n\n    V4_5_7_SNAPSHOT,\n    V4_5_7,\n\n    V4_5_8_SNAPSHOT,\n    V4_5_8,\n\n    V4_5_9_SNAPSHOT,\n    V4_5_9,\n\n    V4_6_0_SNAPSHOT,\n    V4_6_0,\n\n    V4_6_1_SNAPSHOT,\n    V4_6_1,\n\n    V4_6_2_SNAPSHOT,\n    V4_6_2,\n\n    V4_6_3_SNAPSHOT,\n    V4_6_3,\n\n    V4_6_4_SNAPSHOT,\n    V4_6_4,\n\n    V4_6_5_SNAPSHOT,\n    V4_6_5,\n\n    V4_6_6_SNAPSHOT,\n    V4_6_6,\n\n    V4_6_7_SNAPSHOT,\n    V4_6_7,\n\n    V4_6_8_SNAPSHOT,\n    V4_6_8,\n\n    V4_6_9_SNAPSHOT,\n    V4_6_9,\n\n    V4_7_0_SNAPSHOT,\n    V4_7_0,\n\n    V4_7_1_SNAPSHOT,\n    V4_7_1,\n\n    V4_7_2_SNAPSHOT,\n    V4_7_2,\n\n    V4_7_3_SNAPSHOT,\n    V4_7_3,\n\n    V4_7_4_SNAPSHOT,\n    V4_7_4,\n\n    V4_7_5_SNAPSHOT,\n    V4_7_5,\n\n    V4_7_6_SNAPSHOT,\n    V4_7_6,\n\n    V4_7_7_SNAPSHOT,\n    V4_7_7,\n\n    V4_7_8_SNAPSHOT,\n    V4_7_8,\n\n    V4_7_9_SNAPSHOT,\n    V4_7_9,\n\n    V4_8_0_SNAPSHOT,\n    V4_8_0,\n\n    V4_8_1_SNAPSHOT,\n    V4_8_1,\n\n    V4_8_2_SNAPSHOT,\n    V4_8_2,\n\n    V4_8_3_SNAPSHOT,\n    V4_8_3,\n\n    V4_8_4_SNAPSHOT,\n    V4_8_4,\n\n    V4_8_5_SNAPSHOT,\n    V4_8_5,\n\n    V4_8_6_SNAPSHOT,\n    V4_8_6,\n\n    V4_8_7_SNAPSHOT,\n    V4_8_7,\n\n    V4_8_8_SNAPSHOT,\n    V4_8_8,\n\n    V4_8_9_SNAPSHOT,\n    V4_8_9,\n\n    V4_9_0_SNAPSHOT,\n    V4_9_0,\n\n    V4_9_1_SNAPSHOT,\n    V4_9_1,\n\n    V4_9_2_SNAPSHOT,\n    V4_9_2,\n\n    V4_9_3_SNAPSHOT,\n    V4_9_3,\n\n    V4_9_4_SNAPSHOT,\n    V4_9_4,\n\n    V4_9_5_SNAPSHOT,\n    V4_9_5,\n\n    V4_9_6_SNAPSHOT,\n    V4_9_6,\n\n    V4_9_7_SNAPSHOT,\n    V4_9_7,\n\n    V4_9_8_SNAPSHOT,\n    V4_9_8,\n\n    V4_9_9_SNAPSHOT,\n    V4_9_9,\n\n    V5_0_0_SNAPSHOT,\n    V5_0_0,\n\n    V5_0_1_SNAPSHOT,\n    V5_0_1,\n\n    V5_0_2_SNAPSHOT,\n    V5_0_2,\n\n    V5_0_3_SNAPSHOT,\n    V5_0_3,\n\n    V5_0_4_SNAPSHOT,\n    V5_0_4,\n\n    V5_0_5_SNAPSHOT,\n    V5_0_5,\n\n    V5_0_6_SNAPSHOT,\n    V5_0_6,\n\n    V5_0_7_SNAPSHOT,\n    V5_0_7,\n\n    V5_0_8_SNAPSHOT,\n    V5_0_8,\n\n    V5_0_9_SNAPSHOT,\n    V5_0_9,\n\n    V5_1_0_SNAPSHOT,\n    V5_1_0,\n\n    V5_1_1_SNAPSHOT,\n    V5_1_1,\n\n    V5_1_2_SNAPSHOT,\n    V5_1_2,\n\n    V5_1_3_SNAPSHOT,\n    V5_1_3,\n\n    V5_1_4_SNAPSHOT,\n    V5_1_4,\n\n    V5_1_5_SNAPSHOT,\n    V5_1_5,\n\n    V5_1_6_SNAPSHOT,\n    V5_1_6,\n\n    V5_1_7_SNAPSHOT,\n    V5_1_7,\n\n    V5_1_8_SNAPSHOT,\n    V5_1_8,\n\n    V5_1_9_SNAPSHOT,\n    V5_1_9,\n\n    V5_2_0_SNAPSHOT,\n    V5_2_0,\n\n    V5_2_1_SNAPSHOT,\n    V5_2_1,\n\n    V5_2_2_SNAPSHOT,\n    V5_2_2,\n\n    V5_2_3_SNAPSHOT,\n    V5_2_3,\n\n    V5_2_4_SNAPSHOT,\n    V5_2_4,\n\n    V5_2_5_SNAPSHOT,\n    V5_2_5,\n\n    V5_2_6_SNAPSHOT,\n    V5_2_6,\n\n    V5_2_7_SNAPSHOT,\n    V5_2_7,\n\n    V5_2_8_SNAPSHOT,\n    V5_2_8,\n\n    V5_2_9_SNAPSHOT,\n    V5_2_9,\n\n    V5_3_0_SNAPSHOT,\n    V5_3_0,\n\n    V5_3_1_SNAPSHOT,\n    V5_3_1,\n\n    V5_3_2_SNAPSHOT,\n    V5_3_2,\n\n    V5_3_3_SNAPSHOT,\n    V5_3_3,\n\n    V5_3_4_SNAPSHOT,\n    V5_3_4,\n\n    V5_3_5_SNAPSHOT,\n    V5_3_5,\n\n    V5_3_6_SNAPSHOT,\n    V5_3_6,\n\n    V5_3_7_SNAPSHOT,\n    V5_3_7,\n\n    V5_3_8_SNAPSHOT,\n    V5_3_8,\n\n    V5_3_9_SNAPSHOT,\n    V5_3_9,\n\n    V5_4_0_SNAPSHOT,\n    V5_4_0,\n\n    V5_4_1_SNAPSHOT,\n    V5_4_1,\n\n    V5_4_2_SNAPSHOT,\n    V5_4_2,\n\n    V5_4_3_SNAPSHOT,\n    V5_4_3,\n\n    V5_4_4_SNAPSHOT,\n    V5_4_4,\n\n    V5_4_5_SNAPSHOT,\n    V5_4_5,\n\n    V5_4_6_SNAPSHOT,\n    V5_4_6,\n\n    V5_4_7_SNAPSHOT,\n    V5_4_7,\n\n    V5_4_8_SNAPSHOT,\n    V5_4_8,\n\n    V5_4_9_SNAPSHOT,\n    V5_4_9,\n\n    V5_5_0_SNAPSHOT,\n    V5_5_0,\n\n    V5_5_1_SNAPSHOT,\n    V5_5_1,\n\n    V5_5_2_SNAPSHOT,\n    V5_5_2,\n\n    V5_5_3_SNAPSHOT,\n    V5_5_3,\n\n    V5_5_4_SNAPSHOT,\n    V5_5_4,\n\n    V5_5_5_SNAPSHOT,\n    V5_5_5,\n\n    V5_5_6_SNAPSHOT,\n    V5_5_6,\n\n    V5_5_7_SNAPSHOT,\n    V5_5_7,\n\n    V5_5_8_SNAPSHOT,\n    V5_5_8,\n\n    V5_5_9_SNAPSHOT,\n    V5_5_9,\n\n    V5_6_0_SNAPSHOT,\n    V5_6_0,\n\n    V5_6_1_SNAPSHOT,\n    V5_6_1,\n\n    V5_6_2_SNAPSHOT,\n    V5_6_2,\n\n    V5_6_3_SNAPSHOT,\n    V5_6_3,\n\n    V5_6_4_SNAPSHOT,\n    V5_6_4,\n\n    V5_6_5_SNAPSHOT,\n    V5_6_5,\n\n    V5_6_6_SNAPSHOT,\n    V5_6_6,\n\n    V5_6_7_SNAPSHOT,\n    V5_6_7,\n\n    V5_6_8_SNAPSHOT,\n    V5_6_8,\n\n    V5_6_9_SNAPSHOT,\n    V5_6_9,\n\n    V5_7_0_SNAPSHOT,\n    V5_7_0,\n\n    V5_7_1_SNAPSHOT,\n    V5_7_1,\n\n    V5_7_2_SNAPSHOT,\n    V5_7_2,\n\n    V5_7_3_SNAPSHOT,\n    V5_7_3,\n\n    V5_7_4_SNAPSHOT,\n    V5_7_4,\n\n    V5_7_5_SNAPSHOT,\n    V5_7_5,\n\n    V5_7_6_SNAPSHOT,\n    V5_7_6,\n\n    V5_7_7_SNAPSHOT,\n    V5_7_7,\n\n    V5_7_8_SNAPSHOT,\n    V5_7_8,\n\n    V5_7_9_SNAPSHOT,\n    V5_7_9,\n\n    V5_8_0_SNAPSHOT,\n    V5_8_0,\n\n    V5_8_1_SNAPSHOT,\n    V5_8_1,\n\n    V5_8_2_SNAPSHOT,\n    V5_8_2,\n\n    V5_8_3_SNAPSHOT,\n    V5_8_3,\n\n    V5_8_4_SNAPSHOT,\n    V5_8_4,\n\n    V5_8_5_SNAPSHOT,\n    V5_8_5,\n\n    V5_8_6_SNAPSHOT,\n    V5_8_6,\n\n    V5_8_7_SNAPSHOT,\n    V5_8_7,\n\n    V5_8_8_SNAPSHOT,\n    V5_8_8,\n\n    V5_8_9_SNAPSHOT,\n    V5_8_9,\n\n    V5_9_0_SNAPSHOT,\n    V5_9_0,\n\n    V5_9_1_SNAPSHOT,\n    V5_9_1,\n\n    V5_9_2_SNAPSHOT,\n    V5_9_2,\n\n    V5_9_3_SNAPSHOT,\n    V5_9_3,\n\n    V5_9_4_SNAPSHOT,\n    V5_9_4,\n\n    V5_9_5_SNAPSHOT,\n    V5_9_5,\n\n    V5_9_6_SNAPSHOT,\n    V5_9_6,\n\n    V5_9_7_SNAPSHOT,\n    V5_9_7,\n\n    V5_9_8_SNAPSHOT,\n    V5_9_8,\n\n    V5_9_9_SNAPSHOT,\n    V5_9_9,\n\n    HIGHER_VERSION\n}\n#pragma warning restore CS1591 // 缺少对公共可见类型或成员的 XML 注释\n"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/Message.cs",
    "content": "﻿using System.Runtime.Serialization;\nusing System.Xml.Serialization;\nusing NewLife.Collections;\nusing NewLife.Data;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>消息</summary>\npublic class Message\n{\n    #region 属性\n    /// <summary>主题</summary>\n    public String Topic { get; set; }\n\n    /// <summary>标签</summary>\n    public String Tags\n    {\n        get => Properties.TryGetValue(\"TAGS\", out var str) ? str : null;\n        set => Properties[\"TAGS\"] = value;\n    }\n\n    /// <summary>键</summary>\n    public String Keys\n    {\n        get => Properties.TryGetValue(\"KEYS\", out var str) ? str : null;\n        set => Properties[\"KEYS\"] = value;\n    }\n\n    /// <summary>标记</summary>\n    public Int32 Flag { get; set; }\n\n    /// <summary>消息体</summary>\n    [XmlIgnore, IgnoreDataMember]\n    public Byte[] Body { get; set; }\n\n    private String _BodyString;\n    /// <summary>消息体。字符串格式</summary>\n    public String BodyString { get => _BodyString ??= Body?.ToStr(); set => Body = (_BodyString = value)?.GetBytes(); }\n\n    /// <summary>等待存储消息</summary>\n    public Boolean WaitStoreMsgOK\n    {\n        get => Properties.TryGetValue(\"WAIT\", out var str) ? str.ToBoolean() : true;\n        set => Properties[\"WAIT\"] = value.ToString();\n    }\n\n    /// <summary>延迟时间等级</summary>\n    public Int32 DelayTimeLevel\n    {\n        get => Properties.TryGetValue(\"DELAY\", out var str) ? str.ToInt() : 0;\n        set => Properties[\"DELAY\"] = value.ToString();\n    }\n\n    /// <summary>事务标识</summary>\n    public String TransactionId\n    {\n        get => Properties.TryGetValue(\"UNIQ_KEY\", out var str) ? str : null;\n        set => Properties[\"UNIQ_KEY\"] = value;\n    }\n\n    /// <summary>回复地址。用于Request-Reply模式，指示回复消息应发送到的客户端ID</summary>\n    public String ReplyToClient\n    {\n        get => Properties.TryGetValue(\"REPLY_TO_CLIENT\", out var str) ? str : null;\n        set => Properties[\"REPLY_TO_CLIENT\"] = value;\n    }\n\n    /// <summary>关联ID。用于Request-Reply模式，将回复消息与请求消息关联</summary>\n    public String CorrelationId\n    {\n        get => Properties.TryGetValue(\"CORRELATION_ID\", out var str) ? str : null;\n        set => Properties[\"CORRELATION_ID\"] = value;\n    }\n\n    /// <summary>消息类型。用于区分普通消息和回复消息</summary>\n    public String MessageType\n    {\n        get => Properties.TryGetValue(\"MSG_TYPE\", out var str) ? str : null;\n        set => Properties[\"MSG_TYPE\"] = value;\n    }\n\n    /// <summary>请求超时时间(毫秒)。用于Request-Reply模式</summary>\n    public Int32 RequestTimeout\n    {\n        get => Properties.TryGetValue(\"REQUEST_TIMEOUT\", out var str) ? str.ToInt() : 0;\n        set => Properties[\"REQUEST_TIMEOUT\"] = value.ToString();\n    }\n\n    /// <summary>附加属性</summary>\n    public IDictionary<String, String> Properties { get; set; }\n    #endregion\n\n    #region 构造\n    /// <summary>实例化</summary>\n    public Message()\n    {\n        Properties = new NullableDictionary<String, String>(StringComparer.OrdinalIgnoreCase);\n    }\n\n    /// <summary>友好字符串</summary>\n    /// <returns></returns>\n    public override String ToString() => Body != null && Body.Length > 0 ? BodyString : base.ToString();\n    #endregion\n\n    #region 方法\n    /// <summary>\n    /// 设置消息体\n    /// </summary>\n    /// <param name=\"body\"></param>\n    public void SetBody(Object body)\n    {\n        _BodyString = null;\n        if (body is IPacket pk)\n            Body = pk.ReadBytes();\n        else if (body is Byte[] buf)\n            Body = buf;\n        else if (body is String str)\n        {\n            _BodyString = str;\n            Body = str.GetBytes();\n        }\n        else\n        {\n            str = body.ToJson();\n            _BodyString = str;\n            Body = str.GetBytes();\n        }\n    }\n\n    /// <summary>获取属性</summary>\n    /// <returns></returns>\n    public String GetProperties()\n    {\n        var sb = Pool.StringBuilder.Get();\n\n        if (Properties != null && Properties.Count > 0)\n        {\n            foreach (var item in Properties)\n            {\n                sb.AppendFormat(\"{0}\\u0001{1}\\u0002\", item.Key, item.Value);\n            }\n        }\n\n        return sb.Return(true);\n    }\n\n    /// <summary>设置属性</summary>\n    /// <param name=\"key\"></param>\n    /// <param name=\"value\"></param>\n    public void PutUserProperty(String key, String value)\n    {\n        if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));\n        if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));\n\n        Properties[key] = value;\n    }\n\n    /// <summary>获取属性</summary>\n    /// <param name=\"key\"></param>\n    /// <returns></returns>\n    public String GetUserProperty(String key)\n    {\n        Properties.TryGetValue(key, out var value);\n        return value;\n    }\n\n    /// <summary>分析字典属性</summary>\n    /// <param name=\"properties\"></param>\n    public IDictionary<String, String> ParseProperties(String properties)\n    {\n        if (properties.IsNullOrEmpty()) return Properties;\n\n        var dic = SplitAsDictionary(properties, \"\\u0001\", \"\\u0002\");\n\n        Properties = dic;\n\n        if (TryGetAndRemove(dic, nameof(Tags), out var str)) Tags = str;\n        if (TryGetAndRemove(dic, nameof(Keys), out str)) Keys = str;\n        if (TryGetAndRemove(dic, \"DELAY\", out str)) DelayTimeLevel = str.ToInt();\n        if (TryGetAndRemove(dic, \"WAIT\", out str)) WaitStoreMsgOK = str.ToBoolean();\n        \n        return Properties;\n    }\n\n    private static IDictionary<String, String> SplitAsDictionary(String value, String nameValueSeparator, String separator)\n    {\n        var dic = new NullableDictionary<String, String>(StringComparer.OrdinalIgnoreCase);\n        if (value == null || value.IsNullOrWhiteSpace()) return dic;\n\n        var ss = value.Split([separator], StringSplitOptions.RemoveEmptyEntries);\n        if (ss == null || ss.Length <= 0) return dic;\n\n        foreach (var item in ss)\n        {\n            // 如果分隔符是 \\u0001，则必须使用Ordinal，否则无法分割直接返回0\n            var p = item.IndexOf(nameValueSeparator, StringComparison.Ordinal);\n            if (p <= 0) continue;\n\n            var key = item[..p].Trim();\n            var val = item[(p + nameValueSeparator.Length)..].Trim();\n\n#if NETFRAMEWORK || NETSTANDARD2_0\n            if (!dic.ContainsKey(key)) dic.Add(key, val);\n#else\n            dic.TryAdd(key, val);\n#endif\n        }\n\n        return dic;\n    }\n\n    private Boolean TryGetAndRemove(IDictionary<String, String> dic, String key, out String value)\n    {\n        if (dic.TryGetValue(key, out var str))\n        {\n            value = str;\n            dic.Remove(key);\n\n            return true;\n        }\n\n        value = null;\n        return false;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/MessageExt.cs",
    "content": "﻿using System.Net;\nusing NewLife.Buffers;\nusing NewLife.Collections;\nusing NewLife.Data;\nusing NewLife.Serialization;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>消息扩展</summary>\n/// <remarks>\n/// RocketMQ 消息二进制格式（大端序）：\n/// StoreSize(4) + MagicCode(4) + BodyCRC(4) + QueueId(4) + Flag(4) +\n/// QueueOffset(8) + CommitLogOffset(8) + SysFlag(4) + BornTimestamp(8) +\n/// BornHost(4/16+4) + StoreTimestamp(8) + StoreHost(4/16+4) +\n/// ReconsumeTimes(4) + PreparedTransactionOffset(8) +\n/// BodyLength(4) + Body + TopicLength(1) + Topic +\n/// PropertiesLength(2) + Properties\n/// 使用 SpanReader 进行高性能解码。\n/// </remarks>\npublic class MessageExt : Message, IAccessor\n{\n    #region 属性\n    /// <summary>队列编号</summary>\n    public Int32 QueueId { get; set; }\n\n    /// <summary>存储大小</summary>\n    public Int32 StoreSize { get; set; }\n\n    /// <summary>CRC校验</summary>\n    public Int32 BodyCRC { get; set; }\n\n    /// <summary>队列偏移</summary>\n    public Int64 QueueOffset { get; set; }\n\n    /// <summary>提交日志偏移</summary>\n    public Int64 CommitLogOffset { get; set; }\n\n    /// <summary>系统标记</summary>\n    public Int32 SysFlag { get; set; }\n\n    /// <summary>生产时间</summary>\n    public Int64 BornTimestamp { get; set; }\n\n    /// <summary>生产主机</summary>\n    public String BornHost { get; set; }\n\n    /// <summary>存储时间</summary>\n    public Int64 StoreTimestamp { get; set; }\n\n    /// <summary>存储主机</summary>\n    public String StoreHost { get; set; }\n\n    /// <summary>重新消费次数</summary>\n    public Int32 ReconsumeTimes { get; set; }\n\n    /// <summary>准备事务偏移</summary>\n    public Int64 PreparedTransactionOffset { get; set; }\n\n    /// <summary>消息编号</summary>\n    public String MsgId { get; set; }\n\n    /// <summary>Pop检查点信息。Pop消费模式下由Broker在消息属性中返回，Ack/ChangeInvisibleTime操作时需传入此值</summary>\n    public String PopCheckPoint\n    {\n        get => Properties.TryGetValue(\"POP_CK\", out var str) ? str : null;\n        set => Properties[\"POP_CK\"] = value;\n    }\n    #endregion\n\n    #region 构造\n    /// <summary>友好字符串</summary>\n    /// <returns></returns>\n    public override String ToString() => $\"[{CommitLogOffset}]{base.ToString()}\";\n    #endregion\n\n    #region 读写\n    /// <summary>从SpanReader中读取消息</summary>\n    /// <param name=\"reader\">SpanReader引用</param>\n    /// <returns></returns>\n    public Boolean Read(ref SpanReader reader)\n    {\n        // 读取\n        StoreSize = reader.ReadInt32();\n        if (StoreSize <= 0) return false;\n\n        var n = reader.ReadInt32(); // MagicCode\n        BodyCRC = reader.ReadInt32();\n        QueueId = reader.ReadInt32();\n        Flag = reader.ReadInt32();\n        QueueOffset = reader.ReadInt64();\n        CommitLogOffset = reader.ReadInt64();\n        SysFlag = reader.ReadInt32();\n\n        // SysFlag第2位(0x04)标识IPv6地址。IPv4=4字节，IPv6=16字节\n        var isIPv6 = (SysFlag & 4) != 0;\n        var ipLen = isIPv6 ? 16 : 4;\n\n        BornTimestamp = reader.ReadInt64();\n        var buf = reader.ReadBytes(ipLen).ToArray();\n        var ip = new IPAddress(buf);\n        var port = reader.ReadInt32();\n        BornHost = $\"{ip}:{port}\";\n\n        StoreTimestamp = reader.ReadInt64();\n        var buf2 = reader.ReadBytes(ipLen).ToArray();\n        var ip2 = new IPAddress(buf2);\n        var port2 = reader.ReadInt32();\n        StoreHost = $\"{ip2}:{port2}\";\n\n        ReconsumeTimes = reader.ReadInt32();\n        PreparedTransactionOffset = reader.ReadInt64();\n\n        // 主体\n        var len = reader.ReadInt32();\n        Body = reader.ReadBytes(len).ToArray();\n        if ((SysFlag & 1) == 1)\n        {\n            /*uncompress*/\n            // ZLIB格式RFC1950，要去掉头部两个字节\n            Body = Body.ReadBytes(2, -1).Decompress();\n            //var gs = new MemoryStream(Body);\n            //Body = gs.DecompressGZip().ReadBytes();\n        }\n\n        // 主题\n        len = reader.ReadByte();\n        Topic = reader.ReadBytes(len).ToArray().ToStr();\n\n        var len2 = reader.ReadInt16();\n        var str = reader.ReadBytes(len2).ToArray().ToStr();\n        ParseProperties(str);\n\n        // MsgId：IPv4为16字节(4+4+8)，IPv6为28字节(16+4+8)\n        var idLen = isIPv6 ? 28 : 16;\n        var idBuf = new Byte[idLen];\n        var idWriter = new SpanWriter(idBuf) { IsLittleEndian = false };\n        idWriter.Write(buf);\n        idWriter.Write(port);\n        idWriter.Write(CommitLogOffset);\n        MsgId = idBuf.ToHex(0, idLen);\n\n        return true;\n    }\n\n    /// <summary>从数据流中读取（向后兼容）</summary>\n    /// <param name=\"stream\">数据流</param>\n    /// <param name=\"context\">上下文</param>\n    /// <returns></returns>\n    public Boolean Read(Stream stream, Object context = null)\n    {\n        // 读取剩余数据到缓冲区\n        var remaining = (Int32)(stream.Length - stream.Position);\n        if (remaining <= 0) return false;\n\n        var buf = new Byte[remaining];\n        var n = stream.Read(buf, 0, remaining);\n\n        var reader = new SpanReader(buf, 0, n) { IsLittleEndian = false };\n        var rs = Read(ref reader);\n\n        // 将流位置设置到实际读取位置\n        stream.Position = stream.Length - remaining + reader.Position;\n\n        return rs;\n    }\n\n    /// <summary>读取所有消息</summary>\n    /// <param name=\"body\">消息数据包</param>\n    /// <returns></returns>\n    public static IList<MessageExt> ReadAll(IPacket body)\n    {\n        var buf = body.ReadBytes();\n        var reader = new SpanReader(buf) { IsLittleEndian = false };\n\n        var list = new List<MessageExt>();\n        while (reader.FreeCapacity > 0)\n        {\n            var msg = new MessageExt();\n            if (!msg.Read(ref reader)) break;\n\n            // SysFlag第4位(0x10)标识批量消息，Body内嵌多条子消息\n            if ((msg.SysFlag & 0x10) != 0 && msg.Body != null && msg.Body.Length > 0)\n            {\n                var batches = DecodeBatch(msg);\n                list.AddRange(batches);\n            }\n            else\n            {\n                list.Add(msg);\n            }\n        }\n\n        return list;\n    }\n\n    /// <summary>解码批量消息。BatchMessage 的 Body 内嵌多条子消息</summary>\n    /// <param name=\"parent\">父消息，批量消息的外层容器</param>\n    /// <returns></returns>\n    public static IList<MessageExt> DecodeBatch(MessageExt parent)\n    {\n        if (parent == null) throw new ArgumentNullException(nameof(parent));\n        if (parent.Body == null || parent.Body.Length == 0) return [];\n\n        var list = new List<MessageExt>();\n        var reader = new SpanReader(parent.Body) { IsLittleEndian = false };\n\n        while (reader.FreeCapacity > 0)\n        {\n            try\n            {\n                // 批量消息内部格式：\n                // 4字节 TotalSize（含自身）\n                // 4字节 MagicCode\n                // 4字节 BodyCRC\n                // 4字节 Flag\n                // 4字节 Body长度 + Body\n                // 1字节 Topic长度 + Topic\n                // 2字节 Properties长度 + Properties\n                var totalSize = reader.ReadInt32();\n                if (totalSize <= 0) break;\n\n                var magicCode = reader.ReadInt32();\n                var bodyCrc = reader.ReadInt32();\n                var flag = reader.ReadInt32();\n\n                var bodyLen = reader.ReadInt32();\n                var body = reader.ReadBytes(bodyLen).ToArray();\n\n                var topicLen = reader.ReadByte();\n                var topic = reader.ReadBytes(topicLen).ToArray().ToStr();\n\n                var propsLen = reader.ReadInt16();\n                var propsStr = propsLen > 0 ? reader.ReadBytes(propsLen).ToArray().ToStr() : \"\";\n\n                var sub = new MessageExt\n                {\n                    // 从父消息继承上下文信息\n                    QueueId = parent.QueueId,\n                    CommitLogOffset = parent.CommitLogOffset,\n                    SysFlag = parent.SysFlag & ~0x10, // 清除批量标志\n                    BornTimestamp = parent.BornTimestamp,\n                    BornHost = parent.BornHost,\n                    StoreTimestamp = parent.StoreTimestamp,\n                    StoreHost = parent.StoreHost,\n\n                    // 子消息自身信息\n                    StoreSize = totalSize,\n                    BodyCRC = bodyCrc,\n                    Flag = flag,\n                    Body = body,\n                    Topic = topic,\n                };\n                sub.ParseProperties(propsStr);\n\n                // 使用 UNIQ_KEY 作为 MsgId（如果存在）\n                sub.MsgId = sub.TransactionId ?? parent.MsgId;\n\n                list.Add(sub);\n            }\n            catch\n            {\n                break;\n            }\n        }\n\n        return list;\n    }\n\n    /// <summary>写入命令到数据流</summary>\n    /// <param name=\"stream\">数据流</param>\n    /// <param name=\"context\">上下文</param>\n    /// <returns></returns>\n    public Boolean Write(Stream stream, Object context = null) => true;\n    #endregion\n\n    #region 5.x MessageId\n    /// <summary>创建5.x格式的MessageId。格式：01{VERSION}{MAC_HEX}{PID_HEX}{COUNTER_HEX}，共34个十六进制字符</summary>\n    /// <param name=\"version\">版本号，默认1</param>\n    /// <param name=\"macBytes\">MAC地址字节数组（6字节），为空时使用随机字节</param>\n    /// <param name=\"processId\">进程ID</param>\n    /// <param name=\"counter\">消息计数器</param>\n    /// <returns>5.x格式的MessageId（34字符十六进制字符串）</returns>\n    public static String CreateMessageId5x(Byte version, Byte[] macBytes, Int32 processId, Int32 counter)\n    {\n        // 格式：01 + 1字节Version + 6字节MAC + 4字节PID + 4字节Counter = 16字节 = 32 hex + 前缀\"01\" = 34 hex\n        var buf = new Byte[16];\n        var writer = new SpanWriter(buf) { IsLittleEndian = false };\n\n        // 固定前缀 01\n        writer.Write((Byte)0x01);\n\n        // 版本\n        writer.Write(version);\n\n        // MAC地址（6字节）\n        if (macBytes == null || macBytes.Length < 6)\n        {\n            var rand = new Byte[6];\n            new Random().NextBytes(rand);\n            writer.Write(rand);\n        }\n        else\n        {\n            writer.Write(new ReadOnlySpan<Byte>(macBytes, 0, 6));\n        }\n\n        // 进程ID（4字节，大端序）\n        writer.Write(processId);\n\n        // 计数器（4字节，大端序）\n        writer.Write(counter);\n\n        return buf.ToHex(0, 16);\n    }\n\n    /// <summary>尝试解析5.x格式的MessageId</summary>\n    /// <param name=\"messageId\">MessageId字符串</param>\n    /// <param name=\"version\">解析出的版本号</param>\n    /// <param name=\"macBytes\">解析出的MAC地址</param>\n    /// <param name=\"processId\">解析出的进程ID</param>\n    /// <param name=\"counter\">解析出的计数器</param>\n    /// <returns>是否为有效的5.x格式MessageId</returns>\n    public static Boolean TryParseMessageId5x(String messageId, out Byte version, out Byte[] macBytes, out Int32 processId, out Int32 counter)\n    {\n        version = 0;\n        macBytes = null;\n        processId = 0;\n        counter = 0;\n\n        if (String.IsNullOrEmpty(messageId) || messageId.Length != 32) return false;\n\n        // 前两个hex字符必须为\"01\"\n        if (!messageId.StartsWith(\"01\", StringComparison.OrdinalIgnoreCase)) return false;\n\n        try\n        {\n            var bytes = messageId.ToHex();\n            if (bytes == null || bytes.Length != 16) return false;\n\n            var reader = new SpanReader(bytes) { IsLittleEndian = false };\n\n            // bytes[0] = 0x01（前缀），跳过\n            reader.ReadByte();\n            version = reader.ReadByte();\n            macBytes = reader.ReadBytes(6).ToArray();\n\n            // 大端序\n            processId = reader.ReadInt32();\n            counter = reader.ReadInt32();\n\n            return true;\n        }\n        catch\n        {\n            return false;\n        }\n    }\n\n    /// <summary>判断是否为5.x格式的MessageId</summary>\n    /// <param name=\"messageId\">MessageId字符串</param>\n    /// <returns>是否为5.x格式</returns>\n    public static Boolean IsMessageId5x(String messageId)\n    {\n        if (String.IsNullOrEmpty(messageId)) return false;\n\n        // 5.x格式为32个十六进制字符，前缀为\"01\"\n        return messageId.Length == 32 && messageId.StartsWith(\"01\", StringComparison.OrdinalIgnoreCase);\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/MessageQueue.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>消息队列</summary>\npublic class MessageQueue\n{\n    #region 属性\n    /// <summary>主题</summary>\n    public String Topic { get; set; }\n\n    /// <summary>代理名称</summary>\n    public String BrokerName { get; set; }\n\n    /// <summary>队列编号</summary>\n    public Int32 QueueId { get; set; }\n    #endregion\n\n    #region 相等\n    /// <summary>相等比较</summary>\n    /// <param name=\"obj\"></param>\n    /// <returns></returns>\n    public override Boolean Equals(Object obj)\n    {\n        var x = this;\n        if (obj is not MessageQueue y) return false;\n\n        return x.Topic == y.Topic && x.BrokerName == y.BrokerName && x.QueueId == y.QueueId;\n    }\n\n    /// <summary>计算哈希</summary>\n    /// <returns></returns>\n    public override Int32 GetHashCode()\n    {\n        var obj = this;\n        return obj.Topic.GetHashCode() ^ obj.BrokerName.GetHashCode() ^ obj.QueueId;\n    }\n    #endregion\n\n    #region 辅助\n    /// <summary>友好字符串</summary>\n    /// <returns></returns>\n    public override String ToString() => $\"{BrokerName}[{QueueId}]\";\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/MqCodec.cs",
    "content": "﻿using NewLife.Data;\nusing NewLife.Messaging;\nusing NewLife.Model;\nusing NewLife.Net.Handlers;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>编码器</summary>\nclass MqCodec : MessageCodec<Command>\n{\n    /// <summary>实例化编码器</summary>\n    public MqCodec() => UserPacket = false;\n\n    /// <summary>编码</summary>\n    /// <param name=\"context\"></param>\n    /// <param name=\"msg\"></param>\n    /// <returns></returns>\n    protected override Object Encode(IHandlerContext context, Command msg)\n    {\n        if (msg is Command cmd) return cmd.ToPacket();\n\n        return null;\n    }\n\n    /// <summary>加入队列</summary>\n    /// <param name=\"context\"></param>\n    /// <param name=\"msg\"></param>\n    /// <returns></returns>\n    protected override void AddToQueue(IHandlerContext context, Command msg)\n    {\n        if (!msg.Reply && !msg.OneWay) base.AddToQueue(context, msg);\n    }\n\n    /// <summary>解码</summary>\n    /// <param name=\"context\"></param>\n    /// <param name=\"pk\"></param>\n    /// <returns></returns>\n    protected override IEnumerable<Command> Decode(IHandlerContext context, IPacket pk)\n    {\n        var ss = context.Owner as IExtend;\n        if (ss[\"Codec\"] is not PacketCodec pc)\n            ss[\"Codec\"] = pc = new PacketCodec { GetLength = p => GetLength(p, 0, -4) };\n\n        var pks = pc.Parse(pk);\n        foreach (var e in pks)\n        {\n            var msg = new Command();\n            if (msg.Read(e.GetStream())) yield return msg;\n        }\n    }\n\n    /// <summary>连接关闭时，清空粘包编码器</summary>\n    /// <param name=\"context\"></param>\n    /// <param name=\"reason\"></param>\n    /// <returns></returns>\n    public override Boolean Close(IHandlerContext context, String reason)\n    {\n        if (context.Owner is IExtend ss) ss[\"Codec\"] = null;\n\n        return base.Close(context, reason);\n    }\n\n    /// <summary>是否匹配响应</summary>\n    /// <param name=\"request\"></param>\n    /// <param name=\"response\"></param>\n    /// <returns></returns>\n    protected override Boolean IsMatch(Object request, Object response)\n    {\n        return request is Command req && req.Header != null &&\n            response is Command res && res.Header != null &&\n            req.Header.Opaque == res.Header.Opaque;\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ProducerData.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>生产者数据</summary>\n    public class ProducerData\n    {\n        #region 属性\n        /// <summary>组名</summary>\n        public String GroupName { get; set; } = \"CLIENT_INNER_PRODUCER\";\n        #endregion\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/PullMessageRequestHeader.cs",
    "content": "﻿using NewLife.Reflection;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>拉取信息请求头</summary>\npublic class PullMessageRequestHeader\n{\n    #region 属性\n    /// <summary>消费组</summary>\n    public String ConsumerGroup { get; set; }\n\n    /// <summary>主题</summary>\n    public String Topic { get; set; }\n\n    ///// <summary>表达式类型</summary>\n    //public String ExpressionType { get; set; } = \"TAG\";\n\n    /// <summary>订阅表达式</summary>\n    public String Subscription { get; set; } = \"*\";\n\n    /// <summary>表达式类型。TAG或SQL92，默认TAG</summary>\n    public String ExpressionType { get; set; } = \"TAG\";\n\n    /// <summary>挂起超时时间。默认20_000ms</summary>\n    public Int32 SuspendTimeoutMillis { get; set; } = 20_000;\n\n    /// <summary>子版本</summary>\n    public Int64 SubVersion { get; set; }\n\n    /// <summary>队列</summary>\n    public Int32 QueueId { get; set; }\n\n    /// <summary>队列偏移</summary>\n    public Int64 QueueOffset { get; set; }\n\n    /// <summary>最大消息数</summary>\n    public Int32 MaxMsgNums { get; set; }\n\n    /// <summary>提交偏移</summary>\n    public Int64 CommitOffset { get; set; }\n\n    /// <summary>系统标记</summary>\n    public Int32 SysFlag { get; set; }\n    #endregion\n\n    #region 方法\n    /// <summary>获取属性字典</summary>\n    /// <returns></returns>\n    public IDictionary<String, Object> GetProperties()\n    {\n        //var dic = new Dictionary<String, Object>();\n        var dic = new SortedList<String, Object>(StringComparer.Ordinal);\n\n        foreach (var pi in GetType().GetProperties())\n        {\n            //if (pi.GetIndexParameters().Length > 0) continue;\n            //if (pi.GetCustomAttribute<XmlIgnoreAttribute>() != null) continue;\n\n            var name = pi.Name;\n            //var att = pi.GetCustomAttribute<XmlElementAttribute>();\n            //if (att != null && !att.ElementName.IsNullOrEmpty()) name = att.ElementName;\n            name = name.Substring(0, 1).ToLower() + name.Substring(1);\n\n            dic[name] = this.GetValue(pi) + \"\";\n        }\n\n        return dic;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/PullResult.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>拉取状态</summary>\n    public enum PullStatus\n    {\n        /// <summary>已发现</summary>\n        Found = 0,\n\n        /// <summary>没有新的消息</summary>\n        NoNewMessage = 1,\n\n        /// <summary>没有匹配消息</summary>\n        NoMatchedMessage = 2,\n\n        /// <summary>偏移量非法</summary>\n        OffsetIllegal = 3,\n\n        /// <summary>未知类型</summary>\n        Unknown = 4\n    }\n\n    /// <summary>拉取结果</summary>\n    public class PullResult\n    {\n        #region 属性\n        /// <summary>状态</summary>\n        public PullStatus Status { get; set; }\n\n        /// <summary>最小偏移</summary>\n        public Int64 MinOffset { get; set; }\n\n        /// <summary>最大偏移</summary>\n        public Int64 MaxOffset { get; set; }\n\n        /// <summary>下一轮拉取偏移</summary>\n        public Int64 NextBeginOffset { get; set; }\n\n        /// <summary>消息</summary>\n        public MessageExt[] Messages { get; set; }\n        #endregion\n\n        #region 方法\n        /// <summary>友好字符串</summary>\n        /// <returns></returns>\n        public override String ToString() => $\"{Status} ({MinOffset},{MaxOffset})[{((Messages == null) ? 0 : Messages.Length)}]\";\n\n        /// <summary>读取数据</summary>\n        /// <param name=\"dic\"></param>\n        public void Read(IDictionary<String, String> dic)\n        {\n            if (dic == null) return;\n\n            var dic2 = dic.ToNullable(StringComparer.OrdinalIgnoreCase);\n\n            if (dic2.TryGetValue(nameof(MinOffset), out var str)) MinOffset = str.ToLong();\n            if (dic2.TryGetValue(nameof(MaxOffset), out str)) MaxOffset = str.ToLong();\n            if (dic2.TryGetValue(nameof(NextBeginOffset), out str)) NextBeginOffset = str.ToLong();\n        }\n        #endregion\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/QueryResult.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>查询结果</summary>\n    public class QueryResult\n    {\n        /// <summary>最后更新时间</summary>\n        public Int32 IndexLastUpdateTimestamp { get; set; }\n\n        /// <summary>消息列表</summary>\n        public List<MessageExt> MessageList { get; set; }\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/RequestCode.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>请求代码</summary>\npublic enum RequestCode\n{\n    /// <summary>发消息</summary>\n    SEND_MESSAGE = 10,\n\n    /// <summary>收消息</summary>\n    PULL_MESSAGE = 11,\n\n    /// <summary>查询消息</summary>\n    QUERY_MESSAGE = 12,\n\n    /// <summary>查询Broker偏移</summary>\n    QUERY_BROKER_OFFSET = 13,\n\n    /// <summary>查询消费偏移</summary>\n    QUERY_CONSUMER_OFFSET = 14,\n\n    /// <summary>更新消费者偏移</summary>\n    UPDATE_CONSUMER_OFFSET = 15,\n\n    /// <summary>更新或创建Topic</summary>\n    UPDATE_AND_CREATE_TOPIC = 17,\n\n    /// <summary>用于向brokers查询所有的topic和它们的配置</summary>\n    GET_ALL_TOPIC_CONFIG = 21,\n\n    /// <summary>获取Topic配置列表</summary>\n    GET_TOPIC_CONFIG_LIST = 22,\n\n    /// <summary>获取Topic名列表</summary>\n    GET_TOPIC_NAME_LIST = 23,\n\n    /// <summary>更新Broker配置</summary>\n    UPDATE_BROKER_CONFIG = 25,\n\n    /// <summary>获取代理配置</summary>\n    GET_BROKER_CONFIG = 26,\n\n    /// <summary>触发删除文件</summary>\n    TRIGGER_DELETE_FILES = 27,\n\n    /// <summary>获取代理运行时信息,包括broker版本、磁盘容量、系统负载等</summary>\n    GET_BROKER_RUNTIME_INFO = 28,\n\n    /// <summary>按时间戳搜索偏移</summary>\n    SEARCH_OFFSET_BY_TIMESTAMP = 29,\n\n    /// <summary>获取topic/队列偏移量的最大值</summary>\n    GET_MAX_OFFSET = 30,\n\n    /// <summary>获取topic/队列偏移量的最小值</summary>\n    GET_MIN_OFFSET = 31,\n\n    /// <summary>获取最早消息存储时间</summary>\n    GET_EARLIEST_MSG_STORETIME = 32,\n\n    /// <summary>按消息ID查看消息</summary>\n    VIEW_MESSAGE_BY_ID = 33,\n\n    /// <summary>发送心跳</summary>\n    HEART_BEAT = 34,\n\n    /// <summary>注销</summary>\n    UNREGISTER_CLIENT = 35,\n\n    /// <summary>当consumer客户端无法处理消息时,将这些消息发送回brokers,以便将来将这些消息重新发送给consumers</summary>\n    CONSUMER_SEND_MSG_BACK = 36,\n\n    /// <summary>结束事务</summary>\n    END_TRANSACTION = 37,\n\n    /// <summary>查询每个consumer group的存活成员</summary>\n    GET_CONSUMER_LIST_BY_GROUP = 38,\n\n    /// <summary>检查事务状态</summary>\n    CHECK_TRANSACTION_STATE = 39,\n\n    /// <summary>当broker得知一个consumer宕机时,它会通知其他工作的consumers尽快重新平衡</summary>\n    NOTIFY_CONSUMER_IDS_CHANGED = 40,\n\n    /// <summary>锁定批</summary>\n    LOCK_BATCH_MQ = 41,\n\n    /// <summary>解锁批</summary>\n    UNLOCK_BATCH_MQ = 42,\n\n    /// <summary>获取所有的消费的偏移量</summary>\n    GET_ALL_CONSUMER_OFFSET = 43,\n\n    /// <summary>获取延迟topic的偏移量</summary>\n    GET_ALL_DELAY_OFFSET = 45,\n\n    /// <summary>检查客户端配置</summary>\n    CHECK_CLIENT_CONFIG = 46,\n\n    /// <summary>提交KV配置</summary>\n    PUT_KV_CONFIG = 100,\n\n    /// <summary>获取KV配置</summary>\n    GET_KV_CONFIG = 101,\n\n    /// <summary>删除KV配置</summary>\n    DELETE_KV_CONFIG = 102,\n\n    /// <summary>注册Broker</summary>\n    REGISTER_BROKER = 103,\n    /// <summary>取消注册Broker</summary>\n    UNREGISTER_BROKER = 104,\n\n    /// <summary>获取topic路由信息</summary>\n    GET_ROUTEINTO_BY_TOPIC = 105,\n\n    /// <summary>获取群集信息</summary>\n    GET_BROKER_CLUSTER_INFO = 106,\n\n    /// <summary>创建新的consumer group或更新现有的consumer group以更改属性</summary>\n    UPDATE_AND_CREATE_SUBSCRIPTIONGROUP = 200,\n\n    /// <summary>查询所有已知的consumer group配置</summary>\n    GET_ALL_SUBSCRIPTIONGROUP_CONFIG = 201,\n\n    /// <summary>查询topic相关的统计信息</summary>\n    GET_TOPIC_STATS_INFO = 202,\n\n    /// <summary>获取消费者连接列表</summary>\n    GET_CONSUMER_CONNECTION_LIST = 203,\n\n    /// <summary>获取生产者连接列表</summary>\n    GET_PRODUCER_CONNECTION_LIST = 204,\n\n    /// <summary>写Broker权限</summary>\n    WIPE_WRITE_PERM_OF_BROKER = 205,\n\n    /// <summary>查询所有topic</summary>\n    GET_ALL_TOPIC_LIST_FROM_NAMESERVER = 206,\n\n    /// <summary>删除订阅组</summary>\n    DELETE_SUBSCRIPTIONGROUP = 207,\n\n    /// <summary>获取消费者状态</summary>\n    GET_CONSUME_STATS = 208,\n\n    /// <summary>挂起消费者</summary>\n    SUSPEND_CONSUMER = 209,\n\n    /// <summary>恢复消费者</summary>\n    RESUME_CONSUMER = 210,\n\n    /// <summary>重置消费者偏移</summary>\n    RESET_CONSUMER_OFFSET_IN_CONSUMER = 211,\n\n    /// <summary>重置Broker中的消费者偏移</summary>\n    RESET_CONSUMER_OFFSET_IN_BROKER = 212,\n\n    /// <summary>对齐消费者线程池</summary>\n    ADJUST_CONSUMER_THREAD_POOL = 213,\n\n    /// <summary>查询谁消费了指定消息</summary>\n    WHO_CONSUME_THE_MESSAGE = 214,\n\n    /// <summary>在Broker中删除Topic</summary>\n    DELETE_TOPIC_IN_BROKER = 215,\n\n    /// <summary>在Name服务器中删除Topic</summary>\n    DELETE_TOPIC_IN_NAMESRV = 216,\n\n    /// <summary>根据命名空间获取KV列表</summary>\n    GET_KVLIST_BY_NAMESPACE = 219,\n\n    /// <summary>重置消费者客户端偏移</summary>\n    RESET_CONSUMER_CLIENT_OFFSET = 220,\n\n    /// <summary>从客户端获取消费者状态</summary>\n    GET_CONSUMER_STATUS_FROM_CLIENT = 221,\n\n    /// <summary>要求broker从consumer客户端按给定的时间戳重置偏移量</summary>\n    INVOKE_BROKER_TO_RESET_OFFSET = 222,\n\n    /// <summary>要求Broker获取消费者状态</summary>\n    INVOKE_BROKER_TO_GET_CONSUMER_STATUS = 223,\n\n    /// <summary>查询谁消费了指定Topic</summary>\n    QUERY_TOPIC_CONSUME_BY_WHO = 300,\n\n    /// <summary>获取集群中Topic</summary>\n    GET_TOPICS_BY_CLUSTER = 224,\n\n    /// <summary>注册文件服务器</summary>\n    REGISTER_FILTER_SERVER = 301,\n\n    /// <summary>注册消息过滤类</summary>\n    REGISTER_MESSAGE_FILTER_CLASS = 302,\n\n    /// <summary>查询消费时间</summary>\n    QUERY_CONSUME_TIME_SPAN = 303,\n\n    /// <summary>从名称服务器查询系统Topic列表</summary>\n    GET_SYSTEM_TOPIC_LIST_FROM_NS = 304,\n\n    /// <summary>从Broker服务器查询系统Topic列表</summary>\n    GET_SYSTEM_TOPIC_LIST_FROM_BROKER = 305,\n\n    /// <summary>清理过滤消费队列</summary>\n    CLEAN_EXPIRED_CONSUMEQUEUE = 306,\n\n    /// <summary>获取消费者运行消息</summary>\n    GET_CONSUMER_RUNNING_INFO = 307,\n\n    /// <summary>查询协调器偏移</summary>\n    QUERY_CORRECTION_OFFSET = 308,\n\n    /// <summary>直接消费消息</summary>\n    CONSUME_MESSAGE_DIRECTLY = 309,\n\n    /// <summary>发送消息V2</summary>\n    SEND_MESSAGE_V2 = 310,\n\n    /// <summary>获取单元Topic列表</summary>\n    GET_UNIT_TOPIC_LIST = 311,\n\n    /// <summary></summary>\n    GET_HAS_UNIT_SUB_TOPIC_LIST = 312,\n\n    /// <summary></summary>\n    GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST = 313,\n\n    /// <summary>克隆组偏移</summary>\n    CLONE_GROUP_OFFSET = 314,\n\n    /// <summary>查看Broker状态数据</summary>\n    VIEW_BROKER_STATS_DATA = 315,\n\n    /// <summary>清理未使用Topic</summary>\n    CLEAN_UNUSED_TOPIC = 316,\n\n    /// <summary>获取消费状态</summary>\n    GET_BROKER_CONSUME_STATS = 317,\n\n    /// <summary>update the config of name server</summary>\n    UPDATE_NAMESRV_CONFIG = 318,\n\n    /// <summary>get config from name server</summary>\n    GET_NAMESRV_CONFIG = 319,\n\n    /// <summary>批处理模式发送消息</summary>\n    SEND_BATCH_MESSAGE = 320,\n\n    /// <summary>查询消费队列</summary>\n    QUERY_CONSUME_QUEUE = 321,\n\n    /// <summary>查询数据版本</summary>\n    QUERY_DATA_VERSION = 322,\n\n    /// <summary>请求消息(Request-Reply特性)</summary>\n    REQUEST_MESSAGE = 323,\n\n    /// <summary>发送回复消息</summary>\n    SEND_REPLY_MESSAGE = 324,\n\n    /// <summary>发送回复消息V2</summary>\n    SEND_REPLY_MESSAGE_V2 = 325,\n\n    /// <summary>Pop消费消息。5.0新增的轻量消费模式，无需Rebalance</summary>\n    POP_MESSAGE = 200050,\n\n    /// <summary>确认Pop消息。确认消费完成</summary>\n    ACK_MESSAGE = 200051,\n\n    /// <summary>修改Pop消息不可见时间。延长处理窗口</summary>\n    CHANGE_MESSAGE_INVISIBLETIME = 200052,\n\n    /// <summary>批量确认Pop消息</summary>\n    BATCH_ACK_MESSAGE = 200151,\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ResponseCode.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>响应码</summary>\npublic enum ResponseCode\n{\n    /// <summary>成功</summary>\n    SUCCESS = 0,\n\n    /// <summary>系统错误</summary>\n    SYSTEM_ERROR = 1,\n\n    /// <summary>系统忙</summary>\n    SYSTEM_BUSY = 2,\n\n    /// <summary>不支持的请求码</summary>\n    REQUEST_CODE_NOT_SUPPORTED = 3,\n\n    /// <summary>事务失败</summary>\n    TRANSACTION_FAILED = 4,\n\n    /// <summary>刷磁盘超时</summary>\n    FLUSH_DISK_TIMEOUT = 10,\n\n    /// <summary>从机不可用</summary>\n    SLAVE_NOT_AVAILABLE = 11,\n\n    /// <summary>刷从机超时</summary>\n    FLUSH_SLAVE_TIMEOUT = 12,\n\n    /// <summary>非法消息</summary>\n    MESSAGE_ILLEGAL = 13,\n\n    /// <summary>服务不可用</summary>\n    SERVICE_NOT_AVAILABLE = 14,\n\n    /// <summary>不支持的版本</summary>\n    VERSION_NOT_SUPPORTED = 15,\n\n    /// <summary>没有权限</summary>\n    NO_PERMISSION = 16,\n\n    /// <summary>主题不存在</summary>\n    TOPIC_NOT_EXIST = 17,\n\n    /// <summary>主题已存在</summary>\n    TOPIC_EXIST_ALREADY = 18,\n\n    /// <summary>拉取未发现</summary>\n    PULL_NOT_FOUND = 19,\n\n    /// <summary>请重试拉取</summary>\n    PULL_RETRY_IMMEDIATELY = 20,\n\n    /// <summary>拉取偏移被移动</summary>\n    PULL_OFFSET_MOVED = 21,\n\n    /// <summary>查询未发现</summary>\n    QUERY_NOT_FOUND = 22,\n\n    /// <summary>订阅分析失败</summary>\n    SUBSCRIPTION_PARSE_FAILED = 23,\n\n    /// <summary>订阅不存在</summary>\n    SUBSCRIPTION_NOT_EXIST = 24,\n\n    /// <summary>订阅不是最新</summary>\n    SUBSCRIPTION_NOT_LATEST = 25,\n\n    /// <summary>订阅组不存在</summary>\n    SUBSCRIPTION_GROUP_NOT_EXIST = 26,\n\n    /// <summary>过滤数据不存在</summary>\n    FILTER_DATA_NOT_EXIST = 27,\n\n    /// <summary>过滤数据不是最新</summary>\n    FILTER_DATA_NOT_LATEST = 28,\n\n    /// <summary>事务应该提交</summary>\n    TRANSACTION_SHOULD_COMMIT = 200,\n\n    /// <summary>事务应该回滚</summary>\n    TRANSACTION_SHOULD_ROLLBACK = 201,\n\n    /// <summary>事务状态未知</summary>\n    TRANSACTION_STATE_UNKNOW = 202,\n\n    /// <summary>事务状态组错误</summary>\n    TRANSACTION_STATE_GROUP_WRONG = 203,\n\n    /// <summary>未购买</summary>\n    NO_BUYER_ID = 204,\n\n    /// <summary>不在当前单元</summary>\n    NOT_IN_CURRENT_UNIT = 205,\n\n    /// <summary>消费者不在线</summary>\n    CONSUMER_NOT_ONLINE = 206,\n\n    /// <summary>消费消息超时</summary>\n    CONSUME_MSG_TIMEOUT = 207,\n\n    /// <summary>没有消息</summary>\n    NO_MESSAGE = 208,\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ResponseException.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>响应异常</summary>\npublic class ResponseException : Exception\n{\n    /// <summary>响应代码</summary>\n    public ResponseCode Code { get; set; }\n\n    /// <summary>实例化响应异常</summary>\n    /// <param name=\"code\"></param>\n    /// <param name=\"message\"></param>\n    public ResponseException(ResponseCode code, String message) : base(code + \": \" + message) => Code = code;\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/SendMessageRequestHeader.cs",
    "content": "﻿using System.Reflection;\nusing System.Xml.Serialization;\nusing NewLife.Reflection;\n\nnamespace NewLife.RocketMQ.Protocol;\n\n/// <summary>发送消息请求头</summary>\npublic class SendMessageRequestHeader\n{\n    #region 属性\n    /// <summary>生产组</summary>\n    [XmlElement(\"a\")]\n    public String ProducerGroup { get; set; }\n\n    /// <summary>主题</summary>\n    [XmlElement(\"b\")]\n    public String Topic { get; set; }\n\n    /// <summary>默认主题</summary>\n    [XmlElement(\"c\")]\n    public String DefaultTopic { get; set; }\n\n    /// <summary>默认主题队列数</summary>\n    [XmlElement(\"d\")]\n    public Int32 DefaultTopicQueueNums { get; set; }\n\n    /// <summary>队列编号</summary>\n    [XmlElement(\"e\")]\n    public Int32 QueueId { get; set; }\n\n    /// <summary>系统标记</summary>\n    [XmlElement(\"f\")]\n    public Int32 SysFlag { get; set; }\n\n    /// <summary>生产时间。毫秒</summary>\n    [XmlElement(\"g\")]\n    public Int64 BornTimestamp { get; set; }\n\n    /// <summary>标记</summary>\n    [XmlElement(\"h\")]\n    public Int32 Flag { get; set; }\n\n    /// <summary>属性。Tags/Keys等</summary>\n    [XmlElement(\"i\")]\n    public String Properties { get; set; }\n\n    /// <summary>重新消费次数</summary>\n    [XmlElement(\"j\")]\n    public Int32 ReconsumeTimes { get; set; }\n\n    /// <summary>单元模式</summary>\n    [XmlElement(\"k\")]\n    public Boolean UnitMode { get; set; }\n\n    /// <summary>消费重试次数</summary>\n    [XmlElement(\"l\")]\n    public Int32 ConsumeRetryTimes { get; set; }\n\n    /// <summary>批操作</summary>\n    [XmlElement(\"m\")]\n    public Boolean Batch { get; set; }\n\n    /// <summary>Broker名称</summary>\n    [XmlElement(\"n\")]\n    public String BrokerName { get; set; }\n    #endregion\n\n    #region 方法\n    /// <summary>获取属性字典</summary>\n    /// <returns></returns>\n    public IDictionary<String, Object> GetProperties()\n    {\n        var dic = new Dictionary<String, Object>();\n\n        foreach (var pi in GetType().GetProperties())\n        {\n            if (pi.GetIndexParameters().Length > 0) continue;\n            if (pi.GetCustomAttribute<XmlIgnoreAttribute>() != null) continue;\n\n            var name = pi.Name;\n            var att = pi.GetCustomAttribute<XmlElementAttribute>();\n            if (att != null && !att.ElementName.IsNullOrEmpty()) name = att.ElementName;\n\n            dic[name] = this.GetValue(pi);\n        }\n\n        return dic;\n    }\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/SendResult.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>发送状态</summary>\npublic enum SendStatus\n{\n    /// <summary>成功</summary>\n    SendOK = 0,\n\n    /// <summary>刷盘超时</summary>\n    FlushDiskTimeout = 1,\n\n    /// <summary>刷从机超时</summary>\n    FlushSlaveTimeout = 2,\n\n    /// <summary>从机不可用</summary>\n    SlaveNotAvailable = 3,\n\n    /// <summary>发送失败</summary>\n    SendError = 4,\n}\n\n/// <summary>发送结果</summary>\npublic class SendResult\n{\n    #region 属性\n    /// <summary>头部</summary>\n    public Header Header { get; set; }\n\n    /// <summary>状态</summary>\n    public SendStatus Status { get; set; }\n\n    /// <summary>消息编号</summary>\n    public String MsgId { get; set; }\n\n    /// <summary>队列</summary>\n    public MessageQueue Queue { get; set; }\n\n    /// <summary>队列偏移</summary>\n    public Int64 QueueOffset { get; set; }\n\n    /// <summary>事务编号</summary>\n    public String TransactionId { get; set; }\n\n    /// <summary>偏移消息编号</summary>\n    public String OffsetMsgId { get; set; }\n\n    /// <summary>区域</summary>\n    public String RegionId { get; set; }\n    #endregion\n\n    #region 方法\n    /// <summary>读取结果</summary>\n    /// <param name=\"dic\"></param>\n    public void Read(IDictionary<String, String> dic)\n    {\n        if (dic == null) return;\n\n        var dic2 = dic.ToNullable(StringComparer.OrdinalIgnoreCase);\n\n        if (dic2.TryGetValue(nameof(MsgId), out var str)) MsgId = str;\n        if (dic2.TryGetValue(nameof(OffsetMsgId), out str)) OffsetMsgId = str;\n        if (dic2.TryGetValue(nameof(QueueOffset), out str)) QueueOffset = str.ToLong();\n        if (dic2.TryGetValue(nameof(TransactionId), out str)) TransactionId = str;\n        if (dic2.TryGetValue(nameof(RegionId), out str)) RegionId = str;\n        if (dic2.TryGetValue(\"MSG_REGION\", out str)) RegionId = str;\n    }\n\n    /// <summary>\n    /// 已重载。友好显示\n    /// </summary>\n    /// <returns></returns>\n    public override String ToString() => $\"SendStatus={Status} MsgId={MsgId} OffsetMsgId={OffsetMsgId} QueueOffset={QueueOffset} Queue={Queue}\";\n    #endregion\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/SendStatus.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>发送状态</summary>\n    public enum SendStatus\n    {\n        SendOK = 0,\n        FlushDiskTimeout = 1,\n        FlushSlaveTimeout = 2,\n        SlaveNotAvailable = 3,\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/SerializeType.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>序列化类型</summary>\npublic enum SerializeType : Byte\n{\n    /// <summary>Json序列化</summary>\n    JSON = 0,\n\n    /// <summary>二进制序列化</summary>\n    ROCKETMQ = 1,\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/ServiceState.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>服务状态</summary>\n    public enum ServiceState\n    {\n        /// <summary>刚刚建立</summary>\n        CreateJust = 0,\n\n        /// <summary>运行中</summary>\n        Running = 1,\n\n        /// <summary>已经关闭</summary>\n        ShutdownAlready = 2,\n\n        /// <summary>启动失败</summary>\n        StartFailed = 3,\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/SubscriptionData.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol\n{\n    /// <summary>订阅者数据</summary>\n    public class SubscriptionData\n    {\n        #region 属性\n        /// <summary>主题</summary>\n        public String Topic { get; set; }\n\n        /// <summary>表达式类型</summary>\n        public String ExpressionType { get; set; } = \"TAG\";\n\n        /// <summary>子字符串</summary>\n        public String SubString { get; set; } = \"*\";\n\n        /// <summary>标签集合</summary>\n        public String[] TagsSet { get; set; }\n\n        /// <summary>代码集合</summary>\n        public String[] CodeSet { get; set; }\n\n        /// <summary>过滤模式</summary>\n        public Boolean ClassFilterMode { get; set; }\n\n        /// <summary>过滤源</summary>\n        public String FilterClassSource { get; set; }\n\n        /// <summary>子版本</summary>\n        public Int64 SubVersion { get; set; } = DateTime.Now.Ticks;\n        #endregion\n    }\n}"
  },
  {
    "path": "NewLife.RocketMQ/Protocol/TransactionState.cs",
    "content": "﻿namespace NewLife.RocketMQ.Protocol;\n\n/// <summary>事务状态</summary>\npublic enum TransactionState\n{\n    /// <summary>预备事务（半消息）</summary>\n    Prepared = 4,\n\n    /// <summary>提交事务</summary>\n    Commit = 8,\n\n    /// <summary>回滚事务</summary>\n    Rollback = 12,\n}\n"
  },
  {
    "path": "NewLife.RocketMQ/TencentProvider.cs",
    "content": "﻿namespace NewLife.RocketMQ;\n\n/// <summary>腾讯云 TDMQ RocketMQ 适配器</summary>\n/// <remarks>\n/// 腾讯云 TDMQ 提供 RocketMQ 4.x 兼容实例，支持 Remoting 协议。\n/// 签名方式与 Apache ACL 类似，使用 HMAC-SHA1。\n/// VPC 内网访问直接配置 NameServer 地址即可。\n/// </remarks>\npublic class TencentProvider : ICloudProvider\n{\n    /// <summary>提供者名称</summary>\n    public String Name => \"Tencent\";\n\n    /// <summary>访问令牌。腾讯云 SecretId</summary>\n    public String AccessKey { get; set; }\n\n    /// <summary>访问密钥。腾讯云 SecretKey</summary>\n    public String SecretKey { get; set; }\n\n    /// <summary>通道标识。默认TENCENT</summary>\n    public String OnsChannel { get; set; } = \"TENCENT\";\n\n    /// <summary>命名空间。腾讯云 TDMQ 中的命名空间，用于资源隔离</summary>\n    public String Namespace { get; set; }\n\n    /// <summary>转换主题名。腾讯云有命名空间时拼接前缀</summary>\n    /// <param name=\"topic\">原始主题名</param>\n    /// <returns></returns>\n    public String TransformTopic(String topic)\n    {\n        var ns = Namespace;\n        if (!String.IsNullOrEmpty(ns) && !topic.StartsWith(ns))\n            return $\"{ns}%{topic}\";\n\n        return topic;\n    }\n\n    /// <summary>转换消费组名。腾讯云有命名空间时拼接前缀</summary>\n    /// <param name=\"group\">原始消费组名</param>\n    /// <returns></returns>\n    public String TransformGroup(String group)\n    {\n        var ns = Namespace;\n        if (!String.IsNullOrEmpty(ns) && !group.StartsWith(ns))\n            return $\"{ns}%{group}\";\n\n        return group;\n    }\n\n    /// <summary>获取 NameServer 地址。腾讯云不从HTTP获取，使用 VPC 内网直连</summary>\n    public String GetNameServerAddress() => null;\n}\n"
  },
  {
    "path": "NewLife.RocketMQ.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 18\nVisualStudioVersion = 18.2.11430.68\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NewLife.RocketMQ\", \"NewLife.RocketMQ\\NewLife.RocketMQ.csproj\", \"{146B7192-0194-4924-8371-75EC83B05185}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"Test\", \"Test\\Test.csproj\", \"{8D0806BE-ED0A-4D11-847C-38A366A33474}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"XUnitTestRocketMQ\", \"XUnitTestRocketMQ\\XUnitTestRocketMQ.csproj\", \"{670E6004-4A15-4C91-AC70-FED96843B793}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"Solution Items\", \"Solution Items\", \"{AF932CB1-8F4D-4317-8028-62F2CACEA9BD}\"\n\tProjectSection(SolutionItems) = preProject\n\t\t.editorconfig = .editorconfig\n\t\t.github\\workflows\\publish-beta.yml = .github\\workflows\\publish-beta.yml\n\t\t.github\\workflows\\publish.yml = .github\\workflows\\publish.yml\n\t\tReadme.MD = Readme.MD\n\t\t.github\\workflows\\test.yml = .github\\workflows\\test.yml\n\t\tDoc\\架构设计.md = Doc\\架构设计.md\n\t\tDoc\\需求文档.md = Doc\\需求文档.md\n\tEndProjectSection\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{146B7192-0194-4924-8371-75EC83B05185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{146B7192-0194-4924-8371-75EC83B05185}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{146B7192-0194-4924-8371-75EC83B05185}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{146B7192-0194-4924-8371-75EC83B05185}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8D0806BE-ED0A-4D11-847C-38A366A33474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8D0806BE-ED0A-4D11-847C-38A366A33474}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8D0806BE-ED0A-4D11-847C-38A366A33474}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8D0806BE-ED0A-4D11-847C-38A366A33474}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{670E6004-4A15-4C91-AC70-FED96843B793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{670E6004-4A15-4C91-AC70-FED96843B793}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{670E6004-4A15-4C91-AC70-FED96843B793}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{670E6004-4A15-4C91-AC70-FED96843B793}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {9B014964-38CE-4F1B-9D25-0500977CC49B}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "Readme.MD",
    "content": "# NewLife.RocketMQ - 企业级纯托管 RocketMQ 客户端\n\n![GitHub top language](https://img.shields.io/github/languages/top/newlifex/newlife.rocketmq?logo=github)\n![GitHub License](https://img.shields.io/github/license/newlifex/newlife.rocketmq?logo=github)\n![Nuget Downloads](https://img.shields.io/nuget/dt/newlife.rocketmq?logo=nuget)\n![Nuget](https://img.shields.io/nuget/v/newlife.rocketmq?logo=nuget)\n![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/newlife.rocketmq?label=dev%20nuget&logo=nuget)\n\n**纯托管企业级 RocketMQ 客户端**，支持 `.NET Framework 4.5+` / `.NET Standard 2.0+` / `.NET Core` / `.NET 5+`。  \n**完全使用 C# 实现，零外部依赖（无需 Java、gRPC、Protobuf 第三方库）。**\n\n---\n\n## 产品简介\n\nNewLife.RocketMQ 是新生命团队开发的**企业级纯托管 RocketMQ 客户端**，专为 .NET 生态设计。它同时支持 RocketMQ **Remoting 协议（4.x/5.x Broker）** 和 **gRPC Proxy 协议（5.x Proxy）**，覆盖生产者、消费者全部核心功能及企业级特性，统一适配阿里云、华为云、腾讯云及 Apache ACL 认证体系。\n\n**核心优势**：\n\n| 特性 | 说明 |\n|------|------|\n| **双协议支持** | Remoting（4.x 成熟稳定）+ gRPC（5.x 面向未来），自动路由 |\n| **零外部依赖** | 内置 Protobuf 编解码器（ProtoWriter/ProtoReader），无需 Java 或 gRPC 运行时 |\n| **多云适配** | 统一 `ICloudProvider` 接口，已内置阿里云/华为云/腾讯云/Apache ACL 四家适配器 |\n| **生产就绪** | 消费重试、死信队列、事务回查、顺序消费、Pop 消费等企业级特性完整支持 |\n| **最广框架覆盖** | .NET Framework 4.5+ 到 .NET 10，gRPC 功能在 .NET Standard 2.1+ 可用 |\n| **高性能** | 基于 NewLife.Net 高性能网络层，连接复用、VIP 通道、消息压缩、并发控制 |\n\n---\n\n## 安装\n\n```powershell\n# NuGet 包管理器\nInstall-Package NewLife.RocketMQ\n\n# .NET CLI\ndotnet add package NewLife.RocketMQ\n```\n\n```xml\n<!-- PackageReference -->\n<PackageReference Include=\"NewLife.RocketMQ\" Version=\"3.0.*\" />\n```\n\n---\n\n## 快速入门\n\n### 发送消息\n\n```csharp\nusing NewLife.RocketMQ;\n\nvar producer = new Producer\n{\n    Topic = \"test_topic\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    Group = \"producer_group\"\n};\nproducer.Start();\n\n// 同步发送\nvar result = producer.Publish(\"Hello RocketMQ!\");\nConsole.WriteLine($\"消息ID: {result.MsgId}\");\n\n// 异步发送\nawait producer.PublishAsync(\"异步消息\");\n\n// 批量发送\nawait producer.PublishBatch(new[] { \"消息1\", \"消息2\", \"消息3\" });\n```\n\n### 消费消息\n\n```csharp\nvar consumer = new Consumer\n{\n    Topic = \"test_topic\",\n    Group = \"consumer_group\",\n    NameServerAddress = \"127.0.0.1:9876\"\n};\n\nconsumer.OnConsume = (q, messages) =>\n{\n    foreach (var msg in messages)\n    {\n        Console.WriteLine($\"收到消息: {msg.BodyString}\");\n    }\n    return true; // 返回 true 表示消费成功\n};\n\nconsumer.Start();\n```\n\n### 延迟消息\n\n```csharp\n// 18 级预设延迟\nproducer.PublishDelay(\"延迟消息\", DelayTimeLevels.s30);\n\n// gRPC 模式支持任意时间延迟（需 netstandard2.1+）\nproducer.GrpcProxyAddress = \"http://127.0.0.1:8081\";\nawait producer.PublishDelayViaGrpcAsync(\"任意延迟\", DateTime.Now.AddMinutes(30));\n```\n\n### 事务消息\n\n```csharp\nvar producer = new Producer\n{\n    Topic = \"tx_topic\",\n    Group = \"tx_group\",\n    NameServerAddress = \"127.0.0.1:9876\"\n};\n\n// 事务回查回调\nproducer.OnCheckTransaction = (msg, transactionId) =>\n{\n    var success = CheckLocalTransaction(transactionId);\n    return success ? TransactionState.Commit : TransactionState.Rollback;\n};\nproducer.Start();\n\n// 发送半消息 → 执行本地事务 → 提交/回滚\nvar sendResult = producer.PublishTransaction(\"订单创建\");\ntry\n{\n    ExecuteLocalTransaction(sendResult.TransactionId);\n    producer.EndTransaction(sendResult, TransactionState.Commit);\n}\ncatch\n{\n    producer.EndTransaction(sendResult, TransactionState.Rollback);\n}\n```\n\n### 顺序消息\n\n```csharp\n// 相同 key 的消息进入同一队列\nvar queue = producer.SelectQueue(\"order_123\");\nproducer.Publish(\"顺序消息1\", queue);\nproducer.Publish(\"顺序消息2\", queue);\n\n// 消费端启用顺序消费\nconsumer.OrderConsume = true;\n```\n\n### Request-Reply 模式\n\n```csharp\n// 生产者发送请求（同步/异步）\nvar response = producer.Request(\"请求消息\", timeout: 5000);\nvar reply = await producer.RequestAsync(\"异步请求\", timeout: 5000);\n\n// 消费者回复\nconsumer.OnConsume = (q, messages) =>\n{\n    foreach (var msg in messages)\n    {\n        if (!String.IsNullOrEmpty(msg.CorrelationId))\n            consumer.SendReply(msg, \"处理结果\");\n    }\n    return true;\n};\n```\n\n---\n\n## 消费者高级特性\n\n### 消费重试与死信队列\n\n```csharp\nvar consumer = new Consumer\n{\n    Topic = \"test_topic\",\n    Group = \"consumer_group\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    EnableRetry = true,           // 启用消费重试\n    MaxReconsumeTimes = 3         // 最大重试次数，超过进入 %DLQ% 死信队列\n};\n\nconsumer.OnConsume = (q, messages) =>\n{\n    foreach (var msg in messages)\n    {\n        try { ProcessMessage(msg); }\n        catch { return false; } // 返回 false 触发重试\n    }\n    return true;\n};\n```\n\n### Tag / SQL92 过滤\n\n```csharp\n// Tag 过滤\nconsumer.Tags = \"TagA || TagB\";\n\n// SQL92 表达式过滤\nconsumer.ExpressionType = \"SQL92\";\nconsumer.Subscription = \"age > 18 AND city = 'Shanghai'\";\n```\n\n### 多 Topic 订阅\n\n```csharp\nconsumer.Topics = \"topic1;topic2;topic3\";\n```\n\n### Pop 消费模式\n\n```csharp\n// Pop 消费（手动确认）\nvar messages = await consumer.PopMessageAsync(timeout: 10000);\nforeach (var msg in messages)\n{\n    try\n    {\n        ProcessMessage(msg);\n        await consumer.AckMessageAsync(msg);\n    }\n    catch\n    {\n        await consumer.ChangeInvisibleTimeAsync(msg, 30000); // 延长处理时间\n    }\n}\n```\n\n### 消费限流 / VIP 通道 / 消息压缩\n\n```csharp\nconsumer.MaxConcurrentConsume = 10; // 最多同时处理 10 条消息\n\nproducer.VipChannelEnabled = true;  // 启用 VIP 通道（BrokerPort - 2）\nproducer.CompressOverBytes = 4096;  // 消息体超过 4KB 自动 ZLIB 压缩\n```\n\n---\n\n## 云厂商接入\n\n### 阿里云消息队列 RocketMQ\n\n```csharp\nvar producer = new Producer\n{\n    Topic = \"test_topic\",\n    NameServerAddress = \"http://MQ_INST_xxx.aliyuncs.com:80\",\n    CloudProvider = new AliyunProvider\n    {\n        AccessKey = \"你的AccessKey\",\n        SecretKey = \"你的SecretKey\",\n        InstanceId = \"MQ_INST_xxx\"  // 可选，自动从地址解析\n    }\n};\n```\n\n### 华为云 DMS for RocketMQ\n\n```csharp\nvar producer = new Producer\n{\n    Topic = \"test_topic\",\n    NameServerAddress = \"华为云实例地址:9876\",\n    CloudProvider = new HuaweiProvider\n    {\n        AccessKey = \"你的AK\",\n        SecretKey = \"你的SK\",\n        InstanceId = \"实例ID\",\n        EnableSsl = true\n    }\n};\n```\n\n### 腾讯云 TDMQ RocketMQ\n\n```csharp\nvar producer = new Producer\n{\n    Topic = \"test_topic\",\n    NameServerAddress = \"腾讯云实例地址:9876\",\n    CloudProvider = new TencentProvider\n    {\n        AccessKey = \"腾讯云SecretId\",\n        SecretKey = \"腾讯云SecretKey\",\n        Namespace = \"命名空间\"\n    }\n};\n```\n\n### Apache RocketMQ ACL 认证\n\n```csharp\nvar producer = new Producer\n{\n    Topic = \"test_topic\",\n    NameServerAddress = \"127.0.0.1:9876\",\n    CloudProvider = new AclProvider\n    {\n        AccessKey = \"RocketMQ AccessKey\",\n        SecretKey = \"RocketMQ SecretKey\"\n    }\n};\n```\n\n---\n\n## 架构总览\n\n```\nMqBase (业务基类)\n├── Producer (生产者)\n└── Consumer (消费者)\n\n通信层\n├── Remoting 协议（4.x/5.x Broker）\n│   ├── ClusterClient (TCP 长连接，Opaque 复用)\n│   ├── NameClient (路由发现，30s 轮询)\n│   └── BrokerClient (心跳/注销)\n│\n└── gRPC 协议（5.x Proxy，netstandard2.1+）\n    ├── GrpcClient (HTTP/2，Unary + Server Streaming)\n    ├── GrpcMessagingService (11 个 RPC 方法)\n    └── ProtoWriter/ProtoReader (自研 Protobuf 编解码)\n\n云厂商适配层\n├── AliyunProvider  (阿里云：实例ID路由 + HTTP NameServer)\n├── HuaweiProvider  (华为云：SSL/TLS + 实例ID路由)\n├── TencentProvider (腾讯云：Namespace 前缀路由)\n└── AclProvider     (Apache ACL：HMAC-SHA1 签名)\n```\n\n详见 [架构设计文档](Doc/NewLife.RocketMQ架构.md)、[需求文档](Doc/NewLife.RocketMQ需求.md)。\n\n---\n\n## 功能特性一览\n\n### 生产者\n\n| 功能 | 状态 | 说明 |\n|------|:----:|------|\n| 同步/异步/单向发送 | ✅ | Publish / PublishAsync / PublishOneway |\n| 批量消息发送 | ✅ | PublishBatch，合并多条消息为一个请求 |\n| 延迟消息 | ✅ | 18 级预设 + gRPC 任意时间延迟 |\n| 事务消息 | ✅ | 半消息 + 提交/回滚 + 回查回调 |\n| 顺序消息 | ✅ | 指定 MessageQueue 发送 |\n| Request-Reply | ✅ | 同步/异步请求回复 |\n| 消息压缩 | ✅ | CompressOverBytes 阈值自动 ZLIB |\n| 消息轨迹 | ✅ | AsyncTraceDispatcher + MessageTraceHook |\n\n### 消费者\n\n| 功能 | 状态 | 说明 |\n|------|:----:|------|\n| Pull 消费 / 消费调度 | ✅ | 长轮询拉取，自动分配队列 |\n| 集群消费 / 广播消费 | ✅ | Rebalance 平均分配 / 本地偏移持久化 |\n| Tag / SQL92 过滤 | ✅ | 表达式过滤 |\n| 多 Topic 订阅 | ✅ | Topics 属性，按 Topic 分别 Rebalance |\n| 消费重试 + 死信队列 | ✅ | EnableRetry + MaxReconsumeTimes |\n| 顺序消费 | ✅ | 队列锁定（OrderConsume） |\n| Pop 消费 | ✅ | Pop/Ack/BatchAck/ChangeInvisibleTime |\n| 消费限流 | ✅ | MaxConcurrentConsume 信号量控制 |\n\n### 管理与运维\n\n| 功能 | 状态 | 说明 |\n|------|:----:|------|\n| Topic/消费组 CRUD | ✅ | 创建/更新/删除 |\n| 消息查询 | ✅ | 按 ID / 按 Key |\n| 消费统计 / 集群信息 | ✅ | GetConsumeStats / GetClusterInfo |\n| 偏移量管理与重置 | ✅ | 查询/更新/重置 |\n\n### 协议与兼容性\n\n| 服务端版本 | Remoting | gRPC | 说明 |\n|-----------|:--------:|:----:|------|\n| RocketMQ 4.0 ~ 4.9 | ✅ | — | 完全兼容 |\n| RocketMQ 5.x（Broker） | ✅ | — | Remoting 向后兼容 |\n| RocketMQ 5.x（Proxy） | — | ✅ | 通过 GrpcProxyAddress 启用 |\n| 阿里云 4.x | ✅ | — | AliyunProvider 适配 |\n| 华为云 DMS | ✅ | — | HuaweiProvider 适配 |\n| 腾讯云 TDMQ | ✅ | — | TencentProvider 适配 |\n\n---\n\n## 与竞品对比\n\n| 维度 | NewLife.RocketMQ | Apache rocketmq-client-csharp | 官方 Java 客户端 |\n|------|:----------------:|:---------------------------:|:----------------:|\n| 协议支持 | Remoting + gRPC | 仅 gRPC | Remoting + gRPC |\n| 4.x 兼容 | ✅ | ❌ | ✅ |\n| 外部依赖 | **零依赖** | Google.Protobuf / Grpc.Net 等 | 多个依赖 |\n| .NET Framework | ✅ 4.5+ | ❌ | N/A（Java） |\n| 多云适配 | ✅ 内置四家 | ❌ | ❌ |\n| 事务/重试/死信 | ✅ 完整 | ✅ | ✅ |\n| 管理 API | ✅ 完整 | ❌ | ✅ |\n| 维护活跃度 | ✅ 持续维护 | ⚠️ 更新较慢 | ✅ 官方维护 |\n\n---\n\n## 测试覆盖\n\n30+ 测试类（xUnit），覆盖核心功能、高级特性、协议兼容、云厂商适配、性能优化等场景。\n\n---\n\n## 参与贡献\n\n欢迎提交 Issue 和 Pull Request！\n\n1. Fork 本仓库\n2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)\n3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)\n4. 推送到分支 (`git push origin feature/AmazingFeature`)\n5. 提交 Pull Request\n\n---\n\n## 许可协议\n\n本项目采用 [MIT License](LICENSE) 开源协议。\n\n---\n\n## 新生命项目矩阵\n\n各项目默认支持 net10.0/net9.0/netstandard2.1/netstandard2.0/net4.62/net4.5\n\n|                               项目                               | 年份  | 说明                                                                                        |\n| :--------------------------------------------------------------: | :---: | ------------------------------------------------------------------------------------------- |\n|                             基础组件                             |       | 支撑其它中间件以及产品项目                                                                  |\n|          [NewLife.Core](https://github.com/NewLifeX/X)           | 2002  | 核心库，日志、配置、缓存、网络、序列化、APM性能追踪                                         |\n|    [NewLife.XCode](https://github.com/NewLifeX/NewLife.XCode)    | 2005  | 大数据中间件，单表百亿级，MySql/SQLite/SqlServer/Oracle/PostgreSql/达梦，自动分表，读写分离 |\n|      [NewLife.Net](https://github.com/NewLifeX/NewLife.Net)      | 2005  | 网络库，单机千万级吞吐率（2266万tps），单机百万级连接（400万Tcp长连接）                     |\n| [NewLife.Remoting](https://github.com/NewLifeX/NewLife.Remoting) | 2011  | 协议通信库，提供CS应用通信框架，支持Http/RPC通信框架，高吞吐，物联网设备低开销易接入        |\n|     [NewLife.Cube](https://github.com/NewLifeX/NewLife.Cube)     | 2010  | 魔方快速开发平台，集成了用户权限、SSO登录、OAuth服务端等，单表100亿级项目验证               |\n|    [NewLife.Agent](https://github.com/NewLifeX/NewLife.Agent)    | 2008  | 服务管理组件，把应用安装成为操作系统守护进程，Windows服务、Linux的Systemd                   |\n|     [NewLife.Zero](https://github.com/NewLifeX/NewLife.Zero)     | 2020  | Zero零代脚手架，基于NewLife组件生态的项目模板NewLife.Templates，Web、WebApi、Service        |\n|                              中间件                              |       | 对接知名中间件平台                                                                          |\n|    [NewLife.Redis](https://github.com/NewLifeX/NewLife.Redis)    | 2017  | Redis客户端，微秒级延迟，百万级吞吐，丰富的消息队列，百亿级数据量项目验证                   |\n| [NewLife.RocketMQ](https://github.com/NewLifeX/NewLife.RocketMQ) | 2018  | RocketMQ纯托管客户端，支持Apache RocketMQ和阿里云消息队列，十亿级项目验证                   |\n|     [NewLife.MQTT](https://github.com/NewLifeX/NewLife.MQTT)     | 2019  | 物联网消息协议，MqttClient/MqttServer，客户端支持阿里云物联网                               |\n|      [NewLife.IoT](https://github.com/NewLifeX/NewLife.IoT)      | 2022  | IoT标准库，定义物联网领域的各种通信协议标准规范                                             |\n|   [NewLife.Modbus](https://github.com/NewLifeX/NewLife.Modbus)   | 2022  | ModbusTcp/ModbusRTU/ModbusASCII，基于IoT标准库实现，支持ZeroIoT平台和IoTEdge网关            |\n|  [NewLife.Siemens](https://github.com/NewLifeX/NewLife.Siemens)  | 2022  | 西门子PLC协议，基于IoT标准库实现，支持IoT平台和IoTEdge                                      |\n|      [NewLife.Map](https://github.com/NewLifeX/NewLife.Map)      | 2022  | 地图组件库，封装百度地图、高德地图、腾讯地图、天地图                                        |\n|    [NewLife.Audio](https://github.com/NewLifeX/NewLife.Audio)    | 2023  | 音频编解码库，PCM/ADPCMA/G711A/G722U/WAV/AAC                                                |\n|                             产品平台                             |       | 产品平台级，编译部署即用，个性化自定义                                                      |\n|         [Stardust](https://github.com/NewLifeX/Stardust)         | 2018  | 星尘，分布式服务平台，节点管理、APM监控中心、配置中心、注册中心、发布中心                   |\n|           [AntJob](https://github.com/NewLifeX/AntJob)           | 2019  | 蚂蚁调度，分布式大数据计算平台（实时/离线），蚂蚁搬家分片思想，万亿级数据量项目验证         |\n|      [NewLife.ERP](https://github.com/NewLifeX/NewLife.ERP)      | 2021  | 企业ERP，产品管理、客户管理、销售管理、供应商管理                                           |\n|         [CrazyCoder](https://github.com/NewLifeX/XCoder)         | 2006  | 码神工具，众多开发者工具，网络、串口、加解密、正则表达式、Modbus、MQTT                      |\n|           [EasyIO](https://github.com/NewLifeX/EasyIO)           | 2023  | 简易文件存储，支持分布式系统中文件集中存储                                                  |\n|           [XProxy](https://github.com/NewLifeX/XProxy)           | 2005  | 产品级反向代理，NAT代理、Http代理                                                           |\n|        [HttpMeter](https://github.com/NewLifeX/HttpMeter)        | 2022  | Http压力测试工具                                                                            |\n|         [GitCandy](https://github.com/NewLifeX/GitCandy)         | 2015  | Git源代码管理系统                                                                           |\n|          [SmartOS](https://github.com/NewLifeX/SmartOS)          | 2014  | 嵌入式操作系统，完全独立自主，支持ARM Cortex-M芯片架构                                      |\n|          [SmartA2](https://github.com/NewLifeX/SmartA2)          | 2019  | 嵌入式工业计算机，物联网边缘网关，高性能.NET8主机，应用于工业、农业、交通、医疗             |\n|                          FIoT物联网平台                          | 2020  | 物联网整体解决方案，建筑、环保、农业，软硬件及大数据分析一体化，单机十万级点位项目验证      |\n|                        UWB高精度室内定位                         | 2020  | 厘米级（10~20cm）高精度室内定位，软硬件一体化，与其它系统联动，大型展厅项目验证             |\n\n---\n\n## 新生命开发团队\n\n![XCode](https://newlifex.com/logo.png)  \n\n新生命团队（NewLife）成立于2002年，是新时代物联网行业解决方案提供者，致力于提供软硬件应用方案咨询、系统架构规划与开发服务。  \n\n团队主导的80多个开源项目已被广泛应用于各行业，Nuget累计下载量高达**400余万次**。  \n\n团队开发的大数据中间件 **NewLife.XCode**、蚂蚁调度计算平台 **AntJob**、星尘分布式平台 **Stardust**、缓存队列组件 **NewLife.Redis** 以及物联网平台 **FIoT**，均成功应用于电力、高校、互联网、电信、交通、物流、工控、医疗、文博等行业，为客户提供了大量先进、可靠、安全、高质量、易扩展的产品和系统集成服务。  \n\n我们将不断通过服务的持续改进，成为客户长期信赖的合作伙伴，通过不断的创新和发展，成为国内优秀的 IoT 服务供应商。  \n\n**新生命团队始于2002年，部分开源项目具有20年以上漫长历史，源码库保留有2010年以来所有修改记录**  \n\n- 网站：<https://newlifex.com>  \n- 开源：<https://github.com/newlifex>  \n- QQ群：1600800 / 1600838  \n- 微信公众号：\n\n![智能大石头](https://newlifex.com/stone.jpg)"
  },
  {
    "path": "Test/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading;\nusing NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Common;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Serialization;\n\nnamespace Test;\n\nclass Program\n{\n    static void Main(String[] args)\n    {\n        XTrace.UseConsole();\n\n        Test1();\n        //TestAliyun();\n\n        Console.WriteLine(\"OK!\");\n        Console.ReadKey();\n    }\n\n    static void Test1()\n    {\n        //JsonHelper.Default = new SystemJson();\n\n        XTrace.WriteLine(\"\");\n        XTrace.WriteLine(\"创建生产者……\");\n        var producer = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = \"rocketmq.newlifex.com:9876\",\n            Version = MQVersion.V5_2_0,\n            ExternalBroker = true,\n\n            Log = XTrace.Log,\n        };\n\n        //producer.Configure(MqSetting.Current);\n        producer.Start();\n\n        //mq.CreateTopic(\"nx_test\", 2);\n\n        XTrace.WriteLine(\"\");\n        XTrace.WriteLine(\"创建消费者……\");\n        var consumer = new Consumer\n        {\n            Topic = producer.Topic,\n            Group = \"test\",\n            NameServerAddress = producer.NameServerAddress,\n            ExternalBroker = true,\n\n            FromLastOffset = false,\n            //SkipOverStoredMsgCount = 0,\n            //BatchSize = 20,\n\n            Log = XTrace.Log,\n            ClientLog = XTrace.Log,\n        };\n\n        consumer.OnConsume = OnConsume;\n\n        //consumer.Configure(MqSetting.Current);\n        consumer.Start();\n        Thread.Sleep(1000);\n\n        XTrace.WriteLine(\"\");\n        XTrace.WriteLine(\"发布测试消息……\");\n        for (var i = 0; i < 10; i++)\n        {\n            var str = \"学无先后达者为师\" + i;\n            //var str = Rand.NextString(1337);\n\n            var sr = producer.Publish(str, \"TagA\", null);\n\n            //Console.WriteLine(\"[{0}] {1} {2} {3}\", sr.Queue.BrokerName, sr.Queue.QueueId, sr.MsgId, sr.QueueOffset);\n\n            // 阿里云发送消息不能过快，否则报错“服务不可用”\n            Thread.Sleep(500);\n        }\n\n        Console.WriteLine(\"完成\");\n\n        producer.Dispose();\n    }\n\n    private static Consumer _consumer;\n    static void Test2()\n    {\n        var consumer = new Consumer\n        {\n            Topic = \"nx_test\",\n            Group = \"test\",\n            NameServerAddress = \"127.0.0.1:9876\",\n\n            FromLastOffset = false,\n            //SkipOverStoredMsgCount = 0,\n            //BatchSize = 20,\n\n            Log = XTrace.Log,\n            ClientLog = XTrace.Log,\n        };\n\n        consumer.OnConsume = OnConsume;\n\n        consumer.Configure(MqSetting.Current);\n        consumer.Start();\n\n        _consumer = consumer;\n    }\n\n    static void TestAliyun()\n    {\n        // 2024-04-10 对接阿里云RocketMQ v4测试通过。\n        // 创建RocketMQ实例后，需要手工创建Topic和Group，并创建正确的AccessKey\n        var consumer = new Consumer\n        {\n            Topic = \"newlife_test_02\",\n            Group = \"GID_newlife_Group02\",\n            NameServerAddress = \"http://MQ_INST_1827694722767531_BXxCwUhm.mq-internet-access.mq-internet.aliyuncs.com:80\",\n\n            Aliyun = new AliyunOptions\n            {\n                AccessKey = \"LTAI5tKTGShu31C61xRARVC4\",\n                SecretKey = \"a9oPwph1IcMGanWckzUOwOf3Ork8LO\",\n                //InstanceId = \"MQ_INST_1827694722767531_BXxCwUhm\",\n            },\n\n            FromLastOffset = true,\n            //SkipOverStoredMsgCount = 0,\n            BatchSize = 1,\n\n            Log = XTrace.Log,\n            ClientLog = XTrace.Log,\n        };\n\n        consumer.OnConsume = OnConsume;\n\n        consumer.Configure(MqSetting.Current);\n        consumer.Start();\n\n        _consumer = consumer;\n    }\n\n    private static Boolean OnConsume(MessageQueue q, MessageExt[] ms)\n    {\n        Console.WriteLine(\"[{0}@{1}]收到消息[{2}]\", q.BrokerName, q.QueueId, ms.Length);\n\n        foreach (var item in ms.ToList())\n        {\n            Console.WriteLine($\"消息：主键【{item.Keys}】 Topic 【{item.Topic}】，产生时间【{item.BornTimestamp.ToDateTime().ToFullString()}】，内容【{item.Body.ToStr(null, 0, 64)}】\");\n        }\n\n        return true;\n    }\n\n    static void Test3()\n    {\n        var dic = new SortedList<String, String>(StringComparer.Ordinal)\n        {\n            [\"subscription\"] = \"aaa\",\n            [\"subVersion\"] = \"ccc\",\n        };\n        Console.WriteLine(dic.Join(\",\", e => $\"{e.Key}={e.Value}\"));\n\n        Console.WriteLine('s' > 'V');\n\n        Console.WriteLine();\n        var cmp = Comparer<String>.Default;\n        Console.WriteLine(cmp.Compare(\"s\", \"S\"));\n        Console.WriteLine(cmp.Compare(\"s\", \"v\"));\n        Console.WriteLine(cmp.Compare(\"s\", \"V\"));\n\n        Console.WriteLine();\n        var cmp2 = StringComparer.OrdinalIgnoreCase;\n        Console.WriteLine(cmp2.Compare(\"s\", \"S\"));\n        Console.WriteLine(cmp2.Compare(\"s\", \"v\"));\n        Console.WriteLine(cmp2.Compare(\"s\", \"V\"));\n\n        Console.WriteLine();\n        cmp2 = StringComparer.Ordinal;\n        Console.WriteLine(cmp2.Compare(\"s\", \"S\"));\n        Console.WriteLine(cmp2.Compare(\"s\", \"v\"));\n        Console.WriteLine(cmp2.Compare(\"s\", \"V\"));\n\n        //dic.Clear();\n        //dic = dic.OrderBy(e => e.Key).ToDictionary(e => e.Key, e => e.Value);\n        //Console.WriteLine(dic.Join(\",\", e => $\"{e.Key}={e.Value}\"));\n\n        var list = new List<BrokerInfo>\n        {\n            new BrokerInfo { Name = \"A\", WriteQueueNums = 5 },\n            new BrokerInfo { Name = \"B\", WriteQueueNums = 7,Addresses=new[]{ \"111\",\"222\"} },\n            new BrokerInfo { Name = \"C\", WriteQueueNums = 9 },\n        };\n        var list2 = new List<BrokerInfo>\n        {\n            new BrokerInfo { Name = \"A\", WriteQueueNums = 5 },\n            new BrokerInfo { Name = \"B\", WriteQueueNums = 7 ,Addresses=new[]{ \"111\",\"222\"}},\n            new BrokerInfo { Name = \"C\", WriteQueueNums = 9 },\n        };\n\n        Console.WriteLine(list[1].Equals(list2[1]));\n        Console.WriteLine(list2.SequenceEqual(list));\n\n        var robin = new WeightRoundRobin();\n        robin.Set(list.Select(e => e.WriteQueueNums).ToArray());\n        var count = list.Sum(e => e.WriteQueueNums);\n        for (var i = 0; i < count; i++)\n        {\n            var idx = robin.Get(out var times);\n            var bk = list[idx];\n            Console.WriteLine(\"{0} {1} {2}\", i, bk.Name, times - 1);\n        }\n    }\n\n    static void Test4()\n    {\n        var a1 = File.ReadAllBytes(\"a1\".GetFullPath());\n        var a2 = File.ReadAllBytes(\"a2\".GetFullPath());\n\n        //var ms = new MemoryStream(a1);\n        //ms.Position += 2;\n        //var ds = new DeflateStream(ms, CompressionMode.Decompress);\n        //var buf = ds.ReadBytes();\n\n        var buf = a1.ReadBytes(2, -1).Decompress();\n\n        var rs = a2.ToBase64() == buf.ToBase64();\n        Console.WriteLine(rs);\n\n        Console.WriteLine(buf.ToStr());\n    }\n\n    static void Test5()\n    {\n        var _consumers = new List<Consumer>();\n        var topics = new List<string>() { \"flow\", \"flow2\" };\n\n        for (int i = 0; i < 2; i++)\n        {\n            var consumer = new Consumer\n            {\n                Topic = topics[i],\n                //Group = \"test\",\n                NameServerAddress = \"172.19.177.185:9876\",\n\n                BatchSize = 1,\n                Log = XTrace.Log,\n            };\n\n            consumer.OnConsume = OnConsume;\n            consumer.Start();\n\n            _consumers.Add(consumer);\n\n        }\n    }\n}"
  },
  {
    "path": "Test/Test.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFrameworks>net10.0</TargetFrameworks>\n    <AssemblyTitle>RocketMQ测试程序</AssemblyTitle>\n    <Description>NewLife.RocketMQ 功能测试与示例程序</Description>\n    <OutputPath>..\\Bin\\Test</OutputPath>\n    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NewLife.RocketMQ\\NewLife.RocketMQ.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "XUnitTestRocketMQ/.github/copilot-instructions.md",
    "content": "# NewLife Copilot 协作指令\n\n本说明适用于新生命团队（NewLife）及其全部开源/衍生项目，规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。\n\n> 目标：把\"每次请求必须携带的通用规则\"控制在可接受体积；组件/业务专项流程放在 `.github/instructions/`，按需读取。\n\n---\n\n## 1. 核心原则\n\n| 原则 | 说明 |\n|------|------|\n| **提效** | 减少机械样板，聚焦业务/核心算法 |\n| **一致** | 风格、结构、命名、API 行为稳定 |\n| **可控** | 限制改动影响面，可审计，兼容友好 |\n| **可靠** | 先检索再生成，不虚构，不破坏现有合约 |\n| **主动** | 发现问题主动修复，不回避合理优化 |\n\n---\n\n## 2. 适用范围\n\n- 含 NewLife 组件或衍生的全部 C#/.NET 仓库\n- 不含纯前端/非 .NET/市场文案\n- 存在本文件 → 必须遵循\n\n---\n\n## 3. 组件专用指令索引（按需加载）\n\n以下专用指令**仅在相关任务时**才需要读取，避免每次请求都携带大段流程/示例。\n\n### 3.1 XCode / Cube（数据库 & Web 快速开发）\n\n当任务涉及以下任一信号时，请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**，若存在则读取并遵循：\n\n- 需求包含：XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令\n- 解决方案/项目中出现：`NewLife.XCode` 包引用\n- 存在：`Model.xml`、`*.xcode.xml`、`*.Data.csproj`（或项目名以 `.Data` 结尾）\n- 代码出现命名空间/类型：`XCode.*`、`Entity`（XCode 实体基类）、XCode 相关特性/接口\n- **用户提到修改任意 `.xml` 文件**（如 `member.xml`、`area.xml` 等配置文件），应**主动搜索** `xcode.instructions.md` 判断是否需要引入\n\n**主动检测策略**：当用户提及 XML 文件修改时，即使未明确提到 XCode 关键字，也应先用 `file_search` 搜索 `xcode.instructions.md`，若存在则读取，以确定该 XML 文件是否属于 XCode/Cube 体系。\n\n未满足以上条件时，**不要**引入 XCode/Cube 初始化流程，避免干扰其它仓库的常规开发。\n\n---\n\n## 4. 工作流\n\n```\n需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明\n```\n\n1. **需求分类**：功能/修复/性能/重构/文档\n2. **检索**：相关类型、目录、方法、已有扩展/工具（**优先复用**）\n3. **评估**：是否公共 API？是否性能热点？**是否存在潜在问题？**\n4. **设计**：列出改动点 + 兼容/降级策略\n5. **实施**：\n   - 完成用户请求的核心任务\n   - **顺带修复**发现的明显缺陷（资源泄漏、空引用、逻辑错误）\n   - **顺带优化**可简化的重复代码\n   - 保留原注释与结构，除非注释本身有误\n6. **验证**：\n   - 代码变更：必须编译通过；运行相关单元测试（未找到需说明）\n   - 仅文档变更（未修改任何代码文件）：可跳过编译与单元测试\n7. **说明**：变更摘要/影响范围/风险点\n\n### 4.1 主动优化原则\n\n当用户请求分析或优化代码时，**应主动**：\n\n| 类型 | 行动 |\n|------|------|\n| **架构梳理** | 梳理代码架构并进行重构，让代码结构更清晰易懂 |\n| **语法现代化** | 使用最新的 C# 语法来简化代码，提升可读性 |\n| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复，让代码更健壮 |\n| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 |\n| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 |\n| **注释完善** | 补充类、接口、属性、方法头部的注释，以及方法内部重要代码的注释 |\n| **架构参考** | 参考网络上同类功能的优秀架构，给出架构调整建议 |\n\n**架构调整策略**：\n- **改动较小**：直接调整，完成后说明变更内容\n- **改动较大**：先列出调整方案，询问用户意见，待确认后再修改\n\n**不应过度保守**：\n- ❌ 仅添加注释而忽略明显的代码问题\n- ❌ 发现资源泄漏却不修复\n- ❌ 看到重复代码却不提取\n- ❌ 用户要求优化时只做表面工作\n\n**保持谨慎的场景**：\n- 公共 API 签名变更 → 需说明兼容性影响\n- 性能关键路径 → 需有依据或说明推理\n- 大范围重构 → 需先与用户确认范围\n\n### 4.2 防御性注释规则\n\n在旧有代码中，经常可以看到**被注释掉的代码**，这些注释代码前面通常带有说明文字。\n\n**这些是防御性注释**：\n- 记录了过去曾经踩过的坑\n- 目的是告诉后来人不要按照注释代码去写，否则会有问题\n- **禁止删除此类防御性注释**，用于警示后人\n\n**识别特征**：\n```csharp\n// 曾经尝试过 xxx 方案，但会导致 yyy 问题\n// var result = DoSomethingWrong();\n\n// 不要使用 xxx，否则会造成 yyy\n// await client.SendAsync(data);\n\n// 这里不能用 xxx，因为 yyy\n// stream.Flush();\n```\n\n**处理原则**：\n- ✅ 保留这类带说明的注释代码\n- ✅ 可以补充更详细的说明，解释为什么不能这样做\n- ❌ 不要删除这类防御性注释\n- ❌ 不要尝试\"恢复\"这些被注释的代码\n\n---\n\n## 5. 编码规范\n\n### 5.1 基础规范\n\n| 项目 | 规范 |\n|------|------|\n| 语言版本 | `<LangVersion>latest</LangVersion>`，所有目标框架均使用最新 C# 语法 |\n| 命名空间 | file-scoped namespace |\n| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等，避免 `string`/`int`/`bool` |\n| 兼容性 | 代码需兼容 .NET 4.5+；**禁止**使用 `ArgumentNullException.ThrowIfNull`，改用 `if (value == null) throw new ArgumentNullException(nameof(value));` |\n| 单文件 | 每文件一个主要公共类型；较大平台差异使用 `partial` |\n\n### 5.2 命名规范\n\n| 成员类型 | 命名规则 | 示例 |\n|---------|---------|------|\n| 类型/公共成员 | PascalCase | `UserService`、`GetName()` |\n| 参数/局部变量 | camelCase | `userName`、`count` |\n| 私有字段（实例/静态） | `_camelCase` | `_cache`、`_instance` |\n| 属性/方法（实例/静态） | PascalCase | `Name`、`Default`、`Create()` |\n| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` |\n\n### 5.3 代码风格\n\n```csharp\n// ✅ 单行 if：单语句且整行不过长时同行\nif (value == null) return;\nif (key == null) throw new ArgumentNullException(nameof(key));\n\n// ✅ 单行 if：语句较长时另起一行\nif (value == null)\n    throw new ArgumentNullException(nameof(value), \"Value cannot be null\");\n\n// ✅ 多分支单语句：不加花括号\nif (count > 0)\n    DoSomething();\nelse\n    DoOther();\n\n// ✅ 循环必须保留花括号（即使单语句）\nforeach (var item in list)\n{\n    Process(item);\n}\n```\n\n### 5.4 Region 组织结构\n\n较长的类使用 `#region` 分段组织，顺序为：`属性` → `静态`（如有）→ `构造` → `方法` → `辅助`（如有）→ `日志`。\n\n**日志 Region 规则**：\n- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法\n- **必须放在类代码的最后**\n- **必须用名为\"日志\"的 region 包裹**\n- 不要放在\"辅助\" region 中，应单独作为\"日志\" region\n\n### 5.5 现代 C# 语法\n\n优先使用最新语法（switch 表达式、模式匹配、目标类型 `new`、record 等），即使目标框架是 net45。\n\n### 5.6 集合表达式\n\n优先使用集合表达式 `[]` 初始化集合：`List<String> Tags { get; set; } = [];`\n\n### 5.7 Null 条件运算符\n\n优先使用 `?.` / `??` 简化空值检查：`span?.AppendTag(\"test\");` `var name = user?.Profile?.Name ?? \"\";`\n\n---\n\n## 6. 多目标框架\n\nNewLife 支持 `net45` 到 `net10`，常用条件符号：`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。\n\n新增 API 时需评估各框架兼容性，必要时提供降级实现。\n\n---\n\n## 7. 文档注释\n\n| 规则 | 说明 |\n|------|------|\n| `<summary>` | **必须同一行闭合**，简短描述方法用途 |\n| `<param>` | **必须为每个参数添加**，无论方法可见性如何 |\n| `<returns>` | 有返回值时必须添加 |\n| `<remarks>` | 复杂方法可增加详细说明（可多行） |\n| 覆盖范围 | `public`/`protected` 成员必须注释；`private`/`internal` 建议添加 |\n| `[Obsolete]` | 必须包含迁移建议 |\n\n**正确示例**：`/// <summary>获取名称</summary>` `/// <param name=\"id\">编号</param>`\n\n**禁止**：`<summary>` 拆成多行；缺少 `<param>`；有参数但无 param 标签。\n\n---\n\n## 8. 异步与性能\n\n| 规范 | 说明 |\n|------|------|\n| 方法命名 | 异步方法后缀 `Async` |\n| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` |\n| 高频路径 | 优先对象池/`ArrayPool<T>`/`Span`，避免多余分配 |\n| 反射/Linq | 仅用于非热点路径；热点使用手写循环/缓存 |\n| 池化资源 | 明确获取/归还；异常分支不遗失归还 |\n\n**内置工具优先**：`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。\n\n---\n\n## 9. 日志与追踪\n\n规则：若类包含 `ILog Log` 与 `WriteLog`，必须放在类末尾，并用名为\"日志\"的 `#region` 包裹；关键过程可使用 `Tracer?.NewSpan()` 埋点。\n\n---\n\n## 10. 错误处理\n\n- **精准异常类型**：`ArgumentNullException`/`InvalidOperationException` 等\n- **参数校验**：空/越界/格式\n- **TryXxx 模式**：不用异常作常规分支\n- **类型转换**：优先使用 `ToInt()`/`ToBoolean()` 等扩展方法\n- **对外异常**：不暴露内部实现/路径\n\n---\n\n## 11. 测试规范\n\n| 项目 | 规范 |\n|------|------|\n| 框架 | xUnit |\n| 命名 | `{ClassName}Tests` |\n| 描述 | `[DisplayName(\"中文描述意图\")]` |\n| IO | 使用临时目录；端口用 0/随机 |\n| 覆盖 | 正常/边界/异常/并发（必要时） |\n\n### 测试执行策略\n\n1. 优先检索 `{ClassName}` 引用，若落入测试项目则运行\n2. 未命中则查找 `{ClassName}Tests.cs`\n3. **未发现相关测试需明确说明**，不自动创建测试项目\n\n---\n\n## 12. NuGet 发布规范\n\n| 类型 | 命名规则 | 示例 |\n|------|---------|------|\n| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` |\n| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` |\n\n- **正式版**：每月月初发布\n- **测试版**：提交代码到 GitHub 时自动发布\n\n---\n\n## 13. Markdown 文档规范\n\n| 项目 | 规范 |\n|------|------|\n| 文件编码 | **必须** UTF-8，**禁止** GB2312/GBK/UTF-8 BOM |\n| 默认存放 | 代码库根目录下的 `Doc` 目录 |\n| 文件命名 | 优先**中文文件名**，简洁描述内容 |\n\n**注意**：已有文件**必须先读取**再增量修改，**禁止直接覆盖**。\n\n---\n\n## 14. Copilot 行为守则\n\n### 必须\n\n- 简体中文回复\n- 输出前检索现有实现，**禁止重复造轮子**\n- 先列方案再实现\n- 标记不确定上下文为\"需查看文件\"\n- **发现明显缺陷时主动修复**（资源泄漏、空引用、逻辑错误）\n- **用户要求优化时深入分析**，不做表面工作\n\n### 鼓励\n\n- 提取重复代码为公共方法\n- 简化冗余的条件判断\n- 使用现代 C# 语法改进可读性\n- 补充缺失的资源释放逻辑\n- 修正错误或过时的注释\n\n### 禁止\n\n- 虚构 API/文件/类型\n- 伪造测试结果/性能数据\n- 擅自删除公共/受保护成员\n- 擅自删除已有代码注释（除非注释本身错误）\n- **删除防御性注释**（带说明的注释代码，记录历史踩坑经验）\n- 仅删除空白行制造\"格式优化\"提交\n- 删除循环体的花括号\n- 将 `<summary>` 拆成多行\n- 将 `String`/`Int32` 改为 `string`/`int`\n- 新增外部依赖（除非说明理由并给出权衡）\n- 在热点路径添加未缓存反射/复杂 Linq\n- 输出敏感凭据/内部地址\n- **发现问题却视而不见**\n- **用户要求优化时仅做注释/测试等表面工作**\n\n---\n\n## 15. 变更说明模板\n\n提交或答复需包含：\n\n```markdown\n## 概述\n做了什么 / 为什么\n\n## 影响\n- 公共 API：是/否\n- 性能影响：无/有（说明）\n\n## 兼容性\n降级策略 / 条件编译点\n\n## 风险\n潜在回归 / 性能开销\n\n## 后续\n是否补测试 / 文档\n```\n\n---\n\n## 16. 术语说明\n\n| 术语 | 定义 |\n|------|------|\n| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 |\n| **基线** | 变更前的功能/性能参考数据 |\n| **顺带修复** | 在完成主任务过程中，修复发现的相关问题 |\n| **防御性注释** | 被注释掉的代码，前面带有说明，记录历史踩坑经验，用于警示后人 |\n\n---\n\n## 17. 代码优化检查清单\n\n当进行代码优化时，按以下清单逐项检查：\n\n### 架构与结构\n- [ ] 代码架构是否清晰？是否需要重构？\n- [ ] 类的职责是否单一？是否需要拆分？\n- [ ] 是否有重复代码可以提取为公共方法？\n- [ ] Region 组织是否符合规范（属性→静态→构造→方法→辅助→日志）？\n\n### 语法现代化\n- [ ] 是否可以使用更简洁的 C# 语法？（switch 表达式、模式匹配等）\n- [ ] 集合初始化是否使用了集合表达式 `[]`？\n- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码？\n\n### 健壮性\n- [ ] 是否存在空引用风险？\n- [ ] 资源是否正确释放？（IDisposable、流、连接等）\n- [ ] 异常处理是否完善？\n- [ ] 并发场景是否线程安全？\n\n### 性能\n- [ ] 是否存在可以缓存的重复计算？\n- [ ] 是否有不必要的对象分配？\n- [ ] 热点路径是否避免了反射和复杂 Linq？\n- [ ] 是否使用了对象池/ArrayPool 等池化技术？\n\n### 注释与文档\n- [ ] 类、接口是否有 `<summary>` 注释？\n- [ ] 公共方法是否有完整的参数和返回值注释？\n- [ ] 方法内重要逻辑是否有注释说明？\n- [ ] 防御性注释是否保留？\n\n### 日志\n- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后？\n- [ ] 是否用名为\"日志\"的 region 包裹？\n\n---\n\n（完）\n"
  },
  {
    "path": "XUnitTestRocketMQ/AliyunIssuesTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing NewLife;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>\n/// 修复Issues调用阿里云版RocketMQ相关问题\n/// #35、#24\n/// </summary>\npublic class AliyunIssuesTests\n{\n    private readonly String _testTopic = \"newlife_test_01\";\n    private readonly String _testGroup = \"GID_newlife_Group01\";\n    private static readonly AliyunOptions _aliyunOptions = new AliyunOptions()\n    {\n        AccessKey = \"LTAIxxxxxxxxxxxxRARVC4\",\n        SecretKey = \"a9oPwxxxxxxxxxxx3OrxxLO\",\n        Server = \"http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet\",\n        InstanceId = \"MQ_INST_xxxxxxxxxxxx_AXxCwUhm\"\n    };\n\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    public void ProducerForAliyun_Test()\n    {\n        var producer = new Producer()\n        {\n            Topic = _testTopic,\n            Aliyun = _aliyunOptions,\n            //NameServerAddress = \"http://MQ_INST_xxxxxxxxxx_AXxCwUhm.mq-internet-access.mq-internet.aliyuncs.com:80\",\n            //如果不用上面的默认Server地址，直接将NameServerAddress设为你自己的TCP公网接收点地址也是可以的\n        };\n\n        producer.Start();\n\n        var pubResultList = new List<Boolean>();\n        for (var i = 0; i < 2; i++)\n        {\n            var message = \"大家好才是真的好！\";\n            var pubResult = producer.Publish(message, \"newlife_test_tag\");\n            pubResultList.Add(pubResult.Status == SendStatus.SendOK);\n        }\n        Assert.True(pubResultList.All(t => true));\n\n        producer.Dispose();\n    }\n\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    public void ConsumerForAliyun_Test()\n    {\n        var consumer = new Consumer()\n        {\n            Topic = _testTopic,\n            Aliyun = _aliyunOptions,\n            Group = _testGroup,\n            FromLastOffset = true,\n            BatchSize = 1,\n        };\n\n        consumer.OnConsume = OnConsume;\n        consumer.Start();\n        Thread.Sleep(3000);\n       \n        static Boolean OnConsume(MessageQueue q, MessageExt[] ms)\n        {\n            Console.WriteLine(\"[{0}@{1}]收到消息[{2}]\", q.BrokerName, q.QueueId, ms.Length);\n\n            foreach (var item in ms.ToList())\n            {\n                Console.WriteLine($\"消息：主键【{item.Keys}】，产生时间【{item.BornTimestamp.ToDateTime()}】，内容【{item.Body.ToStr()}】\");\n            }\n\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/AliyunTests.cs",
    "content": "﻿using NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Protocol;\nusing System;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class AliyunTests\n{\n    private static void SetConfig(MqBase mq)\n    {\n        //mq.Server = \"http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet\";\n        mq.Configure(MqSetting.Current);\n\n        mq.Log = XTrace.Log;\n    }\n\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    public void CreateTopic()\n    {\n        var mq = new Producer\n        {\n            //Topic = \"nx_test\",\n        };\n        SetConfig(mq);\n\n        mq.Start();\n\n        // 创建topic时，start前不能指定topic，让其使用默认TBW102\n        Assert.Equal(\"TBW102\", mq.Topic);\n\n        mq.CreateTopic(\"nx_test\", 2);\n    }\n\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    static void ProduceTest()\n    {\n        using var mq = new Producer\n        {\n            Topic = \"test1\",\n        };\n        SetConfig(mq);\n\n        mq.Start();\n\n        for (var i = 0; i < 10; i++)\n        {\n            var str = \"学无先后达者为师\" + i;\n            //var str = Rand.NextString(1337);\n\n            var sr = mq.Publish(str, \"TagA\", null);\n        }\n    }\n\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    static async Task ProduceAsyncTest()\n    {\n        using var mq = new Producer\n        {\n            Topic = \"test1\",\n        };\n        SetConfig(mq);\n\n        mq.Start();\n\n        for (var i = 0; i < 10; i++)\n        {\n            var str = \"学无先后达者为师\" + i;\n            //var str = Rand.NextString(1337);\n\n            var sr = await mq.PublishAsync(str, \"TagA\", null);\n        }\n    }\n\n    private static Consumer _consumer;\n    [Fact(Skip = \"需要阿里云RocketMQ服务器支持\")]\n    static void ConsumeTest()\n    {\n        var consumer = new Consumer\n        {\n            Topic = \"test1\",\n            Group = \"test\",\n\n            FromLastOffset = true,\n            BatchSize = 20,\n        };\n        SetConfig(consumer);\n\n        consumer.OnConsume = OnConsume;\n        consumer.Start();\n\n        _consumer = consumer;\n\n        Thread.Sleep(3000);\n    }\n\n    private static Boolean OnConsume(MessageQueue q, MessageExt[] ms)\n    {\n        Console.WriteLine(\"[{0}@{1}]收到消息[{2}]\", q.BrokerName, q.QueueId, ms.Length);\n\n        foreach (var item in ms.ToList())\n        {\n            Console.WriteLine($\"消息：主键【{item.Keys}】，产生时间【{item.BornTimestamp.ToDateTime()}】，内容【{item.Body.ToStr()}】\");\n        }\n\n        return true;\n    }\n}"
  },
  {
    "path": "XUnitTestRocketMQ/BasicTest.cs",
    "content": "﻿using NewLife.Log;\nusing NewLife.RocketMQ;\nusing Xunit;\n\n// 所有测试用例放入一个汇编级集合，除非单独指定Collection特性\n[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]\n\nnamespace XUnitTestRocketMQ;\n\n[Collection(\"Basic\")]\npublic class BasicTest\n{\n    private static MqSetting _config;\n    public static MqSetting GetConfig()\n    {\n        if (_config != null) return _config;\n        lock (typeof(BasicTest))\n        {\n            if (_config != null) return _config;\n\n            var set = MqSetting.Current;\n            if (set.IsNew)\n            {\n                set.NameServer = \"rocketmq.newlifex.com:9876\";\n                set.Save();\n            }\n\n            XTrace.WriteLine(\"RocketMQ配置：{0}\", set.NameServer);\n\n            return _config = set;\n        }\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/BatchAckTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Threading.Tasks;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>批量确认Pop消息测试</summary>\npublic class BatchAckTests\n{\n    [Fact]\n    [DisplayName(\"BatchAckMessageAsync_Null的BrokerName抛出异常\")]\n    public async Task BatchAckMessageAsync_NullBrokerName_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.BatchAckMessageAsync(null, new List<(String, Int64)> { (\"extra\", 0) }));\n    }\n\n    [Fact]\n    [DisplayName(\"BatchAckMessageAsync_空BrokerName抛出异常\")]\n    public async Task BatchAckMessageAsync_EmptyBrokerName_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.BatchAckMessageAsync(\"\", new List<(String, Int64)> { (\"extra\", 0) }));\n    }\n\n    [Fact]\n    [DisplayName(\"BatchAckMessageAsync_空条目列表返回0\")]\n    public async Task BatchAckMessageAsync_EmptyEntries_ReturnsZero()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.BatchAckMessageAsync(\"broker1\", new List<(String, Int64)>());\n        Assert.Equal(0, result);\n    }\n\n    [Fact]\n    [DisplayName(\"BatchAckMessageAsync_Null条目列表返回0\")]\n    public async Task BatchAckMessageAsync_NullEntries_ReturnsZero()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.BatchAckMessageAsync(\"broker1\", null);\n        Assert.Equal(0, result);\n    }\n\n    [Fact]\n    [DisplayName(\"BatchAckMessageAsync_无Broker连接时返回0\")]\n    public async Task BatchAckMessageAsync_NoBroker_ReturnsZero()\n    {\n        using var consumer = new Consumer();\n        // 未Start，无Broker连接\n        var entries = new List<(String extraInfo, Int64 offset)>\n        {\n            (\"extra1\", 100),\n            (\"extra2\", 200),\n        };\n        var result = await consumer.BatchAckMessageAsync(\"nonexistent\", entries);\n        Assert.Equal(0, result);\n    }\n\n    [Fact]\n    [DisplayName(\"RequestCode包含BATCH_ACK_MESSAGE\")]\n    public void RequestCode_ContainsBatchAckMessage()\n    {\n        Assert.Equal(200151, (Int32)RequestCode.BATCH_ACK_MESSAGE);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/BatchMessageTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Text;\nusing NewLife;\nusing NewLife.Data;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>批量消息发送测试</summary>\n[Collection(\"Basic\")]\npublic class BatchMessageTests\n{\n    [Fact]\n    [DisplayName(\"批量消息发送_消息列表为空时抛出异常\")]\n    public void PublishBatch_EmptyList_ThrowsException()\n    {\n        using var mq = new Producer { Topic = \"nx_test\" };\n\n        Assert.Throws<ArgumentException>(() => mq.PublishBatch(new List<Message>()));\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息发送_消息列表为null时抛出异常\")]\n    public void PublishBatch_NullList_ThrowsException()\n    {\n        using var mq = new Producer { Topic = \"nx_test\" };\n\n        Assert.Throws<ArgumentException>(() => mq.PublishBatch((IList<Message>)null));\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息字符串重载_空列表抛出异常\")]\n    public void PublishBatch_StringOverload_EmptyList_ThrowsException()\n    {\n        using var mq = new Producer { Topic = \"nx_test\" };\n\n        Assert.Throws<ArgumentException>(() => mq.PublishBatch(new List<String>()));\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"批量消息发送_发送多条消息\")]\n    public void PublishBatch_SendMultipleMessages()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n        };\n        mq.Start();\n\n        var messages = new List<Message>();\n        for (var i = 0; i < 5; i++)\n        {\n            var msg = new Message();\n            msg.SetBody($\"批量消息{i}\");\n            msg.Tags = \"TagBatch\";\n            messages.Add(msg);\n        }\n\n        var result = mq.PublishBatch(messages);\n        Assert.NotNull(result);\n        Assert.Equal(SendStatus.SendOK, result.Status);\n    }\n\n    #region 批量消息解码辅助方法\n    /// <summary>写入大端序Int32</summary>\n    private static void WriteBE32(MemoryStream ms, Int32 value)\n    {\n        ms.WriteByte((Byte)(value >> 24));\n        ms.WriteByte((Byte)(value >> 16));\n        ms.WriteByte((Byte)(value >> 8));\n        ms.WriteByte((Byte)value);\n    }\n\n    /// <summary>写入大端序Int64</summary>\n    private static void WriteBE64(MemoryStream ms, Int64 value)\n    {\n        WriteBE32(ms, (Int32)(value >> 32));\n        WriteBE32(ms, (Int32)value);\n    }\n\n    /// <summary>写入大端序Int16</summary>\n    private static void WriteBE16(MemoryStream ms, Int16 value)\n    {\n        ms.WriteByte((Byte)(value >> 8));\n        ms.WriteByte((Byte)value);\n    }\n\n    /// <summary>构建批量消息Body（多条子消息的二进制数据）</summary>\n    private static Byte[] BuildBatchBody(params (String body, String topic, String props)[] items)\n    {\n        var ms = new MemoryStream();\n\n        foreach (var (body, topic, props) in items)\n        {\n            var bodyBytes = body.GetBytes();\n            var topicBytes = topic.GetBytes();\n            var propsBytes = props.GetBytes();\n\n            // TotalSize = 4+4+4+4 + 4+bodyLen + 1+topicLen + 2+propsLen\n            var totalSize = 4 + 4 + 4 + 4 + bodyBytes.Length + 1 + topicBytes.Length + 2 + propsBytes.Length;\n\n            WriteBE32(ms, totalSize);       // TotalSize\n            WriteBE32(ms, 0);               // MagicCode\n            WriteBE32(ms, 0);               // BodyCRC\n            WriteBE32(ms, 0);               // Flag\n\n            WriteBE32(ms, bodyBytes.Length); // BodyLength\n            ms.Write(bodyBytes, 0, bodyBytes.Length);\n\n            ms.WriteByte((Byte)topicBytes.Length); // TopicLength\n            ms.Write(topicBytes, 0, topicBytes.Length);\n\n            WriteBE16(ms, (Int16)propsBytes.Length); // PropertiesLength\n            if (propsBytes.Length > 0) ms.Write(propsBytes, 0, propsBytes.Length);\n        }\n\n        return ms.ToArray();\n    }\n\n    /// <summary>构建一条完整的外层消息二进制（带SysFlag=0x10标识批量）</summary>\n    private static Byte[] BuildOuterMessage(Byte[] batchBody)\n    {\n        var ms = new MemoryStream();\n        var ipBytes = new Byte[] { 127, 0, 0, 1 };\n        var topic = \"batch_topic\"u8.ToArray();\n        var props = \"\"u8.ToArray();\n        var sysFlag = 0x10; // 批量消息标志\n\n        var storeSize = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 +\n                        8 + 4 + 4 +\n                        8 + 4 + 4 +\n                        4 + 8 +\n                        4 + batchBody.Length + 1 + topic.Length + 2 + props.Length;\n\n        WriteBE32(ms, storeSize);\n        WriteBE32(ms, 0);            // MagicCode\n        WriteBE32(ms, 0);            // BodyCRC\n        WriteBE32(ms, 0);            // QueueId\n        WriteBE32(ms, 0);            // Flag\n        WriteBE64(ms, 0L);           // QueueOffset\n        WriteBE64(ms, 100L);         // CommitLogOffset\n        WriteBE32(ms, sysFlag);      // SysFlag (batch)\n\n        WriteBE64(ms, 1000L);        // BornTimestamp\n        ms.Write(ipBytes, 0, 4);     // BornHost IP\n        WriteBE32(ms, 9876);         // BornHost Port\n\n        WriteBE64(ms, 2000L);        // StoreTimestamp\n        ms.Write(ipBytes, 0, 4);     // StoreHost IP\n        WriteBE32(ms, 10911);        // StoreHost Port\n\n        WriteBE32(ms, 0);            // ReconsumeTimes\n        WriteBE64(ms, 0L);           // PreparedTransactionOffset\n\n        WriteBE32(ms, batchBody.Length); // BodyLength\n        ms.Write(batchBody, 0, batchBody.Length);\n\n        ms.WriteByte((Byte)topic.Length);\n        ms.Write(topic, 0, topic.Length);\n\n        WriteBE16(ms, (Int16)props.Length);\n\n        return ms.ToArray();\n    }\n    #endregion\n\n    #region 批量消息解码测试\n    [Fact]\n    [DisplayName(\"批量消息解码_解码2条子消息\")]\n    public void DecodeBatch_TwoMessages()\n    {\n        var batchBody = BuildBatchBody(\n            (\"hello\", \"topic1\", \"\"),\n            (\"world\", \"topic2\", \"\")\n        );\n\n        var parent = new MessageExt\n        {\n            QueueId = 1,\n            CommitLogOffset = 100,\n            SysFlag = 0x10,\n            BornTimestamp = 1000,\n            BornHost = \"127.0.0.1:9876\",\n            StoreTimestamp = 2000,\n            StoreHost = \"127.0.0.1:10911\",\n            Body = batchBody,\n            Topic = \"batch_topic\",\n            MsgId = \"PARENT_ID\",\n        };\n\n        var msgs = MessageExt.DecodeBatch(parent);\n\n        Assert.Equal(2, msgs.Count);\n\n        Assert.Equal(\"hello\", msgs[0].BodyString);\n        Assert.Equal(\"topic1\", msgs[0].Topic);\n        Assert.Equal(1, msgs[0].QueueId);\n        Assert.Equal(100, msgs[0].CommitLogOffset);\n        Assert.Equal(\"127.0.0.1:9876\", msgs[0].BornHost);\n\n        Assert.Equal(\"world\", msgs[1].BodyString);\n        Assert.Equal(\"topic2\", msgs[1].Topic);\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息解码_解码带Properties的子消息\")]\n    public void DecodeBatch_WithProperties()\n    {\n        var props = \"TAGS\\u0001TagA\\u0002KEYS\\u0001Key1\\u0002\";\n        var batchBody = BuildBatchBody(\n            (\"data\", \"my_topic\", props)\n        );\n\n        var parent = new MessageExt\n        {\n            SysFlag = 0x10,\n            Body = batchBody,\n            MsgId = \"P1\",\n        };\n\n        var msgs = MessageExt.DecodeBatch(parent);\n\n        Assert.Single(msgs);\n        Assert.Equal(\"data\", msgs[0].BodyString);\n        Assert.Equal(\"my_topic\", msgs[0].Topic);\n        Assert.Equal(\"TagA\", msgs[0].Tags);\n        Assert.Equal(\"Key1\", msgs[0].Keys);\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息解码_空Body返回空列表\")]\n    public void DecodeBatch_EmptyBody()\n    {\n        var parent = new MessageExt\n        {\n            SysFlag = 0x10,\n            Body = [],\n            MsgId = \"P1\",\n        };\n\n        var msgs = MessageExt.DecodeBatch(parent);\n        Assert.Empty(msgs);\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息解码_Null参数抛出异常\")]\n    public void DecodeBatch_NullParent_Throws()\n    {\n        Assert.Throws<ArgumentNullException>(() => MessageExt.DecodeBatch(null));\n    }\n\n    [Fact]\n    [DisplayName(\"批量消息ReadAll自动展开\")]\n    public void ReadAll_BatchMessage_AutoExpand()\n    {\n        var batchBody = BuildBatchBody(\n            (\"msg1\", \"t1\", \"\"),\n            (\"msg2\", \"t2\", \"\"),\n            (\"msg3\", \"t3\", \"\")\n        );\n\n        var data = BuildOuterMessage(batchBody);\n        var pk = new ArrayPacket(data);\n        var msgs = MessageExt.ReadAll(pk);\n\n        Assert.Equal(3, msgs.Count);\n        Assert.Equal(\"msg1\", msgs[0].BodyString);\n        Assert.Equal(\"msg2\", msgs[1].BodyString);\n        Assert.Equal(\"msg3\", msgs[2].BodyString);\n        Assert.Equal(\"t1\", msgs[0].Topic);\n        Assert.Equal(\"t2\", msgs[1].Topic);\n        Assert.Equal(\"t3\", msgs[2].Topic);\n    }\n\n    [Fact]\n    [DisplayName(\"SysFlag批量位判断\")]\n    public void SysFlag_Batch_Bit()\n    {\n        Assert.NotEqual(0, 0x10 & 0x10);  // 批量\n        Assert.Equal(0, 0 & 0x10);        // 普通\n        Assert.NotEqual(0, 0x11 & 0x10);  // 压缩+批量\n        Assert.Equal(0, 0x01 & 0x10);     // 仅压缩\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/BroadcastOffsetTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.IO;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Models;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>广播模式本地偏移持久化测试</summary>\npublic class BroadcastOffsetTests\n{\n    [Fact]\n    [DisplayName(\"MessageModel_默认Clustering\")]\n    public void MessageModel_DefaultClustering()\n    {\n        using var consumer = new Consumer();\n        Assert.Equal(MessageModels.Clustering, consumer.MessageModel);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageModel_可设置为Broadcasting\")]\n    public void MessageModel_CanSetBroadcasting()\n    {\n        using var consumer = new Consumer { MessageModel = MessageModels.Broadcasting };\n        Assert.Equal(MessageModels.Broadcasting, consumer.MessageModel);\n    }\n\n    [Fact]\n    [DisplayName(\"OffsetStorePath_默认为null\")]\n    public void OffsetStorePath_DefaultNull()\n    {\n        using var consumer = new Consumer();\n        Assert.Null(consumer.OffsetStorePath);\n    }\n\n    [Fact]\n    [DisplayName(\"OffsetStorePath_可自定义\")]\n    public void OffsetStorePath_CanBeCustomized()\n    {\n        var path = Path.Combine(Path.GetTempPath(), \"test_offsets.json\");\n        using var consumer = new Consumer { OffsetStorePath = path };\n        Assert.Equal(path, consumer.OffsetStorePath);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/BrokerFailoverTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Broker主从切换测试</summary>\npublic class BrokerFailoverTests\n{\n    [Fact]\n    [DisplayName(\"BrokerInfo_MasterAddress属性\")]\n    public void BrokerInfo_MasterAddress()\n    {\n        var info = new BrokerInfo\n        {\n            Name = \"broker-a\",\n            MasterAddress = \"192.168.1.1:10911\",\n            SlaveAddresses = [\"192.168.1.2:10911\"],\n            Addresses = [\"192.168.1.1:10911\", \"192.168.1.2:10911\"],\n            IsMaster = true,\n        };\n\n        Assert.Equal(\"192.168.1.1:10911\", info.MasterAddress);\n        Assert.Single(info.SlaveAddresses);\n        Assert.Equal(\"192.168.1.2:10911\", info.SlaveAddresses[0]);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_无Slave时SlaveAddresses为空\")]\n    public void BrokerInfo_NoSlave()\n    {\n        var info = new BrokerInfo\n        {\n            Name = \"broker-a\",\n            MasterAddress = \"192.168.1.1:10911\",\n            SlaveAddresses = [],\n            Addresses = [\"192.168.1.1:10911\"],\n            IsMaster = true,\n        };\n\n        Assert.Empty(info.SlaveAddresses);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_Addresses以Master在前\")]\n    public void BrokerInfo_MasterFirst()\n    {\n        var info = new BrokerInfo\n        {\n            Name = \"broker-a\",\n            MasterAddress = \"192.168.1.1:10911\",\n            SlaveAddresses = [\"192.168.1.2:10911\", \"192.168.1.3:10911\"],\n            Addresses = [\"192.168.1.1:10911\", \"192.168.1.2:10911\", \"192.168.1.3:10911\"],\n            IsMaster = true,\n        };\n\n        Assert.Equal(\"192.168.1.1:10911\", info.Addresses[0]);\n        Assert.Equal(3, info.Addresses.Length);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_IsMaster标记\")]\n    public void BrokerInfo_IsMaster_Flag()\n    {\n        var masterInfo = new BrokerInfo { Name = \"broker-a\", IsMaster = true };\n        var slaveInfo = new BrokerInfo { Name = \"broker-b\", IsMaster = false };\n\n        Assert.True(masterInfo.IsMaster);\n        Assert.False(slaveInfo.IsMaster);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/BrokerInfoTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>BrokerInfo代理信息测试</summary>\npublic class BrokerInfoTests\n{\n    #region 属性\n    [Fact]\n    [DisplayName(\"BrokerInfo_默认值\")]\n    public void BrokerInfo_Defaults()\n    {\n        var bi = new BrokerInfo();\n\n        Assert.Null(bi.Name);\n        Assert.Null(bi.Cluster);\n        Assert.Null(bi.Addresses);\n        Assert.Null(bi.MasterAddress);\n        Assert.Null(bi.SlaveAddresses);\n        Assert.False(bi.IsMaster);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_设置所有属性\")]\n    public void BrokerInfo_SetAllProperties()\n    {\n        var bi = new BrokerInfo\n        {\n            Name = \"broker-a\",\n            Cluster = \"DefaultCluster\",\n            Addresses = [\"10.0.0.1:10911\", \"10.0.0.2:10911\"],\n            MasterAddress = \"10.0.0.1:10911\",\n            SlaveAddresses = [\"10.0.0.2:10911\"],\n            Permission = Permissions.Read | Permissions.Write,\n            ReadQueueNums = 4,\n            WriteQueueNums = 4,\n            TopicSynFlag = 0,\n            IsMaster = true,\n        };\n\n        Assert.Equal(\"broker-a\", bi.Name);\n        Assert.Equal(\"DefaultCluster\", bi.Cluster);\n        Assert.Equal(2, bi.Addresses.Length);\n        Assert.Equal(\"10.0.0.1:10911\", bi.MasterAddress);\n        Assert.Single(bi.SlaveAddresses);\n        Assert.True(bi.IsMaster);\n        Assert.Equal(4, bi.ReadQueueNums);\n        Assert.Equal(4, bi.WriteQueueNums);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_无Slave地址\")]\n    public void BrokerInfo_NoSlaves()\n    {\n        var bi = new BrokerInfo\n        {\n            Addresses = [\"10.0.0.1:10911\"],\n            MasterAddress = \"10.0.0.1:10911\",\n            SlaveAddresses = [],\n        };\n\n        Assert.Empty(bi.SlaveAddresses);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_IsMaster标志\")]\n    public void BrokerInfo_IsMasterFlag()\n    {\n        var bi1 = new BrokerInfo { IsMaster = true };\n        var bi2 = new BrokerInfo { IsMaster = false };\n\n        Assert.True(bi1.IsMaster);\n        Assert.False(bi2.IsMaster);\n    }\n    #endregion\n\n    #region 相等比较\n    [Fact]\n    [DisplayName(\"BrokerInfo_相同属性相等\")]\n    public void BrokerInfo_SameProperties_Equal()\n    {\n        var bi1 = new BrokerInfo { Name = \"broker-a\", Addresses = [\"10.0.0.1:10911\"], ReadQueueNums = 4, WriteQueueNums = 4 };\n        var bi2 = new BrokerInfo { Name = \"broker-a\", Addresses = [\"10.0.0.1:10911\"], ReadQueueNums = 4, WriteQueueNums = 4 };\n\n        Assert.True(bi1.Equals(bi2));\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_不同Name不相等\")]\n    public void BrokerInfo_DifferentName_NotEqual()\n    {\n        var bi1 = new BrokerInfo { Name = \"broker-a\", Addresses = [\"10.0.0.1:10911\"] };\n        var bi2 = new BrokerInfo { Name = \"broker-b\", Addresses = [\"10.0.0.1:10911\"] };\n\n        Assert.False(bi1.Equals(bi2));\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_与非BrokerInfo不相等\")]\n    public void BrokerInfo_NonBrokerInfo_NotEqual()\n    {\n        var bi = new BrokerInfo { Name = \"broker-a\" };\n\n        Assert.False(bi.Equals(\"broker-a\"));\n        Assert.False(bi.Equals(null));\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerInfo_相同属性哈希相同\")]\n    public void BrokerInfo_SameProperties_SameHash()\n    {\n        var bi1 = new BrokerInfo { Name = \"broker-a\", Addresses = [\"10.0.0.1:10911\"], ReadQueueNums = 4, WriteQueueNums = 4 };\n        var bi2 = new BrokerInfo { Name = \"broker-a\", Addresses = [\"10.0.0.1:10911\"], ReadQueueNums = 4, WriteQueueNums = 4 };\n\n        // 由于 Addresses 是不同的数组实例，GetHashCode 可能不相等\n        // 只验证各自的哈希值是稳定的\n        Assert.Equal(bi1.GetHashCode(), bi1.GetHashCode());\n        Assert.Equal(bi2.GetHashCode(), bi2.GetHashCode());\n    }\n\n    [Fact]\n    [DisplayName(\"Permissions_读写标记\")]\n    public void Permissions_ReadWriteFlags()\n    {\n        Assert.Equal(2, (Int32)Permissions.Write);\n        Assert.Equal(4, (Int32)Permissions.Read);\n\n        var rw = Permissions.Read | Permissions.Write;\n        Assert.True(rw.HasFlag(Permissions.Read));\n        Assert.True(rw.HasFlag(Permissions.Write));\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/CloudProviderTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>云厂商适配器接口测试</summary>\npublic class CloudProviderTests\n{\n    #region AliyunProvider\n    [Fact]\n    [DisplayName(\"AliyunProvider_默认通道为ALIYUN\")]\n    public void AliyunProvider_DefaultOnsChannel()\n    {\n        var provider = new AliyunProvider();\n        Assert.Equal(\"Aliyun\", provider.Name);\n        Assert.Equal(\"ALIYUN\", provider.OnsChannel);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_有InstanceId时转换Topic\")]\n    public void AliyunProvider_TransformTopic_WithInstanceId()\n    {\n        var provider = new AliyunProvider { InstanceId = \"MQ_INST_123\" };\n\n        var result = provider.TransformTopic(\"test_topic\");\n        Assert.Equal(\"MQ_INST_123%test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_无InstanceId时不转换Topic\")]\n    public void AliyunProvider_TransformTopic_WithoutInstanceId()\n    {\n        var provider = new AliyunProvider();\n\n        var result = provider.TransformTopic(\"test_topic\");\n        Assert.Equal(\"test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_已有前缀时不重复添加\")]\n    public void AliyunProvider_TransformTopic_AlreadyPrefixed()\n    {\n        var provider = new AliyunProvider { InstanceId = \"MQ_INST_123\" };\n\n        var result = provider.TransformTopic(\"MQ_INST_123%test_topic\");\n        Assert.Equal(\"MQ_INST_123%test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_有InstanceId时转换Group\")]\n    public void AliyunProvider_TransformGroup_WithInstanceId()\n    {\n        var provider = new AliyunProvider { InstanceId = \"MQ_INST_123\" };\n\n        var result = provider.TransformGroup(\"test_group\");\n        Assert.Equal(\"MQ_INST_123%test_group\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_无Server时GetNameServerAddress返回null\")]\n    public void AliyunProvider_GetNameServerAddress_NoServer()\n    {\n        var provider = new AliyunProvider();\n\n        var result = provider.GetNameServerAddress();\n        Assert.Null(result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_非HTTP地址时GetNameServerAddress返回null\")]\n    public void AliyunProvider_GetNameServerAddress_NonHttp()\n    {\n        var provider = new AliyunProvider { Server = \"tcp://example.com\" };\n\n        var result = provider.GetNameServerAddress();\n        Assert.Null(result);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_FromOptions转换旧版选项\")]\n    public void AliyunProvider_FromOptions()\n    {\n        var options = new AliyunOptions\n        {\n            AccessKey = \"ak\",\n            SecretKey = \"sk\",\n            InstanceId = \"inst1\",\n            OnsChannel = \"ALIYUN\",\n            Server = \"http://test.com\",\n        };\n\n        var provider = AliyunProvider.FromOptions(options);\n\n        Assert.NotNull(provider);\n        Assert.Equal(\"ak\", provider.AccessKey);\n        Assert.Equal(\"sk\", provider.SecretKey);\n        Assert.Equal(\"inst1\", provider.InstanceId);\n        Assert.Equal(\"ALIYUN\", provider.OnsChannel);\n        Assert.Equal(\"http://test.com\", provider.Server);\n    }\n\n    [Fact]\n    [DisplayName(\"AliyunProvider_FromOptions_Null返回Null\")]\n    public void AliyunProvider_FromOptions_Null()\n    {\n        var result = AliyunProvider.FromOptions(null);\n        Assert.Null(result);\n    }\n    #endregion\n\n    #region AclProvider\n    [Fact]\n    [DisplayName(\"AclProvider_默认属性\")]\n    public void AclProvider_Defaults()\n    {\n        var provider = new AclProvider();\n        Assert.Equal(\"ACL\", provider.Name);\n        Assert.Equal(\"\", provider.OnsChannel);\n    }\n\n    [Fact]\n    [DisplayName(\"AclProvider_不转换Topic和Group\")]\n    public void AclProvider_NoTransform()\n    {\n        var provider = new AclProvider();\n\n        Assert.Equal(\"test_topic\", provider.TransformTopic(\"test_topic\"));\n        Assert.Equal(\"test_group\", provider.TransformGroup(\"test_group\"));\n    }\n\n    [Fact]\n    [DisplayName(\"AclProvider_GetNameServerAddress返回null\")]\n    public void AclProvider_GetNameServerAddress_Null()\n    {\n        var provider = new AclProvider();\n        Assert.Null(provider.GetNameServerAddress());\n    }\n\n    [Fact]\n    [DisplayName(\"AclProvider_FromOptions转换旧版选项\")]\n    public void AclProvider_FromOptions()\n    {\n        var options = new AclOptions\n        {\n            AccessKey = \"acl_ak\",\n            SecretKey = \"acl_sk\",\n            OnsChannel = \"LOCAL\",\n        };\n\n        var provider = AclProvider.FromOptions(options);\n\n        Assert.NotNull(provider);\n        Assert.Equal(\"acl_ak\", provider.AccessKey);\n        Assert.Equal(\"acl_sk\", provider.SecretKey);\n        Assert.Equal(\"LOCAL\", provider.OnsChannel);\n    }\n\n    [Fact]\n    [DisplayName(\"AclProvider_FromOptions_Null返回Null\")]\n    public void AclProvider_FromOptions_Null()\n    {\n        var result = AclProvider.FromOptions(null);\n        Assert.Null(result);\n    }\n    #endregion\n\n    #region HuaweiProvider\n    [Fact]\n    [DisplayName(\"HuaweiProvider_默认通道为HUAWEI\")]\n    public void HuaweiProvider_Defaults()\n    {\n        var provider = new HuaweiProvider();\n        Assert.Equal(\"Huawei\", provider.Name);\n        Assert.Equal(\"HUAWEI\", provider.OnsChannel);\n        Assert.False(provider.EnableSsl);\n    }\n\n    [Fact]\n    [DisplayName(\"HuaweiProvider_不转换Topic和Group\")]\n    public void HuaweiProvider_NoTransform()\n    {\n        var provider = new HuaweiProvider();\n\n        Assert.Equal(\"test_topic\", provider.TransformTopic(\"test_topic\"));\n        Assert.Equal(\"test_group\", provider.TransformGroup(\"test_group\"));\n    }\n\n    [Fact]\n    [DisplayName(\"HuaweiProvider_GetNameServerAddress返回null\")]\n    public void HuaweiProvider_GetNameServerAddress_Null()\n    {\n        var provider = new HuaweiProvider();\n        Assert.Null(provider.GetNameServerAddress());\n    }\n    #endregion\n\n    #region TencentProvider\n    [Fact]\n    [DisplayName(\"TencentProvider_默认通道为TENCENT\")]\n    public void TencentProvider_Defaults()\n    {\n        var provider = new TencentProvider();\n        Assert.Equal(\"Tencent\", provider.Name);\n        Assert.Equal(\"TENCENT\", provider.OnsChannel);\n    }\n\n    [Fact]\n    [DisplayName(\"TencentProvider_有Namespace时转换Topic\")]\n    public void TencentProvider_TransformTopic_WithNamespace()\n    {\n        var provider = new TencentProvider { Namespace = \"ns1\" };\n\n        var result = provider.TransformTopic(\"test_topic\");\n        Assert.Equal(\"ns1%test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"TencentProvider_无Namespace时不转换Topic\")]\n    public void TencentProvider_TransformTopic_WithoutNamespace()\n    {\n        var provider = new TencentProvider();\n\n        var result = provider.TransformTopic(\"test_topic\");\n        Assert.Equal(\"test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"TencentProvider_有Namespace时转换Group\")]\n    public void TencentProvider_TransformGroup_WithNamespace()\n    {\n        var provider = new TencentProvider { Namespace = \"ns1\" };\n\n        var result = provider.TransformGroup(\"test_group\");\n        Assert.Equal(\"ns1%test_group\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"TencentProvider_已有前缀时不重复添加\")]\n    public void TencentProvider_TransformTopic_AlreadyPrefixed()\n    {\n        var provider = new TencentProvider { Namespace = \"ns1\" };\n\n        var result = provider.TransformTopic(\"ns1%test_topic\");\n        Assert.Equal(\"ns1%test_topic\", result);\n    }\n\n    [Fact]\n    [DisplayName(\"TencentProvider_GetNameServerAddress返回null\")]\n    public void TencentProvider_GetNameServerAddress_Null()\n    {\n        var provider = new TencentProvider();\n        Assert.Null(provider.GetNameServerAddress());\n    }\n    #endregion\n\n    #region MqBase集成\n    [Fact]\n    [DisplayName(\"MqBase_CloudProvider默认为null\")]\n    public void MqBase_CloudProvider_DefaultNull()\n    {\n        using var producer = new Producer();\n        Assert.Null(producer.CloudProvider);\n    }\n\n    [Fact]\n    [DisplayName(\"MqBase_设置CloudProvider\")]\n    public void MqBase_CloudProvider_CanBeSet()\n    {\n        using var producer = new Producer\n        {\n            CloudProvider = new AliyunProvider\n            {\n                AccessKey = \"ak\",\n                SecretKey = \"sk\",\n                InstanceId = \"MQ_INST_123\",\n            }\n        };\n\n        Assert.NotNull(producer.CloudProvider);\n        Assert.IsType<AliyunProvider>(producer.CloudProvider);\n        Assert.Equal(\"ak\", producer.CloudProvider.AccessKey);\n    }\n\n    [Fact]\n    [DisplayName(\"MqBase_设置旧版Aliyun自动同步到CloudProvider\")]\n    public void MqBase_LegacyAliyun_SyncsToCloudProvider()\n    {\n#pragma warning disable CS0618\n        using var producer = new Producer\n        {\n            Aliyun = new AliyunOptions\n            {\n                AccessKey = \"ak\",\n                SecretKey = \"sk\",\n            }\n        };\n#pragma warning restore CS0618\n\n        Assert.NotNull(producer.CloudProvider);\n        Assert.IsType<AliyunProvider>(producer.CloudProvider);\n        Assert.Equal(\"ak\", producer.CloudProvider.AccessKey);\n    }\n\n    [Fact]\n    [DisplayName(\"MqBase_设置旧版AclOptions自动同步到CloudProvider\")]\n    public void MqBase_LegacyAclOptions_SyncsToCloudProvider()\n    {\n#pragma warning disable CS0618\n        using var producer = new Producer\n        {\n            AclOptions = new AclOptions\n            {\n                AccessKey = \"acl_ak\",\n                SecretKey = \"acl_sk\",\n            }\n        };\n#pragma warning restore CS0618\n\n        Assert.NotNull(producer.CloudProvider);\n        Assert.IsType<AclProvider>(producer.CloudProvider);\n        Assert.Equal(\"acl_ak\", producer.CloudProvider.AccessKey);\n    }\n\n    [Fact]\n    [DisplayName(\"MqBase_显式设置CloudProvider不被旧版属性覆盖\")]\n    public void MqBase_ExplicitCloudProvider_NotOverridden()\n    {\n        var acl = new AclProvider { AccessKey = \"explicit_ak\", SecretKey = \"sk\" };\n        using var producer = new Producer\n        {\n            CloudProvider = acl,\n        };\n\n#pragma warning disable CS0618\n        // 设置旧版属性时，因为 CloudProvider 已有值，不会覆盖\n        producer.Aliyun = new AliyunOptions { AccessKey = \"old_ak\", SecretKey = \"sk\" };\n#pragma warning restore CS0618\n\n        Assert.Same(acl, producer.CloudProvider);\n        Assert.Equal(\"explicit_ak\", producer.CloudProvider.AccessKey);\n    }\n\n    [Fact]\n    [DisplayName(\"MqBase_腾讯云Provider集成测试\")]\n    public void MqBase_TencentProvider_Integration()\n    {\n        using var producer = new Producer\n        {\n            CloudProvider = new TencentProvider\n            {\n                AccessKey = \"tencent_id\",\n                SecretKey = \"tencent_key\",\n                Namespace = \"ns_test\",\n            }\n        };\n\n        Assert.NotNull(producer.CloudProvider);\n        Assert.IsType<TencentProvider>(producer.CloudProvider);\n        Assert.Equal(\"tencent_id\", producer.CloudProvider.AccessKey);\n\n        var tp = (TencentProvider)producer.CloudProvider;\n        Assert.Equal(\"ns_test%my_topic\", tp.TransformTopic(\"my_topic\"));\n        Assert.Equal(\"ns_test%my_group\", tp.TransformGroup(\"my_group\"));\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/CommandTests.cs",
    "content": "﻿using System;\nusing System.IO;\nusing System.Reflection;\nusing NewLife;\nusing NewLife.Data;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Serialization;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class CommandTests\n{\n    [Fact]\n    public void DecodeJson()\n    {\n        var data = \"\"\"\n            00 00 01 06 00 00 00 72 7b 22 63 6f 64 65 22 3a\n            33 34 2c 22 6c 61 6e 67 75 61 67 65 22 3a 22 44\n            4f 54 4e 45 54 22 2c 22 6f 70 61 71 75 65 22 3a\n            31 35 37 35 2c 22 73 65 72 69 61 6c 69 7a 65 54\n            79 70 65 43 75 72 72 65 6e 74 52 50 43 22 3a 22\n            4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f 6e 22 3a\n            33 37 33 2c 22 72 65 6d 61 72 6b 22 3a 22 48 45\n            41 52 54 5f 42 45 41 54 22 7d 7b 22 43 6c 69 65\n            6e 74 49 44 22 3a 22 31 30 2e 37 2e 36 39 2e 32\n            30 35 40 36 33 32 38 34 22 2c 22 43 6f 6e 73 75\n            6d 65 72 44 61 74 61 53 65 74 22 3a 5b 5d 2c 22\n            50 72 6f 64 75 63 65 72 44 61 74 61 53 65 74 22\n            3a 5b 7b 22 47 72 6f 75 70 4e 61 6d 65 22 3a 22\n            44 45 46 41 55 4c 54 5f 50 52 4f 44 55 43 45 52\n            22 7d 2c 7b 22 47 72 6f 75 70 4e 61 6d 65 22 3a\n            22 43 4c 49 45 4e 54 5f 49 4e 4e 45 52 5f 50 52\n            4f 44 55 43 45 52 22 7d 5d 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.HEART_BEAT, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.DOTNET + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        //Assert.Empty(header.Remark);\n        Assert.Equal(\"HEART_BEAT\", header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Empty(ext);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var dic = JsonParser.Decode(pk.ToStr());\n        Assert.Equal(3, dic.Count);\n    }\n\n    [Fact]\n    public void DecodeJson2()\n    {\n        var data = \"\"\"\n            00 00 03 0c 00 00 01 4c 7b 22 63 6f 64 65 22 3a\n            33 31 30 2c 22 65 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 61 22 3a 22 44 45 46 41 55 4c 54 5f 50 52\n            4f 44 55 43 45 52 22 2c 22 62 22 3a 22 49 4f 43\n            5f 49 4e 53 5f 50 41 52 53 45 5f 54 4f 50 49 43\n            22 2c 22 63 22 3a 22 54 42 57 31 30 32 22 2c 22\n            64 22 3a 22 30 22 2c 22 65 22 3a 22 36 22 2c 22\n            66 22 3a 22 30 22 2c 22 67 22 3a 22 31 36 39 33\n            34 35 39 38 32 32 35 37 33 22 2c 22 68 22 3a 22\n            30 22 2c 22 69 22 3a 22 54 41 47 53 01 54 30 32\n            30 30 02 4b 45 59 53 01 30 61 30 37 34 35 63 64\n            31 36 39 33 34 35 39 38 32 32 35 36 30 62 33 36\n            33 65 66 37 33 34 02 57 41 49 54 01 54 72 75 65\n            02 22 2c 22 6a 22 3a 22 30 22 2c 22 6b 22 3a 22\n            46 61 6c 73 65 22 7d 2c 22 6c 61 6e 67 75 61 67\n            65 22 3a 22 44 4f 54 4e 45 54 22 2c 22 6f 70 61\n            71 75 65 22 3a 31 35 37 36 2c 22 73 65 72 69 61\n            6c 69 7a 65 54 79 70 65 43 75 72 72 65 6e 74 52\n            50 43 22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73\n            69 6f 6e 22 3a 33 37 33 2c 22 72 65 6d 61 72 6b\n            22 3a 22 53 45 4e 44 5f 4d 45 53 53 41 47 45 5f\n            56 32 22 7d 7b 22 4b 69 6e 64 22 3a 22 54 30 32\n            30 30 22 2c 22 56 65 72 73 69 6f 6e 22 3a 22 4a\n            54 32 30 31 33 22 2c 22 4d 6f 62 69 6c 65 22 3a\n            22 31 34 32 37 30 35 37 39 34 35 37 22 2c 22 53\n            65 71 75 65 6e 63 65 22 3a 35 31 35 34 2c 22 42\n            6f 64 79 22 3a 7b 22 41 6c 61 72 6d 22 3a 30 2c\n            22 53 74 61 74 75 73 22 3a 37 38 36 34 33 35 2c\n            22 4c 61 74 69 74 75 64 65 22 3a 33 36 38 37 31\n            33 30 30 2c 22 4c 6f 6e 67 69 74 75 64 65 22 3a\n            31 31 37 31 32 31 38 33 31 2c 22 41 6c 74 69 74\n            75 64 65 22 3a 31 36 2c 22 53 70 65 65 64 22 3a\n            30 2c 22 44 69 72 65 63 74 69 6f 6e 22 3a 31 31\n            35 2c 22 47 50 53 54 69 6d 65 22 3a 22 32 30 32\n            33 2d 30 38 2d 33 31 20 31 33 3a 33 30 3a 31 36\n            22 2c 22 41 64 64 69 74 69 6f 6e 61 6c 73 22 3a\n            7b 22 e9 87 8c e7 a8 8b 22 3a 32 36 35 30 34 30\n            39 2c 22 e9 80 9f e5 ba a6 22 3a 30 2c 22 e8 a7\n            86 e9 a2 91 e6 8a a5 e8 ad a6 22 3a 30 2c 22 e8\n            a7 86 e9 a2 91 e4 b8 a2 e5 a4 b1 22 3a 30 2c 22\n            e8 a7 86 e9 a2 91 e9 81 ae e6 8c a1 22 3a 30 2c\n            22 e5 ad 98 e5 82 a8 e6 95 85 e9 9a 9c 22 3a 30\n            2c 22 e5 bc 82 e5 b8 b8 e9 a9 be e9 a9 b6 22 3a\n            22 41 41 41 41 22 2c 22 e8 bd a6 e8 be 86 e4 bf\n            a1 e5 8f b7 22 3a 30 2c 22 e6 a8 a1 e6 8b 9f e9\n            87 8f 22 3a 30 2c 22 e4 bf a1 e5 8f b7 e5 bc ba\n            e5 ba a6 22 3a 34 2c 22 e5 8d ab e6 98 9f e6 95\n            b0 22 3a 33 32 2c 22 31 38 33 22 3a 30 2c 22 32\n            34 38 22 3a 30 2c 22 32 33 39 22 3a 30 7d 7d 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.SEND_MESSAGE_V2, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.DOTNET + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        Assert.Equal(\"SEND_MESSAGE_V2\", header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.NotEmpty(ext);\n        Assert.Equal(11, ext.Count);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var dic = JsonParser.Decode(pk.ToStr());\n        Assert.Equal(5, dic.Count);\n    }\n\n    [Fact]\n    public void DecodeRocketMQ()\n    {\n        var data = \"\"\"\n            00 00 00 19 01 00 00 15 00 00 00 01 75 00 00 06\n            27 00 00 00 01 00 00 00 00 00 00 00 00\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)ResponseCode.SUCCESS, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.ROCKETMQ + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Empty(ext);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void DecodeRocketMQ2()\n    {\n        var data = \"\"\"\n            00 00 00 99 01 00 00 95 00 00 00 01 75 00 00 06\n            28 00 00 00 01 00 00 00 00 00 00 00 80 00 07 71\n            75 65 75 65 49 64 00 00 00 01 36 00 08 54 52 41\n            43 45 5f 4f 4e 00 00 00 04 74 72 75 65 00 0a 4d\n            53 47 5f 52 45 47 49 4f 4e 00 00 00 0d 44 65 66\n            61 75 6c 74 52 65 67 69 6f 6e 00 05 6d 73 67 49\n            64 00 00 00 20 30 41 30 39 30 46 32 38 30 30 30\n            30 32 41 39 46 30 30 30 30 32 32 39 45 38 39 37\n            43 46 33 38 32 00 0b 71 75 65 75 65 4f 66 66 73\n            65 74 00 00 00 07 36 33 37 39 38 38 31\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)ResponseCode.SUCCESS, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.ROCKETMQ + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.NotEmpty(ext);\n        Assert.Equal(5, ext.Count);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void GetRouteInfo_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 00 8a 00 00 00 86 7b 22 63 6f 64 65 22 3a\n            31 30 35 2c 22 65 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 74 6f 70 69 63 22 3a 22 54 65 5a 5f 54 65\n            73 74 5f 4c 6e 67 22 7d 2c 22 66 6c 61 67 22 3a\n            30 2c 22 6c 61 6e 67 75 61 67 65 22 3a 22 4a 41\n            56 41 22 2c 22 6f 70 61 71 75 65 22 3a 30 2c 22\n            73 65 72 69 61 6c 69 7a 65 54 79 70 65 43 75 72\n            72 65 6e 74 52 50 43 22 3a 22 4a 53 4f 4e 22 2c\n            22 76 65 72 73 69 6f 6e 22 3a 34 35 33 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":105,\"extFields\":{\"topic\":\"TeZ_Test_Lng\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":0,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.GET_ROUTEINTO_BY_TOPIC, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Single(ext);\n        Assert.Equal(\"TeZ_Test_Lng\", ext[\"topic\"]);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void DecodeRouteInfo_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 01 6a 00 00 00 5f 7b 22 63 6f 64 65 22 3a\n            30 2c 22 66 6c 61 67 22 3a 31 2c 22 6c 61 6e 67\n            75 61 67 65 22 3a 22 4a 41 56 41 22 2c 22 6f 70\n            61 71 75 65 22 3a 30 2c 22 73 65 72 69 61 6c 69\n            7a 65 54 79 70 65 43 75 72 72 65 6e 74 52 50 43\n            22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f\n            6e 22 3a 34 35 33 7d 7b 22 62 72 6f 6b 65 72 44\n            61 74 61 73 22 3a 5b 7b 22 62 72 6f 6b 65 72 41\n            64 64 72 73 22 3a 7b 22 30 22 3a 22 31 30 2e 32\n            2e 33 2e 31 31 37 3a 31 30 39 31 31 22 7d 2c 22\n            62 72 6f 6b 65 72 4e 61 6d 65 22 3a 22 62 72 6f\n            6b 65 72 2d 61 22 2c 22 63 6c 75 73 74 65 72 22\n            3a 22 44 65 66 61 75 6c 74 43 6c 75 73 74 65 72\n            22 2c 22 65 6e 61 62 6c 65 41 63 74 69 6e 67 4d\n            61 73 74 65 72 22 3a 66 61 6c 73 65 7d 5d 2c 22\n            66 69 6c 74 65 72 53 65 72 76 65 72 54 61 62 6c\n            65 22 3a 7b 7d 2c 22 71 75 65 75 65 44 61 74 61\n            73 22 3a 5b 7b 22 62 72 6f 6b 65 72 4e 61 6d 65\n            22 3a 22 62 72 6f 6b 65 72 2d 61 22 2c 22 70 65\n            72 6d 22 3a 36 2c 22 72 65 61 64 51 75 65 75 65\n            4e 75 6d 73 22 3a 38 2c 22 74 6f 70 69 63 53 79\n            73 46 6c 61 67 22 3a 30 2c 22 77 72 69 74 65 51\n            75 65 75 65 4e 75 6d 73 22 3a 38 7d 5d 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.True(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":0,\"flag\":1,\"language\":\"JAVA\",\"opaque\":0,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal(0, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Empty(ext);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.2.3.117:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n        var json = pk.ToStr();\n        Assert.Equal(target, json);\n\n        var dic = cmd.ReadBodyAsJson();\n        Assert.True(dic.ContainsKey(\"brokerDatas\"));\n        Assert.True(dic.ContainsKey(\"queueDatas\"));\n    }\n\n    [Fact]\n    public void GetRouteInfo_v520_Dotnet()\n    {\n        var data = \"\"\"\n            00 00 00 a5 00 00 00 a1 7b 22 63 6f 64 65 22 3a\n            31 30 35 2c 22 65 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 74 6f 70 69 63 22 3a 22 54 65 5a 5f 54 65\n            73 74 5f 4c 6e 67 22 7d 2c 22 6c 61 6e 67 75 61\n            67 65 22 3a 22 44 4f 54 4e 45 54 22 2c 22 6f 70\n            61 71 75 65 22 3a 31 2c 22 73 65 72 69 61 6c 69\n            7a 65 54 79 70 65 43 75 72 72 65 6e 74 52 50 43\n            22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f\n            6e 22 3a 33 37 33 2c 22 72 65 6d 61 72 6b 22 3a\n            22 47 45 54 5f 52 4f 55 54 45 49 4e 54 4f 5f 42\n            59 5f 54 4f 50 49 43 22 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":105,\"extFields\":{\"topic\":\"TeZ_Test_Lng\"},\"language\":\"DOTNET\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":373,\"remark\":\"GET_ROUTEINTO_BY_TOPIC\"}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.GET_ROUTEINTO_BY_TOPIC, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.DOTNET + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        //Assert.Null(header.Remark);\n        Assert.Equal(\"GET_ROUTEINTO_BY_TOPIC\", header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Single(ext);\n        Assert.Equal(\"TeZ_Test_Lng\", ext[\"topic\"]);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void DecodeRouteInfo_v520_Dotnet()\n    {\n        var data = \"\"\"\n            00 00 01 68 00 00 00 5f 7b 22 63 6f 64 65 22 3a\n            30 2c 22 66 6c 61 67 22 3a 31 2c 22 6c 61 6e 67\n            75 61 67 65 22 3a 22 4a 41 56 41 22 2c 22 6f 70\n            61 71 75 65 22 3a 31 2c 22 73 65 72 69 61 6c 69\n            7a 65 54 79 70 65 43 75 72 72 65 6e 74 52 50 43\n            22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f\n            6e 22 3a 34 35 33 7d 7b 22 62 72 6f 6b 65 72 44\n            61 74 61 73 22 3a 5b 7b 22 62 72 6f 6b 65 72 41\n            64 64 72 73 22 3a 7b 30 3a 22 31 30 2e 32 2e 33\n            2e 31 31 37 3a 31 30 39 31 31 22 7d 2c 22 62 72\n            6f 6b 65 72 4e 61 6d 65 22 3a 22 62 72 6f 6b 65\n            72 2d 61 22 2c 22 63 6c 75 73 74 65 72 22 3a 22\n            44 65 66 61 75 6c 74 43 6c 75 73 74 65 72 22 2c\n            22 65 6e 61 62 6c 65 41 63 74 69 6e 67 4d 61 73\n            74 65 72 22 3a 66 61 6c 73 65 7d 5d 2c 22 66 69\n            6c 74 65 72 53 65 72 76 65 72 54 61 62 6c 65 22\n            3a 7b 7d 2c 22 71 75 65 75 65 44 61 74 61 73 22\n            3a 5b 7b 22 62 72 6f 6b 65 72 4e 61 6d 65 22 3a\n            22 62 72 6f 6b 65 72 2d 61 22 2c 22 70 65 72 6d\n            22 3a 36 2c 22 72 65 61 64 51 75 65 75 65 4e 75\n            6d 73 22 3a 38 2c 22 74 6f 70 69 63 53 79 73 46\n            6c 61 67 22 3a 30 2c 22 77 72 69 74 65 51 75 65\n            75 65 4e 75 6d 73 22 3a 38 7d 5d 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.True(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":0,\"flag\":1,\"language\":\"JAVA\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal(0, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Empty(ext);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{0:\"10.2.3.117:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n        var json = pk.ToStr();\n        Assert.Equal(target, json);\n\n        var dic = cmd.ReadBodyAsJson();\n        Assert.True(dic.ContainsKey(\"brokerDatas\"));\n        Assert.True(dic.ContainsKey(\"queueDatas\"));\n    }\n\n    [Fact]\n    public void SendMessageV2_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 04 09 00 00 01 81 7b 22 63 6f 64 65 22 3a\n            33 31 30 2c 22 65 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 61 22 3a 22 52 30 31 5f 70 72 6f 64 75 63\n            65 72 5f 31 32 33 22 2c 22 62 22 3a 22 54 65 5a\n            5f 54 65 73 74 5f 4c 6e 67 22 2c 22 63 22 3a 22\n            54 42 57 31 30 32 22 2c 22 64 22 3a 22 34 22 2c\n            22 65 22 3a 22 30 22 2c 22 66 22 3a 22 30 22 2c\n            22 67 22 3a 22 31 37 34 39 38 38 33 37 37 38 36\n            38 36 22 2c 22 68 22 3a 22 30 22 2c 22 69 22 3a\n            22 55 4e 49 51 5f 4b 45 59 5c 75 30 30 30 31 32\n            34 30 30 44 44 30 32 31 30 30 38 30 30 31 35 32\n            42 32 42 37 44 30 34 31 39 44 36 44 44 35 45 39\n            45 46 43 31 38 42 34 41 41 43 32 34 36 32 31 32\n            41 37 44 30 30 30 30 5c 75 30 30 30 32 57 41 49\n            54 5c 75 30 30 30 31 74 72 75 65 5c 75 30 30 30\n            32 54 41 47 53 5c 75 30 30 30 31 2a 5c 75 30 30\n            30 32 22 2c 22 6a 22 3a 22 30 22 2c 22 6b 22 3a\n            22 66 61 6c 73 65 22 2c 22 6d 22 3a 22 66 61 6c\n            73 65 22 2c 22 6e 22 3a 22 62 72 6f 6b 65 72 2d\n            61 22 7d 2c 22 66 6c 61 67 22 3a 30 2c 22 6c 61\n            6e 67 75 61 67 65 22 3a 22 4a 41 56 41 22 2c 22\n            6f 70 61 71 75 65 22 3a 32 2c 22 73 65 72 69 61\n            6c 69 7a 65 54 79 70 65 43 75 72 72 65 6e 74 52\n            50 43 22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73\n            69 6f 6e 22 3a 34 35 33 7d 5b 31 2e 30 2c 31 35\n            2e 30 2c 31 2e 30 31 33 32 35 2c 30 2e 35 2c 35\n            37 32 2e 35 2c 31 2e 30 31 33 32 35 2c 31 35 2e\n            30 2c 31 2e 30 35 30 37 30 38 31 37 32 2c 32 39\n            39 39 2e 39 34 32 37 31 35 2c 31 2e 30 32 35 31\n            38 35 37 34 37 2c 32 2e 31 31 35 37 35 37 34 39\n            37 2c 39 30 2e 30 2c 31 2e 30 35 30 37 30 38 31\n            37 32 2c 32 2e 31 31 35 37 35 37 34 39 37 2c 34\n            2e 32 36 30 33 39 33 30 34 31 2c 35 37 32 2e 35\n            2c 34 2e 32 36 30 33 39 33 30 34 31 2c 34 32 33\n            2e 30 2c 31 38 2e 31 39 2c 31 31 31 31 2e 30 2c\n            31 2e 30 2c 31 35 2e 30 2c 31 2e 30 31 33 32 35\n            2c 30 2e 35 2c 31 38 2e 36 30 38 32 35 38 31 39\n            2c 31 2e 30 31 33 32 35 2c 31 35 2e 30 2c 31 2e\n            30 35 30 37 30 38 31 37 32 2c 32 39 39 39 2e 39\n            34 32 37 31 35 2c 31 2e 30 32 35 31 38 35 37 34\n            37 2c 32 2e 31 31 35 37 35 37 34 39 37 2c 39 30\n            2e 30 2c 31 2e 30 35 30 37 30 38 31 37 32 2c 32\n            2e 31 31 35 37 35 37 34 39 37 2c 34 2e 32 36 30\n            33 39 33 30 34 31 2c 31 38 2e 36 30 38 32 35 38\n            31 39 2c 34 32 33 2e 30 2c 34 32 33 2e 30 2c 31\n            38 2e 31 39 2c 31 31 31 31 2e 30 2c 31 2e 30 2c\n            31 35 2e 30 2c 31 2e 30 31 33 32 35 2c 30 2e 35\n            2c 31 38 2e 36 30 38 32 35 38 31 39 2c 31 2e 30\n            31 33 32 35 2c 31 35 2e 30 2c 31 2e 30 35 30 37\n            30 38 31 37 32 2c 32 39 39 39 2e 39 34 32 37 31\n            35 2c 31 2e 30 32 35 31 38 35 37 34 37 2c 32 2e\n            31 31 35 37 35 37 34 39 37 2c 39 30 2e 30 2c 31\n            2e 30 35 30 37 30 38 31 37 32 2c 32 2e 31 31 35\n            37 35 37 34 39 37 2c 34 2e 32 36 30 33 39 33 30\n            34 31 2c 35 37 32 2e 35 2c 32 39 39 39 2e 39 34\n            32 37 31 35 2c 34 32 33 2e 30 2c 31 38 2e 31 39\n            2c 39 30 2e 30 2c 31 2e 30 2c 31 35 2e 30 2c 31\n            2e 30 31 33 32 35 2c 30 2e 35 2c 31 38 2e 36 30\n            38 32 35 38 31 39 2c 31 2e 30 31 33 32 35 2c 31\n            35 2e 30 2c 31 2e 30 35 30 37 30 38 31 37 32 2c\n            32 39 39 39 2e 39 34 32 37 31 35 2c 31 2e 30 32\n            35 31 38 35 37 34 37 2c 32 2e 31 31 35 37 35 37\n            34 39 37 2c 39 30 2e 30 2c 31 2e 30 35 30 37 30\n            38 31 37 32 2c 32 2e 31 31 35 37 35 37 34 39 37\n            2c 31 2e 30 32 35 31 38 35 37 34 37 5d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":310,\"extFields\":{\"a\":\"R01_producer_123\",\"b\":\"TeZ_Test_Lng\",\"c\":\"TBW102\",\"d\":\"4\",\"e\":\"0\",\"f\":\"0\",\"g\":\"1749883778686\",\"h\":\"0\",\"i\":\"UNIQ_KEY\\u00012400DD02100800152B2B7D0419D6DD5E9EFC18B4AAC246212A7D0000\\u0002WAIT\\u0001true\\u0002TAGS\\u0001*\\u0002\",\"j\":\"0\",\"k\":\"false\",\"m\":\"false\",\"n\":\"broker-a\"},\"flag\":0,\"language\":\"JAVA\",\"opaque\":2,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.SEND_MESSAGE_V2, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Equal(13, ext.Count);\n        Assert.Equal(\"R01_producer_123\", ext[\"a\"]);\n        Assert.Equal(\"TeZ_Test_Lng\", ext[\"b\"]);\n        Assert.Equal(\"TBW102\", ext[\"c\"]);\n        Assert.Equal(\"UNIQ_KEY\\u00012400DD02100800152B2B7D0419D6DD5E9EFC18B4AAC246212A7D0000\\u0002WAIT\\u0001true\\u0002TAGS\\u0001*\\u0002\", ext[\"i\"]);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var json = pk.ToStr();\n        Assert.NotEmpty(json);\n    }\n\n    [Fact]\n    public void DecodeSendMessageV2_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 01 36 00 00 01 32 7b 22 63 6f 64 65 22 3a\n            30 2c 22 65 78 74 46 69 65 6c 64 73 22 3a 7b 22\n            71 75 65 75 65 49 64 22 3a 22 30 22 2c 22 74 72\n            61 6e 73 61 63 74 69 6f 6e 49 64 22 3a 22 32 34\n            30 30 44 44 30 32 31 30 30 38 30 30 31 35 32 42\n            32 42 37 44 30 34 31 39 44 36 44 44 35 45 39 45\n            46 43 31 38 42 34 41 41 43 32 34 36 32 31 32 41\n            37 44 30 30 30 30 22 2c 22 6d 73 67 49 64 22 3a\n            22 30 41 30 32 30 33 37 35 30 30 30 30 32 41 39\n            46 30 30 30 30 30 30 30 30 30 31 45 43 31 38 46\n            43 22 2c 22 54 52 41 43 45 5f 4f 4e 22 3a 22 74\n            72 75 65 22 2c 22 4d 53 47 5f 52 45 47 49 4f 4e\n            22 3a 22 44 65 66 61 75 6c 74 52 65 67 69 6f 6e\n            22 2c 22 71 75 65 75 65 4f 66 66 73 65 74 22 3a\n            22 30 22 7d 2c 22 66 6c 61 67 22 3a 31 2c 22 6c\n            61 6e 67 75 61 67 65 22 3a 22 4a 41 56 41 22 2c\n            22 6f 70 61 71 75 65 22 3a 32 2c 22 73 65 72 69\n            61 6c 69 7a 65 54 79 70 65 43 75 72 72 65 6e 74\n            52 50 43 22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72\n            73 69 6f 6e 22 3a 34 35 33 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.True(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":0,\"extFields\":{\"queueId\":\"0\",\"transactionId\":\"2400DD02100800152B2B7D0419D6DD5E9EFC18B4AAC246212A7D0000\",\"msgId\":\"0A02037500002A9F0000000001EC18FC\",\"TRACE_ON\":\"true\",\"MSG_REGION\":\"DefaultRegion\",\"queueOffset\":\"0\"},\"flag\":1,\"language\":\"JAVA\",\"opaque\":2,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal(0, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Equal(6, ext.Count);\n        Assert.Equal(\"0\", ext[\"queueId\"]);\n        Assert.Equal(\"2400DD02100800152B2B7D0419D6DD5E9EFC18B4AAC246212A7D0000\", ext[\"transactionId\"]);\n        Assert.Equal(\"0A02037500002A9F0000000001EC18FC\", ext[\"msgId\"]);\n        Assert.Equal(\"true\", ext[\"TRACE_ON\"]);\n        Assert.Equal(\"DefaultRegion\", ext[\"MSG_REGION\"]);\n        Assert.Equal(\"0\", ext[\"queueOffset\"]);\n\n        var result = new SendResult();\n        result.Read(ext);\n        Assert.Equal(\"2400DD02100800152B2B7D0419D6DD5E9EFC18B4AAC246212A7D0000\", result.TransactionId);\n        Assert.Equal(\"0A02037500002A9F0000000001EC18FC\", result.MsgId);\n        Assert.Equal(\"DefaultRegion\", result.RegionId);\n        Assert.Equal(0, result.QueueOffset);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void SendMessageV2_v520_Dotnet()\n    {\n        var data = \"\"\"\n            00 00 01 31 00 00 01 1a 7b 22 63 6f 64 65 22 3a\n            33 31 30 2c 22 65 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 61 22 3a 22 52 30 31 5f 70 72 6f 64 75 63\n            65 72 5f 31 32 33 22 2c 22 62 22 3a 22 54 65 5a\n            5f 54 65 73 74 5f 4c 6e 67 22 2c 22 63 22 3a 22\n            54 42 57 31 30 32 22 2c 22 64 22 3a 22 34 22 2c\n            22 65 22 3a 22 30 22 2c 22 66 22 3a 22 30 22 2c\n            22 67 22 3a 22 31 37 34 39 38 32 36 35 32 39 37\n            31 36 22 2c 22 68 22 3a 22 30 22 2c 22 69 22 3a\n            22 54 41 47 53 01 2a 02 57 41 49 54 01 54 72 75\n            65 02 22 2c 22 6a 22 3a 22 30 22 2c 22 6b 22 3a\n            22 46 61 6c 73 65 22 7d 2c 22 6c 61 6e 67 75 61\n            67 65 22 3a 22 44 4f 54 4e 45 54 22 2c 22 6f 70\n            61 71 75 65 22 3a 31 2c 22 73 65 72 69 61 6c 69\n            7a 65 54 79 70 65 43 75 72 72 65 6e 74 52 50 43\n            22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f\n            6e 22 3a 33 37 33 2c 22 72 65 6d 61 72 6b 22 3a\n            22 53 45 4e 44 5f 4d 45 53 53 41 47 45 5f 56 32\n            22 7d 32 30 32 35 2d 30 36 2d 31 33 20 32 32 3a\n            35 34 3a 31 32\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":310,\"extFields\":{\"a\":\"R01_producer_123\",\"b\":\"TeZ_Test_Lng\",\"c\":\"TBW102\",\"d\":\"4\",\"e\":\"0\",\"f\":\"0\",\"g\":\"1749826529716\",\"h\":\"0\",\"i\":\"TAGS\u0001*\u0002WAIT\u0001True\u0002\",\"j\":\"0\",\"k\":\"False\"},\"language\":\"DOTNET\",\"opaque\":1,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":373,\"remark\":\"SEND_MESSAGE_V2\"}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.SEND_MESSAGE_V2, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.DOTNET + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n        //Assert.Null(header.Remark);\n        Assert.Equal(\"SEND_MESSAGE_V2\", header.Remark);\n\n        var ext = header.GetExtFields();\n        //Assert.Equal(13, ext.Count);\n        Assert.Equal(\"R01_producer_123\", ext[\"a\"]);\n        Assert.Equal(\"TeZ_Test_Lng\", ext[\"b\"]);\n        Assert.Equal(\"TBW102\", ext[\"c\"]);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var json = pk.ToStr();\n        Assert.Equal(\"2025-06-13 22:54:12\", json);\n    }\n\n    [Fact]\n    public void SendMessageV2_v520_Dotnet2()\n    {\n        var data = \"\"\"\n            00 00 01 45 00 00 01 2e 7b 22 43 6f 64 65 22 3a\n            33 31 30 2c 22 45 78 74 46 69 65 6c 64 73 22 3a\n            7b 22 61 22 3a 22 52 30 31 5f 70 72 6f 64 75 63\n            65 72 5f 31 32 33 22 2c 22 62 22 3a 22 54 65 5a\n            5f 54 65 73 74 5f 4c 6e 67 22 2c 22 63 22 3a 22\n            54 42 57 31 30 32 22 2c 22 64 22 3a 22 34 22 2c\n            22 65 22 3a 22 30 22 2c 22 66 22 3a 22 30 22 2c\n            22 67 22 3a 22 31 37 34 39 39 32 33 33 35 32 34\n            38 36 22 2c 22 68 22 3a 22 30 22 2c 22 69 22 3a\n            22 54 41 47 53 5c 75 30 30 30 31 2a 5c 75 30 30\n            30 32 57 41 49 54 5c 75 30 30 30 31 54 72 75 65\n            5c 75 30 30 30 32 22 2c 22 6a 22 3a 22 30 22 2c\n            22 6b 22 3a 22 46 61 6c 73 65 22 7d 2c 22 4c 61\n            6e 67 75 61 67 65 22 3a 22 44 4f 54 4e 45 54 22\n            2c 22 4f 70 61 71 75 65 22 3a 31 2c 22 53 65 72\n            69 61 6c 69 7a 65 54 79 70 65 43 75 72 72 65 6e\n            74 52 50 43 22 3a 22 4a 53 4f 4e 22 2c 22 56 65\n            72 73 69 6f 6e 22 3a 34 35 33 2c 22 52 65 6d 61\n            72 6b 22 3a 22 53 45 4e 44 5f 4d 45 53 53 41 47\n            45 5f 56 32 22 7d 32 30 32 35 2d 30 36 2d 31 35\n            20 30 31 3a 34 39 3a 30 39\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"Code\":310,\"ExtFields\":{\"a\":\"R01_producer_123\",\"b\":\"TeZ_Test_Lng\",\"c\":\"TBW102\",\"d\":\"4\",\"e\":\"0\",\"f\":\"0\",\"g\":\"1749923352486\",\"h\":\"0\",\"i\":\"TAGS\\u0001*\\u0002WAIT\\u0001True\\u0002\",\"j\":\"0\",\"k\":\"False\"},\"Language\":\"DOTNET\",\"Opaque\":1,\"SerializeTypeCurrentRPC\":\"JSON\",\"Version\":453,\"Remark\":\"SEND_MESSAGE_V2\"}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.SEND_MESSAGE_V2, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.DOTNET + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        //Assert.Null(header.Remark);\n        Assert.Equal(\"SEND_MESSAGE_V2\", header.Remark);\n\n        var ext = header.GetExtFields();\n        //Assert.Equal(13, ext.Count);\n        Assert.Equal(\"R01_producer_123\", ext[\"a\"]);\n        Assert.Equal(\"TeZ_Test_Lng\", ext[\"b\"]);\n        Assert.Equal(\"TBW102\", ext[\"c\"]);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var json = pk.ToStr();\n        Assert.Equal(\"2025-06-15 01:49:09\", json);\n    }\n\n    [Fact]\n    public void HeartBeat_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 01 3c 00 00 00 6f 7b 22 63 6f 64 65 22 3a\n            33 34 2c 22 65 78 74 46 69 65 6c 64 73 22 3a 7b\n            7d 2c 22 66 6c 61 67 22 3a 30 2c 22 6c 61 6e 67\n            75 61 67 65 22 3a 22 4a 41 56 41 22 2c 22 6f 70\n            61 71 75 65 22 3a 33 2c 22 73 65 72 69 61 6c 69\n            7a 65 54 79 70 65 43 75 72 72 65 6e 74 52 50 43\n            22 3a 22 4a 53 4f 4e 22 2c 22 76 65 72 73 69 6f\n            6e 22 3a 34 35 33 7d 7b 22 63 6c 69 65 6e 74 49\n            44 22 3a 22 31 30 2e 31 2e 35 2e 39 40 34 30 37\n            30 30 23 39 35 35 32 32 30 30 38 37 38 32 32 35\n            30 30 22 2c 22 63 6f 6e 73 75 6d 65 72 44 61 74\n            61 53 65 74 22 3a 5b 5d 2c 22 68 65 61 72 74 62\n            65 61 74 46 69 6e 67 65 72 70 72 69 6e 74 22 3a\n            30 2c 22 70 72 6f 64 75 63 65 72 44 61 74 61 53\n            65 74 22 3a 5b 7b 22 67 72 6f 75 70 4e 61 6d 65\n            22 3a 22 43 4c 49 45 4e 54 5f 49 4e 4e 45 52 5f\n            50 52 4f 44 55 43 45 52 22 7d 2c 7b 22 67 72 6f\n            75 70 4e 61 6d 65 22 3a 22 52 30 31 5f 70 72 6f\n            64 75 63 65 72 5f 31 32 33 22 7d 5d 2c 22 77 69\n            74 68 6f 75 74 53 75 62 22 3a 66 61 6c 73 65 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.False(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":34,\"extFields\":{},\"flag\":0,\"language\":\"JAVA\",\"opaque\":3,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal((Int32)RequestCode.HEART_BEAT, header.Code);\n        Assert.Equal(0, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Empty(ext);\n\n        var pk = cmd.Payload;\n        Assert.NotNull(pk);\n\n        var json = pk.ToStr();\n        Assert.NotEmpty(json);\n        Assert.Equal(\"\"\"\n            {\"clientID\":\"10.1.5.9@40700#955220087822500\",\"consumerDataSet\":[],\"heartbeatFingerprint\":0,\"producerDataSet\":[{\"groupName\":\"CLIENT_INNER_PRODUCER\"},{\"groupName\":\"R01_producer_123\"}],\"withoutSub\":false}\n            \"\"\", json);\n    }\n\n    [Fact]\n    public void DecodeHeartBeat_v520_Java()\n    {\n        var data = \"\"\"\n            00 00 00 aa 00 00 00 a6 7b 22 63 6f 64 65 22 3a\n            30 2c 22 65 78 74 46 69 65 6c 64 73 22 3a 7b 22\n            49 53 5f 53 55 50 50 4f 52 54 5f 48 45 41 52 54\n            5f 42 45 41 54 5f 56 32 22 3a 22 74 72 75 65 22\n            2c 22 49 53 5f 53 55 42 5f 43 48 41 4e 47 45 22\n            3a 22 74 72 75 65 22 7d 2c 22 66 6c 61 67 22 3a\n            31 2c 22 6c 61 6e 67 75 61 67 65 22 3a 22 4a 41\n            56 41 22 2c 22 6f 70 61 71 75 65 22 3a 33 2c 22\n            73 65 72 69 61 6c 69 7a 65 54 79 70 65 43 75 72\n            72 65 6e 74 52 50 43 22 3a 22 4a 53 4f 4e 22 2c\n            22 76 65 72 73 69 6f 6e 22 3a 34 35 33 7d\n            \"\"\";\n        var ms = new MemoryStream(data.ToHex());\n\n        var cmd = new Command();\n        var rs = cmd.Read(ms);\n        Assert.True(rs);\n        Assert.True(cmd.Reply);\n        Assert.Equal(\"\"\"\n            {\"code\":0,\"extFields\":{\"IS_SUPPORT_HEART_BEAT_V2\":\"true\",\"IS_SUB_CHANGE\":\"true\"},\"flag\":1,\"language\":\"JAVA\",\"opaque\":3,\"serializeTypeCurrentRPC\":\"JSON\",\"version\":453}\n            \"\"\", cmd.RawJson);\n\n        var header = cmd.Header;\n        Assert.NotNull(header);\n        Assert.Equal(0, header.Code);\n        Assert.Equal(1, header.Flag);\n        Assert.Equal(LanguageCode.JAVA + \"\", header.Language);\n        Assert.Equal(SerializeType.JSON + \"\", header.SerializeTypeCurrentRPC);\n        Assert.Equal(MQVersion.V5_2_0, header.Version);\n        Assert.Null(header.Remark);\n\n        var ext = header.GetExtFields();\n        Assert.Equal(2, ext.Count);\n        Assert.Equal(\"true\", ext[\"IS_SUPPORT_HEART_BEAT_V2\"]);\n        Assert.Equal(\"true\", ext[\"IS_SUB_CHANGE\"]);\n\n        var pk = cmd.Payload;\n        Assert.Null(pk);\n    }\n\n    [Fact]\n    public void CreateHeader_TransactionMessage_SetsPreparedFlag()\n    {\n        var producer = new Producer { Topic = \"nx_test\", Group = \"nx_group\" };\n        var message = new Message();\n        message.SetBody(\"hello\");\n        message.Properties[\"TRAN_MSG\"] = \"true\";\n        message.Properties[\"PGROUP\"] = \"nx_group\";\n\n        var method = typeof(Producer).GetMethod(\"CreateHeader\", BindingFlags.Instance | BindingFlags.NonPublic);\n        Assert.NotNull(method);\n        var header = (SendMessageRequestHeader)method.Invoke(producer, new Object[] { message });\n\n        Assert.Equal((Int32)TransactionState.Prepared, header.SysFlag);\n    }\n\n    [Fact]\n    public void EndTransactionRequestHeader_ToProperties_UsesCamelCase()\n    {\n        var header = new EndTransactionRequestHeader\n        {\n            ProducerGroup = \"nx_group\",\n            TranStateTableOffset = 11,\n            CommitLogOffset = 22,\n            CommitOrRollback = (Int32)TransactionState.Commit,\n            FromTransactionCheck = false,\n            MsgId = \"msg_1\",\n            TransactionId = \"tx_1\",\n        };\n\n        var ext = header.GetProperties();\n\n        Assert.Equal(\"nx_group\", ext[\"producerGroup\"]);\n        Assert.Equal(\"11\", ext[\"tranStateTableOffset\"]?.ToString());\n        Assert.Equal(\"22\", ext[\"commitLogOffset\"]?.ToString());\n        Assert.Equal(\"8\", ext[\"commitOrRollback\"]?.ToString());\n        Assert.Equal(\"False\", ext[\"fromTransactionCheck\"]?.ToString());\n        Assert.Equal(\"msg_1\", ext[\"msgId\"]);\n        Assert.Equal(\"tx_1\", ext[\"transactionId\"]);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/CompressionTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消息压缩功能测试</summary>\npublic class CompressionTests\n{\n    [Fact]\n    [DisplayName(\"压缩阈值_默认值为4096\")]\n    public void CompressOverBytes_DefaultValue()\n    {\n        using var producer = new Producer();\n        Assert.Equal(4096, producer.CompressOverBytes);\n    }\n\n    [Fact]\n    [DisplayName(\"压缩阈值_可自定义设置\")]\n    public void CompressOverBytes_CanBeSet()\n    {\n        using var producer = new Producer\n        {\n            CompressOverBytes = 1024,\n        };\n        Assert.Equal(1024, producer.CompressOverBytes);\n    }\n\n    [Fact]\n    [DisplayName(\"压缩阈值_设为0禁用压缩\")]\n    public void CompressOverBytes_ZeroDisablesCompression()\n    {\n        using var producer = new Producer\n        {\n            CompressOverBytes = 0,\n        };\n        Assert.Equal(0, producer.CompressOverBytes);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ConcurrentConsumeTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消费限流测试</summary>\npublic class ConcurrentConsumeTests\n{\n    [Fact]\n    [DisplayName(\"MaxConcurrentConsume_默认为0\")]\n    public void MaxConcurrentConsume_DefaultZero()\n    {\n        using var consumer = new Consumer();\n        Assert.Equal(0, consumer.MaxConcurrentConsume);\n    }\n\n    [Fact]\n    [DisplayName(\"MaxConcurrentConsume_可设置正整数\")]\n    public void MaxConcurrentConsume_CanSetPositive()\n    {\n        using var consumer = new Consumer { MaxConcurrentConsume = 10 };\n        Assert.Equal(10, consumer.MaxConcurrentConsume);\n    }\n\n    [Fact]\n    [DisplayName(\"MaxConcurrentConsume_设为1时串行消费\")]\n    public void MaxConcurrentConsume_Serial()\n    {\n        using var consumer = new Consumer { MaxConcurrentConsume = 1 };\n        Assert.Equal(1, consumer.MaxConcurrentConsume);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ConsumeStatsTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消费统计和过滤服务器测试</summary>\npublic class ConsumeStatsTests\n{\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"GetConsumeStats_获取消费统计\")]\n    public void GetConsumeStats_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        var stats = mq.GetConsumeStats(\"CID_nx_test\");\n        // 不做严格断言，仅验证不抛出异常\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"GetTopicStatsInfo_获取Topic统计\")]\n    public void GetTopicStatsInfo_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        var stats = mq.GetTopicStatsInfo(\"nx_test\");\n        // 不做严格断言，仅验证不抛出异常\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"RegisterFilterServer_注册过滤服务器\")]\n    public void RegisterFilterServer_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        var count = mq.RegisterFilterServer(\"127.0.0.1:9999\");\n        Assert.True(count >= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"RegisterFilterServer_空地址抛出异常\")]\n    public void RegisterFilterServer_EmptyAddress_ThrowsException()\n    {\n        using var mq = new Producer();\n        Assert.Throws<ArgumentNullException>(() => mq.RegisterFilterServer(null));\n        Assert.Throws<ArgumentNullException>(() => mq.RegisterFilterServer(\"\"));\n    }\n\n    [Fact]\n    [DisplayName(\"RequestCode包含过滤服务器和统计相关码\")]\n    public void RequestCode_ContainsFilterAndStatsCodes()\n    {\n        Assert.Equal(301, (Int32)RequestCode.REGISTER_FILTER_SERVER);\n        Assert.Equal(208, (Int32)RequestCode.GET_CONSUME_STATS);\n        Assert.Equal(202, (Int32)RequestCode.GET_TOPIC_STATS_INFO);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ConsumerStatesModelTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol.ConsumerStates;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消费者状态模型测试</summary>\npublic class ConsumerStatesModelTests\n{\n    #region ConsumerStatesModel\n    [Fact]\n    [DisplayName(\"ConsumerStatesModel_默认值\")]\n    public void ConsumerStatesModel_Defaults()\n    {\n        var model = new ConsumerStatesModel();\n\n        Assert.Equal(0, model.ConsumeTps);\n        Assert.Null(model.OffsetTable);\n    }\n\n    [Fact]\n    [DisplayName(\"ConsumerStatesModel_设置属性\")]\n    public void ConsumerStatesModel_SetProperties()\n    {\n        var mqModel = new MessageQueueModel { BrokerName = \"broker-a\", QueueId = 0 };\n        var model = new ConsumerStatesModel\n        {\n            ConsumeTps = 1500.5,\n            OffsetTable = new Dictionary<MessageQueueModel, OffsetWrapperModel>\n            {\n                [mqModel] = new OffsetWrapperModel { BrokerOffset = 100, ConsumerOffset = 90 }\n            }\n        };\n\n        Assert.Equal(1500.5, model.ConsumeTps);\n        Assert.NotNull(model.OffsetTable);\n        Assert.Single(model.OffsetTable);\n        Assert.Equal(100, model.OffsetTable[mqModel].BrokerOffset);\n    }\n    #endregion\n\n    #region MessageQueueModel\n    [Fact]\n    [DisplayName(\"MessageQueueModel_默认值\")]\n    public void MessageQueueModel_Defaults()\n    {\n        var model = new MessageQueueModel();\n\n        Assert.Null(model.BrokerName);\n        Assert.Equal(0, model.QueueId);\n        Assert.Null(model.Topic);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageQueueModel_设置属性\")]\n    public void MessageQueueModel_SetProperties()\n    {\n        var model = new MessageQueueModel\n        {\n            BrokerName = \"broker-a\",\n            QueueId = 3,\n            Topic = \"test_topic\",\n        };\n\n        Assert.Equal(\"broker-a\", model.BrokerName);\n        Assert.Equal(3, model.QueueId);\n        Assert.Equal(\"test_topic\", model.Topic);\n    }\n    #endregion\n\n    #region OffsetWrapperModel\n    [Fact]\n    [DisplayName(\"OffsetWrapperModel_默认值\")]\n    public void OffsetWrapperModel_Defaults()\n    {\n        var model = new OffsetWrapperModel();\n\n        Assert.Equal(0, model.BrokerOffset);\n        Assert.Equal(0, model.ConsumerOffset);\n        Assert.Equal(0, model.LastTimestamp);\n        Assert.Equal(0, model.PullOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"OffsetWrapperModel_设置所有属性\")]\n    public void OffsetWrapperModel_SetProperties()\n    {\n        var model = new OffsetWrapperModel\n        {\n            BrokerOffset = 1000,\n            ConsumerOffset = 900,\n            LastTimestamp = 1234567890L,\n            PullOffset = 950,\n        };\n\n        Assert.Equal(1000, model.BrokerOffset);\n        Assert.Equal(900, model.ConsumerOffset);\n        Assert.Equal(1234567890L, model.LastTimestamp);\n        Assert.Equal(950, model.PullOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"OffsetWrapperModel_偏移差计算\")]\n    public void OffsetWrapperModel_OffsetDifference()\n    {\n        var model = new OffsetWrapperModel\n        {\n            BrokerOffset = 1000,\n            ConsumerOffset = 800,\n        };\n\n        var diff = model.BrokerOffset - model.ConsumerOffset;\n        Assert.Equal(200, diff);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ConsumerTests.cs",
    "content": "﻿using NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing System;\nusing System.Linq;\nusing System.Threading;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class ConsumerTests\n{\n    private static Consumer _consumer;\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public static void ConsumeTest()\n    {\n        var set = BasicTest.GetConfig();\n        var consumer = new Consumer\n        {\n            Topic = \"nx_test\",\n            Group = \"test\",\n            NameServerAddress = set.NameServer,\n\n            FromLastOffset = true,\n            BatchSize = 20,\n\n            Log = XTrace.Log,\n        };\n\n        consumer.OnConsume = OnConsume;\n        consumer.Start();\n\n        _consumer = consumer;\n\n        Thread.Sleep(3000);\n        //foreach (var item in consumer.Clients)\n        //{\n        //    var rs = item.GetRuntimeInfo();\n        //    Console.WriteLine(\"{0}\\t{1}\", item.Name, rs[\"brokerVersionDesc\"]);\n        //}\n    }\n\n    private static Boolean OnConsume(MessageQueue q, MessageExt[] ms)\n    {\n        Console.WriteLine(\"[{0}@{1}]收到消息[{2}]\", q.BrokerName, q.QueueId, ms.Length);\n\n        foreach (var item in ms.ToList())\n        {\n            Console.WriteLine($\"消息：主键【{item.Keys}】，产生时间【{item.BornTimestamp.ToDateTime()}】，内容【{item.Body.ToStr()}】\");\n        }\n\n        return true;\n    }\n}"
  },
  {
    "path": "XUnitTestRocketMQ/HeaderTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Header协议头测试</summary>\npublic class HeaderTests\n{\n    #region 默认值\n    [Fact]\n    [DisplayName(\"Header_默认Language为CPP\")]\n    public void Header_DefaultLanguage()\n    {\n        var header = new Header();\n\n        Assert.Equal(\"CPP\", header.Language);\n    }\n\n    [Fact]\n    [DisplayName(\"Header_默认SerializeType为JSON\")]\n    public void Header_DefaultSerializeType()\n    {\n        var header = new Header();\n\n        Assert.Equal(\"JSON\", header.SerializeTypeCurrentRPC);\n    }\n\n    [Fact]\n    [DisplayName(\"Header_默认Version为V4_8_0\")]\n    public void Header_DefaultVersion()\n    {\n        var header = new Header();\n\n        Assert.Equal(MQVersion.V4_8_0, header.Version);\n    }\n\n    [Fact]\n    [DisplayName(\"Header_默认ExtFields为null\")]\n    public void Header_DefaultExtFieldsNull()\n    {\n        var header = new Header();\n\n        Assert.Null(header.ExtFields);\n    }\n    #endregion\n\n    #region GetExtFields\n    [Fact]\n    [DisplayName(\"GetExtFields_首次调用创建空字典\")]\n    public void GetExtFields_CreatesNewDictionary()\n    {\n        var header = new Header();\n\n        var fields = header.GetExtFields();\n\n        Assert.NotNull(fields);\n        Assert.Empty(fields);\n    }\n\n    [Fact]\n    [DisplayName(\"GetExtFields_多次调用返回同一实例\")]\n    public void GetExtFields_ReturnsSameInstance()\n    {\n        var header = new Header();\n\n        var fields1 = header.GetExtFields();\n        var fields2 = header.GetExtFields();\n\n        Assert.Same(fields1, fields2);\n    }\n\n    [Fact]\n    [DisplayName(\"GetExtFields_已有ExtFields时返回原字典\")]\n    public void GetExtFields_ExistingFields_ReturnsSame()\n    {\n        var existing = new Dictionary<String, String> { [\"key\"] = \"value\" };\n        var header = new Header { ExtFields = existing };\n\n        var fields = header.GetExtFields();\n\n        Assert.Same(existing, fields);\n        Assert.Equal(\"value\", fields[\"key\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"GetExtFields_大小写不敏感\")]\n    public void GetExtFields_CaseInsensitive()\n    {\n        var header = new Header();\n\n        var fields = header.GetExtFields();\n        fields[\"TestKey\"] = \"hello\";\n\n        Assert.Equal(\"hello\", fields[\"testkey\"]);\n        Assert.Equal(\"hello\", fields[\"TESTKEY\"]);\n    }\n    #endregion\n\n    #region CreateException\n    [Fact]\n    [DisplayName(\"CreateException_基本异常创建\")]\n    public void CreateException_BasicCreation()\n    {\n        var header = new Header\n        {\n            Code = (Int32)ResponseCode.SYSTEM_ERROR,\n            Remark = \"Something went wrong\"\n        };\n\n        var ex = header.CreateException();\n\n        Assert.NotNull(ex);\n        Assert.Equal(ResponseCode.SYSTEM_ERROR, ex.Code);\n        Assert.Contains(\"Something went wrong\", ex.Message);\n    }\n\n    [Fact]\n    [DisplayName(\"CreateException_解析Exception后缀\")]\n    public void CreateException_ParsesExceptionSuffix()\n    {\n        var header = new Header\n        {\n            Code = (Int32)ResponseCode.TOPIC_NOT_EXIST,\n            Remark = \"org.apache.rocketmq.client.exception.MQClientException: No topic route info\"\n        };\n\n        var ex = header.CreateException();\n\n        Assert.Equal(ResponseCode.TOPIC_NOT_EXIST, ex.Code);\n        Assert.Contains(\"No topic route info\", ex.Message);\n    }\n\n    [Fact]\n    [DisplayName(\"CreateException_解析逗号后文本\")]\n    public void CreateException_ParsesCommaDelimited()\n    {\n        var header = new Header\n        {\n            Code = (Int32)ResponseCode.SYSTEM_ERROR,\n            Remark = \"SomeException: Error message, extra info\"\n        };\n\n        var ex = header.CreateException();\n\n        Assert.Contains(\"Error message\", ex.Message);\n        Assert.DoesNotContain(\"extra info\", ex.Message);\n    }\n\n    [Fact]\n    [DisplayName(\"CreateException_空Remark不抛异常\")]\n    public void CreateException_EmptyRemark_NoThrow()\n    {\n        var header = new Header\n        {\n            Code = (Int32)ResponseCode.SUCCESS,\n            Remark = null\n        };\n\n        var ex = header.CreateException();\n\n        Assert.NotNull(ex);\n        Assert.Equal(ResponseCode.SUCCESS, ex.Code);\n    }\n\n    [Fact]\n    [DisplayName(\"CreateException_无Exception关键字保持原文\")]\n    public void CreateException_NoExceptionKeyword_KeepsOriginal()\n    {\n        var header = new Header\n        {\n            Code = (Int32)ResponseCode.NO_PERMISSION,\n            Remark = \"Access denied for this topic\"\n        };\n\n        var ex = header.CreateException();\n\n        Assert.Contains(\"Access denied for this topic\", ex.Message);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/IPv6Tests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Net;\nusing NewLife.Data;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>IPv6消息解码测试</summary>\npublic class IPv6Tests\n{\n    /// <summary>写入大端序Int32</summary>\n    private static void WriteBigEndianInt32(MemoryStream ms, Int32 value)\n    {\n        ms.WriteByte((Byte)(value >> 24));\n        ms.WriteByte((Byte)(value >> 16));\n        ms.WriteByte((Byte)(value >> 8));\n        ms.WriteByte((Byte)value);\n    }\n\n    /// <summary>写入大端序Int64</summary>\n    private static void WriteBigEndianInt64(MemoryStream ms, Int64 value)\n    {\n        WriteBigEndianInt32(ms, (Int32)(value >> 32));\n        WriteBigEndianInt32(ms, (Int32)value);\n    }\n\n    /// <summary>写入大端序Int16</summary>\n    private static void WriteBigEndianInt16(MemoryStream ms, Int16 value)\n    {\n        ms.WriteByte((Byte)(value >> 8));\n        ms.WriteByte((Byte)value);\n    }\n\n    /// <summary>构造消息的二进制数据</summary>\n    private static Byte[] BuildMessageBinary(Boolean ipv6)\n    {\n        var ms = new MemoryStream();\n\n        var ipBytes = ipv6 ? IPAddress.IPv6Loopback.GetAddressBytes() : new Byte[] { 127, 0, 0, 1 };\n        // SysFlag: 第2位(0x04)标识IPv6\n        var sysFlag = ipv6 ? 4 : 0;\n\n        var body = \"hello\"u8.ToArray();\n        var topic = \"test_topic\"u8.ToArray();\n        var props = \"\"u8.ToArray();\n\n        // 计算StoreSize\n        var ipSize = ipv6 ? 16 : 4;\n        // StoreSize(4) + MagicCode(4) + BodyCRC(4) + QueueId(4) + Flag(4) +\n        // QueueOffset(8) + CommitLogOffset(8) + SysFlag(4) +\n        // BornTimestamp(8) + BornIP(ipSize) + BornPort(4) +\n        // StoreTimestamp(8) + StoreIP(ipSize) + StorePort(4) +\n        // ReconsumeTimes(4) + PreparedTransactionOffset(8) +\n        // BodyLen(4) + body + TopicLen(1) + topic + PropsLen(2) + props\n        var storeSize = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 +\n                        8 + ipSize + 4 +\n                        8 + ipSize + 4 +\n                        4 + 8 +\n                        4 + body.Length + 1 + topic.Length + 2 + props.Length;\n\n        WriteBigEndianInt32(ms, storeSize);  // StoreSize\n        WriteBigEndianInt32(ms, 0);          // MagicCode\n        WriteBigEndianInt32(ms, 0);          // BodyCRC\n        WriteBigEndianInt32(ms, 1);          // QueueId\n        WriteBigEndianInt32(ms, 0);          // Flag\n        WriteBigEndianInt64(ms, 100L);       // QueueOffset\n        WriteBigEndianInt64(ms, 200L);       // CommitLogOffset\n        WriteBigEndianInt32(ms, sysFlag);    // SysFlag\n\n        WriteBigEndianInt64(ms, 1000L);      // BornTimestamp\n        ms.Write(ipBytes, 0, ipBytes.Length);// BornHost IP\n        WriteBigEndianInt32(ms, 9876);       // BornHost Port\n\n        WriteBigEndianInt64(ms, 2000L);      // StoreTimestamp\n        ms.Write(ipBytes, 0, ipBytes.Length);// StoreHost IP\n        WriteBigEndianInt32(ms, 10911);      // StoreHost Port\n\n        WriteBigEndianInt32(ms, 0);          // ReconsumeTimes\n        WriteBigEndianInt64(ms, 0L);         // PreparedTransactionOffset\n\n        WriteBigEndianInt32(ms, body.Length); // BodyLength\n        ms.Write(body, 0, body.Length);\n\n        ms.WriteByte((Byte)topic.Length);    // Topic length (1 byte)\n        ms.Write(topic, 0, topic.Length);\n\n        WriteBigEndianInt16(ms, (Int16)props.Length); // Properties length (2 bytes)\n        if (props.Length > 0) ms.Write(props, 0, props.Length);\n\n        return ms.ToArray();\n    }\n\n    [Fact]\n    [DisplayName(\"IPv4消息正确解码\")]\n    public void ReadMessage_IPv4()\n    {\n        var data = BuildMessageBinary(false);\n        var pk = new ArrayPacket(data);\n        var msgs = MessageExt.ReadAll(pk);\n\n        Assert.Single(msgs);\n        var msg = msgs[0];\n        Assert.Equal(1, msg.QueueId);\n        Assert.Equal(100, msg.QueueOffset);\n        Assert.Equal(200, msg.CommitLogOffset);\n        Assert.Equal(0, msg.SysFlag & 4); // 不是IPv6\n        Assert.StartsWith(\"127.0.0.1:\", msg.BornHost);\n        Assert.StartsWith(\"127.0.0.1:\", msg.StoreHost);\n        Assert.Contains(\"9876\", msg.BornHost);\n        Assert.Contains(\"10911\", msg.StoreHost);\n        Assert.Equal(\"hello\", msg.BodyString);\n        Assert.Equal(\"test_topic\", msg.Topic);\n        // IPv4 MsgId应该是32个hex字符(16字节)\n        Assert.Equal(32, msg.MsgId.Length);\n    }\n\n    [Fact]\n    [DisplayName(\"IPv6消息正确解码\")]\n    public void ReadMessage_IPv6()\n    {\n        var data = BuildMessageBinary(true);\n        var pk = new ArrayPacket(data);\n        var msgs = MessageExt.ReadAll(pk);\n\n        Assert.Single(msgs);\n        var msg = msgs[0];\n        Assert.Equal(1, msg.QueueId);\n        Assert.Equal(100, msg.QueueOffset);\n        Assert.Equal(200, msg.CommitLogOffset);\n        Assert.NotEqual(0, msg.SysFlag & 4); // 是IPv6\n        Assert.Contains(\"::1\", msg.BornHost);\n        Assert.Contains(\"::1\", msg.StoreHost);\n        Assert.Contains(\"9876\", msg.BornHost);\n        Assert.Contains(\"10911\", msg.StoreHost);\n        Assert.Equal(\"hello\", msg.BodyString);\n        Assert.Equal(\"test_topic\", msg.Topic);\n        // IPv6 MsgId应该是56个hex字符(28字节)\n        Assert.Equal(56, msg.MsgId.Length);\n    }\n\n    [Fact]\n    [DisplayName(\"SysFlag第2位判断IPv6\")]\n    public void SysFlag_IPv6_Bit()\n    {\n        // SysFlag=0 -> IPv4\n        Assert.Equal(0, 0 & 4);\n\n        // SysFlag=4 -> IPv6\n        Assert.NotEqual(0, 4 & 4);\n\n        // SysFlag=5(压缩+IPv6) -> IPv6\n        Assert.NotEqual(0, 5 & 4);\n\n        // SysFlag=1(仅压缩) -> IPv4\n        Assert.Equal(0, 1 & 4);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MQVersionTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class MQVersionTests\n{\n    [Fact]\n    public void Test1()\n    {\n        var ver = (MQVersion)275;\n        Assert.Equal(\"V4_3_1\", ver.ToString());\n\n        ver = (MQVersion)373;\n        Assert.Equal(\"V4_8_0\", ver.ToString());\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MQVersionUpdateTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>MQVersion相关测试</summary>\npublic class MQVersionUpdateTests\n{\n    [Fact]\n    [DisplayName(\"MqBase默认版本为V4_9_7\")]\n    public void MqBase_DefaultVersion_V4_9_7()\n    {\n        using var producer = new Producer();\n        Assert.Equal(MQVersion.V4_9_7, producer.Version);\n    }\n\n    [Fact]\n    [DisplayName(\"MQVersion枚举包含5.x版本\")]\n    public void MQVersion_Contains_5x()\n    {\n        Assert.True(Enum.IsDefined(typeof(MQVersion), MQVersion.V5_0_0));\n        Assert.True(Enum.IsDefined(typeof(MQVersion), MQVersion.V5_9_9));\n        Assert.True(Enum.IsDefined(typeof(MQVersion), MQVersion.HIGHER_VERSION));\n    }\n\n    [Fact]\n    [DisplayName(\"MQVersion_V4_9_7对应正确的枚举值\")]\n    public void MQVersion_V4_9_7_Value()\n    {\n        var ver = MQVersion.V4_9_7;\n        Assert.Equal(\"V4_9_7\", ver.ToString());\n        // 确保是有效的枚举值\n        Assert.True((Int32)ver > (Int32)MQVersion.V4_8_0);\n    }\n\n    [Fact]\n    [DisplayName(\"MQVersion可自定义为5.x版本\")]\n    public void MqBase_Version_CanSet5x()\n    {\n        using var producer = new Producer { Version = MQVersion.V5_0_0 };\n        Assert.Equal(MQVersion.V5_0_0, producer.Version);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ManagementTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.Threading.Tasks;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>管理功能测试</summary>\npublic class ManagementTests\n{\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"DeleteTopic_删除主题\")]\n    public void DeleteTopic_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        // 先创建再删除\n        mq.CreateTopic(\"nx_delete_test\", 2);\n        var count = mq.DeleteTopic(\"nx_delete_test\");\n        Assert.True(count >= 0);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"CreateSubscriptionGroup_创建消费组\")]\n    public void CreateSubscriptionGroup_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        var count = mq.CreateSubscriptionGroup(\"nx_test_group\", true, 16, 1);\n        Assert.True(count >= 0);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"DeleteSubscriptionGroup_删除消费组\")]\n    public void DeleteSubscriptionGroup_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        mq.CreateSubscriptionGroup(\"nx_delete_group\");\n        var count = mq.DeleteSubscriptionGroup(\"nx_delete_group\");\n        Assert.True(count >= 0);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"ViewMessage_按ID查看消息\")]\n    public void ViewMessage_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        // 先发一条消息\n        var sr = mq.Publish(\"ViewMessage_Test\", \"TagA\", null);\n        Assert.NotNull(sr?.MsgId);\n\n        // 尝试查看（可能因Broker索引延迟返回null）\n        var msg = mq.ViewMessage(sr.MsgId);\n        // 不做严格断言，仅验证不抛出异常\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"GetClusterInfo_获取集群信息\")]\n    public void GetClusterInfo_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        mq.Start();\n\n        var info = mq.GetClusterInfo();\n        // 不做严格断言，仅验证不抛出异常\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"GetConsumerConnectionList_获取消费者连接列表\")]\n    public async Task GetConsumerConnectionList_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var consumer = new Consumer\n        {\n            Topic = \"nx_test\",\n            Group = \"test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        consumer.Start();\n\n        var list = await consumer.GetConsumerConnectionList(\"test\");\n        // 不做严格断言，仅验证不抛出异常\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"ResetConsumerOffset_重置消费偏移\")]\n    public async Task ResetConsumerOffset_Test()\n    {\n        var set = BasicTest.GetConfig();\n        using var consumer = new Consumer\n        {\n            Topic = \"nx_test\",\n            Group = \"test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        consumer.Start();\n\n        // 重置到一小时前\n        var timestamp = DateTimeOffset.UtcNow.AddHours(-1).ToUnixTimeMilliseconds();\n        var result = await consumer.ResetConsumerOffset(timestamp);\n        // 不做严格断言，仅验证不抛出异常\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MessageExtendedTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Message扩展属性测试（补充覆盖）</summary>\npublic class MessageExtendedTests\n{\n    #region Request-Reply属性\n    [Fact]\n    [DisplayName(\"Message_ReplyToClient属性读写\")]\n    public void Message_ReplyToClient()\n    {\n        var msg = new Message();\n        Assert.Null(msg.ReplyToClient);\n\n        msg.ReplyToClient = \"client-001\";\n        Assert.Equal(\"client-001\", msg.ReplyToClient);\n    }\n\n    [Fact]\n    [DisplayName(\"Message_CorrelationId属性读写\")]\n    public void Message_CorrelationId()\n    {\n        var msg = new Message();\n        Assert.Null(msg.CorrelationId);\n\n        msg.CorrelationId = \"CID-001\";\n        Assert.Equal(\"CID-001\", msg.CorrelationId);\n    }\n\n    [Fact]\n    [DisplayName(\"Message_MessageType属性读写\")]\n    public void Message_MessageType()\n    {\n        var msg = new Message();\n        Assert.Null(msg.MessageType);\n\n        msg.MessageType = \"reply\";\n        Assert.Equal(\"reply\", msg.MessageType);\n    }\n\n    [Fact]\n    [DisplayName(\"Message_RequestTimeout属性读写\")]\n    public void Message_RequestTimeout()\n    {\n        var msg = new Message();\n        Assert.Equal(0, msg.RequestTimeout);\n\n        msg.RequestTimeout = 5000;\n        Assert.Equal(5000, msg.RequestTimeout);\n    }\n    #endregion\n\n    #region DelayTimeLevel\n    [Fact]\n    [DisplayName(\"Message_DelayTimeLevel属性读写\")]\n    public void Message_DelayTimeLevel()\n    {\n        var msg = new Message();\n        Assert.Equal(0, msg.DelayTimeLevel);\n\n        msg.DelayTimeLevel = 3;\n        Assert.Equal(3, msg.DelayTimeLevel);\n    }\n    #endregion\n\n    #region WaitStoreMsgOK\n    [Fact]\n    [DisplayName(\"Message_WaitStoreMsgOK默认为true\")]\n    public void Message_WaitStoreMsgOK_DefaultTrue()\n    {\n        var msg = new Message();\n        Assert.True(msg.WaitStoreMsgOK);\n    }\n\n    [Fact]\n    [DisplayName(\"Message_WaitStoreMsgOK可设置为false\")]\n    public void Message_WaitStoreMsgOK_SetFalse()\n    {\n        var msg = new Message { WaitStoreMsgOK = false };\n        Assert.False(msg.WaitStoreMsgOK);\n    }\n    #endregion\n\n    #region TransactionId\n    [Fact]\n    [DisplayName(\"Message_TransactionId属性读写\")]\n    public void Message_TransactionId()\n    {\n        var msg = new Message();\n        Assert.Null(msg.TransactionId);\n\n        msg.TransactionId = \"TX-001\";\n        Assert.Equal(\"TX-001\", msg.TransactionId);\n    }\n    #endregion\n\n    #region PutUserProperty / GetUserProperty\n    [Fact]\n    [DisplayName(\"PutUserProperty_存入自定义属性\")]\n    public void PutUserProperty_StoresProperty()\n    {\n        var msg = new Message();\n        msg.PutUserProperty(\"orderId\", \"12345\");\n\n        Assert.Equal(\"12345\", msg.GetUserProperty(\"orderId\"));\n    }\n\n    [Fact]\n    [DisplayName(\"PutUserProperty_Key为null时抛异常\")]\n    public void PutUserProperty_NullKey_ThrowsException()\n    {\n        var msg = new Message();\n        Assert.Throws<ArgumentNullException>(() => msg.PutUserProperty(null, \"value\"));\n    }\n\n    [Fact]\n    [DisplayName(\"PutUserProperty_Value为null时抛异常\")]\n    public void PutUserProperty_NullValue_ThrowsException()\n    {\n        var msg = new Message();\n        Assert.Throws<ArgumentNullException>(() => msg.PutUserProperty(\"key\", null));\n    }\n\n    [Fact]\n    [DisplayName(\"GetUserProperty_不存在的Key返回null\")]\n    public void GetUserProperty_NonExistentKey_ReturnsNull()\n    {\n        var msg = new Message();\n        Assert.Null(msg.GetUserProperty(\"nonexistent\"));\n    }\n    #endregion\n\n    #region SetBody\n    [Fact]\n    [DisplayName(\"SetBody_设置对象自动JSON序列化\")]\n    public void SetBody_Object_SerializesToJson()\n    {\n        var msg = new Message();\n        msg.SetBody(new { Name = \"test\", Value = 42 });\n\n        Assert.NotNull(msg.Body);\n        Assert.Contains(\"test\", msg.BodyString);\n        Assert.Contains(\"42\", msg.BodyString);\n    }\n\n    [Fact]\n    [DisplayName(\"SetBody_Byte数组直接设置\")]\n    public void SetBody_ByteArray_SetDirectly()\n    {\n        var msg = new Message();\n        var data = new Byte[] { 1, 2, 3 };\n        msg.SetBody(data);\n\n        Assert.Equal(data, msg.Body);\n    }\n    #endregion\n\n    #region ToString\n    [Fact]\n    [DisplayName(\"Message_ToString有Body时返回BodyString\")]\n    public void Message_ToString_WithBody()\n    {\n        var msg = new Message();\n        msg.SetBody(\"Hello\");\n\n        Assert.Equal(\"Hello\", msg.ToString());\n    }\n\n    [Fact]\n    [DisplayName(\"Message_ToString无Body时返回类型名\")]\n    public void Message_ToString_WithoutBody()\n    {\n        var msg = new Message();\n\n        var str = msg.ToString();\n        Assert.NotNull(str);\n    }\n    #endregion\n\n    #region GetProperties序列化\n    [Fact]\n    [DisplayName(\"GetProperties_多个属性序列化为分隔字符串\")]\n    public void GetProperties_MultipleProperties()\n    {\n        var msg = new Message\n        {\n            Tags = \"TagA\",\n            Keys = \"Key1\"\n        };\n\n        var props = msg.GetProperties();\n\n        Assert.NotNull(props);\n        Assert.Contains(\"TAGS\", props);\n        Assert.Contains(\"TagA\", props);\n        Assert.Contains(\"KEYS\", props);\n        Assert.Contains(\"Key1\", props);\n    }\n\n    [Fact]\n    [DisplayName(\"GetProperties_空属性返回空字符串\")]\n    public void GetProperties_EmptyProperties()\n    {\n        var msg = new Message();\n\n        var props = msg.GetProperties();\n\n        Assert.NotNull(props);\n        Assert.Equal(String.Empty, props);\n    }\n    #endregion\n\n    #region ParseProperties\n    [Fact]\n    [DisplayName(\"ParseProperties_解析标签和键\")]\n    public void ParseProperties_ParsesTagsAndKeys()\n    {\n        var msg = new Message();\n        msg.ParseProperties(\"TAGS\\u0001TagA\\u0002KEYS\\u0001Key1\\u0002\");\n\n        Assert.Equal(\"TagA\", msg.Tags);\n        Assert.Equal(\"Key1\", msg.Keys);\n    }\n\n    [Fact]\n    [DisplayName(\"ParseProperties_解析延迟等级\")]\n    public void ParseProperties_ParsesDelayLevel()\n    {\n        var msg = new Message();\n        msg.ParseProperties(\"DELAY\\u00013\\u0002\");\n\n        Assert.Equal(3, msg.DelayTimeLevel);\n    }\n\n    [Fact]\n    [DisplayName(\"ParseProperties_空字符串返回原Properties\")]\n    public void ParseProperties_EmptyString_ReturnsOriginal()\n    {\n        var msg = new Message();\n        msg.PutUserProperty(\"existing\", \"value\");\n\n        var result = msg.ParseProperties(\"\");\n\n        Assert.Equal(\"value\", msg.GetUserProperty(\"existing\"));\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MessageId5xTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>5.x MessageId格式测试</summary>\npublic class MessageId5xTests\n{\n    [Fact]\n    [DisplayName(\"CreateMessageId5x_生成有效的5x格式ID\")]\n    public void CreateMessageId5x_GeneratesValidId()\n    {\n        var mac = new Byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };\n        var id = MessageExt.CreateMessageId5x(1, mac, 12345, 67890);\n\n        Assert.NotNull(id);\n        Assert.Equal(32, id.Length);\n        Assert.StartsWith(\"01\", id, StringComparison.OrdinalIgnoreCase);\n    }\n\n    [Fact]\n    [DisplayName(\"TryParseMessageId5x_解析生成的ID\")]\n    public void TryParseMessageId5x_ParseCreatedId()\n    {\n        var mac = new Byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };\n        var id = MessageExt.CreateMessageId5x(1, mac, 12345, 67890);\n\n        var ok = MessageExt.TryParseMessageId5x(id, out var version, out var parsedMac, out var processId, out var counter);\n\n        Assert.True(ok);\n        Assert.Equal(1, version);\n        Assert.Equal(mac, parsedMac);\n        Assert.Equal(12345, processId);\n        Assert.Equal(67890, counter);\n    }\n\n    [Fact]\n    [DisplayName(\"TryParseMessageId5x_非5x格式返回false\")]\n    public void TryParseMessageId5x_Invalid_ReturnsFalse()\n    {\n        // 4.x格式（32个hex，但前缀不是01）\n        var id = \"AABBCCDD00001111000000000000FFFF\";\n        var ok = MessageExt.TryParseMessageId5x(id, out _, out _, out _, out _);\n        Assert.False(ok);\n    }\n\n    [Fact]\n    [DisplayName(\"TryParseMessageId5x_Null输入返回false\")]\n    public void TryParseMessageId5x_Null_ReturnsFalse()\n    {\n        var ok = MessageExt.TryParseMessageId5x(null, out _, out _, out _, out _);\n        Assert.False(ok);\n    }\n\n    [Fact]\n    [DisplayName(\"TryParseMessageId5x_空字符串返回false\")]\n    public void TryParseMessageId5x_Empty_ReturnsFalse()\n    {\n        var ok = MessageExt.TryParseMessageId5x(\"\", out _, out _, out _, out _);\n        Assert.False(ok);\n    }\n\n    [Fact]\n    [DisplayName(\"TryParseMessageId5x_长度不匹配返回false\")]\n    public void TryParseMessageId5x_WrongLength_ReturnsFalse()\n    {\n        var ok = MessageExt.TryParseMessageId5x(\"0101AABB\", out _, out _, out _, out _);\n        Assert.False(ok);\n    }\n\n    [Fact]\n    [DisplayName(\"IsMessageId5x_有效5x格式返回true\")]\n    public void IsMessageId5x_Valid_ReturnsTrue()\n    {\n        var mac = new Byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };\n        var id = MessageExt.CreateMessageId5x(1, mac, 100, 200);\n        Assert.True(MessageExt.IsMessageId5x(id));\n    }\n\n    [Fact]\n    [DisplayName(\"IsMessageId5x_4x格式返回false\")]\n    public void IsMessageId5x_4xFormat_ReturnsFalse()\n    {\n        // 典型的4.x格式MsgId（16字节=32hex，但前缀不是01）\n        Assert.False(MessageExt.IsMessageId5x(\"C0A80001000030390000000000000001\"));\n    }\n\n    [Fact]\n    [DisplayName(\"IsMessageId5x_Null返回false\")]\n    public void IsMessageId5x_Null_ReturnsFalse()\n    {\n        Assert.False(MessageExt.IsMessageId5x(null));\n    }\n\n    [Fact]\n    [DisplayName(\"CreateMessageId5x_无MAC时使用随机字节\")]\n    public void CreateMessageId5x_NullMac_UsesRandom()\n    {\n        var id1 = MessageExt.CreateMessageId5x(1, null, 1, 1);\n        var id2 = MessageExt.CreateMessageId5x(1, null, 1, 1);\n\n        Assert.NotNull(id1);\n        Assert.Equal(32, id1.Length);\n        Assert.StartsWith(\"01\", id1, StringComparison.OrdinalIgnoreCase);\n        // 随机MAC，两次结果可能不同（概率极高）\n    }\n\n    [Fact]\n    [DisplayName(\"CreateMessageId5x_不同计数器产生不同ID\")]\n    public void CreateMessageId5x_DifferentCounter_DifferentId()\n    {\n        var mac = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };\n        var id1 = MessageExt.CreateMessageId5x(1, mac, 100, 1);\n        var id2 = MessageExt.CreateMessageId5x(1, mac, 100, 2);\n\n        Assert.NotEqual(id1, id2);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MessageQueueTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>MessageQueue消息队列标识测试</summary>\npublic class MessageQueueTests\n{\n    #region Equals\n    [Fact]\n    [DisplayName(\"Equals_相同属性相等\")]\n    public void Equals_SameProperties_ReturnsTrue()\n    {\n        var q1 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.True(q1.Equals(q2));\n        Assert.True(q2.Equals(q1));\n    }\n\n    [Fact]\n    [DisplayName(\"Equals_不同Topic不相等\")]\n    public void Equals_DifferentTopic_ReturnsFalse()\n    {\n        var q1 = new MessageQueue { Topic = \"test1\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test2\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.False(q1.Equals(q2));\n    }\n\n    [Fact]\n    [DisplayName(\"Equals_不同BrokerName不相等\")]\n    public void Equals_DifferentBrokerName_ReturnsFalse()\n    {\n        var q1 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-b\", QueueId = 0 };\n\n        Assert.False(q1.Equals(q2));\n    }\n\n    [Fact]\n    [DisplayName(\"Equals_不同QueueId不相等\")]\n    public void Equals_DifferentQueueId_ReturnsFalse()\n    {\n        var q1 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 1 };\n\n        Assert.False(q1.Equals(q2));\n    }\n\n    [Fact]\n    [DisplayName(\"Equals_非MessageQueue对象不相等\")]\n    public void Equals_NonMessageQueue_ReturnsFalse()\n    {\n        var q = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.False(q.Equals(\"not a queue\"));\n        Assert.False(q.Equals(null));\n        Assert.False(q.Equals(42));\n    }\n\n    [Fact]\n    [DisplayName(\"Equals_自身比较相等\")]\n    public void Equals_Self_ReturnsTrue()\n    {\n        var q = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.True(q.Equals(q));\n    }\n    #endregion\n\n    #region GetHashCode\n    [Fact]\n    [DisplayName(\"GetHashCode_相同属性相同哈希\")]\n    public void GetHashCode_SameProperties_SameHash()\n    {\n        var q1 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.Equal(q1.GetHashCode(), q2.GetHashCode());\n    }\n\n    [Fact]\n    [DisplayName(\"GetHashCode_同一实例多次调用相同哈希\")]\n    public void GetHashCode_SameInstance_MultipleCalls_SameHash()\n    {\n        var q = new MessageQueue { Topic = \"test1\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        // 同一实例在一次执行过程中多次调用 GetHashCode，结果应保持一致\n        var h1 = q.GetHashCode();\n        var h2 = q.GetHashCode();\n\n        Assert.Equal(h1, h2);\n    }\n\n    [Fact]\n    [DisplayName(\"GetHashCode_可用于字典键\")]\n    public void GetHashCode_WorksAsDictionaryKey()\n    {\n        var q1 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n        var q2 = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        var dict = new Dictionary<MessageQueue, String>();\n        dict[q1] = \"value\";\n\n        // q2 与 q1 属性相同且相等，应能作为键查到\n        // 注意：Dictionary 使用 GetHashCode + Equals 处理键的相等性\n        // 前提是 MessageQueue 正确重写了 Equals 和 GetHashCode\n        Assert.True(dict.ContainsKey(q2));\n        Assert.Equal(\"value\", dict[q2]);\n    }\n    #endregion\n\n    #region ToString\n    [Fact]\n    [DisplayName(\"ToString_格式正确\")]\n    public void ToString_CorrectFormat()\n    {\n        var q = new MessageQueue { Topic = \"test\", BrokerName = \"broker-a\", QueueId = 3 };\n\n        var str = q.ToString();\n\n        Assert.Equal(\"broker-a[3]\", str);\n    }\n\n    [Fact]\n    [DisplayName(\"ToString_Null属性不抛异常\")]\n    public void ToString_NullBrokerName()\n    {\n        var q = new MessageQueue { QueueId = 0 };\n\n        var str = q.ToString();\n\n        Assert.Contains(\"[0]\", str);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MessageTests.cs",
    "content": "﻿using System;\nusing NewLife;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing NewLife.Serialization;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class MessageTests\n{\n    [Fact]\n    public void SetBody_WithString_SetsBodyCorrectly()\n    {\n        // Arrange\n        var message = new Message();\n        var body = \"Hello, World!\";\n\n        // Act\n        message.SetBody(body);\n\n        // Assert\n        Assert.Equal(body, message.BodyString);\n        Assert.Equal(body.GetBytes(), message.Body);\n    }\n\n    [Fact]\n    public void SetBody_WithByteArray_SetsBodyCorrectly()\n    {\n        // Arrange\n        var message = new Message();\n        var body = new byte[] { 1, 2, 3, 4 };\n\n        // Act\n        message.SetBody(body);\n\n        // Assert\n        Assert.Equal(body, message.Body);\n    }\n\n    [Fact]\n    public void GetProperties_ReturnsCorrectProperties()\n    {\n        // Arrange\n        var message = new Message\n        {\n            Tags = \"Tag1\",\n            Keys = \"Key1\",\n            DelayTimeLevel = 2,\n            WaitStoreMsgOK = false\n        };\n\n        // Act\n        var properties = message.GetProperties();\n\n        // Assert\n        Assert.Contains(\"TAGS\\u0001Tag1\\u0002\", properties);\n        Assert.Contains(\"KEYS\\u0001Key1\\u0002\", properties);\n        Assert.Contains(\"DELAY\\u00012\\u0002\", properties);\n        Assert.Contains(\"WAIT\\u0001False\\u0002\", properties);\n\n        var header = new SendMessageRequestHeader\n        {\n            ProducerGroup = \"TestGroup\",\n            Topic = \"TestTopic\",\n            SysFlag = 0,\n            BornTimestamp = DateTime.UtcNow.ToLong(),\n            Flag = message.Flag,\n            Properties = message.GetProperties(),\n        };\n\n        var ext = header.GetProperties();\n        //Assert.Equal(11, ext.Count);\n        Assert.Equal(\"TAGS\\u0001Tag1\\u0002KEYS\\u0001Key1\\u0002DELAY\\u00012\\u0002WAIT\\u0001False\\u0002\", ext[\"i\"]);\n\n        var broker = new BrokerClient([\"\"]);\n        var cmd = broker.CreateCommand(RequestCode.SEND_MESSAGE_V2, null, ext);\n        var json = cmd.Header.ToJson(false, false, false);\n        var js = new SystemJson();\n        var json2 = js.Write(cmd.Header, false, false, true);\n        //Assert.Equal(json, json2);\n    }\n\n    [Fact]\n    public void ParseProperties_SetsPropertiesCorrectly()\n    {\n        // Arrange\n        var message = new Message();\n        var properties = \"TAGS\\u0001Tag1\\u0002KEYS\\u0001Key1\\u0002DELAY\\u00012\\u0002WAIT\\u0001False\\u0002\";\n\n        // Act\n        var result = message.ParseProperties(properties);\n\n        // Assert\n        Assert.Equal(\"Tag1\", message.Tags);\n        Assert.Equal(\"Key1\", message.Keys);\n        Assert.Equal(2, message.DelayTimeLevel);\n        Assert.False(message.WaitStoreMsgOK);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MessageTraceTests.cs",
    "content": "using System;\nusing System.Threading;\nusing NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ\n{\n    public class MessageTraceTests\n    {\n        private const String Topic = \"TopicDemo\";\n        private const String Group = \"TraceTestGroup\";\n        private const String NameServerAddress = \"127.0.0.1:9876\";\n\n        [Fact(Skip = \"需要RocketMQ服务器支持\")]\n        public void Producer_And_Consumer_With_Trace_Enabled_Should_Work()\n        {\n            // 使用 ManualResetEvent 来同步测试的完成\n            var mre = new ManualResetEvent(false);\n\n            // 1. 创建并启动消费者\n            var consumer = new Consumer\n            {\n                Topic = Topic,\n                Group = Group,\n                NameServerAddress = NameServerAddress,\n                EnableMessageTrace = true, // 启用消息轨迹\n                Log = XTrace.Log\n            };\n\n            consumer.OnConsume = (q, msgs) =>\n            {\n                foreach (var msg in msgs)\n                {\n                    XTrace.WriteLine($\"消费到消息: {msg.Body.ToStr()}\");\n                }\n                // 设置事件，表示消费成功\n                mre.Set();\n                return true;\n            };\n\n            consumer.Start();\n\n            // 2. 创建并启动生产者\n            var producer = new Producer\n            {\n                Topic = Topic,\n                Group = Group, // 生产者组可以和消费者组不同，这里为了简单使用同一个\n                NameServerAddress = NameServerAddress,\n                EnableMessageTrace = true, // 启用消息轨迹\n                Log = XTrace.Log\n            };\n\n            producer.Start();\n\n            // 3. 发送消息\n            var messageBody = \"Hello, RocketMQ with Message Trace!\";\n            var sendResult = producer.Publish(messageBody);\n\n            Assert.NotNull(sendResult);\n            Assert.Equal(SendStatus.SendOK, sendResult.Status);\n            XTrace.WriteLine($\"消息发送成功: MsgId={sendResult.MsgId}\");\n\n            // 4. 等待消费者处理消息，设置一个超时时间以防测试挂起\n            bool consumed = mre.WaitOne(TimeSpan.FromSeconds(30));\n\n            // 5. 清理资源\n            producer.Stop();\n            consumer.Stop();\n\n            // 6. 断言\n            Assert.True(consumed, \"消费者在超时时间内没有收到消息。\");\n        }\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ModelTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Models;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>模型和枚举测试</summary>\npublic class ModelTests\n{\n    #region DelayTimeLevels\n    [Fact]\n    [DisplayName(\"DelayTimeLevels_包含18个等级\")]\n    public void DelayTimeLevels_Has18Levels()\n    {\n        var values = Enum.GetValues(typeof(DelayTimeLevels));\n\n        Assert.Equal(18, values.Length);\n    }\n\n    [Fact]\n    [DisplayName(\"DelayTimeLevels_等级值从1开始\")]\n    public void DelayTimeLevels_StartsFrom1()\n    {\n        Assert.Equal(1, (Int32)DelayTimeLevels.S1);\n        Assert.Equal(2, (Int32)DelayTimeLevels.S5);\n        Assert.Equal(18, (Int32)DelayTimeLevels.Hour2);\n    }\n\n    [Fact]\n    [DisplayName(\"DelayTimeLevels_关键等级值正确\")]\n    public void DelayTimeLevels_KeyValues()\n    {\n        Assert.Equal(5, (Int32)DelayTimeLevels.Min1);\n        Assert.Equal(14, (Int32)DelayTimeLevels.Min10);\n        Assert.Equal(16, (Int32)DelayTimeLevels.Min30);\n        Assert.Equal(17, (Int32)DelayTimeLevels.Hour1);\n    }\n    #endregion\n\n    #region MessageModels\n    [Fact]\n    [DisplayName(\"MessageModels_集群和广播模式\")]\n    public void MessageModels_Values()\n    {\n        Assert.Equal(0, (Int32)MessageModels.Clustering);\n        Assert.Equal(1, (Int32)MessageModels.Broadcasting);\n    }\n    #endregion\n\n    #region ConsumeTypes\n    [Fact]\n    [DisplayName(\"ConsumeTypes_包含Pull和Push\")]\n    public void ConsumeTypes_Values()\n    {\n        Assert.True(Enum.IsDefined(typeof(ConsumeTypes), \"Pull\"));\n        Assert.True(Enum.IsDefined(typeof(ConsumeTypes), \"Push\"));\n    }\n    #endregion\n\n    #region ConsumeEventArgs\n    [Fact]\n    [DisplayName(\"ConsumeEventArgs_属性可设置\")]\n    public void ConsumeEventArgs_PropertiesCanBeSet()\n    {\n        var mq = new MessageQueue { BrokerName = \"b1\", QueueId = 0 };\n        var msgs = new[] { new MessageExt { Topic = \"t1\" } };\n        var pr = new PullResult { Status = PullStatus.Found };\n\n        var args = new ConsumeEventArgs\n        {\n            Queue = mq,\n            Messages = msgs,\n            Result = pr,\n        };\n\n        Assert.Same(mq, args.Queue);\n        Assert.Single(args.Messages);\n        Assert.Equal(PullStatus.Found, args.Result.Status);\n    }\n\n    [Fact]\n    [DisplayName(\"ConsumeEventArgs_默认值为null\")]\n    public void ConsumeEventArgs_Defaults()\n    {\n        var args = new ConsumeEventArgs();\n\n        Assert.Null(args.Queue);\n        Assert.Null(args.Messages);\n        Assert.Null(args.Result);\n    }\n    #endregion\n\n    #region ServiceState\n    [Fact]\n    [DisplayName(\"ServiceState_枚举包含基本状态\")]\n    public void ServiceState_Values()\n    {\n        Assert.True(Enum.IsDefined(typeof(ServiceState), \"CreateJust\"));\n        Assert.True(Enum.IsDefined(typeof(ServiceState), \"Running\"));\n        Assert.True(Enum.IsDefined(typeof(ServiceState), \"ShutdownAlready\"));\n    }\n    #endregion\n\n    #region RequestCode\n    [Fact]\n    [DisplayName(\"RequestCode_核心指令码正确\")]\n    public void RequestCode_CoreValues()\n    {\n        Assert.Equal(10, (Int32)RequestCode.SEND_MESSAGE);\n        Assert.Equal(11, (Int32)RequestCode.PULL_MESSAGE);\n        Assert.Equal(12, (Int32)RequestCode.QUERY_MESSAGE);\n        Assert.Equal(34, (Int32)RequestCode.HEART_BEAT);\n        Assert.Equal(35, (Int32)RequestCode.UNREGISTER_CLIENT);\n        Assert.Equal(100, (Int32)RequestCode.PUT_KV_CONFIG);\n        Assert.Equal(104, (Int32)RequestCode.UNREGISTER_BROKER);\n        Assert.Equal(105, (Int32)RequestCode.GET_ROUTEINTO_BY_TOPIC);\n    }\n    #endregion\n\n    #region ResponseCode\n    [Fact]\n    [DisplayName(\"ResponseCode_核心响应码正确\")]\n    public void ResponseCode_CoreValues()\n    {\n        Assert.Equal(0, (Int32)ResponseCode.SUCCESS);\n        Assert.Equal(1, (Int32)ResponseCode.SYSTEM_ERROR);\n        Assert.Equal(16, (Int32)ResponseCode.NO_PERMISSION);\n        Assert.Equal(17, (Int32)ResponseCode.TOPIC_NOT_EXIST);\n    }\n    #endregion\n\n    #region LanguageCode\n    [Fact]\n    [DisplayName(\"LanguageCode_包含主要语言\")]\n    public void LanguageCode_MainValues()\n    {\n        Assert.True(Enum.IsDefined(typeof(LanguageCode), \"JAVA\"));\n        Assert.True(Enum.IsDefined(typeof(LanguageCode), \"CPP\"));\n        Assert.True(Enum.IsDefined(typeof(LanguageCode), \"DOTNET\"));\n        Assert.True(Enum.IsDefined(typeof(LanguageCode), \"GO\"));\n    }\n    #endregion\n\n    #region TransactionState\n    [Fact]\n    [DisplayName(\"TransactionState_枚举值正确\")]\n    public void TransactionState_Values()\n    {\n        Assert.Equal(4, (Int32)TransactionState.Prepared);\n        Assert.Equal(8, (Int32)TransactionState.Commit);\n        Assert.Equal(12, (Int32)TransactionState.Rollback);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MqBasePropertyTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Client;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>MqBase基类属性测试</summary>\npublic class MqBasePropertyTests\n{\n    #region 默认值\n    [Fact]\n    [DisplayName(\"Producer_默认Group为DEFAULT_PRODUCER\")]\n    public void Producer_DefaultGroup()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(\"DEFAULT_PRODUCER\", producer.Group);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认Topic为TBW102\")]\n    public void Producer_DefaultTopic()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(\"TBW102\", producer.Topic);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认DefaultTopicQueueNums为4\")]\n    public void Producer_DefaultTopicQueueNums()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(4, producer.DefaultTopicQueueNums);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认InstanceName不为空\")]\n    public void Producer_DefaultInstanceName()\n    {\n        using var producer = new Producer();\n\n        // InstanceName 默认为进程 ID\n        Assert.NotNull(producer.InstanceName);\n        Assert.NotEmpty(producer.InstanceName);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认PollNameServerInterval为30000\")]\n    public void Producer_DefaultPollInterval()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(30_000, producer.PollNameServerInterval);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认HeartbeatBrokerInterval为30000\")]\n    public void Producer_DefaultHeartbeatInterval()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(30_000, producer.HeartbeatBrokerInterval);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认SerializeType为JSON\")]\n    public void Producer_DefaultSerializeType()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(SerializeType.JSON, producer.SerializeType);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认Version为V4_9_7\")]\n    public void Producer_DefaultVersion()\n    {\n        using var producer = new Producer();\n\n        Assert.Equal(MQVersion.V4_9_7, producer.Version);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认不启用VIP通道\")]\n    public void Producer_DefaultVipChannelDisabled()\n    {\n        using var producer = new Producer();\n\n        Assert.False(producer.VipChannelEnabled);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认不启用消息轨迹\")]\n    public void Producer_DefaultTraceDisabled()\n    {\n        using var producer = new Producer();\n\n        Assert.False(producer.EnableMessageTrace);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_默认不使用外部代理\")]\n    public void Producer_DefaultExternalBrokerDisabled()\n    {\n        using var producer = new Producer();\n\n        Assert.False(producer.ExternalBroker);\n    }\n    #endregion\n\n    #region ClientId\n    [Fact]\n    [DisplayName(\"ClientId_包含IP和InstanceName\")]\n    public void ClientId_ContainsIPAndInstance()\n    {\n        using var producer = new Producer();\n\n        var clientId = producer.ClientId;\n\n        Assert.NotNull(clientId);\n        Assert.Contains(\"@\", clientId);\n    }\n\n    [Fact]\n    [DisplayName(\"ClientId_含UnitName时追加\")]\n    public void ClientId_WithUnitName()\n    {\n        using var producer = new Producer { UnitName = \"unit1\" };\n\n        var clientId = producer.ClientId;\n\n        Assert.Contains(\"@unit1\", clientId);\n    }\n    #endregion\n\n    #region 属性设置\n    [Fact]\n    [DisplayName(\"Producer_可设置Name\")]\n    public void Producer_SetName()\n    {\n        using var producer = new Producer { Name = \"TestProducer\" };\n\n        Assert.Equal(\"TestProducer\", producer.Name);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_可设置NameServerAddress\")]\n    public void Producer_SetNameServerAddress()\n    {\n        using var producer = new Producer { NameServerAddress = \"127.0.0.1:9876\" };\n\n        Assert.Equal(\"127.0.0.1:9876\", producer.NameServerAddress);\n    }\n\n    [Fact]\n    [DisplayName(\"Producer_DefaultTopic静态属性为TBW102\")]\n    public void DefaultTopic_IsTBW102()\n    {\n        Assert.Equal(\"TBW102\", MqBase.DefaultTopic);\n    }\n\n    [Fact]\n    [DisplayName(\"Consumer_默认Group为DEFAULT_PRODUCER\")]\n    public void Consumer_DefaultGroup()\n    {\n        using var consumer = new Consumer();\n\n        Assert.Equal(\"DEFAULT_PRODUCER\", consumer.Group);\n    }\n\n    [Fact]\n    [DisplayName(\"Consumer_可设置属性\")]\n    public void Consumer_SetProperties()\n    {\n        using var consumer = new Consumer\n        {\n            Topic = \"order_topic\",\n            Group = \"CG_ORDER\",\n            NameServerAddress = \"10.0.0.1:9876\",\n        };\n\n        Assert.Equal(\"order_topic\", consumer.Topic);\n        Assert.Equal(\"CG_ORDER\", consumer.Group);\n        Assert.Equal(\"10.0.0.1:9876\", consumer.NameServerAddress);\n    }\n    #endregion\n\n    #region Active状态\n    [Fact]\n    [DisplayName(\"Producer_未启动时Active为false\")]\n    public void Producer_NotStarted_ActiveFalse()\n    {\n        using var producer = new Producer();\n\n        Assert.False(producer.Active);\n    }\n\n    [Fact]\n    [DisplayName(\"Consumer_未启动时Active为false\")]\n    public void Consumer_NotStarted_ActiveFalse()\n    {\n        using var consumer = new Consumer();\n\n        Assert.False(consumer.Active);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MqSettingTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>MqSetting配置测试</summary>\npublic class MqSettingTests\n{\n    [Fact]\n    [DisplayName(\"MqSetting_属性可设置\")]\n    public void MqSetting_SetProperties()\n    {\n        var setting = new MqSetting\n        {\n            NameServer = \"127.0.0.1:9876\",\n            Topic = \"test_topic\",\n            Group = \"test_group\",\n            Server = \"http://ons.aliyun.com\",\n            AccessKey = \"ak\",\n            SecretKey = \"sk\",\n        };\n\n        Assert.Equal(\"127.0.0.1:9876\", setting.NameServer);\n        Assert.Equal(\"test_topic\", setting.Topic);\n        Assert.Equal(\"test_group\", setting.Group);\n        Assert.Equal(\"http://ons.aliyun.com\", setting.Server);\n        Assert.Equal(\"ak\", setting.AccessKey);\n        Assert.Equal(\"sk\", setting.SecretKey);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/MultiTopicTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>多Topic订阅测试</summary>\npublic class MultiTopicTests\n{\n    [Fact]\n    [DisplayName(\"Consumer_Topics属性默认为null\")]\n    public void Consumer_Topics_DefaultNull()\n    {\n        using var consumer = new Consumer();\n        Assert.Null(consumer.Topics);\n    }\n\n    [Fact]\n    [DisplayName(\"Consumer_可设置多个Topics\")]\n    public void Consumer_Topics_CanBeSet()\n    {\n        using var consumer = new Consumer\n        {\n            Topics = [\"topic_a\", \"topic_b\", \"topic_c\"]\n        };\n\n        Assert.NotNull(consumer.Topics);\n        Assert.Equal(3, consumer.Topics.Length);\n        Assert.Equal(\"topic_a\", consumer.Topics[0]);\n    }\n\n    [Fact]\n    [DisplayName(\"Consumer_Topics为空时保持单Topic行为\")]\n    public void Consumer_Topics_EmptyFallsBackToSingleTopic()\n    {\n        using var consumer = new Consumer\n        {\n            Topic = \"default_topic\",\n            Topics = null\n        };\n\n        // Topics为null时，内部应使用单Topic\n        Assert.Equal(\"default_topic\", consumer.Topic);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageQueue_已包含Topic属性\")]\n    public void MessageQueue_HasTopicProperty()\n    {\n        var mq1 = new MessageQueue { Topic = \"topic_a\", BrokerName = \"broker-a\", QueueId = 0 };\n        var mq2 = new MessageQueue { Topic = \"topic_b\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        // 不同Topic的队列不相等\n        Assert.NotEqual(mq1, mq2);\n        Assert.NotEqual(mq1.GetHashCode(), mq2.GetHashCode());\n    }\n\n    [Fact]\n    [DisplayName(\"MessageQueue_相同Topic相等\")]\n    public void MessageQueue_SameTopic_Equal()\n    {\n        var mq1 = new MessageQueue { Topic = \"topic_a\", BrokerName = \"broker-a\", QueueId = 0 };\n        var mq2 = new MessageQueue { Topic = \"topic_a\", BrokerName = \"broker-a\", QueueId = 0 };\n\n        Assert.Equal(mq1, mq2);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"Consumer_多Topic启动消费\")]\n    public void Consumer_MultiTopic_Start()\n    {\n        using var consumer = new Consumer\n        {\n            Topic = \"topic_main\",\n            Topics = [\"topic_a\", \"topic_b\"],\n            Group = \"test_multi_group\",\n            NameServerAddress = \"127.0.0.1:9876\",\n        };\n\n        // 验证Topics设置正确\n        Assert.Equal(2, consumer.Topics.Length);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/NameClientTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing Moq;\nusing NewLife;\nusing NewLife.Data;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class NameClientTests\n{\n    [Fact]\n    [DisplayName(\"GetRouteInfo_解析标准JSON格式的路由信息\")]\n    public void GetRouteInfo()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.2.3.117:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        var brokers = client.GetRouteInfo(null);\n        Assert.Single(brokers);\n\n        var broker = brokers[0];\n        Assert.Equal(\"broker-a\", broker.Name);\n        Assert.Equal(Permissions.Read | Permissions.Write, broker.Permission);\n        Assert.Equal(8, broker.ReadQueueNums);\n        Assert.Equal(8, broker.WriteQueueNums);\n        Assert.Equal(0, broker.TopicSynFlag);\n        Assert.Equal(\"DefaultCluster\", broker.Cluster);\n        Assert.Single(broker.Addresses);\n        Assert.Equal(\"10.2.3.117:10911\", broker.Addresses[0]);\n    }\n\n    [Fact]\n    [DisplayName(\"GetRouteInfo_解析整数Key的brokerAddrs格式\")]\n    public void GetRouteInfo2()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{0:\"10.2.3.117:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        var brokers = client.GetRouteInfo(null);\n        Assert.Single(brokers);\n\n        var broker = brokers[0];\n        Assert.Equal(\"broker-a\", broker.Name);\n        Assert.Equal(Permissions.Read | Permissions.Write, broker.Permission);\n        Assert.Equal(8, broker.ReadQueueNums);\n        Assert.Equal(8, broker.WriteQueueNums);\n        Assert.Equal(0, broker.TopicSynFlag);\n        Assert.Equal(\"DefaultCluster\", broker.Cluster);\n        Assert.Single(broker.Addresses);\n        Assert.Equal(\"10.2.3.117:10911\", broker.Addresses[0]);\n    }\n\n    [Fact]\n    [DisplayName(\"GetRouteInfo_非零topicSysFlag正确解析\")]\n    public void GetRouteInfo_NonZeroTopicSysFlag()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.0.0.1:10911\"},\"brokerName\":\"broker-b\",\"cluster\":\"TestCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-b\",\"perm\":6,\"readQueueNums\":4,\"topicSysFlag\":3,\"writeQueueNums\":4}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        var brokers = client.GetRouteInfo(\"test_topic\");\n        Assert.Single(brokers);\n        Assert.Equal(3, brokers[0].TopicSynFlag);\n    }\n\n    [Fact]\n    [DisplayName(\"GetRouteInfo_指定topic时缓存路由信息可通过GetTopicBrokers获取\")]\n    public void GetRouteInfo_WithTopic_CachesResult()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.0.0.1:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        client.GetRouteInfo(\"my_topic\");\n\n        // 缓存应可通过 GetTopicBrokers 取回\n        var cached = client.GetTopicBrokers(\"my_topic\");\n        Assert.NotNull(cached);\n        Assert.Single(cached);\n        Assert.Equal(\"broker-a\", cached[0].Name);\n    }\n\n    [Fact]\n    [DisplayName(\"GetRouteInfo_null_topic不缓存且不抛出异常\")]\n    public void GetRouteInfo_NullTopic_DoesNotThrow()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.0.0.1:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        // null topic 不应抛出 ArgumentNullException\n        var brokers = client.GetRouteInfo(null);\n        Assert.Single(brokers);\n        Assert.Equal(\"broker-a\", brokers[0].Name);\n    }\n\n    [Fact]\n    [DisplayName(\"GetRouteInfo_Master地址排首位_Slave地址正确解析\")]\n    public void GetRouteInfo_MasterSlave_Addresses()\n    {\n        var target = \"\"\"\n            {\"brokerDatas\":[{\"brokerAddrs\":{\"0\":\"10.0.0.1:10911\",\"1\":\"10.0.0.2:10911\"},\"brokerName\":\"broker-a\",\"cluster\":\"DefaultCluster\",\"enableActingMaster\":false}],\"filterServerTable\":{},\"queueDatas\":[{\"brokerName\":\"broker-a\",\"perm\":6,\"readQueueNums\":8,\"topicSysFlag\":0,\"writeQueueNums\":8}]}\n            \"\"\";\n\n        var pb = new Producer();\n        var nc = new Mock<NameClient>(\"clientId\", pb) { CallBase = true };\n        nc.Setup(e => e.Invoke(RequestCode.GET_ROUTEINTO_BY_TOPIC, null, It.IsAny<Object>(), false))\n            .Returns(new Command { Payload = (ArrayPacket)target.GetBytes() });\n\n        var client = nc.Object;\n        var brokers = client.GetRouteInfo(null);\n        Assert.Single(brokers);\n\n        var broker = brokers[0];\n        Assert.Equal(\"10.0.0.1:10911\", broker.MasterAddress);\n        Assert.Single(broker.SlaveAddresses);\n        Assert.Equal(\"10.0.0.2:10911\", broker.SlaveAddresses[0]);\n        // Master 地址排在第一位\n        Assert.Equal(\"10.0.0.1:10911\", broker.Addresses[0]);\n        Assert.Equal(2, broker.Addresses.Length);\n        Assert.True(broker.IsMaster);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/OrderConsumeTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>顺序消费锁定测试</summary>\npublic class OrderConsumeTests\n{\n    [Fact]\n    [DisplayName(\"OrderConsume_默认为false\")]\n    public void OrderConsume_DefaultFalse()\n    {\n        using var consumer = new Consumer();\n        Assert.False(consumer.OrderConsume);\n    }\n\n    [Fact]\n    [DisplayName(\"OrderConsume_可启用\")]\n    public void OrderConsume_CanBeEnabled()\n    {\n        using var consumer = new Consumer { OrderConsume = true };\n        Assert.True(consumer.OrderConsume);\n    }\n\n    [Fact]\n    [DisplayName(\"LockBatchMQAsync_空列表返回空\")]\n    public async void LockBatchMQAsync_EmptyList_ReturnsEmpty()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.LockBatchMQAsync([]);\n        Assert.Empty(result);\n    }\n\n    [Fact]\n    [DisplayName(\"LockBatchMQAsync_Null列表返回空\")]\n    public async void LockBatchMQAsync_NullList_ReturnsEmpty()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.LockBatchMQAsync(null);\n        Assert.Empty(result);\n    }\n\n    [Fact]\n    [DisplayName(\"UnlockBatchMQAsync_空列表不抛异常\")]\n    public async void UnlockBatchMQAsync_EmptyList_NoException()\n    {\n        using var consumer = new Consumer();\n        await consumer.UnlockBatchMQAsync([]);\n    }\n\n    [Fact]\n    [DisplayName(\"UnlockBatchMQAsync_Null列表不抛异常\")]\n    public async void UnlockBatchMQAsync_NullList_NoException()\n    {\n        using var consumer = new Consumer();\n        await consumer.UnlockBatchMQAsync(null);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/PopConsumeTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Pop消费模式测试</summary>\npublic class PopConsumeTests\n{\n    [Fact]\n    [DisplayName(\"PopMessageAsync_Null的BrokerName抛出异常\")]\n    public async void PopMessageAsync_NullBrokerName_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.PopMessageAsync(null));\n    }\n\n    [Fact]\n    [DisplayName(\"PopMessageAsync_空BrokerName抛出异常\")]\n    public async void PopMessageAsync_EmptyBrokerName_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.PopMessageAsync(\"\"));\n    }\n\n    [Fact]\n    [DisplayName(\"PopMessageAsync_可指定queueId参数\")]\n    public async void PopMessageAsync_WithQueueId_ThrowsWhenBrokerNameNull()\n    {\n        using var consumer = new Consumer();\n        // 验证带queueId的重载依然会在brokerName为null时抛出异常\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.PopMessageAsync(null, queueId: 0));\n    }\n\n    [Fact]\n    [DisplayName(\"AckMessageAsync_无Broker连接时返回false\")]\n    public async void AckMessageAsync_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        // 未Start，无Broker连接\n        var result = await consumer.AckMessageAsync(\"nonexistent\", \"extra\", 0);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"AckMessageAsync_指定queueId_无Broker连接时返回false\")]\n    public async void AckMessageAsync_WithQueueId_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.AckMessageAsync(\"nonexistent\", \"extra\", 0, queueId: 2);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"AckMessageAsync_传入MessageExt_Null消息抛出异常\")]\n    public async void AckMessageAsync_NullMsg_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.AckMessageAsync(\"broker\", (MessageExt)null));\n    }\n\n    [Fact]\n    [DisplayName(\"AckMessageAsync_传入MessageExt_缺少POP_CK属性抛出异常\")]\n    public async void AckMessageAsync_MsgWithoutPopCk_ThrowsArgumentException()\n    {\n        using var consumer = new Consumer();\n        var msg = new MessageExt { QueueId = 1, QueueOffset = 100 };\n        // 没有设置 PopCheckPoint（POP_CK）\n        await Assert.ThrowsAsync<ArgumentException>(() =>\n            consumer.AckMessageAsync(\"broker\", msg));\n    }\n\n    [Fact]\n    [DisplayName(\"AckMessageAsync_传入MessageExt_无Broker连接时返回false\")]\n    public async void AckMessageAsync_WithMsgExt_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        var msg = new MessageExt { QueueId = 1, QueueOffset = 100 };\n        msg.PopCheckPoint = \"100 1700000000000 60000 1 broker-a 1\";\n        var result = await consumer.AckMessageAsync(\"nonexistent\", msg);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"ChangeInvisibleTimeAsync_无Broker连接时返回false\")]\n    public async void ChangeInvisibleTimeAsync_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.ChangeInvisibleTimeAsync(\"nonexistent\", \"extra\", 0, 30000);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"ChangeInvisibleTimeAsync_指定queueId_无Broker连接时返回false\")]\n    public async void ChangeInvisibleTimeAsync_WithQueueId_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        var result = await consumer.ChangeInvisibleTimeAsync(\"nonexistent\", \"extra\", 0, 30000, queueId: 3);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"ChangeInvisibleTimeAsync_传入MessageExt_Null消息抛出异常\")]\n    public async void ChangeInvisibleTimeAsync_NullMsg_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.ChangeInvisibleTimeAsync(\"broker\", (MessageExt)null, 30000));\n    }\n\n    [Fact]\n    [DisplayName(\"ChangeInvisibleTimeAsync_传入MessageExt_缺少POP_CK属性抛出异常\")]\n    public async void ChangeInvisibleTimeAsync_MsgWithoutPopCk_ThrowsArgumentException()\n    {\n        using var consumer = new Consumer();\n        var msg = new MessageExt { QueueId = 2, QueueOffset = 200 };\n        // 没有设置 PopCheckPoint（POP_CK）\n        await Assert.ThrowsAsync<ArgumentException>(() =>\n            consumer.ChangeInvisibleTimeAsync(\"broker\", msg, 30000));\n    }\n\n    [Fact]\n    [DisplayName(\"ChangeInvisibleTimeAsync_传入MessageExt_无Broker连接时返回false\")]\n    public async void ChangeInvisibleTimeAsync_WithMsgExt_NoBroker_ReturnsFalse()\n    {\n        using var consumer = new Consumer();\n        var msg = new MessageExt { QueueId = 2, QueueOffset = 200 };\n        msg.PopCheckPoint = \"200 1700000000000 60000 1 broker-a 2\";\n        var result = await consumer.ChangeInvisibleTimeAsync(\"nonexistent\", msg, 30000);\n        Assert.False(result);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageExt_PopCheckPoint属性读写正常\")]\n    public void MessageExt_PopCheckPoint_GetSet()\n    {\n        var msg = new MessageExt();\n        Assert.Null(msg.PopCheckPoint);\n\n        msg.PopCheckPoint = \"100 1700000000000 60000 1 broker-a 1\";\n        Assert.Equal(\"100 1700000000000 60000 1 broker-a 1\", msg.PopCheckPoint);\n        Assert.Equal(\"100 1700000000000 60000 1 broker-a 1\", msg.Properties[\"POP_CK\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"RequestCode包含Pop消费相关码\")]\n    public void RequestCode_ContainsPopCodes()\n    {\n        Assert.Equal(200050, (Int32)RequestCode.POP_MESSAGE);\n        Assert.Equal(200051, (Int32)RequestCode.ACK_MESSAGE);\n        Assert.Equal(200052, (Int32)RequestCode.CHANGE_MESSAGE_INVISIBLETIME);\n        Assert.Equal(200151, (Int32)RequestCode.BATCH_ACK_MESSAGE);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ProducerTests.cs",
    "content": "﻿using Moq;\nusing NewLife;\nusing NewLife.Data;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\npublic class ProducerTests\n{\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public void CreateTopic()\n    {\n        var set = BasicTest.GetConfig();\n        var mq = new Producer\n        {\n            //Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n\n            Log = XTrace.Log,\n        };\n\n        mq.Start();\n\n        // 创建topic时，start前不能指定topic，让其使用默认TBW102\n        Assert.Equal(\"TBW102\", mq.Topic);\n\n        var rs = mq.CreateTopic(\"nx_test\", 2);\n        Assert.True(rs > 0);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public static void ProduceTest()\n    {\n        var set = BasicTest.GetConfig();\n        using var mq = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n\n            Log = XTrace.Log,\n        };\n\n        mq.Start();\n\n        for (var i = 0; i < 10; i++)\n        {\n            var str = \"学无先后达者为师\" + i;\n            //var str = Rand.NextString(1337);\n\n            var sr = mq.Publish(str, \"TagA\", null);\n        }\n    }\n}"
  },
  {
    "path": "XUnitTestRocketMQ/ProducerTracerTests.cs",
    "content": "using System;\nusing System.Threading;\nusing NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ\n{\n    public class ProducerTracerTests\n    {\n        private const String Topic = \"TopicDemo\";\n        private const String Group = \"TraceTestGroup\";\n        private const String NameServerAddress = \"127.0.0.1:9876\";\n\n        [Fact(Skip = \"需要RocketMQ服务器支持\")]\n        public void Producer_And_Consumer_With_Trace_Enabled_Should_Work()\n        {\n            XTrace.UseConsole();\n            // 使用 ManualResetEvent 来同步测试的完成\n            var mre = new ManualResetEvent(false);\n\n            // 2. 创建并启动生产者\n            var producer = new Producer\n            {\n                Topic = Topic,\n                Group = Group, // 生产者组可以和消费者组不同，这里为了简单使用同一个\n                NameServerAddress = NameServerAddress,\n                EnableMessageTrace = true, // 启用消息轨迹\n                Log = XTrace.Log\n            };\n\n            producer.Start();\n\n            // 3. 发送消息\n            var messageBody = \"Hello, RocketMQ with Message Trace!\";\n            var sendResult = producer.Publish(messageBody);\n\n            Assert.NotNull(sendResult);\n            Assert.Equal(SendStatus.SendOK, sendResult.Status);\n            XTrace.WriteLine($\"消息发送成功: MsgId={sendResult.MsgId}\");\n\n            // 4. 等待消费者处理消息，设置一个超时时间以防测试挂起\n            bool consumed = mre.WaitOne(TimeSpan.FromSeconds(300));\n\n            // 5. 清理资源\n            producer.Stop();\n\n            // 6. 断言\n            Assert.True(consumed, \"消费者在超时时间内没有收到消息。\");\n        }\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ProtoTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.Buffers;\nusing NewLife.RocketMQ.Grpc;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Protobuf编解码器测试</summary>\npublic class ProtoTests\n{\n    #region SpanWriter/SpanReader 基础编解码\n    [Fact]\n    [DisplayName(\"Varint_编码解码正确\")]\n    public void Varint_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteRawVarint(0);\n        writer.WriteRawVarint(1);\n        writer.WriteRawVarint(127);\n        writer.WriteRawVarint(128);\n        writer.WriteRawVarint(300);\n        writer.WriteRawVarint(UInt64.MaxValue);\n\n        var reader = new SpanReader(writer.WrittenSpan.ToArray());\n        Assert.Equal(0UL, reader.ReadRawVarint());\n        Assert.Equal(1UL, reader.ReadRawVarint());\n        Assert.Equal(127UL, reader.ReadRawVarint());\n        Assert.Equal(128UL, reader.ReadRawVarint());\n        Assert.Equal(300UL, reader.ReadRawVarint());\n        Assert.Equal(UInt64.MaxValue, reader.ReadRawVarint());\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Fixed32_编码解码正确\")]\n    public void Fixed32_RoundTrip()\n    {\n        var buf = new Byte[64];\n        var writer = new SpanWriter(buf);\n        writer.WriteRawFixed32(0);\n        writer.WriteRawFixed32(12345);\n        writer.WriteRawFixed32(UInt32.MaxValue);\n\n        var reader = new SpanReader(writer.WrittenSpan.ToArray());\n        Assert.Equal(0U, reader.ReadFixed32());\n        Assert.Equal(12345U, reader.ReadFixed32());\n        Assert.Equal(UInt32.MaxValue, reader.ReadFixed32());\n    }\n\n    [Fact]\n    [DisplayName(\"Fixed64_编码解码正确\")]\n    public void Fixed64_RoundTrip()\n    {\n        var buf = new Byte[64];\n        var writer = new SpanWriter(buf);\n        writer.WriteRawFixed64(0);\n        writer.WriteRawFixed64(1234567890123456789);\n        writer.WriteRawFixed64(UInt64.MaxValue);\n\n        var reader = new SpanReader(writer.WrittenSpan.ToArray());\n        Assert.Equal(0UL, reader.ReadFixed64());\n        Assert.Equal(1234567890123456789UL, reader.ReadFixed64());\n        Assert.Equal(UInt64.MaxValue, reader.ReadFixed64());\n    }\n\n    [Fact]\n    [DisplayName(\"String字段_编码解码正确\")]\n    public void StringField_RoundTrip()\n    {\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteString(1, \"hello\");\n        writer.WriteString(2, \"世界\");\n        writer.WriteString(3, \"\");  // 空字符串不写入\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        // field 1: string \"hello\"\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(2, wt1); // length-delimited\n        Assert.Equal(\"hello\", reader.ReadProtoString());\n\n        // field 2: string \"世界\"\n        var (fn2, wt2) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(2, wt2);\n        Assert.Equal(\"世界\", reader.ReadProtoString());\n\n        // 没有 field 3（空字符串跳过）\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Int32字段_编码解码正确\")]\n    public void Int32Field_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteInt32(1, 42);\n        writer.WriteInt32(2, -1); // 负数编码为10字节varint\n        writer.WriteInt32(3, 0);  // 0不写入\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(0, wt1); // varint\n        Assert.Equal(42, reader.ReadProtoInt32());\n\n        var (fn2, wt2) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(-1, reader.ReadProtoInt32());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SInt32_ZigZag编码解码正确\")]\n    public void SInt32_ZigZag_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteSInt32(1, 0);   // 不写入\n        writer.WriteSInt32(2, 1);\n        writer.WriteSInt32(3, -1);\n        writer.WriteSInt32(4, Int32.MinValue);\n        writer.WriteSInt32(5, Int32.MaxValue);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        // field 2: sint32 = 1\n        var (fn2, _) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(1, reader.ReadSInt32());\n\n        // field 3: sint32 = -1\n        var (fn3, _) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(-1, reader.ReadSInt32());\n\n        // field 4: sint32 = Int32.MinValue\n        var (fn4, _) = reader.ReadTag();\n        Assert.Equal(4, fn4);\n        Assert.Equal(Int32.MinValue, reader.ReadSInt32());\n\n        // field 5: sint32 = Int32.MaxValue\n        var (fn5, _) = reader.ReadTag();\n        Assert.Equal(5, fn5);\n        Assert.Equal(Int32.MaxValue, reader.ReadSInt32());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Bool字段_编码解码正确\")]\n    public void BoolField_RoundTrip()\n    {\n        var buf = new Byte[64];\n        var writer = new SpanWriter(buf);\n        writer.WriteBool(1, true);\n        writer.WriteBool(2, false); // false不写入\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.True(reader.ReadBool());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Bytes字段_编码解码正确\")]\n    public void BytesField_RoundTrip()\n    {\n        var testData = new Byte[] { 0x01, 0x02, 0x03, 0xFF };\n        var buf = new Byte[64];\n        var writer = new SpanWriter(buf);\n        writer.WriteBytes(1, testData);\n        writer.WriteBytes(2, null);    // null不写入\n        writer.WriteBytes(3, []);      // 空不写入\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(2, wt1);\n        Assert.Equal(testData, reader.ReadProtoBytes());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Map字段_编码解码正确\")]\n    public void MapField_RoundTrip()\n    {\n        var map = new Dictionary<String, String>\n        {\n            [\"key1\"] = \"value1\",\n            [\"key2\"] = \"value2\",\n        };\n\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteMap(1, map);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var result = new Dictionary<String, String>();\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            Assert.Equal(1, fn);\n            Assert.Equal(2, wt);\n            var (k, v) = reader.ReadMapEntry();\n            result[k] = v;\n        }\n\n        Assert.Equal(2, result.Count);\n        Assert.Equal(\"value1\", result[\"key1\"]);\n        Assert.Equal(\"value2\", result[\"key2\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"SkipField_正确跳过未知字段\")]\n    public void SkipField_Works()\n    {\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteInt32(1, 42);       // varint\n        writer.WriteString(2, \"skip\");  // length-delimited\n        writer.WriteInt32(3, 99);       // varint\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        // 读field 1\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(42, reader.ReadProtoInt32());\n\n        // 跳过field 2\n        var (fn2, wt2) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        reader.SkipField(wt2);\n\n        // 读field 3\n        var (fn3, wt3) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(99, reader.ReadProtoInt32());\n\n        Assert.True(reader.Available <= 0);\n    }\n    #endregion\n\n    #region Timestamp/Duration\n    [Fact]\n    [DisplayName(\"Timestamp_编码解码正确\")]\n    public void Timestamp_RoundTrip()\n    {\n        var time = new DateTime(2024, 6, 15, 12, 30, 45, DateTimeKind.Utc);\n\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteTimestamp(1, time);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn, wt) = reader.ReadTag();\n        Assert.Equal(1, fn);\n        Assert.Equal(2, wt);\n\n        var result = reader.ReadTimestamp();\n        // 允许毫秒级误差（因为Timestamp精度是纳秒/100）\n        Assert.Equal(time.Year, result.Year);\n        Assert.Equal(time.Month, result.Month);\n        Assert.Equal(time.Day, result.Day);\n        Assert.Equal(time.Hour, result.Hour);\n        Assert.Equal(time.Minute, result.Minute);\n        Assert.Equal(time.Second, result.Second);\n    }\n\n    [Fact]\n    [DisplayName(\"Duration_编码解码正确\")]\n    public void Duration_RoundTrip()\n    {\n        var duration = TimeSpan.FromSeconds(90);\n\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteDuration(1, duration);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn, _) = reader.ReadTag();\n        Assert.Equal(1, fn);\n\n        var result = reader.ReadDuration();\n        Assert.Equal(90, (Int32)result.TotalSeconds);\n    }\n    #endregion\n\n    #region 嵌套消息\n    [Fact]\n    [DisplayName(\"嵌套消息_编码解码正确\")]\n    public void NestedMessage_RoundTrip()\n    {\n        var resource = new GrpcResource\n        {\n            ResourceNamespace = \"test-ns\",\n            Name = \"test-topic\",\n        };\n\n        var data = ProtoExtensions.Serialize(resource);\n        var reader = new SpanReader(data);\n\n        // 手工读取外层结构\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(\"test-ns\", reader.ReadProtoString());\n        var (fn2, _) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(\"test-topic\", reader.ReadProtoString());\n    }\n\n    [Fact]\n    [DisplayName(\"嵌套消息_WriteMessage编码解码正确\")]\n    public void NestedMessage_WriteMessage_RoundTrip()\n    {\n        var resource = new GrpcResource\n        {\n            ResourceNamespace = \"test-ns\",\n            Name = \"test-topic\",\n        };\n\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteMessage(1, resource);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn, wt) = reader.ReadTag();\n        Assert.Equal(1, fn);\n        Assert.Equal(2, wt);\n\n        var result = reader.ReadProtoMessage<GrpcResource>();\n        Assert.Equal(\"test-ns\", result.ResourceNamespace);\n        Assert.Equal(\"test-topic\", result.Name);\n    }\n\n    [Fact]\n    [DisplayName(\"GrpcMessage_完整消息编码解码\")]\n    public void GrpcMessage_FullRoundTrip()\n    {\n        var msg = new GrpcMessage\n        {\n            Topic = new GrpcResource { ResourceNamespace = \"ns\", Name = \"topic1\" },\n            SystemProperties = new GrpcSystemProperties\n            {\n                Tag = \"tagA\",\n                MessageId = \"msg-001\",\n                MessageType = GrpcMessageType.NORMAL,\n                BornTimestamp = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),\n                BornHost = \"localhost\",\n                QueueId = 3,\n                QueueOffset = 12345,\n            },\n            Body = new Byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F },\n        };\n        msg.UserProperties[\"user_key\"] = \"user_value\";\n\n        var data = ProtoExtensions.Serialize(msg);\n\n        var reader = new SpanReader(data);\n        var result = new GrpcMessage();\n        result.Read(ref reader);\n\n        Assert.Equal(\"ns\", result.Topic.ResourceNamespace);\n        Assert.Equal(\"topic1\", result.Topic.Name);\n        Assert.Equal(\"tagA\", result.SystemProperties.Tag);\n        Assert.Equal(\"msg-001\", result.SystemProperties.MessageId);\n        Assert.Equal(GrpcMessageType.NORMAL, result.SystemProperties.MessageType);\n        Assert.Equal(\"localhost\", result.SystemProperties.BornHost);\n        Assert.Equal(3, result.SystemProperties.QueueId);\n        Assert.Equal(12345, result.SystemProperties.QueueOffset);\n        Assert.Equal(new Byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F }, result.Body);\n        Assert.Equal(\"user_value\", result.UserProperties[\"user_key\"]);\n    }\n    #endregion\n\n    #region GrpcClient帧编码\n    [Fact]\n    [DisplayName(\"gRPC帧编码_正确\")]\n    public void GrpcFrame_Encode()\n    {\n        var data = new Byte[] { 0x01, 0x02, 0x03 };\n        var frame = GrpcClient.FrameEncode(data);\n\n        Assert.Equal(8, frame.Length);\n        Assert.Equal(0, frame[0]);  // 不压缩\n        Assert.Equal(0, frame[1]);  // 长度高位\n        Assert.Equal(0, frame[2]);\n        Assert.Equal(0, frame[3]);\n        Assert.Equal(3, frame[4]);  // 长度=3\n        Assert.Equal(0x01, frame[5]);\n        Assert.Equal(0x02, frame[6]);\n        Assert.Equal(0x03, frame[7]);\n    }\n\n    [Fact]\n    [DisplayName(\"gRPC帧解码_正确\")]\n    public void GrpcFrame_Decode()\n    {\n        var data = new Byte[] { 0x01, 0x02, 0x03 };\n        var frame = GrpcClient.FrameEncode(data);\n        var decoded = GrpcClient.FrameDecode(frame);\n\n        Assert.Equal(data, decoded);\n    }\n\n    [Fact]\n    [DisplayName(\"gRPC帧_空数据编解码\")]\n    public void GrpcFrame_EmptyData()\n    {\n        var frame = GrpcClient.FrameEncode(null);\n        Assert.Equal(5, frame.Length);\n        Assert.Equal(0, frame[4]); // 长度0\n\n        var decoded = GrpcClient.FrameDecode(frame);\n        Assert.Empty(decoded);\n    }\n\n    [Fact]\n    [DisplayName(\"gRPC帧解码_数据不足返回空\")]\n    public void GrpcFrame_Decode_TooShort()\n    {\n        var decoded = GrpcClient.FrameDecode(new Byte[] { 0, 0, 0 });\n        Assert.Empty(decoded);\n    }\n    #endregion\n\n    #region 服务消息\n    [Fact]\n    [DisplayName(\"QueryRouteRequest_编码解码正确\")]\n    public void QueryRouteRequest_RoundTrip()\n    {\n        var request = new QueryRouteRequest\n        {\n            Topic = new GrpcResource { ResourceNamespace = \"ns\", Name = \"test\" },\n            Endpoints = new GrpcEndpoints\n            {\n                Scheme = AddressScheme.IPv4,\n                Addresses = [new GrpcAddress { Host = \"127.0.0.1\", Port = 8081 }],\n            },\n        };\n\n        var data = ProtoExtensions.Serialize(request);\n\n        var reader = new SpanReader(data);\n        var result = new QueryRouteRequest();\n        result.Read(ref reader);\n\n        Assert.Equal(\"ns\", result.Topic.ResourceNamespace);\n        Assert.Equal(\"test\", result.Topic.Name);\n        Assert.Equal(AddressScheme.IPv4, result.Endpoints.Scheme);\n        Assert.Single(result.Endpoints.Addresses);\n        Assert.Equal(\"127.0.0.1\", result.Endpoints.Addresses[0].Host);\n        Assert.Equal(8081, result.Endpoints.Addresses[0].Port);\n    }\n\n    [Fact]\n    [DisplayName(\"SendMessageRequest_编码解码正确\")]\n    public void SendMessageRequest_RoundTrip()\n    {\n        var request = new SendMessageRequest();\n        request.Messages.Add(new GrpcMessage\n        {\n            Topic = new GrpcResource { Name = \"topic1\" },\n            Body = new Byte[] { 1, 2, 3 },\n            SystemProperties = new GrpcSystemProperties\n            {\n                Tag = \"tag1\",\n                MessageType = GrpcMessageType.NORMAL,\n            },\n        });\n\n        var data = ProtoExtensions.Serialize(request);\n\n        var reader = new SpanReader(data);\n        var result = new SendMessageRequest();\n        result.Read(ref reader);\n\n        Assert.Single(result.Messages);\n        Assert.Equal(\"topic1\", result.Messages[0].Topic.Name);\n        Assert.Equal(new Byte[] { 1, 2, 3 }, result.Messages[0].Body);\n        Assert.Equal(\"tag1\", result.Messages[0].SystemProperties.Tag);\n    }\n\n    [Fact]\n    [DisplayName(\"GrpcStatus_编码解码正确\")]\n    public void GrpcStatus_RoundTrip()\n    {\n        var status = new GrpcStatus\n        {\n            Code = GrpcCode.OK,\n            Message = \"Success\",\n        };\n\n        var data = ProtoExtensions.Serialize(status);\n\n        var reader = new SpanReader(data);\n        var result = new GrpcStatus();\n        result.Read(ref reader);\n\n        Assert.Equal(GrpcCode.OK, result.Code);\n        Assert.Equal(\"Success\", result.Message);\n    }\n\n    [Fact]\n    [DisplayName(\"GrpcMessageQueue_含AcceptMessageTypes编解码\")]\n    public void GrpcMessageQueue_WithTypes_RoundTrip()\n    {\n        var mq = new GrpcMessageQueue\n        {\n            Topic = new GrpcResource { Name = \"topic1\" },\n            Id = 2,\n            Permission = GrpcPermission.READ_WRITE,\n            Broker = new GrpcBroker\n            {\n                Name = \"broker-a\",\n                Id = 0,\n                Endpoints = new GrpcEndpoints\n                {\n                    Scheme = AddressScheme.IPv4,\n                    Addresses = [new GrpcAddress { Host = \"10.0.0.1\", Port = 8081 }],\n                },\n            },\n            AcceptMessageTypes = [GrpcMessageType.NORMAL, GrpcMessageType.DELAY],\n        };\n\n        var data = ProtoExtensions.Serialize(mq);\n\n        var reader = new SpanReader(data);\n        var result = new GrpcMessageQueue();\n        result.Read(ref reader);\n\n        Assert.Equal(\"topic1\", result.Topic.Name);\n        Assert.Equal(2, result.Id);\n        Assert.Equal(GrpcPermission.READ_WRITE, result.Permission);\n        Assert.Equal(\"broker-a\", result.Broker.Name);\n        Assert.Equal(2, result.AcceptMessageTypes.Count);\n        Assert.Contains(GrpcMessageType.NORMAL, result.AcceptMessageTypes);\n        Assert.Contains(GrpcMessageType.DELAY, result.AcceptMessageTypes);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ProtocolDataTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>协议数据结构测试</summary>\npublic class ProtocolDataTests\n{\n    #region HeartbeatData\n    [Fact]\n    [DisplayName(\"HeartbeatData_默认属性为null\")]\n    public void HeartbeatData_Defaults()\n    {\n        var hb = new HeartbeatData();\n\n        Assert.Null(hb.ClientID);\n        Assert.Null(hb.ConsumerDataSet);\n        Assert.Null(hb.ProducerDataSet);\n    }\n\n    [Fact]\n    [DisplayName(\"HeartbeatData_设置所有属性\")]\n    public void HeartbeatData_SetAllProperties()\n    {\n        var hb = new HeartbeatData\n        {\n            ClientID = \"client-001\",\n            ConsumerDataSet = [new ConsumerData { GroupName = \"CG1\" }],\n            ProducerDataSet = [new ProducerData { GroupName = \"PG1\" }],\n        };\n\n        Assert.Equal(\"client-001\", hb.ClientID);\n        Assert.Single(hb.ConsumerDataSet);\n        Assert.Equal(\"CG1\", hb.ConsumerDataSet[0].GroupName);\n        Assert.Single(hb.ProducerDataSet);\n        Assert.Equal(\"PG1\", hb.ProducerDataSet[0].GroupName);\n    }\n    #endregion\n\n    #region ProducerData\n    [Fact]\n    [DisplayName(\"ProducerData_默认GroupName\")]\n    public void ProducerData_DefaultGroupName()\n    {\n        var pd = new ProducerData();\n\n        Assert.Equal(\"CLIENT_INNER_PRODUCER\", pd.GroupName);\n    }\n\n    [Fact]\n    [DisplayName(\"ProducerData_可自定义GroupName\")]\n    public void ProducerData_CustomGroupName()\n    {\n        var pd = new ProducerData { GroupName = \"MY_PRODUCER\" };\n\n        Assert.Equal(\"MY_PRODUCER\", pd.GroupName);\n    }\n    #endregion\n\n    #region ConsumerData\n    [Fact]\n    [DisplayName(\"ConsumerData_默认值\")]\n    public void ConsumerData_Defaults()\n    {\n        var cd = new ConsumerData();\n\n        Assert.Equal(\"CONSUME_FROM_LAST_OFFSET\", cd.ConsumeFromWhere);\n        Assert.Equal(\"CONSUME_ACTIVELY\", cd.ConsumeType);\n        Assert.Null(cd.GroupName);\n        Assert.Equal(\"CLUSTERING\", cd.MessageModel);\n        Assert.Null(cd.SubscriptionDataSet);\n        Assert.False(cd.UnitMode);\n    }\n\n    [Fact]\n    [DisplayName(\"ConsumerData_设置所有属性\")]\n    public void ConsumerData_SetAllProperties()\n    {\n        var cd = new ConsumerData\n        {\n            ConsumeFromWhere = \"CONSUME_FROM_FIRST_OFFSET\",\n            ConsumeType = \"CONSUME_PASSIVELY\",\n            GroupName = \"CG_TEST\",\n            MessageModel = \"BROADCASTING\",\n            UnitMode = true,\n            SubscriptionDataSet = [new SubscriptionData { Topic = \"test\" }],\n        };\n\n        Assert.Equal(\"CONSUME_FROM_FIRST_OFFSET\", cd.ConsumeFromWhere);\n        Assert.Equal(\"CONSUME_PASSIVELY\", cd.ConsumeType);\n        Assert.Equal(\"CG_TEST\", cd.GroupName);\n        Assert.Equal(\"BROADCASTING\", cd.MessageModel);\n        Assert.True(cd.UnitMode);\n        Assert.Single(cd.SubscriptionDataSet);\n    }\n    #endregion\n\n    #region SubscriptionData\n    [Fact]\n    [DisplayName(\"SubscriptionData_默认值\")]\n    public void SubscriptionData_Defaults()\n    {\n        var sd = new SubscriptionData();\n\n        Assert.Null(sd.Topic);\n        Assert.Equal(\"TAG\", sd.ExpressionType);\n        Assert.Equal(\"*\", sd.SubString);\n        Assert.Null(sd.TagsSet);\n        Assert.Null(sd.CodeSet);\n        Assert.False(sd.ClassFilterMode);\n        Assert.Null(sd.FilterClassSource);\n        Assert.True(sd.SubVersion > 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SubscriptionData_设置SQL92过滤\")]\n    public void SubscriptionData_SQL92Filter()\n    {\n        var sd = new SubscriptionData\n        {\n            Topic = \"order_topic\",\n            ExpressionType = \"SQL92\",\n            SubString = \"price > 100\",\n        };\n\n        Assert.Equal(\"order_topic\", sd.Topic);\n        Assert.Equal(\"SQL92\", sd.ExpressionType);\n        Assert.Equal(\"price > 100\", sd.SubString);\n    }\n\n    [Fact]\n    [DisplayName(\"SubscriptionData_设置标签集合\")]\n    public void SubscriptionData_TagsSet()\n    {\n        var sd = new SubscriptionData\n        {\n            Topic = \"test\",\n            TagsSet = [\"TagA\", \"TagB\", \"TagC\"],\n            CodeSet = [\"1\", \"2\"],\n        };\n\n        Assert.Equal(3, sd.TagsSet.Length);\n        Assert.Equal(\"TagA\", sd.TagsSet[0]);\n        Assert.Equal(2, sd.CodeSet.Length);\n    }\n    #endregion\n\n    #region QueryResult\n    [Fact]\n    [DisplayName(\"QueryResult_默认值\")]\n    public void QueryResult_Defaults()\n    {\n        var qr = new QueryResult();\n\n        Assert.Equal(0, qr.IndexLastUpdateTimestamp);\n        Assert.Null(qr.MessageList);\n    }\n\n    [Fact]\n    [DisplayName(\"QueryResult_设置属性\")]\n    public void QueryResult_SetProperties()\n    {\n        var qr = new QueryResult\n        {\n            IndexLastUpdateTimestamp = 12345,\n            MessageList = [new MessageExt { Topic = \"t1\" }],\n        };\n\n        Assert.Equal(12345, qr.IndexLastUpdateTimestamp);\n        Assert.Single(qr.MessageList);\n        Assert.Equal(\"t1\", qr.MessageList[0].Topic);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/PullResultTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>PullResult拉取结果测试</summary>\npublic class PullResultTests\n{\n    #region Read方法\n    [Fact]\n    [DisplayName(\"Read_解析所有偏移字段\")]\n    public void Read_ParsesAllOffsetFields()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"MinOffset\"] = \"100\",\n            [\"MaxOffset\"] = \"9999\",\n            [\"NextBeginOffset\"] = \"500\",\n        };\n\n        var result = new PullResult();\n        result.Read(dic);\n\n        Assert.Equal(100, result.MinOffset);\n        Assert.Equal(9999, result.MaxOffset);\n        Assert.Equal(500, result.NextBeginOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_Null字典不抛异常\")]\n    public void Read_NullDictionary_NoException()\n    {\n        var result = new PullResult();\n        result.Read(null);\n\n        Assert.Equal(0, result.MinOffset);\n        Assert.Equal(0, result.MaxOffset);\n        Assert.Equal(0, result.NextBeginOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_空字典不影响属性\")]\n    public void Read_EmptyDictionary_NoEffect()\n    {\n        var result = new PullResult();\n        result.Read(new Dictionary<String, String>());\n\n        Assert.Equal(0, result.MinOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_部分字段解析\")]\n    public void Read_PartialFields()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"MaxOffset\"] = \"1000\",\n        };\n\n        var result = new PullResult();\n        result.Read(dic);\n\n        Assert.Equal(0, result.MinOffset);\n        Assert.Equal(1000, result.MaxOffset);\n        Assert.Equal(0, result.NextBeginOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_大小写不敏感\")]\n    public void Read_CaseInsensitive()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"minoffset\"] = \"50\",\n            [\"MAXOFFSET\"] = \"200\",\n        };\n\n        var result = new PullResult();\n        result.Read(dic);\n\n        Assert.Equal(50, result.MinOffset);\n        Assert.Equal(200, result.MaxOffset);\n    }\n    #endregion\n\n    #region ToString\n    [Fact]\n    [DisplayName(\"ToString_包含状态和偏移信息\")]\n    public void ToString_ContainsStatusAndOffsets()\n    {\n        var result = new PullResult\n        {\n            Status = PullStatus.Found,\n            MinOffset = 10,\n            MaxOffset = 100,\n            Messages = [new MessageExt(), new MessageExt()]\n        };\n\n        var str = result.ToString();\n\n        Assert.Contains(\"Found\", str);\n        Assert.Contains(\"10\", str);\n        Assert.Contains(\"100\", str);\n        Assert.Contains(\"2\", str);\n    }\n\n    [Fact]\n    [DisplayName(\"ToString_无消息时显示0\")]\n    public void ToString_NullMessages_ShowsZero()\n    {\n        var result = new PullResult { Status = PullStatus.NoNewMessage };\n\n        var str = result.ToString();\n\n        Assert.Contains(\"NoNewMessage\", str);\n        Assert.Contains(\"0\", str);\n    }\n    #endregion\n\n    #region 枚举\n    [Fact]\n    [DisplayName(\"PullStatus_枚举值正确\")]\n    public void PullStatus_EnumValues()\n    {\n        Assert.Equal(0, (Int32)PullStatus.Found);\n        Assert.Equal(1, (Int32)PullStatus.NoNewMessage);\n        Assert.Equal(2, (Int32)PullStatus.NoMatchedMessage);\n        Assert.Equal(3, (Int32)PullStatus.OffsetIllegal);\n        Assert.Equal(4, (Int32)PullStatus.Unknown);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/QueryMessageTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>按Key查询消息测试</summary>\npublic class QueryMessageTests\n{\n    [Fact]\n    [DisplayName(\"QueryMessageByKey_Null的Key抛出异常\")]\n    public void QueryMessageByKey_NullKey_ThrowsException()\n    {\n        using var producer = new Producer();\n        Assert.Throws<ArgumentNullException>(() => producer.QueryMessageByKey(\"test\", null));\n    }\n\n    [Fact]\n    [DisplayName(\"QueryMessageByKey_空Key抛出异常\")]\n    public void QueryMessageByKey_EmptyKey_ThrowsException()\n    {\n        using var producer = new Producer();\n        Assert.Throws<ArgumentNullException>(() => producer.QueryMessageByKey(\"test\", \"\"));\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器\")]\n    [DisplayName(\"QueryMessageByKey_按Key查询消息\")]\n    public void QueryMessageByKey_Integration()\n    {\n        var set = BasicTest.GetConfig();\n        using var producer = new Producer\n        {\n            Topic = \"nx_test\",\n            NameServerAddress = set.NameServer,\n        };\n        producer.Start();\n\n        var msgs = producer.QueryMessageByKey(\"nx_test\", \"test_key_123\");\n        // 不做严格断言，仅验证不抛出异常\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/RequestHeaderTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Linq;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>请求头GetProperties方法测试</summary>\npublic class RequestHeaderTests\n{\n    #region SendMessageRequestHeader\n    [Fact]\n    [DisplayName(\"SendMessageRequestHeader_GetProperties返回所有属性\")]\n    public void SendMessageRequestHeader_GetProperties_ReturnsAll()\n    {\n        var header = new SendMessageRequestHeader\n        {\n            ProducerGroup = \"PG_TEST\",\n            Topic = \"test_topic\",\n            DefaultTopic = \"TBW102\",\n            DefaultTopicQueueNums = 4,\n            QueueId = 1,\n            SysFlag = 0,\n            BornTimestamp = 1000L,\n            Flag = 0,\n            Properties = \"TAGS\\u0001test\",\n            ReconsumeTimes = 0,\n            UnitMode = false,\n            ConsumeRetryTimes = 0,\n            Batch = false,\n            BrokerName = \"broker-0\",\n        };\n\n        var dic = header.GetProperties();\n\n        Assert.NotNull(dic);\n        Assert.True(dic.Count > 0);\n\n        // XmlElement(ElementName=\"a\") 映射\n        Assert.True(dic.ContainsKey(\"a\"), \"ProducerGroup应映射为a\");\n        Assert.Equal(\"PG_TEST\", dic[\"a\"]);\n\n        Assert.True(dic.ContainsKey(\"b\"), \"Topic应映射为b\");\n        Assert.Equal(\"test_topic\", dic[\"b\"]);\n\n        Assert.True(dic.ContainsKey(\"c\"), \"DefaultTopic应映射为c\");\n        Assert.Equal(\"TBW102\", dic[\"c\"]);\n\n        Assert.True(dic.ContainsKey(\"e\"), \"QueueId应映射为e\");\n        Assert.True(dic.ContainsKey(\"i\"), \"Properties应映射为i\");\n    }\n\n    [Fact]\n    [DisplayName(\"SendMessageRequestHeader_默认值GetProperties\")]\n    public void SendMessageRequestHeader_DefaultValues_GetProperties()\n    {\n        var header = new SendMessageRequestHeader();\n\n        var dic = header.GetProperties();\n\n        Assert.NotNull(dic);\n        // 即使默认值，属性也应该被包含\n        Assert.True(dic.Count >= 10);\n    }\n    #endregion\n\n    #region PullMessageRequestHeader\n    [Fact]\n    [DisplayName(\"PullMessageRequestHeader_GetProperties返回camelCase键\")]\n    public void PullMessageRequestHeader_GetProperties_CamelCaseKeys()\n    {\n        var header = new PullMessageRequestHeader\n        {\n            ConsumerGroup = \"CG_TEST\",\n            Topic = \"test_topic\",\n            Subscription = \"*\",\n            QueueId = 2,\n            QueueOffset = 100,\n            MaxMsgNums = 32,\n            SuspendTimeoutMillis = 20000,\n        };\n\n        var dic = header.GetProperties();\n\n        Assert.NotNull(dic);\n        Assert.True(dic.ContainsKey(\"consumerGroup\"), \"应转为camelCase\");\n        Assert.True(dic.ContainsKey(\"topic\"));\n        Assert.True(dic.ContainsKey(\"subscription\"));\n        Assert.True(dic.ContainsKey(\"queueId\"));\n        Assert.True(dic.ContainsKey(\"queueOffset\"));\n        Assert.True(dic.ContainsKey(\"maxMsgNums\"));\n        Assert.True(dic.ContainsKey(\"suspendTimeoutMillis\"));\n    }\n\n    [Fact]\n    [DisplayName(\"PullMessageRequestHeader_默认ExpressionType为TAG\")]\n    public void PullMessageRequestHeader_DefaultExpressionType()\n    {\n        var header = new PullMessageRequestHeader();\n\n        Assert.Equal(\"TAG\", header.ExpressionType);\n        Assert.Equal(\"*\", header.Subscription);\n        Assert.Equal(20000, header.SuspendTimeoutMillis);\n    }\n\n    [Fact]\n    [DisplayName(\"PullMessageRequestHeader_GetProperties值为字符串\")]\n    public void PullMessageRequestHeader_GetProperties_ValuesAreStrings()\n    {\n        var header = new PullMessageRequestHeader\n        {\n            QueueId = 5,\n            MaxMsgNums = 32,\n        };\n\n        var dic = header.GetProperties();\n\n        // PullMessageRequestHeader 的 GetProperties 会 + \"\" 转为字符串\n        Assert.IsType<String>(dic[\"queueId\"]);\n        Assert.Equal(\"5\", dic[\"queueId\"]);\n    }\n    #endregion\n\n    #region EndTransactionRequestHeader\n    [Fact]\n    [DisplayName(\"EndTransactionRequestHeader_GetProperties返回camelCase键\")]\n    public void EndTransactionRequestHeader_GetProperties_CamelCaseKeys()\n    {\n        var header = new EndTransactionRequestHeader\n        {\n            ProducerGroup = \"PG_TX\",\n            TranStateTableOffset = 100,\n            CommitLogOffset = 200,\n            CommitOrRollback = 1,\n            FromTransactionCheck = true,\n            MsgId = \"MSG001\",\n            TransactionId = \"TX001\",\n        };\n\n        var dic = header.GetProperties();\n\n        Assert.NotNull(dic);\n        Assert.True(dic.ContainsKey(\"producerGroup\"));\n        Assert.True(dic.ContainsKey(\"tranStateTableOffset\"));\n        Assert.True(dic.ContainsKey(\"commitLogOffset\"));\n        Assert.True(dic.ContainsKey(\"commitOrRollback\"));\n        Assert.True(dic.ContainsKey(\"fromTransactionCheck\"));\n        Assert.True(dic.ContainsKey(\"msgId\"));\n        Assert.True(dic.ContainsKey(\"transactionId\"));\n\n        Assert.Equal(\"PG_TX\", dic[\"producerGroup\"]);\n        Assert.Equal(\"TX001\", dic[\"transactionId\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"EndTransactionRequestHeader_布尔值序列化\")]\n    public void EndTransactionRequestHeader_BooleanSerialization()\n    {\n        var header = new EndTransactionRequestHeader\n        {\n            FromTransactionCheck = true,\n        };\n\n        var dic = header.GetProperties();\n\n        // 值是原始 Boolean 对象（非字符串），且为 true\n        Assert.True(dic.ContainsKey(\"fromTransactionCheck\"));\n        var fromTransactionCheck = Assert.IsType<Boolean>(dic[\"fromTransactionCheck\"]);\n        Assert.True(fromTransactionCheck);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/RequestReplyTests.cs",
    "content": "using System;\nusing System.Threading.Tasks;\nusing NewLife;\nusing NewLife.Log;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>Request-Reply 特性测试</summary>\npublic class RequestReplyTests\n{\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public void RequestSyncTest()\n    {\n        var set = BasicTest.GetConfig();\n        \n        // 创建生产者\n        using var producer = new Producer\n        {\n            Topic = \"nx_request_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        producer.Start();\n\n        // 创建消费者\n        using var consumer = new Consumer\n        {\n            Topic = \"nx_request_test\",\n            Group = \"nx_request_group\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n            FromLastOffset = true,\n        };\n\n        // 消费者处理请求并返回回复\n        consumer.OnConsume = (q, ms) =>\n        {\n            foreach (var item in ms)\n            {\n                XTrace.WriteLine(\"收到请求: {0}\", item.BodyString);\n                \n                // 如果是请求消息，发送回复\n                if (!String.IsNullOrEmpty(item.CorrelationId))\n                {\n                    var replyBody = $\"Reply to: {item.BodyString}\";\n                    consumer.SendReply(item, replyBody);\n                }\n            }\n            return true;\n        };\n\n        consumer.Start();\n\n        // 发送请求并等待响应\n        var requestBody = \"Hello, this is a request!\";\n        var response = producer.Request(requestBody, 5000);\n\n        Assert.NotNull(response);\n        Assert.Contains(\"Reply to:\", response.BodyString);\n        XTrace.WriteLine(\"收到响应: {0}\", response.BodyString);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public async Task RequestAsyncTest()\n    {\n        var set = BasicTest.GetConfig();\n        \n        // 创建生产者\n        using var producer = new Producer\n        {\n            Topic = \"nx_request_test_async\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n        };\n        producer.Start();\n\n        // 创建消费者\n        using var consumer = new Consumer\n        {\n            Topic = \"nx_request_test_async\",\n            Group = \"nx_request_async_group\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n            FromLastOffset = true,\n        };\n\n        // 消费者异步处理请求并返回回复\n        consumer.OnConsumeAsync = async (q, ms, ct) =>\n        {\n            foreach (var item in ms)\n            {\n                XTrace.WriteLine(\"收到请求: {0}\", item.BodyString);\n                \n                // 如果是请求消息，发送回复\n                if (!String.IsNullOrEmpty(item.CorrelationId))\n                {\n                    var replyBody = $\"Async Reply to: {item.BodyString}\";\n                    await consumer.SendReplyAsync(item, replyBody, ct).ConfigureAwait(false);\n                }\n            }\n            return true;\n        };\n\n        consumer.Start();\n\n        // 异步发送请求并等待响应\n        var requestBody = \"Hello, this is an async request!\";\n        var response = await producer.RequestAsync(requestBody, 5000).ConfigureAwait(false);\n\n        Assert.NotNull(response);\n        Assert.Contains(\"Async Reply to:\", response.BodyString);\n        XTrace.WriteLine(\"收到响应: {0}\", response.BodyString);\n    }\n\n    [Fact(Skip = \"需要RocketMQ服务器支持\")]\n    public async Task RequestTimeoutTest()\n    {\n        var set = BasicTest.GetConfig();\n        \n        // 创建生产者\n        using var producer = new Producer\n        {\n            Topic = \"nx_request_timeout_test\",\n            NameServerAddress = set.NameServer,\n            Log = XTrace.Log,\n            RequestTimeout = 1000, // 1秒超时\n        };\n        producer.Start();\n\n        // 不启动消费者，请求应该超时\n\n        // 发送请求，期望超时\n        var requestBody = \"This should timeout\";\n        \n        await Assert.ThrowsAsync<TimeoutException>(async () =>\n        {\n            await producer.RequestAsync(requestBody).ConfigureAwait(false);\n        });\n\n        XTrace.WriteLine(\"请求超时测试通过\");\n    }\n\n    [Fact]\n    public void MessagePropertiesTest()\n    {\n        // 测试消息属性\n        var message = new Message\n        {\n            Topic = \"test_topic\",\n            ReplyToClient = \"client_123\",\n            CorrelationId = \"corr_456\",\n            MessageType = \"REQUEST\",\n            RequestTimeout = 3000\n        };\n        message.SetBody(\"test body\");\n\n        Assert.Equal(\"client_123\", message.ReplyToClient);\n        Assert.Equal(\"corr_456\", message.CorrelationId);\n        Assert.Equal(\"REQUEST\", message.MessageType);\n        Assert.Equal(3000, message.RequestTimeout);\n\n        // 测试属性序列化\n        var props = message.GetProperties();\n        Assert.Contains(\"REPLY_TO_CLIENT\", props);\n        Assert.Contains(\"CORRELATION_ID\", props);\n        Assert.Contains(\"MSG_TYPE\", props);\n        Assert.Contains(\"REQUEST_TIMEOUT\", props);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/ResponseExceptionTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>ResponseException响应异常测试</summary>\npublic class ResponseExceptionTests\n{\n    [Fact]\n    [DisplayName(\"构造函数_设置Code和Message\")]\n    public void Constructor_SetsCodeAndMessage()\n    {\n        var ex = new ResponseException(ResponseCode.SYSTEM_ERROR, \"test error\");\n\n        Assert.Equal(ResponseCode.SYSTEM_ERROR, ex.Code);\n        Assert.Contains(\"SYSTEM_ERROR\", ex.Message);\n        Assert.Contains(\"test error\", ex.Message);\n    }\n\n    [Fact]\n    [DisplayName(\"构造函数_不同ResponseCode\")]\n    public void Constructor_DifferentCodes()\n    {\n        var ex1 = new ResponseException(ResponseCode.SUCCESS, \"ok\");\n        var ex2 = new ResponseException(ResponseCode.TOPIC_NOT_EXIST, \"topic missing\");\n        var ex3 = new ResponseException(ResponseCode.NO_PERMISSION, \"denied\");\n\n        Assert.Equal(ResponseCode.SUCCESS, ex1.Code);\n        Assert.Equal(ResponseCode.TOPIC_NOT_EXIST, ex2.Code);\n        Assert.Equal(ResponseCode.NO_PERMISSION, ex3.Code);\n    }\n\n    [Fact]\n    [DisplayName(\"异常可以被捕获\")]\n    public void Exception_CanBeCaught()\n    {\n        try\n        {\n            throw new ResponseException(ResponseCode.SYSTEM_ERROR, \"Test\");\n        }\n        catch (ResponseException ex)\n        {\n            Assert.Equal(ResponseCode.SYSTEM_ERROR, ex.Code);\n            return;\n        }\n\n        Assert.Fail(\"异常未被捕获\");\n    }\n\n    [Fact]\n    [DisplayName(\"异常继承自Exception\")]\n    public void Exception_InheritsFromException()\n    {\n        var ex = new ResponseException(ResponseCode.SUCCESS, \"msg\");\n\n        Assert.IsAssignableFrom<Exception>(ex);\n    }\n\n    [Fact]\n    [DisplayName(\"Null消息不抛异常\")]\n    public void Constructor_NullMessage_NoThrow()\n    {\n        var ex = new ResponseException(ResponseCode.SUCCESS, null);\n\n        Assert.Equal(ResponseCode.SUCCESS, ex.Code);\n        Assert.NotNull(ex.Message);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/RetryTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Threading.Tasks;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消费重试功能测试</summary>\npublic class RetryTests\n{\n    [Fact]\n    [DisplayName(\"MaxReconsumeTimes_默认值为16\")]\n    public void MaxReconsumeTimes_DefaultValue()\n    {\n        using var consumer = new Consumer();\n        Assert.Equal(16, consumer.MaxReconsumeTimes);\n    }\n\n    [Fact]\n    [DisplayName(\"EnableRetry_默认启用\")]\n    public void EnableRetry_DefaultTrue()\n    {\n        using var consumer = new Consumer();\n        Assert.True(consumer.EnableRetry);\n    }\n\n    [Fact]\n    [DisplayName(\"RetryDelayLevel_默认为0\")]\n    public void RetryDelayLevel_DefaultZero()\n    {\n        using var consumer = new Consumer();\n        Assert.Equal(0, consumer.RetryDelayLevel);\n    }\n\n    [Fact]\n    [DisplayName(\"MaxReconsumeTimes_可自定义\")]\n    public void MaxReconsumeTimes_CanBeCustomized()\n    {\n        using var consumer = new Consumer\n        {\n            MaxReconsumeTimes = 5,\n        };\n        Assert.Equal(5, consumer.MaxReconsumeTimes);\n    }\n\n    [Fact]\n    [DisplayName(\"EnableRetry_可禁用\")]\n    public void EnableRetry_CanBeDisabled()\n    {\n        using var consumer = new Consumer\n        {\n            EnableRetry = false,\n        };\n        Assert.False(consumer.EnableRetry);\n    }\n\n    [Fact]\n    [DisplayName(\"SendMessageBack_Null消息抛出异常\")]\n    public async Task SendMessageBack_NullMessage_ThrowsException()\n    {\n        using var consumer = new Consumer();\n        await Assert.ThrowsAsync<ArgumentNullException>(() =>\n            consumer.SendMessageBackAsync(null));\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/SQL92FilterTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>SQL92过滤功能测试</summary>\npublic class SQL92FilterTests\n{\n    [Fact]\n    [DisplayName(\"ExpressionType_默认值为TAG\")]\n    public void ExpressionType_DefaultIsTAG()\n    {\n        using var consumer = new Consumer();\n        Assert.Equal(\"TAG\", consumer.ExpressionType);\n    }\n\n    [Fact]\n    [DisplayName(\"ExpressionType_可设置为SQL92\")]\n    public void ExpressionType_CanSetToSQL92()\n    {\n        using var consumer = new Consumer\n        {\n            ExpressionType = \"SQL92\",\n            Subscription = \"age > 18 AND region = 'hangzhou'\",\n        };\n\n        Assert.Equal(\"SQL92\", consumer.ExpressionType);\n        Assert.Contains(\"age > 18\", consumer.Subscription);\n    }\n\n    [Fact]\n    [DisplayName(\"PullMessageRequestHeader_ExpressionType默认TAG\")]\n    public void PullHeader_ExpressionType_Default()\n    {\n        var header = new PullMessageRequestHeader();\n        Assert.Equal(\"TAG\", header.ExpressionType);\n    }\n\n    [Fact]\n    [DisplayName(\"PullMessageRequestHeader_ExpressionType可设为SQL92\")]\n    public void PullHeader_ExpressionType_SQL92()\n    {\n        var header = new PullMessageRequestHeader\n        {\n            ExpressionType = \"SQL92\",\n            Subscription = \"price BETWEEN 10 AND 100\",\n        };\n\n        Assert.Equal(\"SQL92\", header.ExpressionType);\n        Assert.Equal(\"price BETWEEN 10 AND 100\", header.Subscription);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/SendResultTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>SendResult发送结果测试</summary>\npublic class SendResultTests\n{\n    #region Read方法\n    [Fact]\n    [DisplayName(\"Read_解析所有字段\")]\n    public void Read_ParsesAllFields()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"MsgId\"] = \"0A0A0A0A00002A9F0000000000000001\",\n            [\"OffsetMsgId\"] = \"0A0A0A0A00002A9F0000000000000002\",\n            [\"QueueOffset\"] = \"12345\",\n            [\"TransactionId\"] = \"TX001\",\n            [\"RegionId\"] = \"DefaultRegion\",\n        };\n\n        var result = new SendResult();\n        result.Read(dic);\n\n        Assert.Equal(\"0A0A0A0A00002A9F0000000000000001\", result.MsgId);\n        Assert.Equal(\"0A0A0A0A00002A9F0000000000000002\", result.OffsetMsgId);\n        Assert.Equal(12345, result.QueueOffset);\n        Assert.Equal(\"TX001\", result.TransactionId);\n        Assert.Equal(\"DefaultRegion\", result.RegionId);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_MSG_REGION设置RegionId\")]\n    public void Read_MsgRegion_SetsRegionId()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"MSG_REGION\"] = \"us-east-1\",\n        };\n\n        var result = new SendResult();\n        result.Read(dic);\n\n        Assert.Equal(\"us-east-1\", result.RegionId);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_Null字典不抛异常\")]\n    public void Read_NullDictionary_NoException()\n    {\n        var result = new SendResult();\n        result.Read(null);\n\n        Assert.Null(result.MsgId);\n        Assert.Equal(0, result.QueueOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_空字典不影响属性\")]\n    public void Read_EmptyDictionary_NoEffect()\n    {\n        var result = new SendResult();\n        result.Read(new Dictionary<String, String>());\n\n        Assert.Null(result.MsgId);\n        Assert.Null(result.OffsetMsgId);\n        Assert.Equal(0, result.QueueOffset);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_部分字段解析\")]\n    public void Read_PartialFields()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"MsgId\"] = \"ABC123\",\n        };\n\n        var result = new SendResult();\n        result.Read(dic);\n\n        Assert.Equal(\"ABC123\", result.MsgId);\n        Assert.Null(result.OffsetMsgId);\n        Assert.Null(result.TransactionId);\n    }\n\n    [Fact]\n    [DisplayName(\"Read_大小写不敏感\")]\n    public void Read_CaseInsensitive()\n    {\n        var dic = new Dictionary<String, String>\n        {\n            [\"msgid\"] = \"LOWER_ID\",\n            [\"QUEUEOFFSET\"] = \"999\",\n        };\n\n        var result = new SendResult();\n        result.Read(dic);\n\n        Assert.Equal(\"LOWER_ID\", result.MsgId);\n        Assert.Equal(999, result.QueueOffset);\n    }\n    #endregion\n\n    #region ToString\n    [Fact]\n    [DisplayName(\"ToString_包含所有关键信息\")]\n    public void ToString_ContainsAllInfo()\n    {\n        var result = new SendResult\n        {\n            Status = SendStatus.SendOK,\n            MsgId = \"MSG001\",\n            OffsetMsgId = \"OFFSET001\",\n            QueueOffset = 42,\n            Queue = new MessageQueue { BrokerName = \"broker-a\", QueueId = 3 }\n        };\n\n        var str = result.ToString();\n\n        Assert.Contains(\"SendOK\", str);\n        Assert.Contains(\"MSG001\", str);\n        Assert.Contains(\"OFFSET001\", str);\n        Assert.Contains(\"42\", str);\n    }\n    #endregion\n\n    #region 属性\n    [Fact]\n    [DisplayName(\"SendStatus_枚举值正确\")]\n    public void SendStatus_EnumValues()\n    {\n        Assert.Equal(0, (Int32)SendStatus.SendOK);\n        Assert.Equal(1, (Int32)SendStatus.FlushDiskTimeout);\n        Assert.Equal(2, (Int32)SendStatus.FlushSlaveTimeout);\n        Assert.Equal(3, (Int32)SendStatus.SlaveNotAvailable);\n        Assert.Equal(4, (Int32)SendStatus.SendError);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/SpanRefactorTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Text;\nusing NewLife;\nusing NewLife.Buffers;\nusing NewLife.Data;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Grpc;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>SpanReader/SpanWriter重构验证测试</summary>\n/// <remarks>\n/// 验证 Command、MessageExt、ProtoExtensions 使用 SpanReader/SpanWriter 重构后的正确性。\n/// 包含：二进制协议往返测试、边界条件测试、跨框架兼容性测试。\n/// </remarks>\n[DisplayName(\"SpanReader/SpanWriter重构测试\")]\npublic class SpanRefactorTests\n{\n    #region Command 二进制协议测试\n    [Fact]\n    [DisplayName(\"Command_RocketMQ二进制格式_写入后读取一致\")]\n    public void Command_RocketMQ_WriteRead_RoundTrip()\n    {\n        // 构造一个Command，使用ROCKETMQ序列化写入后再读取\n        var cmd = new Command\n        {\n            Header = new Header\n            {\n                Code = 34,\n                Flag = 0,\n                Language = \"DOTNET\",\n                Version = MQVersion.V4_8_0,\n                Opaque = 100,\n                SerializeTypeCurrentRPC = \"ROCKETMQ\",\n                Remark = \"Test\",\n            }\n        };\n        cmd.Header.GetExtFields()[\"key1\"] = \"value1\";\n        cmd.Header.GetExtFields()[\"key2\"] = \"value2\";\n\n        // 写入到流\n        var ms = new MemoryStream();\n        cmd.Write(ms, null);\n        ms.Position = 0;\n\n        // 读取\n        var cmd2 = new Command();\n        var ok = cmd2.Read(ms);\n\n        Assert.True(ok);\n        Assert.Equal(cmd.Header.Code, cmd2.Header.Code);\n        Assert.Equal(cmd.Header.Flag, cmd2.Header.Flag);\n        Assert.Equal(cmd.Header.Language, cmd2.Header.Language);\n        Assert.Equal(cmd.Header.Version, cmd2.Header.Version);\n        Assert.Equal(cmd.Header.Opaque, cmd2.Header.Opaque);\n        Assert.Equal(cmd.Header.Remark, cmd2.Header.Remark);\n\n        var ext = cmd2.Header.GetExtFields();\n        Assert.Equal(2, ext.Count);\n        Assert.Equal(\"value1\", ext[\"key1\"]);\n        Assert.Equal(\"value2\", ext[\"key2\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"Command_RocketMQ二进制格式_无备注无扩展字段\")]\n    public void Command_RocketMQ_NoRemarkNoExt_RoundTrip()\n    {\n        var cmd = new Command\n        {\n            Header = new Header\n            {\n                Code = 0,\n                Flag = 1,\n                Language = \"JAVA\",\n                Version = MQVersion.V5_2_0,\n                Opaque = 0,\n                SerializeTypeCurrentRPC = \"ROCKETMQ\",\n            }\n        };\n\n        var ms = new MemoryStream();\n        cmd.Write(ms, null);\n        ms.Position = 0;\n\n        var cmd2 = new Command();\n        var ok = cmd2.Read(ms);\n\n        Assert.True(ok);\n        Assert.Equal(0, cmd2.Header.Code);\n        Assert.Equal(1, cmd2.Header.Flag);\n        Assert.Null(cmd2.Header.Remark);\n\n        var ext = cmd2.Header.GetExtFields();\n        Assert.Empty(ext);\n    }\n\n    [Fact]\n    [DisplayName(\"Command_带Body的消息_写入读取一致\")]\n    public void Command_WithPayload_RoundTrip()\n    {\n        var body = Encoding.UTF8.GetBytes(\"{\\\"test\\\":\\\"hello\\\"}\");\n        var cmd = new Command\n        {\n            Header = new Header\n            {\n                Code = 310,\n                Flag = 0,\n                Language = \"DOTNET\",\n                Version = MQVersion.V4_8_0,\n                Opaque = 1,\n                SerializeTypeCurrentRPC = \"ROCKETMQ\",\n                Remark = \"SEND\",\n            },\n            Payload = new ArrayPacket(body),\n        };\n\n        var ms = new MemoryStream();\n        cmd.Write(ms, null);\n        ms.Position = 0;\n\n        var cmd2 = new Command();\n        var ok = cmd2.Read(ms);\n\n        Assert.True(ok);\n        Assert.Equal(310, cmd2.Header.Code);\n        var pk = cmd2.Payload;\n        Assert.NotNull(pk);\n        Assert.Equal(\"{\\\"test\\\":\\\"hello\\\"}\", pk.ToStr());\n    }\n\n    [Fact]\n    [DisplayName(\"Command_中文备注_SpanWriter编码正确\")]\n    public void Command_ChineseRemark_RoundTrip()\n    {\n        var cmd = new Command\n        {\n            Header = new Header\n            {\n                Code = 100,\n                Flag = 0,\n                Language = \"DOTNET\",\n                Version = MQVersion.V4_8_0,\n                Opaque = 5,\n                SerializeTypeCurrentRPC = \"ROCKETMQ\",\n                Remark = \"测试备注\",\n            }\n        };\n\n        var ms = new MemoryStream();\n        cmd.Write(ms, null);\n        ms.Position = 0;\n\n        var cmd2 = new Command();\n        var ok = cmd2.Read(ms);\n\n        Assert.True(ok);\n        Assert.Equal(\"测试备注\", cmd2.Header.Remark);\n    }\n    #endregion\n\n    #region MessageExt SpanReader解码测试\n    [Fact]\n    [DisplayName(\"MessageExt_5x消息ID_SpanWriter创建SpanReader解析往返\")]\n    public void MessageExt_5xId_CreateParse_RoundTrip()\n    {\n        var mac = new Byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };\n        var pid = 54321;\n        var counter = 999999;\n\n        var id = MessageExt.CreateMessageId5x(2, mac, pid, counter);\n\n        Assert.NotNull(id);\n        Assert.Equal(32, id.Length);\n        Assert.StartsWith(\"01\", id);\n\n        var ok = MessageExt.TryParseMessageId5x(id, out var ver, out var parsedMac, out var parsedPid, out var parsedCounter);\n        Assert.True(ok);\n        Assert.Equal(2, ver);\n        Assert.Equal(mac, parsedMac);\n        Assert.Equal(pid, parsedPid);\n        Assert.Equal(counter, parsedCounter);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageExt_5xID_空MAC使用随机字节\")]\n    public void MessageExt_5xId_NullMac_UsesRandom()\n    {\n        var id1 = MessageExt.CreateMessageId5x(1, null, 100, 200);\n        var id2 = MessageExt.CreateMessageId5x(1, null, 100, 200);\n\n        // 两次生成应不同（随机MAC）\n        Assert.NotNull(id1);\n        Assert.NotNull(id2);\n        Assert.Equal(32, id1.Length);\n        Assert.Equal(32, id2.Length);\n\n        // 前缀和版本相同\n        Assert.StartsWith(\"0101\", id1);\n        Assert.StartsWith(\"0101\", id2);\n        // MAC部分不同（极小概率相同）\n        Assert.NotEqual(id1, id2);\n    }\n\n    [Fact]\n    [DisplayName(\"MessageExt_IsMessageId5x_正确识别5x格式\")]\n    public void MessageExt_IsMessageId5x_CorrectDetection()\n    {\n        var mac = new Byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };\n        var id5x = MessageExt.CreateMessageId5x(1, mac, 1000, 2000);\n        Assert.True(MessageExt.IsMessageId5x(id5x));\n\n        // 4.x格式（前缀不是01）\n        Assert.False(MessageExt.IsMessageId5x(\"AABBCCDD00001111000000000000FFFF\"));\n        // 长度不对\n        Assert.False(MessageExt.IsMessageId5x(\"0101AABB\"));\n        // null\n        Assert.False(MessageExt.IsMessageId5x(null));\n    }\n    #endregion\n\n    #region ProtoExtensions SpanReader/SpanWriter重构测试\n    [Fact]\n    [DisplayName(\"SpanWriter_Fixed32_使用扩展方法正确编解码\")]\n    public void SpanWriter_Fixed32_ExtensionMethod()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteFixed32(1, 0x12345678);\n        writer.WriteFixed32(2, UInt32.MaxValue);\n        writer.WriteFixed32(3, 1);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(5, wt1); // wireType 5 = 32-bit\n        Assert.Equal(0x12345678U, reader.ReadFixed32());\n\n        var (fn2, wt2) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(UInt32.MaxValue, reader.ReadFixed32());\n\n        var (fn3, _) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(1U, reader.ReadFixed32());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Fixed64_使用扩展方法正确编解码\")]\n    public void SpanWriter_Fixed64_ExtensionMethod()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteFixed64(1, 0x123456789ABCDEF0);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(1, wt1); // wireType 1 = 64-bit\n        Assert.Equal(0x123456789ABCDEF0UL, reader.ReadFixed64());\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Float_编码解码正确\")]\n    public void SpanWriter_Float_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteFloat(1, 3.14f);\n        writer.WriteFloat(2, -1.5f);\n        writer.WriteFloat(3, Single.MaxValue);\n        writer.WriteFloat(4, Single.MinValue);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(3.14f, reader.ReadFloat());\n\n        var (fn2, _) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(-1.5f, reader.ReadFloat());\n\n        var (fn3, _) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(Single.MaxValue, reader.ReadFloat());\n\n        var (fn4, _) = reader.ReadTag();\n        Assert.Equal(4, fn4);\n        Assert.Equal(Single.MinValue, reader.ReadFloat());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Double_编码解码正确\")]\n    public void SpanWriter_Double_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteDouble(1, 3.141592653589793);\n        writer.WriteDouble(2, Double.MaxValue);\n        writer.WriteDouble(3, Double.Epsilon);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(3.141592653589793, reader.ReadProtoDouble());\n\n        var (fn2, _) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(Double.MaxValue, reader.ReadProtoDouble());\n\n        var (fn3, _) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(Double.Epsilon, reader.ReadProtoDouble());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Timestamp_编码解码正确\")]\n    public void SpanWriter_Timestamp_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        var time = new DateTime(2025, 6, 15, 12, 30, 45, DateTimeKind.Utc);\n        writer.WriteTimestamp(1, time);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        var parsed = reader.ReadTimestamp();\n\n        Assert.Equal(time.Year, parsed.Year);\n        Assert.Equal(time.Month, parsed.Month);\n        Assert.Equal(time.Day, parsed.Day);\n        Assert.Equal(time.Hour, parsed.Hour);\n        Assert.Equal(time.Minute, parsed.Minute);\n        Assert.Equal(time.Second, parsed.Second);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Duration_编码解码正确\")]\n    public void SpanWriter_Duration_RoundTrip()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        var duration = TimeSpan.FromSeconds(3600) + TimeSpan.FromMilliseconds(500);\n        writer.WriteDuration(1, duration);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        var parsed = reader.ReadDuration();\n\n        Assert.Equal(3600, (Int32)parsed.TotalSeconds);\n        Assert.Equal(500, parsed.Milliseconds);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_嵌套消息_子缓冲区编解码\")]\n    public void SpanWriter_NestedMessage_SubBuffer_RoundTrip()\n    {\n        var resource = new GrpcResource\n        {\n            Name = \"test_topic\",\n            ResourceNamespace = \"ns1\",\n        };\n\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteMessage(1, resource);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, wt1) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(2, wt1); // length-delimited\n\n        var parsed = reader.ReadProtoMessage<GrpcResource>();\n        Assert.Equal(\"test_topic\", parsed.Name);\n        Assert.Equal(\"ns1\", parsed.ResourceNamespace);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Map字段_多个entry编解码\")]\n    public void SpanWriter_Map_MultiEntry_RoundTrip()\n    {\n        var map = new Dictionary<String, String>\n        {\n            [\"host\"] = \"10.0.0.1\",\n            [\"port\"] = \"8080\",\n            [\"env\"] = \"production\",\n        };\n\n        var buf = new Byte[512];\n        var writer = new SpanWriter(buf);\n        writer.WriteMap(1, map);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var result = new Dictionary<String, String>();\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            Assert.Equal(1, fn);\n            Assert.Equal(2, wt);\n            var (k, v) = reader.ReadMapEntry();\n            result[k] = v;\n        }\n\n        Assert.Equal(3, result.Count);\n        Assert.Equal(\"10.0.0.1\", result[\"host\"]);\n        Assert.Equal(\"8080\", result[\"port\"]);\n        Assert.Equal(\"production\", result[\"env\"]);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_RepeatedString_多值编解码\")]\n    public void SpanWriter_RepeatedString_RoundTrip()\n    {\n        var values = new List<String> { \"alpha\", \"beta\", \"gamma\" };\n\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteRepeatedString(1, values);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var result = new List<String>();\n        while (reader.Available > 0)\n        {\n            var (fn, _) = reader.ReadTag();\n            if (fn == 0) break;\n            Assert.Equal(1, fn);\n            result.Add(reader.ReadProtoString());\n        }\n\n        Assert.Equal(values, result);\n    }\n    #endregion\n\n    #region 边界测试\n    [Fact]\n    [DisplayName(\"SpanReader_读取超出边界_抛出异常\")]\n    public void SpanReader_ReadBeyondLimit_ThrowsException()\n    {\n        var data = new Byte[] { 0x01, 0x02 };\n        var reader = new SpanReader(data);\n\n        // 读取2字节OK\n        reader.ReadBytes(2);\n\n        // 再读1字节应失败\n        var thrown = false;\n        try\n        {\n            reader.ReadBytes(1);\n        }\n        catch\n        {\n            thrown = true;\n        }\n        Assert.True(thrown);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanReader_空数据_Available为0\")]\n    public void SpanReader_EmptyData_AvailableZero()\n    {\n        var reader = new SpanReader([]);\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"ProtoExtensions_Serialize_空消息返回空\")]\n    public void ProtoExtensions_Serialize_NullReturnsEmpty()\n    {\n        var data = ProtoExtensions.Serialize(null);\n        Assert.Empty(data);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_Int32负数_10字节varint编码\")]\n    public void SpanWriter_NegativeInt32_10ByteVarint()\n    {\n        var buf = new Byte[64];\n        var writer = new SpanWriter(buf);\n        writer.WriteInt32(1, -1);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn, _) = reader.ReadTag();\n        Assert.Equal(1, fn);\n        Assert.Equal(-1, reader.ReadProtoInt32());\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_SInt64_ZigZag边界值\")]\n    public void SpanWriter_SInt64_ZigZag_BoundaryValues()\n    {\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WriteSInt64(1, Int64.MinValue);\n        writer.WriteSInt64(2, Int64.MaxValue);\n        writer.WriteSInt64(3, -1);\n        writer.WriteSInt64(4, 1);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn1, _) = reader.ReadTag();\n        Assert.Equal(1, fn1);\n        Assert.Equal(Int64.MinValue, reader.ReadSInt64());\n\n        var (fn2, _) = reader.ReadTag();\n        Assert.Equal(2, fn2);\n        Assert.Equal(Int64.MaxValue, reader.ReadSInt64());\n\n        var (fn3, _) = reader.ReadTag();\n        Assert.Equal(3, fn3);\n        Assert.Equal(-1L, reader.ReadSInt64());\n\n        var (fn4, _) = reader.ReadTag();\n        Assert.Equal(4, fn4);\n        Assert.Equal(1L, reader.ReadSInt64());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanReader_SkipField_未知wireType抛异常\")]\n    public void SpanReader_SkipField_UnknownWireType_Throws()\n    {\n        var reader = new SpanReader(new Byte[] { 0x08, 0x01 }); // field 1, varint, value 1\n        reader.ReadTag();\n        var thrown = false;\n        try\n        {\n            reader.SkipField(3); // wireType 3 is deprecated/unknown\n        }\n        catch (InvalidDataException)\n        {\n            thrown = true;\n        }\n        Assert.True(thrown);\n    }\n    #endregion\n\n    #region 综合场景测试\n    [Fact]\n    [DisplayName(\"SpanWriter_各种字段类型_完整编解码\")]\n    public void SpanWriter_CompleteMessage_RoundTrip()\n    {\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        // 写入各种类型的字段\n        writer.WriteString(1, \"TAG_test\");   // tag\n        writer.WriteString(2, \"key_123\");    // keys\n        writer.WriteString(3, \"msg_001\");    // message_id\n        writer.WriteString(4, \"body_crc\");   // body_digest.checksum\n        writer.WriteEnum(5, (Int32)GrpcMessageType.NORMAL);\n        writer.WriteTimestamp(6, new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));\n        writer.WriteInt32(7, 3);             // born_host.port  \n        writer.WriteInt32(8, 0);             // queue_id\n\n        var data = writer.WrittenSpan.ToArray();\n        Assert.True(data.Length > 0);\n\n        // 确认能正确读取\n        var reader = new SpanReader(data);\n        while (reader.Available > 0)\n        {\n            var (fn, wt) = reader.ReadTag();\n            if (fn == 0) break;\n            reader.SkipField(wt);\n        }\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_PackedEnum_编解码\")]\n    public void SpanWriter_PackedEnum_RoundTrip()\n    {\n        var enums = new List<Int32> { 1, 2, 4, 8, 16 };\n\n        var buf = new Byte[128];\n        var writer = new SpanWriter(buf);\n        writer.WritePackedEnum(1, enums);\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        var (fn, wt) = reader.ReadTag();\n        Assert.Equal(1, fn);\n        Assert.Equal(2, wt); // packed = length-delimited\n\n        // 读取packed数据\n        var packedLen = (Int32)reader.ReadRawVarint();\n        var packedData = reader.ReadBytes(packedLen).ToArray();\n        var subReader = new SpanReader(packedData);\n\n        var result = new List<Int32>();\n        while (subReader.Available > 0)\n        {\n            result.Add((Int32)subReader.ReadRawVarint());\n        }\n\n        Assert.Equal(enums, result);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanWriter_大数据量写入_Serialize正确处理\")]\n    public void SpanWriter_LargeData_SerializeHandles()\n    {\n        var resource = new GrpcResource\n        {\n            ResourceNamespace = new String('N', 500),\n            Name = new String('T', 500),\n        };\n\n        var data = ProtoExtensions.Serialize(resource);\n        Assert.True(data.Length > 1000);\n\n        var reader = new SpanReader(data);\n        var result = new GrpcResource();\n        result.Read(ref reader);\n\n        Assert.Equal(resource.ResourceNamespace, result.ResourceNamespace);\n        Assert.Equal(resource.Name, result.Name);\n    }\n\n    [Fact]\n    [DisplayName(\"SpanReader_SkipField_各线路类型正确跳过\")]\n    public void SpanReader_SkipField_AllWireTypes()\n    {\n        var buf = new Byte[256];\n        var writer = new SpanWriter(buf);\n        writer.WriteInt32(1, 42);         // varint (wireType 0)\n        writer.WriteFixed64(2, 100);      // 64-bit (wireType 1)\n        writer.WriteString(3, \"skip_me\"); // length-delimited (wireType 2)\n        writer.WriteFixed32(4, 200);      // 32-bit (wireType 5)\n        writer.WriteInt32(5, 999);        // 这是目标字段\n\n        var data = writer.WrittenSpan.ToArray();\n        var reader = new SpanReader(data);\n\n        // 跳过字段1-4\n        for (var i = 0; i < 4; i++)\n        {\n            var (_, wt) = reader.ReadTag();\n            reader.SkipField(wt);\n        }\n\n        // 读取字段5\n        var (fn5, _) = reader.ReadTag();\n        Assert.Equal(5, fn5);\n        Assert.Equal(999, reader.ReadProtoInt32());\n\n        Assert.True(reader.Available <= 0);\n    }\n\n    [Fact]\n    [DisplayName(\"Command_JSON格式_不受SpanWriter影响\")]\n    public void Command_JSON_NotAffected_ByRefactoring()\n    {\n        // JSON格式应继续使用JSON序列化，不受二进制重构影响\n        var cmd = new Command\n        {\n            Header = new Header\n            {\n                Code = 105,\n                Flag = 0,\n                Language = \"JAVA\",\n                Version = MQVersion.V5_2_0,\n                Opaque = 0,\n                SerializeTypeCurrentRPC = \"JSON\",\n            }\n        };\n        cmd.Header.GetExtFields()[\"topic\"] = \"TestTopic\";\n\n        var ms = new MemoryStream();\n        cmd.Write(ms, null);\n        ms.Position = 0;\n\n        var cmd2 = new Command();\n        var ok = cmd2.Read(ms);\n\n        Assert.True(ok);\n        Assert.Equal(105, cmd2.Header.Code);\n\n        var ext = cmd2.Header.GetExtFields();\n        Assert.Equal(\"TestTopic\", ext[\"topic\"]);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/SupportApacheAclTest.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing NewLife;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ\n{\n    public class SupportApacheAclTest\n    {\n        private const String NameServerAddress = \"127.0.0.1:9876\";\n        private const String TestTopic = \"newlife_acl_test_topic\";\n\n        /// <summary>\n        /// 创建Topic时默认为系统 Topic => TBW102,可省略\n        /// </summary>\n        private const String DefaultSysTopic = \"TBW102\";\n\n        private readonly AclOptions _aclOptions = new AclOptions() {AccessKey = \"rocketmq2AcKey\", SecretKey = \"rocketmq2SeKey\", OnsChannel = \"LOCAL\"};\n        \n        [Fact(Skip = \"需要配置ACL的RocketMQ服务器支持\")]\n        public void CreateTopicTest()\n        {\n            using var producer = CreateProducerInstance(DefaultSysTopic);\n            producer.Start();\n            producer.CreateTopic(TestTopic, 2);\n            producer.Dispose();\n        }\n\n        [Fact(Skip = \"需要配置ACL的RocketMQ服务器支持\")]\n        public void PublishMessageTest()\n        {\n            using var producer = CreateProducerInstance(TestTopic);\n            producer.Start();\n\n            var pubResultList = new List<Boolean>();\n            for (var i = 0; i < 10; i++)\n            {\n                const String message = \"大家好才是真的好！\";\n                var pubResult = producer.Publish(message, \"new_life_test_tag\");\n                pubResultList.Add(pubResult.Status == SendStatus.SendOK);\n            }\n\n            Assert.True(pubResultList.All(_ => true));\n            producer.Dispose();\n        }\n\n        [Fact(Skip = \"需要配置ACL的RocketMQ服务器支持\")]\n        public void ConsumeMessageTest()\n        {\n            using var consumer = CreateConsumerInstance(TestTopic);\n            consumer.OnConsume = OnConsume;\n            consumer.Start();\n            Thread.Sleep(3000);\n           \n            static Boolean OnConsume(MessageQueue q, MessageExt[] ms)\n            {\n                Console.WriteLine(\"[{0}@{1}]收到消息[{2}]\", q.BrokerName, q.QueueId, ms.Length);\n\n                foreach (var item in ms.ToList())\n                {\n                    Console.WriteLine($\"消息：主键【{item.Keys}】，产生时间【{item.BornTimestamp.ToDateTime()}】，内容【{item.Body.ToStr()}】\");\n                }\n\n                return true;\n            }\n        }\n\n        private Producer CreateProducerInstance(String topic)\n        {\n            var producer = new Producer();\n\n            producer.NameServerAddress = NameServerAddress;\n            producer.Topic = topic;\n            producer.AclOptions = _aclOptions;\n\n            return producer;\n        }\n        \n        private Consumer CreateConsumerInstance(String topic)\n        {\n            var consumer = new Consumer();\n\n            consumer.NameServerAddress = NameServerAddress;\n            consumer.Topic = topic;\n            consumer.AclOptions = _aclOptions;\n            consumer.Group = \"new_life_test_group\";\n            consumer.FromLastOffset = true;\n            consumer.BatchSize = 5;\n            \n            return consumer;\n        }\n    }\n}"
  },
  {
    "path": "XUnitTestRocketMQ/TraceModelTests.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.MessageTrace;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>消息轨迹数据模型测试</summary>\npublic class TraceModelTests\n{\n    #region TraceContext\n    [Fact]\n    [DisplayName(\"TraceContext_默认值\")]\n    public void TraceContext_Defaults()\n    {\n        var ctx = new TraceContext();\n\n        Assert.Equal(default, ctx.TraceType);\n        Assert.Null(ctx.RegionId);\n        Assert.Null(ctx.GroupName);\n        Assert.Equal(0, ctx.CostTime);\n        Assert.False(ctx.Success);\n        Assert.Null(ctx.RequestId);\n        Assert.NotNull(ctx.TraceBeans);\n        Assert.Empty(ctx.TraceBeans);\n    }\n\n    [Fact]\n    [DisplayName(\"TraceContext_设置属性\")]\n    public void TraceContext_SetProperties()\n    {\n        var ctx = new TraceContext\n        {\n            TraceType = TraceType.Pub,\n            GroupName = \"PG_TEST\",\n            CostTime = 100,\n            Success = true,\n            RequestId = \"REQ001\",\n            RegionId = \"cn-hangzhou\",\n        };\n\n        Assert.Equal(TraceType.Pub, ctx.TraceType);\n        Assert.Equal(\"PG_TEST\", ctx.GroupName);\n        Assert.Equal(100, ctx.CostTime);\n        Assert.True(ctx.Success);\n        Assert.Equal(\"REQ001\", ctx.RequestId);\n        Assert.Equal(\"cn-hangzhou\", ctx.RegionId);\n    }\n\n    [Fact]\n    [DisplayName(\"TraceContext_添加TraceBeans\")]\n    public void TraceContext_AddTraceBeans()\n    {\n        var ctx = new TraceContext();\n\n        ctx.TraceBeans.Add(new TraceBean { Topic = \"t1\", MsgId = \"M1\" });\n        ctx.TraceBeans.Add(new TraceBean { Topic = \"t2\", MsgId = \"M2\" });\n\n        Assert.Equal(2, ctx.TraceBeans.Count);\n        Assert.Equal(\"t1\", ctx.TraceBeans[0].Topic);\n        Assert.Equal(\"M2\", ctx.TraceBeans[1].MsgId);\n    }\n    #endregion\n\n    #region TraceBean\n    [Fact]\n    [DisplayName(\"TraceBean_默认值\")]\n    public void TraceBean_Defaults()\n    {\n        var bean = new TraceBean();\n\n        Assert.Null(bean.Topic);\n        Assert.Null(bean.MsgId);\n        Assert.Null(bean.OffsetMsgId);\n        Assert.Null(bean.Tags);\n        Assert.Null(bean.Keys);\n        Assert.Null(bean.StoreHost);\n        Assert.Equal(0, bean.BodyLength);\n        Assert.Null(bean.ClientHost);\n        Assert.Null(bean.MsgType);\n        Assert.Equal(0, bean.StoreTime);\n    }\n\n    [Fact]\n    [DisplayName(\"TraceBean_设置所有属性\")]\n    public void TraceBean_SetAllProperties()\n    {\n        var bean = new TraceBean\n        {\n            Topic = \"test_topic\",\n            MsgId = \"MSG001\",\n            OffsetMsgId = \"OFFSET001\",\n            Tags = \"TagA\",\n            Keys = \"Key1\",\n            StoreHost = \"127.0.0.1:10911\",\n            BodyLength = 256,\n            ClientHost = \"192.168.1.1\",\n            MsgType = \"Normal\",\n            StoreTime = 1000L,\n        };\n\n        Assert.Equal(\"test_topic\", bean.Topic);\n        Assert.Equal(\"MSG001\", bean.MsgId);\n        Assert.Equal(\"OFFSET001\", bean.OffsetMsgId);\n        Assert.Equal(\"TagA\", bean.Tags);\n        Assert.Equal(\"Key1\", bean.Keys);\n        Assert.Equal(\"127.0.0.1:10911\", bean.StoreHost);\n        Assert.Equal(256, bean.BodyLength);\n        Assert.Equal(\"192.168.1.1\", bean.ClientHost);\n        Assert.Equal(\"Normal\", bean.MsgType);\n        Assert.Equal(1000L, bean.StoreTime);\n    }\n    #endregion\n\n    #region TraceType枚举\n    [Fact]\n    [DisplayName(\"TraceType_枚举值正确\")]\n    public void TraceType_EnumValues()\n    {\n        Assert.Equal(0, (Int32)TraceType.Pub);\n        Assert.Equal(1, (Int32)TraceType.SubBefore);\n        Assert.Equal(2, (Int32)TraceType.SubAfter);\n    }\n    #endregion\n\n    #region SendMessageContext\n    [Fact]\n    [DisplayName(\"SendMessageContext_字段可设置\")]\n    public void SendMessageContext_FieldsCanBeSet()\n    {\n        var msg = new Message { Topic = \"test\" };\n        var mq = new MessageQueue { BrokerName = \"broker-a\", QueueId = 0 };\n        var result = new SendResult { Status = SendStatus.SendOK, MsgId = \"M1\" };\n\n        var ctx = new SendMessageContext\n        {\n            ProducerGroup = \"PG_TEST\",\n            Message = msg,\n            Mq = mq,\n            BrokerAddr = \"127.0.0.1:10911\",\n            SendResult = result,\n            MsgType = \"Normal\",\n            BornHost = DateTime.Now,\n        };\n\n        Assert.Equal(\"PG_TEST\", ctx.ProducerGroup);\n        Assert.Same(msg, ctx.Message);\n        Assert.Same(mq, ctx.Mq);\n        Assert.Equal(\"127.0.0.1:10911\", ctx.BrokerAddr);\n        Assert.Same(result, ctx.SendResult);\n        Assert.Equal(\"Normal\", ctx.MsgType);\n    }\n\n    [Fact]\n    [DisplayName(\"SendMessageContext_TraceContext可设置\")]\n    public void SendMessageContext_TraceContextCanBeSet()\n    {\n        var traceCtx = new TraceContext { TraceType = TraceType.Pub };\n        var ctx = new SendMessageContext { TraceContext = traceCtx };\n\n        Assert.Same(traceCtx, ctx.TraceContext);\n    }\n    #endregion\n\n    #region ConsumeMessageContext\n    [Fact]\n    [DisplayName(\"ConsumeMessageContext_字段可设置\")]\n    public void ConsumeMessageContext_FieldsCanBeSet()\n    {\n        var msgList = new List<MessageExt> { new() { Topic = \"t1\" } };\n        var mq = new MessageQueue { BrokerName = \"b1\", QueueId = 0 };\n\n        var ctx = new ConsumeMessageContext\n        {\n            ConsumerGroup = \"CG_TEST\",\n            MsgList = msgList,\n            Mq = mq,\n            Success = true,\n            MsgType = \"Normal\",\n        };\n\n        Assert.Equal(\"CG_TEST\", ctx.ConsumerGroup);\n        Assert.Single(ctx.MsgList);\n        Assert.Same(mq, ctx.Mq);\n        Assert.True(ctx.Success);\n        Assert.Equal(\"Normal\", ctx.MsgType);\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/TransactionCheckTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing System.Threading.Tasks;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Protocol;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>事务回查功能测试</summary>\npublic class TransactionCheckTests\n{\n    [Fact]\n    [DisplayName(\"OnCheckTransaction_默认为null\")]\n    public void OnCheckTransaction_DefaultNull()\n    {\n        using var producer = new Producer();\n        Assert.Null(producer.OnCheckTransaction);\n        Assert.Null(producer.OnCheckTransactionAsync);\n    }\n\n    [Fact]\n    [DisplayName(\"OnCheckTransaction_可设置回调委托\")]\n    public void OnCheckTransaction_CanSetCallback()\n    {\n        var callbackInvoked = false;\n        using var producer = new Producer\n        {\n            OnCheckTransaction = (msg, transactionId) =>\n            {\n                callbackInvoked = true;\n                return TransactionState.Commit;\n            }\n        };\n\n        Assert.NotNull(producer.OnCheckTransaction);\n        var state = producer.OnCheckTransaction(new MessageExt(), \"test-txid\");\n        Assert.True(callbackInvoked);\n        Assert.Equal(TransactionState.Commit, state);\n    }\n\n    [Fact]\n    [DisplayName(\"OnCheckTransactionAsync_可设置异步回调委托\")]\n    public async Task OnCheckTransactionAsync_CanSetCallback()\n    {\n        var callbackInvoked = false;\n        using var producer = new Producer\n        {\n            OnCheckTransactionAsync = async (msg, transactionId, ct) =>\n            {\n                await Task.CompletedTask;\n                callbackInvoked = true;\n                return TransactionState.Rollback;\n            }\n        };\n\n        Assert.NotNull(producer.OnCheckTransactionAsync);\n        var state = await producer.OnCheckTransactionAsync(new MessageExt(), \"test-txid\", default);\n        Assert.True(callbackInvoked);\n        Assert.Equal(TransactionState.Rollback, state);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/VipChannelTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ;\nusing NewLife.RocketMQ.Client;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>VIP通道测试</summary>\npublic class VipChannelTests\n{\n    [Fact]\n    [DisplayName(\"VipChannelEnabled_默认为false\")]\n    public void VipChannelEnabled_DefaultFalse()\n    {\n        using var producer = new Producer();\n        Assert.False(producer.VipChannelEnabled);\n    }\n\n    [Fact]\n    [DisplayName(\"VipChannelEnabled_可设置为true\")]\n    public void VipChannelEnabled_CanSetTrue()\n    {\n        using var producer = new Producer();\n        producer.VipChannelEnabled = true;\n        Assert.True(producer.VipChannelEnabled);\n    }\n\n    [Fact]\n    [DisplayName(\"VipChannelEnabled_Consumer也支持\")]\n    public void VipChannelEnabled_ConsumerSupport()\n    {\n        using var consumer = new Consumer();\n        consumer.VipChannelEnabled = true;\n        Assert.True(consumer.VipChannelEnabled);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerClient_VIP模式端口偏移为端口减2\")]\n    public void BrokerClient_VipPortOffset()\n    {\n        // 验证 BrokerClient 构造时如果启用VIP，端口会减2\n        // 通过 MqBase 的属性间接验证\n        using var producer = new Producer\n        {\n            VipChannelEnabled = true,\n        };\n\n        // VipChannelEnabled 设置为 true 时，BrokerClient 创建时会使用 port - 2\n        Assert.True(producer.VipChannelEnabled);\n    }\n\n    [Fact]\n    [DisplayName(\"BrokerClient_非VIP模式端口不变\")]\n    public void BrokerClient_NonVipPortUnchanged()\n    {\n        using var producer = new Producer\n        {\n            VipChannelEnabled = false,\n        };\n\n        Assert.False(producer.VipChannelEnabled);\n    }\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/WeightRoundRobinTests.cs",
    "content": "﻿using System;\nusing System.ComponentModel;\nusing NewLife.RocketMQ.Common;\nusing Xunit;\n\nnamespace XUnitTestRocketMQ;\n\n/// <summary>带权重负载均衡算法测试</summary>\npublic class WeightRoundRobinTests\n{\n    #region Set方法\n    [Fact]\n    [DisplayName(\"Set_Null参数抛出异常\")]\n    public void Set_NullWeights_ThrowsArgumentNullException()\n    {\n        var lb = new WeightRoundRobin();\n\n        Assert.Throws<ArgumentNullException>(() => lb.Set(null));\n    }\n\n    [Fact]\n    [DisplayName(\"Set_设置权重后Ready为true\")]\n    public void Set_ValidWeights_SetsReadyTrue()\n    {\n        var lb = new WeightRoundRobin();\n        Assert.False(lb.Ready);\n\n        lb.Set([1, 2, 3]);\n\n        Assert.True(lb.Ready);\n        Assert.Equal(3, lb.Weights.Length);\n    }\n\n    [Fact]\n    [DisplayName(\"Set_相同权重不重复设置\")]\n    public void Set_SameWeights_NoReset()\n    {\n        var lb1 = new WeightRoundRobin();\n        var lb2 = new WeightRoundRobin();\n\n        // 初次设置相同权重\n        lb1.Set([1, 2, 3]);\n        lb2.Set([1, 2, 3]);\n\n        // 先各选一次改变状态\n        lb1.Get();\n        lb2.Get();\n\n        // 对 lb1 再次设置相同权重，不应重置状态\n        lb1.Set([1, 2, 3]);\n\n        // Ready 仍应为 true\n        Assert.True(lb1.Ready);\n\n        // 后续多次 Get 的返回序列应与未再次 Set 的 lb2 完全一致\n        for (var i = 0; i < 10; i++)\n        {\n            var expected = lb2.Get();\n            var actual = lb1.Get();\n            Assert.Equal(expected, actual);\n        }\n    }\n\n    [Fact]\n    [DisplayName(\"Set_不同权重重新初始化\")]\n    public void Set_DifferentWeights_Reinitializes()\n    {\n        var lb = new WeightRoundRobin();\n        lb.Set([1, 2]);\n\n        lb.Set([3, 4, 5]);\n\n        Assert.Equal(3, lb.Weights.Length);\n        Assert.Equal(3, lb.Weights[0]);\n        Assert.Equal(4, lb.Weights[1]);\n        Assert.Equal(5, lb.Weights[2]);\n    }\n    #endregion\n\n    #region Get方法\n    [Fact]\n    [DisplayName(\"Get_未初始化时返回0\")]\n    public void Get_NotInitialized_ReturnsZero()\n    {\n        var lb = new WeightRoundRobin();\n\n        var idx = lb.Get(out var times);\n\n        Assert.Equal(0, idx);\n        Assert.Equal(1, times);\n    }\n\n    [Fact]\n    [DisplayName(\"Get_等权重均匀分配\")]\n    public void Get_EqualWeights_EvenDistribution()\n    {\n        var lb = new WeightRoundRobin();\n        lb.Set([1, 1, 1]);\n\n        var counts = new Int32[3];\n        for (var i = 0; i < 30; i++)\n        {\n            var idx = lb.Get();\n            counts[idx]++;\n        }\n\n        // 等权重应该近似均匀分配\n        Assert.Equal(10, counts[0]);\n        Assert.Equal(10, counts[1]);\n        Assert.Equal(10, counts[2]);\n    }\n\n    [Fact]\n    [DisplayName(\"Get_不等权重按比例分配\")]\n    public void Get_UnequalWeights_ProportionalDistribution()\n    {\n        var lb = new WeightRoundRobin();\n        lb.Set([3, 1]);\n\n        var counts = new Int32[2];\n        for (var i = 0; i < 40; i++)\n        {\n            var idx = lb.Get();\n            counts[idx]++;\n        }\n\n        // 权重3:1，40次中应大约30次和10次\n        Assert.True(counts[0] > counts[1], $\"权重高的选中次数({counts[0]})应多于权重低的({counts[1]})\");\n    }\n\n    [Fact]\n    [DisplayName(\"Get_输出正确的次数\")]\n    public void Get_ReturnsTimes_Correctly()\n    {\n        var lb = new WeightRoundRobin();\n        lb.Set([1, 1]);\n\n        lb.Get(out var times1);\n        Assert.Equal(1, times1);\n\n        lb.Get(out _);\n        lb.Get(out var times3);\n        // 第一个索引被选中第2次\n        Assert.Equal(2, times3);\n    }\n\n    [Fact]\n    [DisplayName(\"Get_单个权重总是返回0\")]\n    public void Get_SingleWeight_AlwaysReturnsZero()\n    {\n        var lb = new WeightRoundRobin();\n        lb.Set([5]);\n\n        for (var i = 0; i < 10; i++)\n        {\n            var idx = lb.Get();\n            Assert.Equal(0, idx);\n        }\n    }\n\n    [Fact]\n    [DisplayName(\"Get无参版本与有参版本一致\")]\n    public void Get_NoOutParam_SameAsWithOutParam()\n    {\n        var lb1 = new WeightRoundRobin();\n        var lb2 = new WeightRoundRobin();\n        lb1.Set([2, 3, 1]);\n        lb2.Set([2, 3, 1]);\n\n        for (var i = 0; i < 20; i++)\n        {\n            var idx1 = lb1.Get();\n            var idx2 = lb2.Get(out _);\n            Assert.Equal(idx1, idx2);\n        }\n    }\n    #endregion\n}\n"
  },
  {
    "path": "XUnitTestRocketMQ/XUnitTestRocketMQ.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <AssemblyTitle>RocketMQ单元测试</AssemblyTitle>\n    <Description>NewLife.RocketMQ 单元测试项目</Description>\n    <OutputPath>..\\Bin\\UnitTest</OutputPath>\n    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"18.5.1\" />\n    <PackageReference Include=\"Moq\" Version=\"4.20.72\" />\n    <PackageReference Include=\"xunit\" Version=\"2.9.3\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"3.1.5\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"coverlet.collector\" Version=\"10.0.0\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NewLife.RocketMQ\\NewLife.RocketMQ.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  }
]