NewLife/NewLife.Skills

Add Redis client, RocketMQ messaging, and Stardust platform skills documentation

- Introduced Redis client documentation covering high-performance Redis operations, connection pooling, various data structures, and cluster configurations.
- Added RocketMQ messaging documentation detailing message production and consumption, including support for different message types and cloud vendor integrations.
- Included Stardust platform documentation for service registration, configuration management, APM tracing, and integration with ASP.NET Core applications.
大石头 authored at 2026-04-02 20:01:39
39b9730
Tree
1 Parent(s) d4e2b53
Summary: 9 changed files with 2043 additions and 0 deletions.
Added +236 -0
Added +175 -0
Added +163 -0
Added +220 -0
Added +234 -0
Added +282 -0
Added +219 -0
Added +273 -0
Added +241 -0
Added +236 -0
diff --git a/.github/skills/agent-service/SKILL.md b/.github/skills/agent-service/SKILL.md
new file mode 100644
index 0000000..bba8ad0
--- /dev/null
+++ b/.github/skills/agent-service/SKILL.md
@@ -0,0 +1,236 @@
+---
+name: agent-service
+description: >
+  使用 NewLife.Agent 将 .NET 应用注册为跨平台系统服务(Windows Service、Linux systemd、macOS LaunchAgent),
+  涵盖 ServiceBase 生命周期钩子(StartWork/StopWork/DoLoop)、IHost 平台实现、
+  命令行管理(-install/-start/-stop/-uninstall)、自定义命令处理器、
+  看门狗/内存监控/自动重启,以及 ASP.NET Core UseAgentService 集成。
+  适用于后台服务、数据采集守护进程、定时任务服务的系统级部署。
+argument-hint: >
+  说明你的服务场景:新建独立后台服务还是将 ASP.NET Core 应用注册为系统服务;
+  目标平台(Windows/Linux/macOS);是否需要自定义命令;
+  是否需要看门狗保护其他进程;是否需要内存/线程超限自动重启。
+---
+
+# 跨平台系统服务技能(NewLife.Agent)
+
+## 适用场景
+
+- 将控制台应用一行代码注册为 Windows Service 或 Linux systemd 服务,实现开机自启。
+- 数据采集、定时任务、消息消费等长期运行后台服务的系统级部署与管理。
+- 需要在服务内同时启动多个子任务(`TimerX`/`NetServer` 等),统一生命周期管理。
+- 生产环境需要进程自我保护:内存超限自动重启、崩溃自动恢复、看门狗保护其他服务。
+- ASP.NET Core 应用需要以系统服务方式运行(替代 `UseWindowsService`/`UseSystemd`)。
+
+## 核心原则
+
+1. **继承 `ServiceBase`,覆写 `StartWork`/`StopWork`**:这是唯一的业务接入点;`Starting` 阶段初始化资源、启动后台任务;`Stopping` 阶段释放资源、等待任务完成;两个方法都**必须调用 `base.xxx(reason)`**,否则框架状态标志不正确。
+2. **`Main(args)` 是统一入口**:一行代码 `new MyService().Main(args)` 处理全部逻辑——命令行参数解析(`-install`/`-run` 等)、系统服务注册、交互式菜单;无需多个 `Main` 函数。
+3. **`-run` 参数用于控制台调试**:开发阶段用 `dotnet run -- -run` 在控制台中模拟服务运行(前台执行),无需安装;CI/CD 中通过 `-install` 注册到系统。
+4. **平台由框架自动选择**:无需编写平台判断代码;`IHost` 的实现(`WindowsService`/`Systemd`/`OSXLaunch`/`DefaultHost`)由运行时环境自动决定,同一份代码跨平台运行。
+5. **看门狗和内存监控通过 `Setting` 配置**:在 `Agent.config` 或代码中设置 `WatchDog`/`MaxMemory`/`AutoRestart` 属性,不需要修改 `DoLoop` 逻辑;框架在 `DoLoop` 内自动检查并执行动作。
+6. **自定义命令继承 `BaseCommandHandler`**:在 `Process(args)` 中实现命令逻辑,设置 `Cmd`、`ShortcutKey`、`Description`,框架自动发现并挂入命令行和交互菜单。
+
+## 执行步骤
+
+### 一、创建最简系统服务
+
+```csharp
+using NewLife.Agent;
+
+// 1. 定义服务类
+public class MyService : ServiceBase
+{
+    public MyService()
+    {
+        ServiceName = "MyAwesomeService";
+        DisplayName = "我的后台服务";
+        Description = "数据采集与同步服务";
+    }
+
+    // 服务启动时调用(初始化资源、启动后台任务)
+    protected override void StartWork(String reason)
+    {
+        base.StartWork(reason);  // 必须调用
+
+        // 启动定时采集任务
+        _timer = new TimerX(CollectDataAsync, null, 1_000, 30_000);
+        WriteLog("服务启动,原因:{0}", reason);
+    }
+
+    // 服务停止时调用(释放资源)
+    protected override void StopWork(String reason)
+    {
+        _timer?.Dispose();
+        WriteLog("服务停止,原因:{0}", reason);
+
+        base.StopWork(reason);  // 必须调用
+    }
+
+    private TimerX _timer;
+    
+    private async Task CollectDataAsync(Object? state)
+    {
+        // 采集逻辑...
+        await Task.Delay(100);
+    }
+}
+
+// 2. Program.cs 入口(一行代码)
+static void Main(String[] args) => new MyService().Main(args);
+```
+
+### 二、命令行管理
+
+```bash
+# 安装并自动启动系统服务
+dotnet MyService.dll -install
+
+# 仅安装,不启动
+dotnet MyService.dll -install -ns
+
+# 卸载服务
+dotnet MyService.dll -uninstall
+# 或短命令
+dotnet MyService.dll -u
+
+# 启动 / 停止 / 重启已安装的服务
+dotnet MyService.dll -start
+dotnet MyService.dll -stop
+dotnet MyService.dll -restart
+
+# 查询服务状态
+dotnet MyService.dll -status
+
+# 控制台前台运行(开发调试)
+dotnet MyService.dll -run
+```
+
+### 三、配置看门狗和资源限制
+
+```csharp
+// 方式 1:在构造函数中设置(代码级配置,高优先级)
+public MyService()
+{
+    ServiceName = "MyService";
+    
+    // 内存超过 512MB 自动重启
+    // (通过 Setting 配置,见方式 2)
+}
+
+// 方式 2:通过 Agent.config 配置(推荐生产环境)
+// Agent.config 位于应用目录,首次运行自动生成
+// 也可在 Setting.Current 中修改
+var setting = Setting.Current;
+setting.MaxMemory       = 512;   // 最大内存(MB),超过自动重启
+setting.MaxThread       = 1000;  // 最大线程数,超过告警
+setting.MaxHandle       = 5000;  // 最大句柄数,超过告警
+setting.AutoRestart     = 360;   // 运行满 N 分钟自动重启(0=禁用)
+setting.WatchDog        = "OtherService";  // 监视其他服务名,停止则启动
+setting.Save();
+```
+
+### 四、自定义命令与菜单
+
+```csharp
+// 添加自定义命令(例如:手动触发数据同步)
+public class SyncCommandHandler : BaseCommandHandler
+{
+    private readonly MyService _service;
+
+    public SyncCommandHandler(ServiceBase service) : base(service)
+    {
+        Cmd         = "-sync";          // 命令行参数
+        Description = "立即执行数据同步";
+        ShortcutKey = 'S';              // 交互菜单快捷键(大小写不敏感)
+    }
+
+    public override void Process(String[] args)
+    {
+        XTrace.WriteLine("开始手动同步...");
+        // 执行同步逻辑
+        (_service as MyService)?.SyncNow();
+    }
+
+    // 是否在交互菜单中显示
+    public override Boolean IsShowMenu() => true;
+}
+
+// 在服务中注册(框架通过反射自动发现同 Assembly 内的 BaseCommandHandler)
+// 无需手动注册,只要类在同一程序集中即自动生效
+```
+
+### 五、ASP.NET Core 集成
+
+```csharp
+// Program.cs
+var builder = WebApplication.CreateBuilder(args);
+
+// 替代 builder.Host.UseWindowsService() 或 UseSystemd()
+builder.Host.UseAgentService(options =>
+{
+    options.ServiceName = "MyWebService";
+    options.DisplayName = "我的 Web 服务";
+    options.Description = "ASP.NET Core 应用系统服务";
+});
+
+builder.Services.AddControllers();
+var app = builder.Build();
+app.MapControllers();
+app.Run();
+```
+
+### 六、Linux(systemd)服务文件
+
+```bash
+# 安装后,框架自动在 /etc/systemd/system/ 生成 .service 文件
+# 内容大致如下(框架自动生成,无需手动编写):
+# [Unit]
+# Description=我的后台服务
+# After=network.target
+#
+# [Service]
+# Type=simple
+# ExecStart=/usr/bin/dotnet /opt/myservice/MyService.dll -run
+# Restart=on-failure
+# RestartSec=5
+#
+# [Install]
+# WantedBy=multi-user.target
+
+# 安装命令
+sudo dotnet MyService.dll -install
+
+# 验证
+sudo systemctl status MyService
+sudo journalctl -u MyService -f
+```
+
+## ServiceBase 生命周期钩子速查
+
+| 方法 | 调用时机 | 常见用途 |
+|------|---------|---------|
+| `StartWork(reason)` | 服务启动后(平台服务线程中) | 初始化资源,启动 TimerX/NetServer 等 |
+| `DoLoop()` | 服务运行期间(阻塞循环中) | 一般不需重写;框架在此做资源监控、WatchDog |
+| `StopWork(reason)` | 服务停止前 | 取消任务,释放资源(Dispose),等待 I/O 完成 |
+
+## 命令行参数速查
+
+| 参数 | 功能 |
+|------|------|
+| `-install` | 安装并启动系统服务 |
+| `-uninstall` / `-u` | 卸载系统服务 |
+| `-start` | 启动已安装服务 |
+| `-stop` | 停止已安装服务 |
+| `-restart` | 重启已安装服务 |
+| `-status` | 查询服务状态 |
+| `-run` | 前台控制台运行(调试) |
+| `-watch` | 看门狗模式(监视并守护目标服务) |
+
+## 常见错误与注意事项
+
+- **`StartWork`/`StopWork` 忘记调用 `base.xxx(reason)`**:基类维护 `Running` 标志和日志,遗漏调用会导致状态不一致,`DoLoop` 可能不退出。
+- **在 `StartWork` 中做长时间 I/O(如等待数据库连接)**:SCM(Windows 服务控制器)对启动时间有限制(默认 30 秒),超时会被强制终止;应在 `StartWork` 中仅启动后台任务,不等待完成。
+- **`StopWork` 中释放资源后还持有引用**:`Dispose` 后将字段置 `null`,避免定时器回调在停止后仍然执行导致 `ObjectDisposedException`。
+- **Linux 下用 `UseAutorun=true` 与 systemd 冲突**:`UseAutorun` 是 Windows 注册表自启方式;Linux 上应由 systemd 管理重启策略,不要混用。
+- **多实例部署需不同 `ServiceName`**:同一程序部署多份时,每份实例的 `ServiceName` 必须唯一,否则系统服务注册冲突。
Added +175 -0
diff --git a/.github/skills/holiday-calendar/SKILL.md b/.github/skills/holiday-calendar/SKILL.md
new file mode 100644
index 0000000..0297f01
--- /dev/null
+++ b/.github/skills/holiday-calendar/SKILL.md
@@ -0,0 +1,175 @@
+---
+name: holiday-calendar
+description: >
+  使用 NewLife.Holiday 判断中国法定节假日与调休工作日、查询农历日期与生肖节气,
+  涵盖 DateTime 扩展方法(IsChinaHoliday/IsGuangxiHoliday)、IHoliday 接口、
+  HolidayInfo 详情查询、Lunar 农历结构体与 SolarTerm 二十四节气枚举,
+  以及自定义区域假期扩展(继承 ChinaHoliday/IHoliday)。
+  适用于工作日/休假判断、薪资计算、业务日历、提醒系统等场景。
+argument-hint: >
+  说明你的假期需求:判断某日是否为法定假日;还是需要调休详情(放假几天/哪天补班);
+  是否需要广西三月三等地方节假日;是否需要农历转换或节气信息;
+  是否需要自定义区域假期。
+---
+
+# 中国节假日与农历日历技能(NewLife.Holiday)
+
+## 适用场景
+
+- 薪资系统判断某日是否为工作日、法定节假日或调休补班日。
+- 业务系统在节假日自动切换服务策略(如电商大促、节日提醒)。
+- 物流、快递系统计算有效运营日(排除节假日)。
+- 需要展示农历日期、生肖、天干地支、二十四节气的日历/日程应用。
+- 广西等有地方特色节假日(农历三月三)的业务系统。
+
+## 核心原则
+
+1. **零配置自动加载**:库使用嵌入式 CSV 资源文件,构造时自动加载,无需显式初始化或配置文件;直接引入 NuGet 包即可使用扩展方法。
+2. **`IsChinaHoliday()` 含双休日判断**:返回 `true` 表示"不用上班"——既包括法定节假日,也包括普通週六/日;调休工作日(`HolidayStatus.Off`)返回 `false`,即"需要上班"。
+3. **调休补班要单独查询**:`IsChinaHoliday()` 只返回 bool;要知道某天是"放假几天"还是"调休上班",应用 `IHoliday.Query(date)` 取 `HolidayInfo` 列表,检查 `HolidayStatus.Off`。
+4. **数据覆盖范围 2020–2026 年**:超出此范围的日期仅能按周末规则判断,无法识别法定节假日和调休安排;数据按年度更新,使用前确认已引用最新 NuGet 版本。
+5. **`Lunar` 是 readonly struct,不可为 null**:`Lunar.FromDateTime(date)` 始终返回有效值;年份范围 1901–2100;不要与 `DateTime.MinValue` 混用。
+6. **地方节假日通过继承扩展**:广西三月三等地方节假日由 `GuangxiHoliday` 提供;自定义区域假期继承 `ChinaHoliday` 并重写相关逻辑,保留全国假期基础。
+
+## 执行步骤
+
+### 一、判断是否为节假日(最简用法)
+
+```csharp
+using NewLife.Holiday;
+
+// 是否为节假日(含周末,但调休工作日返回 false)
+var date = new DateTime(2024, 2, 10);   // 2024 年春节
+if (date.IsChinaHoliday())
+{
+    Console.WriteLine("今天放假");
+}
+
+// 是否为工作日(包含调休补班)
+var isWorkday = !date.IsChinaHoliday();
+
+// 广西三月三额外假期
+var guangxiDate = new DateTime(2024, 4, 11);  // 2024 年农历三月三
+if (guangxiDate.IsGuangxiHoliday())
+{
+    Console.WriteLine("广西三月三放假");
+}
+```
+
+### 二、查询假期详情
+
+```csharp
+// 查询指定日期的假期详情列表
+var holidays = HolidayExtensions.China.Query(new DateTime(2024, 2, 10));
+foreach (var h in holidays)
+{
+    // h.Name    = "春节"
+    // h.Date    = 2024/2/10
+    // h.Days    = 8  (本次假期总天数)
+    // h.Status  = HolidayStatus.On(放假) / HolidayStatus.Off(调休补班)
+    Console.WriteLine($"{h.Name}: 共{h.Days}天, 状态:{h.Status}");
+}
+
+// 检查是否为调休补班日
+var compDays = HolidayExtensions.China.Query(new DateTime(2024, 2, 4));
+var isCompensation = compDays.Any(h => h.Status == HolidayStatus.Off);
+Console.WriteLine($"2024-02-04 是调休补班日: {isCompensation}");  // true(春节前调休)
+```
+
+### 三、农历转换
+
+```csharp
+// DateTime → 农历
+var lunar = Lunar.FromDateTime(new DateTime(2024, 2, 10));
+
+Console.WriteLine($"农历月份: {lunar.MonthText}");    // "正月"
+Console.WriteLine($"农历日期: {lunar.DayText}");     // "初一"
+Console.WriteLine($"生肖:     {lunar.Zodiac}");       // "龙"
+Console.WriteLine($"天干地支: {lunar.YearGanzhi}");  // "甲辰"
+Console.WriteLine($"闰月:     {lunar.IsLeapMonth}"); // false
+
+// 组合显示
+Console.WriteLine($"{lunar.YearGanzhi}年 {lunar.MonthText}{lunar.DayText}");
+// 输出:"甲辰年 正月初一"
+```
+
+### 四、二十四节气
+
+```csharp
+// SolarTerm 枚举(从小寒开始)
+var term = SolarTerm.QingMing;   // 清明
+
+// 通过 HolidayInfo.Category 区分节气与假期
+var infos = HolidayExtensions.China.Query(new DateTime(2024, 4, 4));
+var qingming = infos.FirstOrDefault(h => h.Name == "清明节");
+Console.WriteLine($"清明节: {qingming?.Status}");  // On(法定假日)
+```
+
+### 五、计算区间内的工作日天数
+
+```csharp
+// 统计 2024 年 2 月有多少个工作日
+var start  = new DateTime(2024, 2, 1);
+var end    = new DateTime(2024, 2, 29);
+var workdays = Enumerable.Range(0, (end - start).Days + 1)
+    .Select(i => start.AddDays(i))
+    .Count(d => !d.IsChinaHoliday());
+Console.WriteLine($"2024 年 2 月工作日: {workdays} 天");
+```
+
+### 六、自定义区域假期
+
+```csharp
+// 继承 ChinaHoliday,添加企业内部假期
+public class CompanyHoliday : ChinaHoliday
+{
+    protected override IEnumerable<HolidayInfo> GetExtraHolidays(DateTime date)
+    {
+        // 公司年会日(每年 1 月 15 日)
+        if (date.Month == 1 && date.Day == 15)
+        {
+            yield return new HolidayInfo
+            {
+                Name     = "公司年会",
+                Date     = date,
+                Days     = 1,
+                Status   = HolidayStatus.On,
+                Category = "Company",
+            };
+        }
+    }
+}
+
+// 注册为 DI 服务
+services.AddSingleton<IHoliday, CompanyHoliday>();
+```
+
+## HolidayInfo 字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `Name` | `String` | 假期名称,如"春节"、"国庆节" |
+| `Category` | `String` | 来源分类:"China" / "Guangxi" / 自定义 |
+| `Date` | `DateTime` | 假期起始日期 |
+| `Days` | `Int32` | 本次假期总天数(从 Date 起连续) |
+| `Status` | `HolidayStatus` | `On`=放假,`Off`=调休补班,`Normal`=普通工作日 |
+
+## Lunar(农历)属性速查
+
+| 属性 | 类型 | 示例 |
+|------|------|------|
+| `Year` | `Int32` | 2024 |
+| `Month` | `Int32` | 1(正月)|
+| `Day` | `Int32` | 1(初一)|
+| `IsLeapMonth` | `Boolean` | false |
+| `MonthText` | `String` | "正月" |
+| `DayText` | `String` | "初一" |
+| `Zodiac` | `String` | "龙" |
+| `YearGanzhi` | `String` | "甲辰" |
+
+## 常见错误与注意事项
+
+- **误用 `IsChinaHoliday()` 代替"非法定节假日"**:返回 true 包含普通周末;要判断"仅法定节假日"需检查 `Query()` 结果的 `Category` 和 `Status`。
+- **忽略调休补班日**:`IsChinaHoliday()` 的调休补班日返回 `false`(需要上班),依赖此方法计算工资时不要漏判。
+- **超出数据年份范围**:2020 年前或 2026 年后的日期只能按普通周末规则判断,法定节假日和调休无法识别。
+- **`Lunar.FromDateTime` 不抛异常**:日期超出范围时返回默认(零值)结构体而非 null,应检查 `Year > 0` 进行有效性验证。
Added +163 -0
diff --git a/.github/skills/ip-location/SKILL.md b/.github/skills/ip-location/SKILL.md
new file mode 100644
index 0000000..0366dbc
--- /dev/null
+++ b/.github/skills/ip-location/SKILL.md
@@ -0,0 +1,163 @@
+---
+name: ip-location
+description: >
+  使用 NewLife.IP 对 IPv4 地址进行本地高速归属地查询(省市区 + 运营商),
+  涵盖 Ip/IpDatabase 初始化与查询、IpResolver.Register() 注入 NewLife.Net 生态、
+  IpHelper 扩展方法(IPToAddress/ToUInt32IP)、内存映射文件(MMF)+ 二分查找架构,
+  以及自动下载/更新 IP 数据库的机制。
+  适用于 Web 访问日志归属地分析、用户注册地域统计、风控 IP 黑名单等场景。
+argument-hint: >
+  说明你的 IP 查询场景:单次查询还是批量处理;是否集成到 NewLife.Net 生态;
+  是否需要自定义 IP 数据库文件路径;是否有并发高频查询需求。
+---
+
+# IP 地址归属地查询技能(NewLife.IP)
+
+## 适用场景
+
+- 用户注册/登录时记录 IP 归属地(省市区 + 运营商)。
+- Web API 访问日志分析,统计用户地域分布。
+- 风控系统识别异地登录、境外 IP 访问。
+- 高并发网关/代理服务中对每个请求做 IP 地域标记(O(log N) 查询,不影响主路径性能)。
+- 与 NewLife.Net 框架集成,Session 连接信息自动附带 IP 归属地。
+
+## 核心原则
+
+1. **`IpResolver.Register()` 是集成 NewLife.Net 的最简方式**:调用一次后,`NetHelper.GetAddress(ip)` / `IPAddress.IPToAddress()` 均使用本地数据库查询;无需每次实例化 `Ip` 对象。
+2. **`Ip` 对象线程安全,可全局共享**:底层使用 `MemoryMappedFile`(MMF)只读访问 + `ThreadStatic` 缓冲区,高并发环境下无锁竞争;不要在每次查询时 `new Ip()`,开销大。
+3. **`Init()` 幂等可重复调用**:内部有懒加载锁,重复调用不会重复加载;但第一次 `Init()` 若数据库文件不存在,会自动从 `PluginServer` 下载,建议应用启动阶段主动调用。
+4. **`GetAddress` 返回元组 `(area, addr)`**:`area` 是地区部分("中国–广东–深圳"),`addr` 是附加信息(运营商/机房 ISP 描述);两者均为 GB2312 解码后的中文字符串。
+5. **数据库文件自动下载条件**:本地 `ip.gz` 不存在、文件大小 < 3MB、或文件最后修改时间早于内置基线日期;生产离线部署时预先复制 `ip.gz` 到 `DataPath` 目录,避免运行时下载。
+6. **仅支持 IPv4**:库不处理 IPv6 地址(返回空字符串);混合 IPv4/IPv6 环境中需先做地址类型判断。
+
+## 执行步骤
+
+### 一、最简集成(推荐:注册到 NewLife.Net)
+
+```csharp
+using NewLife.IP;
+
+// 应用启动时调用一次(注册到全局 NetHelper.IpResolver)
+IpResolver.Register();
+
+// 之后在任意位置使用扩展方法
+var addr = "116.234.91.199".IPToAddress();
+// 输出: "中国–上海–上海 <运营商描述>"
+
+// 或用 IPAddress 对象
+var ip = IPAddress.Parse("39.144.10.35");
+var fullAddr = ip.IPToAddress();
+// 输出: "中国–广东–深圳 <运营商描述>"
+```
+
+### 二、独立实例查询
+
+```csharp
+var ipService = new Ip();
+ipService.Init();  // 首次调用,加载/下载数据库
+
+// 查询(返回 area + addr 元组)
+var (area, addr) = ipService.GetAddress("61.160.219.25");
+Console.WriteLine($"地区: {area}");   // "中国–江苏–常州"
+Console.WriteLine($"附加: {addr}");   // "<运营商描述>"
+
+// 联合字符串
+var full = $"{area} {addr}".Trim();
+```
+
+### 三、自定义数据库路径
+
+```csharp
+// 指定自定义 IP 数据库(支持 .gz 压缩)
+var ip = new Ip { DbFile = @"D:\data\ip2025.gz" };
+ip.Init();
+
+var (area, addr) = ip.GetAddress("223.5.5.5");
+```
+
+### 四、批量查询(高并发场景)
+
+```csharp
+// IpResolver.Register() 后,线程安全的全局查询
+Parallel.ForEach(logEntries, entry =>
+{
+    entry.Region = entry.IpAddress.IPToAddress();
+});
+```
+
+### 五、Web API 中间件集成
+
+```csharp
+// Startup.cs / Program.cs
+IpResolver.Register();  // 启动时注册,只需一次
+
+// Controller 中
+[HttpPost("login")]
+public IActionResult Login([FromBody] LoginRequest req)
+{
+    var clientIp   = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "";
+    var ipLocation = clientIp.IPToAddress();
+
+    _logger.Info($"登录IP: {clientIp} | 归属地: {ipLocation}");
+    // ...
+}
+```
+
+### 六、IP 地址数值转换(IpHelper 扩展)
+
+```csharp
+using NewLife.IP;
+
+// 字符串 IP → UInt32(Big-Endian 数值,用于范围查询)
+var uint32Ip = "192.168.1.1".ToUInt32IP();   // 0xC0A80101
+
+// UInt32 → IPAddress
+var ipAddr = uint32Ip.ToAddress();            // 192.168.1.1
+
+// UInt32 → 格式化字符串(3 位补零)
+var formatted = uint32Ip.ToStringIP();        // "192.168.001.001"
+
+// IPAddress → UInt32
+var uint32 = IPAddress.Parse("10.0.0.1").ToUInt32();
+```
+
+### 七、离线部署(无网络环境)
+
+```csharp
+// 方式 1:预先将 ip.gz 文件放到应用的 DataPath 目录
+// DataPath 默认为应用运行目录(由 NewLife.Core Setting.DataPath 决定)
+
+// 方式 2:指定绝对路径
+var ip = new Ip { DbFile = "/opt/app/data/ip.gz" };
+ip.Init();
+
+// 方式 3:修改 Setting.DataPath
+NewLife.Setting.Current.DataPath = "/opt/app/data";
+IpResolver.Register();
+```
+
+## 数据库格式说明
+
+| 属性 | 说明 |
+|------|------|
+| 格式 | 纯真 IPv4 库格式(兼容 qqwry.dat/ip.gz) |
+| 压缩 | GZip(`.gz`),首次使用自动解压到临时文件 |
+| 编码 | GB2312(仅命中记录时解码,不全表加载) |
+| 索引 | 7 字节/记录,二分查找 O(log N),~18 次比较 |
+| 存储 | MemoryMappedFile 只读映射,零 GC 压力 |
+
+## 自动更新触发条件
+
+| 条件 | 触发动作 |
+|------|---------|
+| 本地文件不存在 | 从 `PluginServer` 下载 |
+| 文件大小 < 3MB | 重新下载(认为文件损坏) |
+| 最后修改时间早于基线 | 重新下载(可能数据过旧) |
+
+## 常见错误与注意事项
+
+- **每次查询 `new Ip()` 导致内存映射文件重复打开**:`Ip` 对象应单例化或使用 `IpResolver.Register()` 全局注册。
+- **IPv6 返回空字符串**:`::1`(localhost IPv6)或纯 IPv6 地址无法查询,调用方需先判断 `IPAddress.AddressFamily`。
+- **GB2312 在 .NET Core 下需注册编码**:框架内部已处理(调用 `Encoding.RegisterProvider`),但如果宿主应用有自定义编码配置,需确认不会覆盖。
+- **`PluginServer` 默认指向 NewLife 官方 CDN**:内网/离线环境需预先放置 `ip.gz`,或通过 `Setting.PluginServer` 指向内网镜像。
+- **`Init()` 的下载过程是同步阻塞的**:第一次调用若触发下载,会阻塞当前线程;建议在应用启动阶段(非请求路径)预热调用。
Added +220 -0
diff --git a/.github/skills/map-geocoding/SKILL.md b/.github/skills/map-geocoding/SKILL.md
new file mode 100644
index 0000000..86d2786
--- /dev/null
+++ b/.github/skills/map-geocoding/SKILL.md
@@ -0,0 +1,220 @@
+---
+name: map-geocoding
+description: >
+  使用 NewLife.Map 通过统一 IMap 接口调用百度/高德/腾讯/天地图进行地理编码、
+  逆地理编码、驾车距离规划,以及 WGS84/GCJ02/BD09 三种坐标系在线/离线转换。
+  涵盖 MapFactory 工厂创建、多 AppKey 轮询与自动熔断恢复、MapHelper 离线坐标算法,
+  以及通过 Stardust 服务发现接入 NewLifeMap 聚合后端。
+  适用于地址解析、LBS 定位、物流配送距离计算、地图展示等场景。
+argument-hint: >
+  说明你的地图场景:地址→坐标(正向编码),还是坐标→地址(逆向编码);
+  需要哪个地图服务商(百度/高德/腾讯);是否需要驾车距离;
+  是否需要坐标系转换(GPS 设备 WGS84 转百度 BD09 等);
+  是否需要多 Key 轮询。
+---
+
+# 地图地理编码技能(NewLife.Map)
+
+## 适用场景
+
+- 将用户输入的中文地址解析为经纬度坐标(正向地理编码)。
+- 将 GPS/网络定位的经纬度转换为可读地址(逆向地理编码),如"北京市海淀区中关村大街"。
+- 计算两点间驾车时间与距离,用于物流、外卖配送费用估算和 ETA 展示。
+- GPS 设备上报 WGS84 坐标,转换为百度(BD09)或高德(GCJ02)以在地图上正确显示。
+- 多地图服务商冗余:主服务商配额耗尽时自动切换备用服务商。
+- 代码审查:确认坐标系类型使用一致,避免不同系统间坐标偏移。
+
+## 核心原则
+
+1. **所有操作面向 `IMap` 接口**:业务代码只依赖 `IMap`,不直接引用 `BaiduMap` 具体类;通过 DI 或工厂注入,方便切换服务商。
+2. **多 AppKey 逗号分隔实现轮询**:`AppKey = "key1,key2,key3"` 后,每次请求递增取余选取 key;某个 key 报错(配额超限/无效)时自动暂时下线,定时恢复,无需人工干预。
+3. **坐标系类型必须明确传入**:`GetGeoAsync` 和 `GetReverseGeoAsync` 的 `coordtype` 参数指定返回/输入坐标系;不传则使用各服务商默认坐标系(通常是各自私有系统),导致坐标偏移。
+4. **离线坐标转换优先用 `MapHelper`**:WGS84 ↔ GCJ02 ↔ BD09 的数学转换公式内置在 `MapHelper`,零网络请求、零 API 配额消耗;仅在需要 > 100 个点批量精确转换时才用在线 API。
+5. **`GetDistanceAsync` 不是直线距离而是驾车路径**:返回的 `Driving.Distance`(米)是实际路线距离,`Duration`(秒)是预估驾车时间;计算直线距离应用 Haversine 公式或 `RedisGeo`。
+6. **`NewLifeMap` 需要 Stardust 服务发现**:`NewLifeMap` 客户端通过 Stardust 注册中心发现后端 MapApi 服务地址,适合企业内部统一管理 API Key 的场景;单应用直接调用外部 API 用其他提供者。
+
+## 执行步骤
+
+### 一、创建地图客户端
+
+```csharp
+using NewLife.Map;
+
+// 方式 1:直接实例化(最简单)
+IMap map = new BaiduMap { AppKey = "你的百度 AK" };
+// 高德地图
+IMap amap = new AMap { AppKey = "你的高德 Key" };
+// 多 Key 轮询(自动熔断)
+IMap mapMulti = new BaiduMap { AppKey = "key1,key2,key3" };
+
+// 方式 2:工厂创建
+var map = MapFactory.Create(MapKinds.Baidu);
+map.AppKey = "你的百度 AK";
+
+// 方式 3:DI 注入
+services.AddSingleton<IMap>(_ => new AMap { AppKey = "你的高德 Key" });
+// 使用
+public class OrderService(IMap map) { }
+```
+
+### 二、正向地理编码(地址 → 坐标)
+
+```csharp
+// 地址串 → GeoAddress(包含经纬度坐标)
+var geo = await map.GetGeoAsync(
+    address: "北京市海淀区上地十街10号",
+    city: "北京",              // 可选:限制城市提高精度
+    coordtype: "bd09ll"        // 返回坐标系:bd09ll=百度坐标
+);
+
+if (geo != null)
+{
+    Console.WriteLine($"经度: {geo.Location.Longitude}");  // 116.3xxx
+    Console.WriteLine($"纬度: {geo.Location.Latitude}");   // 40.0xxx
+    Console.WriteLine($"地址: {geo.Address}");
+    Console.WriteLine($"区域代码: {geo.Code}");            // 行政区划代码
+}
+```
+
+### 三、逆向地理编码(坐标 → 地址)
+
+```csharp
+// GPS 记录的 WGS84 坐标 → 中文地址
+var address = await map.GetReverseGeoAsync(
+    point: new GeoPoint(116.30815, 40.056885),
+    coordtype: "gcj02"   // 输入坐标系类型
+);
+
+if (address != null)
+{
+    Console.WriteLine($"地址: {address.Address}");      // "北京市海淀区..."
+    Console.WriteLine($"省份: {address.Province}");     // "北京市"
+    Console.WriteLine($"城市: {address.City}");          // "北京市"
+    Console.WriteLine($"区县: {address.District}");     // "海淀区"
+    Console.WriteLine($"POI: {address.Title}");          // 最近兴趣点名称
+}
+```
+
+### 四、驾车距离与时间
+
+```csharp
+var route = await map.GetDistanceAsync(
+    origin:      new GeoPoint(116.30815, 40.056885),     // 起点(公司)
+    destination: new GeoPoint(116.39745, 39.909187),     // 终点(客户)
+    coordtype:   "bd09ll",
+    type:        0   // 0=驾车,可扩展其他出行方式
+);
+
+if (route != null)
+{
+    Console.WriteLine($"距离: {route.Distance / 1000.0:F1} 公里");
+    Console.WriteLine($"时间: {route.Duration / 60} 分钟");
+}
+```
+
+### 五、坐标系转换
+
+```csharp
+// 离线转换(无 API 调用,推荐大批量)
+var wgs84Point = new GeoPoint(116.3912, 39.9074);  // GPS 坐标(WGS84)
+
+// WGS84 → GCJ02(火星坐标)
+var gcj02 = MapHelper.Wgs84ToGcj02(wgs84Point.Longitude, wgs84Point.Latitude);
+
+// GCJ02 → BD09(百度坐标)
+var bd09 = MapHelper.Gcj02ToBd09(gcj02.Lng, gcj02.Lat);
+
+// WGS84 → BD09 一步到位
+var bd09Direct = MapHelper.Wgs84ToBd09(wgs84Point.Longitude, wgs84Point.Latitude);
+
+// 在线转换(百度 API,适合精度要求高的单点/少量转换)
+var points = await map.ConvertAsync(
+    new[] { wgs84Point },
+    from: "wgs84ll",
+    to:   "bd09ll"
+);
+```
+
+### 六、坐标系类型代码速查
+
+```csharp
+// 各服务商 coordtype 参数值
+// 百度 BaiduMap
+"bd09ll"    // 百度坐标系(默认)
+"gcj02ll"   // 火星坐标系
+"wgs84ll"   // WGS84 坐标系
+
+// 高德 AMap
+"gcj02"     // 高德/火星坐标(默认,驾车/POI 返回此格式)
+"wgs84"     // GPS 原始坐标
+```
+
+### 七、MapHelper 离线算法
+
+```csharp
+using NewLife.Map;
+
+// WGS84(GPS/国际) → GCJ02(高德/腾讯)
+var (gcjLng, gcjLat) = MapHelper.Wgs84ToGcj02(116.3912, 39.9074);
+
+// GCJ02(高德/腾讯) → BD09(百度)
+var (bdLng, bdLat) = MapHelper.Gcj02ToBd09(gcjLng, gcjLat);
+
+// BD09(百度) → GCJ02
+var (gcj2Lng, gcj2Lat) = MapHelper.Bd09ToGcj02(bdLng, bdLat);
+
+// WGS84 → BD09(直接转换)
+var (bdLng2, bdLat2) = MapHelper.Wgs84ToBd09(116.3912, 39.9074);
+```
+
+### 八、多 Key 轮询与熔断
+
+```csharp
+// 配置多个 Key,自动轮询 + 失败自动下线
+var map = new BaiduMap
+{
+    AppKey = "primaryKey,backupKey1,backupKey2",
+};
+
+// 当某个 key 因限流/无效报错时:
+// 1. 框架自动将该 key 暂时下线(1 小时后自动恢复)
+// 2. 下次请求自动切到下一个可用 key
+// 3. 全部 key 下线时 Available 属性为 false
+if (!map.Available)
+{
+    throw new Exception("所有 API Key 均不可用,请检查配额或 key 状态");
+}
+```
+
+## 地图服务商功能对比
+
+| 服务商 | 类名 | 地理编码 | 逆编码 | 驾车距离 | IP定位 | 坐标转换 |
+|--------|------|:-------:|:-----:|:-------:|:------:|:-------:|
+| 百度地图 | `BaiduMap` | ✓ | ✓ | ✓ | ✓ | ✓(在线) |
+| 高德地图 | `AMap` | ✓ | ✓ | ✓ | — | ✓(离线) |
+| 腾讯地图 | `WeMap` | ✓ | ✓ | ✓ | — | — |
+| 天地图 | `TianDiTu` | ✓ | ✓ | — | — | — |
+| 新生命图 | `NewLifeMap` | ※ | ※ | ※ | — | — |
+
+> ※ `NewLifeMap` 通过 Stardust 服务发现调用内部聚合服务
+
+## GeoAddress 主要字段
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `Location` | `GeoPoint` | 经纬度坐标(Longitude/Latitude) |
+| `Address` | `String` | 完整地址字符串 |
+| `Province` | `String` | 省份 |
+| `City` | `String` | 城市 |
+| `District` | `String` | 区县 |
+| `Title` | `String` | 附近 POI 名称 |
+| `Code` | `String` | 行政区划代码 |
+| `Towncode` | `String` | 街道/镇代码 |
+
+## 常见错误与注意事项
+
+- **坐标系混用导致偏移**:GPS 设备输出 WGS84,若不转换直接在百度地图展示会偏移数百米;转换前必须确认输入坐标系。
+- **`coordtype` 参数大小写敏感**:百度 API 要求 `bd09ll`(全小写),传 `BD09LL` 会报参数错误。
+- **配额超限后不重试同一 Key**:框架检测到 `TOO_FREQUENT`/`LIMIT` 错误后自动下线该 Key;不要在业务层捕获异常后重试,会加速配额耗尽。
+- **批量地址解析有 QPS 限制**:百度/高德均有每秒查询上限(个人免费版 30 QPS),批量处理时加 `Task.Delay` 控速,或使用 `NewLifeMap` 聚合后端统一管理。
+- **`GetDistanceAsync` type 参数含义因服务商而异**:百度 `type=0` 是驾车,高德也是 `type=0`;具体值含义参考各服务商文档,不要跨服务商假设一致。
Added +234 -0
diff --git a/.github/skills/mqtt-client-server/SKILL.md b/.github/skills/mqtt-client-server/SKILL.md
new file mode 100644
index 0000000..f95af42
--- /dev/null
+++ b/.github/skills/mqtt-client-server/SKILL.md
@@ -0,0 +1,234 @@
+---
+name: mqtt-client-server
+description: >
+  使用 NewLife.MQTT 构建 MQTT 客户端(MqttClient)和内嵌 Broker(MqttServer),
+  涵盖连接管理、发布/订阅、QoS 0/1/2、遗嘱消息、保留消息、断线自动重连重订阅,
+  支持 MQTT 3.1/3.1.1/5.0 协议,以及集群/桥接/规则引擎/WebHook/ACL 企业功能。
+  适用于 IoT 设备接入、实时消息推送、设备影子同步等场景。
+argument-hint: >
+  说明你的 MQTT 场景:客户端连接发布订阅还是搭建 Broker;
+  协议版本(3.1.1 还是 5.0);QoS 等级需求;是否需要 TLS/SSL;
+  是否需要遗嘱消息、保留消息;Broker 是否需要认证/集群/规则引擎。
+---
+
+# MQTT 客户端与服务端技能(NewLife.MQTT)
+
+## 适用场景
+
+- IoT 设备(传感器、网关、工业设备)通过 MQTT 上报数据、接收指令。
+- 需要内嵌轻量级 Broker,不依赖第三方 MQTT 服务(如 Mosquitto/EMQ)。
+- 设备异常断线需要自动重连并恢复全部订阅关系。
+- 需要企业级功能:ACL 权限控制、消息桥接(跨集群转发)、规则引擎(消息路由/处理)。
+- 代码审查:确认 QoS 使用正确、遗嘱消息配置规范、断线重连策略合理。
+
+## 核心原则
+
+1. **`MqttClient` 自动重连重订阅**:设置 `Reconnect = true`(默认)后,连接断开时框架自动按指数退避重连,并恢复 `SubscribeAsync` 注册的全部订阅关系,业务层无需手动实现断线恢复。
+2. **QoS 级别按需选择**:`AtMostOnce(0)` 高吞吐/可接受丢消息(传感器遥测);`AtLeastOnce(1)` 可靠投递适合指令下发;`ExactlyOnce(2)` 四次握手开销大,仅用于支付/告警等幂等性场景。
+3. **遗嘱消息在 `Connect` 前配置**:`WillTopic`/`WillMessage`/`WillQoS` 属性必须在 `ConnectAsync()` 调用前设置,连接后无法更改。
+4. **保留消息用于状态同步**:发布时设置 `Retain = true`,Broker 保留该主题最后一条消息,新订阅者连接后立即收到最新状态,无需等待下一次发布。
+5. **服务端必须注入 `IMqttExchange`**:`MqttExchange` 是消息路由中枢,负责发布/订阅匹配、保留消息存储、QoS 消息持久化;不注入则服务端只能接收消息但无法路由分发。
+6. **通配符订阅 `+` 和 `#` 区别**:`+` 匹配单层(`sensor/+/temperature` 匹配 `sensor/device1/temperature`),`#` 匹配多层(`sensor/#` 匹配 `sensor/device1/data/raw`);`#` 只能出现在末尾。
+7. **遗嘱消息 vs 正常断开**:正常 `DisconnectAsync()` 不触发遗嘱;异常断线(网络超时、进程崩溃)才触发遗嘱消息发布。
+
+## 执行步骤
+
+### 一、客户端连接与发布订阅
+
+```csharp
+using NewLife.MQTT;
+using NewLife.MQTT.Messaging;
+
+// 创建客户端
+var client = new MqttClient
+{
+    Server    = "tcp://127.0.0.1:1883",
+    ClientId  = Guid.NewGuid().ToString(),
+    UserName  = "admin",
+    Password  = "admin",
+    KeepAlive = 60,          // 心跳间隔(秒)
+    Reconnect = true,        // 自动重连(默认 true)
+    Version   = MqttVersion.V311,
+    Log       = XTrace.Log,
+};
+
+// 连接
+await client.ConnectAsync();
+
+// 订阅主题(通配符 + 回调)
+await client.SubscribeAsync("sensor/+/temperature", msg =>
+{
+    var payload = msg.Payload.ToStr();
+    Console.WriteLine($"主题: {msg.Topic}, 数据: {payload}");
+});
+
+// 发布消息(QoS 1)
+await client.PublishAsync("sensor/device1/temperature", "25.6",
+    QualityOfService.AtLeastOnce);
+
+// 发布保留消息(新订阅者立即可见最新状态)
+await client.PublishAsync(new PublishMessage
+{
+    Topic   = "device/online-status",
+    Payload = Encoding.UTF8.GetBytes("online"),
+    QoS     = QualityOfService.AtLeastOnce,
+    Retain  = true,
+});
+```
+
+### 二、连接字符串配置
+
+```csharp
+// 等价于上面的属性配置
+client.Init("Server=tcp://127.0.0.1:1883;UserName=admin;Password=admin;ClientId=client01");
+await client.ConnectAsync();
+```
+
+### 三、遗嘱消息配置
+
+```csharp
+// 遗嘱消息:设备异常掉线时 Broker 自动发布
+var client = new MqttClient
+{
+    Server      = "tcp://127.0.0.1:1883",
+    ClientId    = "device-001",
+    WillTopic   = "device/device-001/status",
+    WillMessage = Encoding.UTF8.GetBytes("offline"),
+    WillQoS     = QualityOfService.AtLeastOnce,
+    WillRetain  = true,    // 遗嘱消息也作为保留消息存储
+};
+await client.ConnectAsync();
+```
+
+### 四、MQTT 5.0 特性
+
+```csharp
+var client = new MqttClient
+{
+    Server  = "tcp://127.0.0.1:1883",
+    Version = MqttVersion.V500,  // 启用 MQTT 5.0
+};
+await client.ConnectAsync();
+
+// 共享订阅(负载均衡,多个消费者)
+await client.SubscribeAsync("$share/group1/sensor/#", msg =>
+{
+    Console.WriteLine($"[5.0 共享订阅] {msg.Topic}: {msg.Payload.ToStr()}");
+});
+```
+
+### 五、TLS/SSL 安全连接
+
+```csharp
+var client = new MqttClient
+{
+    Server      = "ssl://broker.example.com:8883",
+    SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
+    Certificate = new X509Certificate2("client.pfx", "password"),
+};
+await client.ConnectAsync();
+```
+
+### 六、搭建内嵌 Broker(MqttServer)
+
+```csharp
+using NewLife.MQTT;
+using NewLife.MQTT.Handlers;
+using NewLife.Remoting;
+
+// IoC 容器注册
+var services = ObjectContainer.Current;
+services.AddSingleton<ILog>(XTrace.Log);
+services.AddTransient<IMqttHandler, MqttHandler>();   // 协议处理器
+services.AddSingleton<IMqttExchange, MqttExchange>(); // 消息路由(必须)
+
+// 创建并启动 Broker
+var server = new MqttServer
+{
+    Port             = 1883,
+    ServiceProvider  = services.BuildServiceProvider(),
+    Log              = XTrace.Log,
+};
+server.Start();
+```
+
+### 七、自定义认证与 ACL
+
+```csharp
+public class MyAuthenticator : IMqttAuthenticator
+{
+    public Boolean Authenticate(ConnectMessage message, out String? reason)
+    {
+        var valid = CheckCredentials(message.Username, message.Password);
+        reason = valid ? null : "用户名或密码错误";
+        return valid;
+    }
+
+    public Boolean CanPublish(String clientId, String topic)
+        => topic.StartsWith($"device/{clientId}/");  // 设备只能发布自己的主题
+
+    public Boolean CanSubscribe(String clientId, String topicFilter)
+        => topicFilter.StartsWith("sensor/");
+}
+
+// 注册
+services.AddSingleton<IMqttAuthenticator, MyAuthenticator>();
+```
+
+### 八、规则引擎(消息路由/处理)
+
+```csharp
+// 规则引擎在 MqttExchange 内配置
+var exchange = new MqttExchange();
+
+// 规则:匹配主题 sensor/# → 转发到另一主题
+exchange.AddRule(new MqttRule
+{
+    TopicFilter = "sensor/#",
+    Action      = RuleAction.Republish,
+    TargetTopic = "log/sensor",
+});
+
+// 规则:匹配告警主题 → 触发 WebHook
+exchange.AddRule(new MqttRule
+{
+    TopicFilter = "alarm/#",
+    Action      = RuleAction.WebHook,
+    WebHookUrl  = "https://api.example.com/alarm",
+});
+```
+
+### 九、集群部署
+
+```csharp
+var server = new MqttServer
+{
+    Port         = 1883,
+    ClusterPort  = 2883,
+    ClusterNodes = new[] { "192.168.1.2:2883", "192.168.1.3:2883" },
+};
+server.Start();
+```
+
+## 配置参数速查(MqttClient)
+
+| 参数 | 默认值 | 说明 |
+|------|--------|------|
+| `Server` | — | 服务器地址(`tcp://host:1883`,`ssl://` 表示 TLS) |
+| `ClientId` | 随机 GUID | 客户端唯一标识(断线重连建议固定) |
+| `KeepAlive` | `600` | 心跳间隔(秒),0 = 禁用 |
+| `CleanSession` | `true` | false = 断线后服务端保留会话(离线消息) |
+| `Version` | `V311` | MQTT 协议版本 |
+| `Reconnect` | `true` | 自动重连 |
+| `MaxReconnectAttempts` | `0` | 0 = 无限重连 |
+| `InitialReconnectDelay` | `1000` | 初始重连延迟(ms) |
+| `MaxReconnectDelay` | `60000` | 最大重连延迟(ms,指数退避上限) |
+| `Timeout` | `15000` | 操作超时(ms) |
+
+## 常见错误与注意事项
+
+- **`CleanSession=false` 时 `ClientId` 必须固定**:持久会话靠 `ClientId` 识别;每次用随机 ID 会导致离线消息永远堆积,服务端会话膨胀。
+- **QoS 2 不支持 Retain + 消息幂等场景**:QoS 2 保证恰好一次,但网络抖动时四次握手可能被重复触发;业务层仍需做幂等处理。
+- **`IMqttExchange` 是 Broker 的必要组件**:不注入 `MqttExchange` 则消息无法在客户端间路由,所有消息只进不出。
+- **通配符 `#` 订阅所有主题有性能风险**:`#` 匹配全部消息,高吞吐场景下回调处理不及时会导致内存积压。
+- **遗嘱消息不要设置过大 Payload**:遗嘱消息在 CONNECT 报文中一次性传输,过大(>256KB)会拒绝连接。
Added +282 -0
diff --git a/.github/skills/office-documents/SKILL.md b/.github/skills/office-documents/SKILL.md
new file mode 100644
index 0000000..5c6b175
--- /dev/null
+++ b/.github/skills/office-documents/SKILL.md
@@ -0,0 +1,282 @@
+---
+name: office-documents
+description: >
+  使用 NewLife.Office 生成和读取 Office 文档,涵盖 Excel(ExcelWriter/ExcelReader/ExcelTemplate/BiffReader)、
+  Word(WordWriter/WordReader/WordTemplate/WordHtmlConverter)、
+  PowerPoint(PptxWriter/PptxReader)、PDF(PdfFluentDocument/PdfWriter)、
+  Markdown(MarkdownParser/MarkdownWriter),以及 RTF/ODS/EML/iCalendar/vCard/EPUB 等格式。
+  零第三方依赖,仅依赖 NewLife.Core,适用于报表生成、文档导出、模板填充等场景。
+argument-hint: >
+  说明你的文档场景:生成还是读取;文件格式(Excel/Word/PPT/PDF);
+  是否需要模板填充({{Key}} 占位符);是否需要转换(Word→HTML/PDF);
+  是否需要样式、图片、表格等高级元素。
+---
+
+# Office 文档生成与处理技能(NewLife.Office)
+
+## 适用场景
+
+- 服务端动态生成 Excel 报表(数据导出、财务报告)并提供下载。
+- 基于 Word 模板生成合同、通知函等个性化文档(`{{Key}}` 占位符替换)。
+- 生成 PDF 报告(发票、报告单、处方)支持页眉/页脚/书签/密码保护。
+- 读取上传的 xls/xlsx/docx/pptx,提取文字内容做分析处理。
+- 在服务端(无 Office/COM 依赖)做高性能文档操作,免除 Interop 许可问题。
+
+## 核心原则
+
+1. **`ExcelWriter` 和 `WordWriter` 必须 `Dispose`**:两者内部持有 OpenXML 文件流;不调用 `Dispose`/`using` 会导致文件流未刷新、zip 包损坏,生成的文件无法打开。
+2. **模板占位符 `{{Key}}` 保留原有样式**:`ExcelTemplate`/`WordTemplate`/`PptxTemplate` 在替换时保留单元格格式、字体、颜色;手动修改模板文件样式比代码设置样式更高效。
+3. **`PdfFluentDocument` 是链式 API,强于 `PdfWriter`**:`PdfFluentDocument` 管理页面布局(自动换页、当前 Y 坐标、页眉页脚),适合报告/发票场景;`PdfWriter` 是低级绝对坐标 API,适合精确排版。
+4. **`BiffReader` 读取旧 `.xls` 格式**:解析 BIFF8/OLE2 容器(Excel 97-2003),只能读取不能写入;现代 xlsx 用 `ExcelReader`。
+5. **中文 PDF 需要显式创建中文字体**:`.NET` 的 PDF 实现默认不含中文字体;`PdfWriter.CreateSimplifiedChineseFont()` 内置宋体嵌入,必须在 `DrawText` 前设置字体,否则中文字符丢失。
+6. **`ExcelWriter.WriteObjects<T>` 自动映射属性**:通过反射读取公共属性名作为列标题,属性值作为行数据,适合快速导出实体列表;列顺序默认与属性声明顺序一致。
+
+## 执行步骤
+
+### 一、Excel 写入(生成报表)
+
+```csharp
+using NewLife.Office;
+
+// 基础行写入
+using var writer = new ExcelWriter("report.xlsx");
+
+// 写入表头(指定工作表名)
+writer.WriteHeader("销售数据", new[] { "订单号", "金额", "日期", "状态" });
+
+// 逐行写入
+writer.WriteRow("销售数据", new Object[] { "ORD-001", 1250.00, DateTime.Today, "已付款" });
+
+// 批量写入(推荐,适合大数据量)
+var rows = orders.Select(o => new Object[] { o.Id, o.Amount, o.Date, o.Status });
+writer.WriteRows("销售数据", rows);
+
+// 自动映射实体类属性
+writer.WriteObjects("订单列表", orders);   // Order 类的公共属性自动变为列
+
+// 设置列宽
+writer.SetColumnWidth("销售数据", colIndex: 1, width: 15.0);
+// 冻结首行(表头)
+writer.FreezePane("销售数据", freezeRowCount: 1);
+// 自动筛选
+writer.SetAutoFilter("销售数据", range: "A1:D1");
+
+writer.Save();  // 保存到构造时指定的路径
+// 或:writer.Save(stream); 写入输出流(用于 HTTP 响应)
+```
+
+### 二、Excel 读取
+
+```csharp
+using var reader = new ExcelReader("upload.xlsx");
+
+// 获取所有工作表名
+var sheets = reader.Sheets;
+
+// 逐行读取(懒加载,节省内存)
+foreach (var row in reader.ReadRows("Sheet1"))
+{
+    var orderId = row[0]?.ToString();
+    var amount  = Convert.ToDecimal(row[1]);
+}
+
+// 映射到实体类(按列名映射)
+var orders = reader.ReadObjects<Order>("销售数据").ToList();
+
+// 读取为 DataTable(与旧代码数据绑定兼容)
+var dt = reader.ReadDataTable("Sheet1");
+```
+
+### 三、Excel 模板填充
+
+```csharp
+// 模板文件中使用 {{Key}} 占位符(如 {{CompanyName}}、{{Total}})
+var template = new ExcelTemplate("template/invoice-template.xlsx");
+
+template.Fill("output/invoice-001.xlsx", new Dictionary<String, Object>
+{
+    ["CompanyName"] = "北京科技有限公司",
+    ["InvoiceDate"] = DateTime.Today.ToString("yyyy年MM月dd日"),
+    ["Total"]       = 12500.00m,
+    ["Items"]       = itemList,   // 列表类型自动展开为多行
+});
+```
+
+### 四、Excel 高级功能
+
+```csharp
+// 添加数据验证(下拉列表)
+writer.AddDropdownValidation("Sheet1", "C2:C100", new[] { "待处理", "进行中", "已完成" });
+
+// 条件格式(超过阈值变红)
+writer.AddConditionalFormat("Sheet1", "B2:B100",
+    ConditionalFormatType.GreaterThan, value: "10000", color: "#FF0000");
+
+// 插入图片
+var imageBytes = File.ReadAllBytes("logo.png");
+writer.AddImage("封面", row: 1, col: 1, imageBytes, "png", width: 120, height: 60);
+
+// 超链接
+writer.AddHyperlink("说明", row: 1, col: 5, url: "https://docs.example.com", display: "查看文档");
+
+// 页面设置(A4 横向,用于宽表格打印)
+writer.SetPageSetup("销售数据", PageOrientation.Landscape, PaperSize.A4);
+writer.SetHeaderFooter("销售数据", header: "公司内部报告", footer: "页码:&P/&N");
+
+// 工作表保护密码
+writer.ProtectSheet("敏感数据", password: "123456");
+```
+
+### 五、Word 写入(生成合同/通知)
+
+```csharp
+using var word = new WordWriter("contract.docx");
+
+// 设置文档属性
+word.DocumentProperties.Title  = "服务合同";
+word.DocumentProperties.Author = "北京科技有限公司";
+
+// 写入标题和正文
+word.AppendHeading("服务合同", level: 1);
+word.AppendParagraph($"合同编号:{contractId}", WordParagraphStyle.Normal);
+word.AppendParagraph("");  // 空行
+
+// 格式化段落(混合样式)
+word.AppendFormattedParagraph(new[]
+{
+    new WordRun("甲方:", bold: true),
+    new WordRun("北京科技有限公司"),
+}, WordParagraphStyle.Normal);
+
+// 插入表格
+var table = word.AppendTable(rows: 5, cols: 3);
+table.SetCell(0, 0, "服务项目");
+table.SetCell(0, 1, "单价");
+table.SetCell(0, 2, "数量");
+
+// 插入图片
+var imgBytes = File.ReadAllBytes("signature.png");
+word.AppendImage(imgBytes, "png", widthCm: 5.0, heightCm: 2.0);
+
+// 页眉页脚
+word.SetPageHeader("机密文件");
+word.SetPageFooter($"生成时间:{DateTime.Now:yyyy-MM-dd HH:mm}");
+
+word.Save("output/contract.docx");
+```
+
+### 六、Word 模板填充
+
+```csharp
+// Word 模板中使用 {{FieldName}} 占位符
+var template = new WordTemplate("templates/contract-template.docx");
+template.Fill("output/contract-001.docx", new Dictionary<String, Object>
+{
+    ["PartyA"]        = "北京科技有限公司",
+    ["PartyB"]        = "上海贸易有限公司",
+    ["ContractDate"]  = "2024年3月15日",
+    ["Amount"]        = "¥ 120,000.00",
+});
+```
+
+### 七、Word 读取与转换
+
+```csharp
+// 读取 docx 文本内容
+using var wordReader = new WordReader("document.docx");
+var fullText = wordReader.ReadFullText();   // 全文文本
+var paragraphs = wordReader.ReadParagraphs().ToList();  // 逐段
+var tables = wordReader.ReadTables().ToList();           // 表格(二维数组)
+
+// 转换为 HTML
+var converter = new WordHtmlConverter();
+var html = converter.ToHtml("document.docx");
+
+// 转换为 PDF
+var pdfConverter = new WordPdfConverter();
+pdfConverter.ToPdf("document.docx", "output.pdf");
+```
+
+### 八、PDF 生成(Fluent API)
+
+```csharp
+using var doc = new PdfFluentDocument();
+doc.Title            = "销售月报";
+doc.Author           = "财务部";
+doc.Header           = "Confidential";
+doc.Footer           = "公司内部文件";
+doc.ShowPageNumbers  = true;
+
+// 中文字体(必须设置,否则中文乱码)
+var font = doc.CreateChineseFont(fontSize: 12);
+
+doc
+    .AddText("2024年3月 销售月报", fontSize: 20, font: font)
+    .AddEmptyLine()
+    .AddText($"制表日期:{DateTime.Today:yyyy年MM月dd日}", fontSize: 10, font: font)
+    .AddEmptyLine()
+    .AddTable(tableRows, firstRowHeader: true)
+    .AddEmptyLine()
+    .AddText("附件图表", fontSize: 14, font: font)
+    .AddImage(chartImageBytes, width: 400, height: 250)
+    .PageBreak()
+    .AddText("说明与备注", fontSize: 14, font: font)
+    .AddText(remarkText, font: font);
+
+doc.Save("sales-report.pdf");
+```
+
+### 九、PPT 生成
+
+```csharp
+using var ppt = new PptxWriter("presentation.pptx");
+
+// 添加幻灯片
+var slide1 = ppt.AddSlide();
+
+// 标题文本框(EMU 单位:914400 EMU = 1 英寸 = 2.54 cm)
+ppt.AddTextBox(0, "季度业绩报告",
+    leftCm: 2, topCm: 2, widthCm: 22, heightCm: 3,
+    fontSize: 36, bold: true);
+
+// 数据表格
+ppt.AddTable(0,
+    rows: new[] { new[] { "项目", "目标", "实际", "完成率" }, ... },
+    leftCm: 1, topCm: 6, widthCm: 22);
+
+// 插入图片
+ppt.AddImage(0, chartBytes, "png",
+    leftCm: 1, topCm: 12, widthCm: 10, heightCm: 7);
+
+// 设置幻灯片背景色
+ppt.SetBackground(0, "#F5F5F5");
+
+ppt.Save("output/presentation.pptx");
+```
+
+## 格式支持速查
+
+| 格式 | 读取 | 写入 | 模板 | 核心类 |
+|------|:----:|:----:|:----:|--------|
+| xlsx | ✓ | ✓ | ✓ | `ExcelWriter`/`ExcelReader`/`ExcelTemplate` |
+| xls | ✓ | — | — | `BiffReader` |
+| docx | ✓ | ✓ | ✓ | `WordWriter`/`WordReader`/`WordTemplate` |
+| doc | ✓(文本) | — | — | `DocReader` |
+| pptx | ✓ | ✓ | ✓ | `PptxWriter`/`PptxReader` |
+| PDF | ✓ | ✓ | — | `PdfFluentDocument`/`PdfWriter`/`PdfReader` |
+| Markdown | ✓ | ✓ | — | `MarkdownParser`/`MarkdownWriter` |
+| RTF | ✓ | ✓ | ✓ | `RtfReader`/`RtfWriter` |
+| ODS | ✓ | ✓ | — | `OdsReader`/`OdsWriter` |
+| EML | ✓ | ✓ | — | `EmlReader`/`EmlWriter` |
+| iCalendar | ✓ | ✓ | — | `ICalReader`/`ICalWriter` |
+| vCard | ✓ | ✓ | — | `VCardReader`/`VCardWriter` |
+| EPUB | ✓ | ✓ | — | `EpubReader`/`EpubWriter` |
+
+## 常见错误与注意事项
+
+- **忘记 `Dispose`/`using`**:`ExcelWriter`/`WordWriter` 等持有文件流,不 `Dispose` 生成的文件是不完整的 zip 包,打开时报"文件损坏"。
+- **中文 PDF 不设置字体**:默认 PDF 字体不含中文,不调用 `CreateSimplifiedChineseFont()` 则中文字符完全丢失(显示为方块或空白)。
+- **模板占位符大小写**:`{{CompanyName}}` 和 `{{companyname}}` 是不同的 key,`Dictionary<String, Object>` 的默认比较是大小写敏感的。
+- **`BiffReader` 只读不写**:旧 `.xls` 格式只支持读取;如需生成旧版 Excel,建议生成 `.xlsx` 后建议用户另存,或用 LibreOffice 服务转换。
+- **`PdfFluentDocument.AddTable` 超宽会截断**:表格宽度由 `ContentWidth`(页面宽度减去左右边距)决定;列数过多时内容可能被截断,应减少列数或用横向纵向页面。
+- **`WriteObjects<T>` 忽略无公共 getter 的属性**:仅映射有公开 `get` 访问器的属性;计算属性/内部属性不会出现在导出列中,若需要则手动指定列映射。
Added +219 -0
diff --git a/.github/skills/redis-client/SKILL.md b/.github/skills/redis-client/SKILL.md
new file mode 100644
index 0000000..3d7beef
--- /dev/null
+++ b/.github/skills/redis-client/SKILL.md
@@ -0,0 +1,219 @@
+---
+name: redis-client
+description: >
+  使用 NewLife.Redis(FullRedis/Redis)通过 ICache 接口进行高性能 Redis 操作,
+  涵盖连接池配置、基础 KV 操作、四种队列(Queue/Reliable/Delay/Stream)、
+  数据结构(List/Hash/Set/SortedSet/Geo/HyperLogLog)、发布订阅、Pipeline 管道,
+  以及集群(Cluster/Sentinel/Replication)接入。
+  适用于缓存加速、可靠消息队列、发布订阅、分布式锁等场景。
+argument-hint: >
+  说明你的 Redis 场景:缓存读写、消息队列(简单/可靠/延迟/Stream)、发布订阅、
+  数据结构操作;是否需要集群/哨兵/主从;是否需要 Pipeline 批量提交。
+---
+
+# Redis 客户端技能(NewLife.Redis)
+
+## 适用场景
+
+- 应用缓存层对接 Redis,需要面向 `ICache` 接口,在开发/生产环境自由切换。
+- 分布式消息队列:简单队列(至多一次)、可靠队列(至少一次 + Ack 确认)、延迟队列、Stream 消费组。
+- 各类 Redis 数据结构操作:`RedisList`、`RedisHash`、`RedisSet`、`RedisSortedSet`、`RedisGeo`、`HyperLogLog`。
+- 发布订阅(Pub/Sub)实时推送。
+- 高并发场景下使用 Pipeline 合并命令,降低网络 RTT。
+- 集群/哨兵/主从模式接入。
+
+## 核心原则
+
+1. **优先用 `FullRedis`,而非基础 `Redis`**:`FullRedis` 支持集群自动检测(`Mode` 属性:`cluster`/`sentinel`/`standalone`)、数据结构工厂方法(`GetList<T>`/`GetStream<T>` 等),向下兼容基础 `Redis` 所有能力。
+2. **通过连接字符串工厂创建**:`FullRedis.Create("server=127.0.0.1:6379;password=xxx;db=0")` 是最简洁的创建方式;连接字符串中多路地址用逗号分隔可实现多节点故障切换。
+3. **`Redis` 实现 `ICache` 接口**:过期时间语义与 `MemoryCache` 一致:`expire < 0` 使用默认值,`expire = 0` 永不过期,`expire > 0` 为相对秒数。不要用 `0` 表示默认。
+4. **队列选型**:简单 `RedisQueue` 无确认(至多一次,不丢不行的场景不适用);`RedisReliableQueue` 基于 `RPOPLPUSH` + Ack 确认(适合订单/支付消息);`RedisDelayQueue` 基于有序集合实现延迟投递;`RedisStream` 支持消费组多消费者(大吞吐)。
+5. **Pipeline 减少 RTT**:高频批量写操作启用 `AutoPipeline`(达到指定命令数自动提交)或手动 `StartPipeline`/`StopPipeline`;注意 Pipeline 内命令结果不可立即读取。
+6. **连接池默认参数合理,不要随意缩小**:最小连接 `MinPool=10`,最大连接 `MaxPool=100000`;读写超时默认 `3000ms`,生产环境根据 P99 调整,不要设得太小。
+7. **集群模式无需手动配置**:`FullRedis.InitCluster()` 自动检测服务端模式;哨兵模式在 `Server` 地址中包含 `sentinel://` 前缀;原生集群自动路由分片。
+
+## 执行步骤
+
+### 一、创建连接
+
+```csharp
+using NewLife.Caching;
+
+// 方式 1:连接字符串(推荐)
+var redis = FullRedis.Create("server=127.0.0.1:6379;password=;db=0");
+
+// 方式 2:属性配置
+var redis = new FullRedis
+{
+    Server   = "127.0.0.1:6379",
+    Password = "",
+    Db       = 0,
+    Timeout  = 3000,   // 读写超时 ms
+};
+redis.Init(null);
+
+// 方式 3:DI 注册(ASP.NET Core)
+services.AddSingleton<ICache>(sp =>
+    FullRedis.Create("server=127.0.0.1:6379;db=1"));
+```
+
+### 二、基础 KV 操作
+
+```csharp
+// 写入(60 秒过期)
+redis.Set("user:1", user, expire: 60);
+
+// 读取(区分不存在与默认值)
+if (redis.TryGetValue<User>("user:1", out var user))
+    return user;
+
+// 批量读写(减少 RTT)
+redis.SetAll(dict, expire: 300);
+var map = redis.GetAll<User>(new[] { "user:1", "user:2" });
+
+// 原子递增(计数、限流)
+var count = redis.Increment("page:views", 1);
+
+// 分布式锁(配合 using 确保释放)
+using var locker = redis.AcquireLock("lock:order:42", 30_000);
+if (locker == null) throw new Exception("获取锁失败");
+// 互斥操作...
+```
+
+### 三、四种队列
+
+```csharp
+// 1. 简单队列(LPUSH/RPOP,无确认,至多一次)
+var queue = redis.GetQueue<String>("tasks");
+queue.Add("task1", "task2");
+var task = queue.TakeOne(timeout: 5);  // 阻塞等待 5 秒
+
+// 2. 可靠队列(RPOPLPUSH + Ack,至少一次)
+var reliable = redis.GetReliableQueue<Order>("orders");
+reliable.Add(order);
+var msg = reliable.TakeOne(timeout: 5);
+if (msg != null)
+{
+    Process(msg);
+    reliable.Acknowledge(msg);  // 确认消费
+}
+// 后台启动死信恢复:把超时未 Ack 的消息重新入队
+reliable.RetryDeadDelay = 60;
+
+// 3. 延迟队列(基于 ZADD,指定延迟秒数)
+var delay = redis.GetDelayQueue<String>("delayed-tasks");
+delay.Add("send-email", delaySeconds: 300);  // 5 分钟后可消费
+
+// 4. Redis Stream(消费组,高吞吐)
+var stream = redis.GetStream<Event>("events");
+stream.Group = "group1";
+stream.Add(new Event { ... });
+var events = stream.Read(count: 100);  // 消费组拉取
+stream.Acknowledge(events.Select(e => e.Id));
+```
+
+### 四、数据结构
+
+```csharp
+// List(有序列表)
+var list = redis.GetList<String>("my-list");
+list.Add("a", "b", "c");
+var all = list.GetAll();
+
+// Hash(字典)
+var hash = redis.GetDictionary<String, Int32>("scores");
+hash["alice"] = 100;
+hash["bob"]   = 200;
+var score = hash["alice"];
+
+// Set(去重集合)
+var set = redis.GetSet<String>("tags");
+set.Add("redis", "cache");
+var contains = set.Contains("redis");
+
+// SortedSet(排行榜)
+var sorted = redis.GetSortedSet<String>("rank");
+sorted.Add("alice", 100.0);
+sorted.Add("bob",   200.0);
+var top10 = sorted.GetRange(0, 9);  // 前 10 名
+
+// Geo(地理位置)
+var geo = redis.GetGeo("locations");
+geo.Add("beijing", 116.4074, 39.9042);
+var dist = geo.Distance("beijing", "shanghai");  // 单位:米
+
+// HyperLogLog(基数统计,去重计数)
+var hll = redis.GetHyperLogLog("uv:2024-01-01");
+hll.Add("user:1", "user:2", "user:3");
+var count = hll.Count();
+```
+
+### 五、发布订阅
+
+```csharp
+// 订阅(异步,独立后台线程)
+var sub = redis.GetPubSub("events");
+_ = sub.SubscribeAsync(async (topic, msg) =>
+{
+    Console.WriteLine($"[{topic}] {msg}");
+}, cancellationToken);
+
+// 发布
+sub.Publish("hello from producer");
+```
+
+### 六、Pipeline 管道
+
+```csharp
+// 自动管道(命令数达阈值自动提交)
+redis.AutoPipeline = 100;
+
+// 手动管道(精确控制)
+var client = redis.StartPipeline();
+client.Set("k1", "v1");
+client.Set("k2", "v2");
+client.Increment("counter", 1);
+var results = redis.StopPipeline(requireResult: true);
+```
+
+### 七、集群 / 哨兵 / 主从
+
+```csharp
+// 原生集群(多节点逗号分隔,自动路由)
+var cluster = FullRedis.Create("server=node1:7001,node2:7002,node3:7003");
+cluster.InitCluster();  // 自动检测 cluster 模式
+
+// 哨兵模式(sentinel:// 前缀)
+var sentinel = FullRedis.Create(
+    "server=sentinel://sentinel1:26379,sentinel2:26380;masterName=mymaster");
+
+// SSL/TLS
+var tls = new FullRedis
+{
+    Server      = "redis.example.com:6380",
+    SslProtocol = System.Security.Authentication.SslProtocols.Tls12,
+};
+```
+
+## 配置参数速查
+
+| 参数 | 默认值 | 说明 |
+|------|--------|------|
+| `Server` | — | Redis 地址,多地址逗号分隔 |
+| `Password` | — | 认证密码 |
+| `Db` | `0` | 数据库索引 |
+| `Timeout` | `3000` | 读写超时(ms) |
+| `MinPool` | `2` | 连接池最小连接数 |
+| `MaxPool` | `100000` | 连接池最大连接数 |
+| `Retry` | `3` | 失败重试次数 |
+| `AutoPipeline` | `0` | 自动管道命令阈值(0=关闭) |
+| `Encoder` | `RedisJsonEncoder` | 序列化编码器 |
+
+## 常见错误与注意事项
+
+- **`expire=0` 永不过期**,不代表使用默认值。使用默认值应传 `expire=-1`。
+- **可靠队列消费后必须 Ack**,否则消息长期在备份队列中,触发死信堆积。
+- **Stream 消费组需先创建 Group**:第一次消费前调用 `stream.SetGroup(groupName)`。
+- **Pipeline 内不能立即读取结果**:`StartPipeline` 期间所有 `Get` 返回值为空,需 `StopPipeline` 后从返回数组读取。
+- **集群模式下 `Db` 只能为 0**:Redis Cluster 不支持多数据库。
+- **不要在高并发下用 `Keys` 扫描全库**:`redis.Keys` 在 Redis Cluster 下遍历全部 slot,性能极低,仅适合调试。
Added +273 -0
diff --git a/.github/skills/rocketmq-messaging/SKILL.md b/.github/skills/rocketmq-messaging/SKILL.md
new file mode 100644
index 0000000..b1be484
--- /dev/null
+++ b/.github/skills/rocketmq-messaging/SKILL.md
@@ -0,0 +1,273 @@
+---
+name: rocketmq-messaging
+description: >
+  使用 NewLife.RocketMQ 接入 Apache RocketMQ(4.x/5.x)进行消息生产与消费,
+  涵盖 Producer/Consumer 生命周期、五种消息类型(普通/顺序/事务/延迟/批量)、
+  Pop 消费模式(5.x)、四大云厂商适配(阿里/腾讯/华为/Apache ACL)、
+  gRPC 代理、消息轨迹追踪,以及消费重试与死信队列。
+  适用于电商订单、支付通知、IoT 数据接入等高可靠消息场景。
+argument-hint: >
+  说明你的消息场景:发送还是消费;消息类型(普通/顺序/事务/延迟/批量);
+  RocketMQ 版本(4.x 还是 5.x);是否使用云服务(阿里/腾讯/华为)还是自建;
+  是否需要 gRPC 代理(5.x 专用);消费是集群模式还是广播模式。
+---
+
+# RocketMQ 消息收发技能(NewLife.RocketMQ)
+
+## 适用场景
+
+- 电商、支付场景中,需要通过消息队列解耦服务,保证消息可靠投递。
+- 需要事务消息(二阶段提交)保证数据库操作与消息发送的原子性。
+- IoT 设备数据接入,需要高吞吐、顺序消费。
+- 迁移或部署在阿里云/腾讯云/华为云的 RocketMQ 实例,需要特定认证适配。
+- 升级到 RocketMQ 5.x,需要使用 Pop 消费模式或 gRPC 代理。
+
+## 核心原则
+
+1. **Producer 和 Consumer 独立生命周期**:`Start()` 后才能收发;程序退出前必须 `Stop()` 优雅关闭,否则消费位点不会及时提交,重启后可能重复消费。
+2. **消费回调返回 `false` 触发重试**:`OnConsume` 委托返回 `true` 表示消费成功;返回 `false` 或抛出异常时,消息会按 `MaxReconsumeTimes`(默认 16 次)重试,超次进死信队列(`%DLQ%{Group}`)。
+3. **顺序消费必须锁定队列**:设置 `OrderConsume = true` 后,框架会调用 `LockBatchMQAsync` 在 Broker 端加锁,同一队列内消息串行消费;锁定失败的队列消息延后处理。
+4. **事务消息两步发送**:先 `PublishTransaction`(半消息)执行本地事务,再通过 `OnCheckTransaction` 回查委托告知 Broker 提交还是回滚;Broker 1 分钟内发起回查,回查超 15 次视为回滚。
+5. **云厂商适配通过 `CloudProvider` 插入认证逻辑**:不同云厂商签名算法不同(HMAC-SHA1/MD5/AK-SK),通过 `ICloudProvider` 接口注入,不改变业务代码。
+6. **RocketMQ 5.x 使用 gRPC 需设置 `GrpcProxyAddress`**:5.x 支持 Remoting 和 gRPC 双协议,`GrpcProxyAddress` 优先,不设则使用 Remoting(兼容 4.x)。
+
+## 执行步骤
+
+### 一、发送普通消息
+
+```csharp
+using NewLife.RocketMQ;
+
+var producer = new Producer
+{
+    Topic             = "order-topic",
+    NameServerAddress = "127.0.0.1:9876",
+    Group             = "order-producer-group",
+};
+producer.Start();
+
+// 同步发送(带标签和业务键)
+var result = producer.Publish(
+    body: new { OrderId = 42, Amount = 100.0m },
+    tags: "create",
+    keys: "order-42"
+);
+Console.WriteLine($"MsgId: {result.MsgId}, Status: {result.SendStatus}");
+
+// 异步发送
+var asyncResult = await producer.PublishAsync(new Message
+{
+    Topic = "order-topic",
+    Tags  = "pay",
+    Body  = Encoding.UTF8.GetBytes(order.ToJson()),
+});
+
+// 单向发送(不等结果,适合日志/监控)
+producer.PublishOneway(message, queue: null);
+
+producer.Stop();
+```
+
+### 二、批量发送
+
+```csharp
+var messages = orders.Select(o => new Message
+{
+    Topic = "order-topic",
+    Tags  = "batch",
+    Body  = Encoding.UTF8.GetBytes(o.ToJson()),
+}).ToList();
+
+var result = producer.PublishBatch(messages);
+```
+
+### 三、延迟消息
+
+```csharp
+// RocketMQ 4.x:18 级预设延迟(1s/5s/10s/30s/1m/2m/3m/4m/5m/6m/7m/8m/9m/10m/20m/30m/1h/2h)
+var msg = new Message
+{
+    Topic           = "order-topic",
+    Body            = Encoding.UTF8.GetBytes(order.ToJson()),
+    DelayTimeLevel  = 3,  // 第 3 级 = 10 秒后投递
+};
+producer.Publish(msg);
+
+// RocketMQ 5.x:精确延迟时间(毫秒)
+var msg5 = new Message
+{
+    Topic            = "order-topic",
+    Body             = Encoding.UTF8.GetBytes(order.ToJson()),
+    DeliveryTimestamp = DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeMilliseconds(),
+};
+```
+
+### 四、事务消息
+
+```csharp
+producer.OnCheckTransaction = (msg, transId) =>
+{
+    // 回查本地事务状态(查询数据库等)
+    var state = CheckOrderInDb(transId);
+    return state == OrderState.Success
+        ? TransactionState.Commit
+        : TransactionState.Rollback;
+};
+producer.Start();
+
+// 发送事务消息
+var result = producer.PublishTransaction(new Message
+{
+    Topic = "finance-topic",
+    Body  = Encoding.UTF8.GetBytes(transfer.ToJson()),
+});
+
+// 执行本地事务(数据库落库)
+using var tx = db.BeginTransaction();
+db.InsertTransfer(transfer);
+tx.Commit();
+
+// 提交事务消息(告知 Broker 提交)
+producer.ConfirmTransaction(result.TransactionId, TransactionState.Commit);
+```
+
+### 五、消费消息
+
+```csharp
+var consumer = new Consumer
+{
+    Topic             = "order-topic",
+    Group             = "order-consumer-group",
+    NameServerAddress = "127.0.0.1:9876",
+    BatchSize         = 32,           // 每批拉取数量
+    FromLastOffset    = false,        // false = 从头消费(首次)
+    MaxReconsumeTimes = 16,           // 失败最大重试次数
+};
+
+// 同步消费回调(返回 false = 消费失败,触发重试)
+consumer.OnConsume = (queue, messages) =>
+{
+    foreach (var msg in messages)
+    {
+        try
+        {
+            ProcessOrder(Encoding.UTF8.GetString(msg.Body));
+        }
+        catch
+        {
+            return false;  // 消费失败,重试
+        }
+    }
+    return true;
+};
+
+consumer.Start();
+
+// 异步消费回调
+consumer.OnConsumeAsync = async (queue, messages, ct) =>
+{
+    await ProcessBatchAsync(messages, ct);
+    return true;
+};
+```
+
+### 六、顺序消费
+
+```csharp
+var consumer = new Consumer
+{
+    Topic         = "order-topic",
+    Group         = "order-seq-group",
+    NameServerAddress = "127.0.0.1:9876",
+    OrderConsume  = true,  // 启用顺序消费
+};
+consumer.OnConsume = (queue, messages) => { /* 串行处理 */ return true; };
+consumer.Start();
+```
+
+### 七、Pop 消费(RocketMQ 5.x)
+
+```csharp
+var consumer = new Consumer
+{
+    Topic             = "order-topic",
+    Group             = "order-consumer-group",
+    NameServerAddress = "127.0.0.1:9876",
+    Version           = MQVersion.V5_2_0,  // 指定 5.x
+};
+
+// Pop 模式:消息不再归属某个 Broker 队列,消费后 Ack
+consumer.OnConsumeAsync = async (queue, messages, ct) =>
+{
+    foreach (var msg in messages)
+    {
+        await ProcessAsync(msg);
+        // PopConsume 模式下 Ack 由框架自动完成(ConsumerState.Success)
+    }
+    return true;
+};
+consumer.Start();
+```
+
+### 八、云厂商适配
+
+```csharp
+// 阿里云公共云 RocketMQ
+var producer = new Producer
+{
+    Topic             = "topic-xxx",
+    NameServerAddress = "http://xxx.mq.aliyuncs.com:80",
+    CloudProvider     = new AliyunProvider
+    {
+        AccessKey  = "LTAI5t...",
+        SecretKey  = "xxx",
+        OnsChannel = "ALIYUN",
+    },
+};
+
+// Apache ACL(自建集群)
+var producer2 = new Producer
+{
+    Topic             = "topic-xxx",
+    NameServerAddress = "127.0.0.1:9876",
+    CloudProvider     = new AclProvider
+    {
+        AccessKey = "rocketmq_ak",
+        SecretKey = "rocketmq_sk",
+    },
+};
+
+// gRPC 代理(RocketMQ 5.x Proxy)
+var producer3 = new Producer
+{
+    Topic            = "topic-xxx",
+    GrpcProxyAddress = "http://localhost:8081",
+    Group            = "test-group",
+};
+```
+
+## 配置参数速查
+
+| 参数 | 默认值 | 说明 |
+|------|--------|------|
+| `NameServerAddress` | — | NameServer 地址(必填) |
+| `Topic` | — | 主题名(必填) |
+| `Group` | — | 生产/消费组 |
+| `Version` | `V4_9_7` | 协议版本(V5_2_0 = 5.x) |
+| `RequestTimeout` | `3000` | 请求超时(ms) |
+| `RetryTimesWhenSendFailed` | `3` | 发送失败重试次数 |
+| `MaxMessageSize` | `4MB` | 消息体最大限制 |
+| `CompressOverBytes` | `4096` | 超过此大小自动 ZLIB 压缩 |
+| `BatchSize` | `32` | Consumer 每批拉取数量 |
+| `MaxReconsumeTimes` | `16` | 消费失败最大重试次数 |
+| `FromLastOffset` | `false` | 首次消费起始位置 |
+| `EnableMessageTrace` | `false` | 是否开启消息轨迹 |
+
+## 常见错误与注意事项
+
+- **`Stop()` 必须调用**:消费位点通过心跳维护,不调用 `Stop()` 会导致重复消费。
+- **事务消息 Broker 回查最多 15 次**:超限视为回滚;回查委托内不要有耗时 I/O,应直接查库状态。
+- **延迟级别从 1 开始(不是 0)**:`DelayTimeLevel=0` 表示不延迟,与 `1`(1 秒)不同。
+- **顺序消费与多线程冲突**:`OrderConsume=true` 时框架串行处理同一队列,不要在 `OnConsume` 内再起并发任务。
+- **批量消息超 4MB 会被拒绝**:发批量消息前检查总大小,建议每批 < 1MB。
+- **5.x gRPC 代理需单独部署 Proxy 组件**:`GrpcProxyAddress` 指向 RocketMQ Proxy,而非 Broker 直连地址。
Added +241 -0
diff --git a/.github/skills/stardust-platform/SKILL.md b/.github/skills/stardust-platform/SKILL.md
new file mode 100644
index 0000000..7eebb62
--- /dev/null
+++ b/.github/skills/stardust-platform/SKILL.md
@@ -0,0 +1,241 @@
+---
+name: stardust-platform
+description: >
+  使用 NewLife.Stardust 接入星尘分布式服务平台,涵盖 StarFactory 统一接入入口、
+  服务注册与发现(IRegistry)、配置中心(star.Config → IConfigProvider)、
+  APM 性能追踪(StarTracer + 全链路诊断监听器)、应用心跳监控,
+  以及 IoC 集成(AddStardust/UseStardust)和 StarAgent 守护进程接入。
+  适用于微服务注册发现、集中配置管理、分布式链路追踪、应用运维监控等场景。
+argument-hint: >
+  说明你的星尘使用场景:服务注册发现、配置中心、性能监控(APM)、
+  还是以上全部;是否集成在 ASP.NET Core 中;
+  是否需要通过 StarAgent 接入本地代理;
+  配置中心是否需要热更新。
+---
+
+# 星尘分布式服务平台技能(NewLife.Stardust)
+
+## 适用场景
+
+- 微服务架构中通过星尘注册中心实现服务注册、发现与负载均衡。
+- 集中管理多套环境(开发/测试/生产)配置,支持动态热更新无需重启。
+- 服务间调用链路追踪(APM),自动采集 HTTP/SQL/EF Core/gRPC/MQ 调用链。
+- 应用心跳监控:CPU 占用、内存、线程、GC 等指标定期上报到星尘平台。
+- ASP.NET Core 应用快速接入(`AddStardust`),不改业务代码实现全链路可观测性。
+- StarAgent 守护进程接管应用部署、进程监控、远程发布。
+
+## 核心原则
+
+1. **`StarFactory` 是唯一的接入入口**:通过 `new StarFactory(server, appId, secret)` 或 `AddStardust()` 获取;不要直接 `new StarClient()`/`new AppClient()`,内部初始化顺序有依赖。
+2. **配置加载优先级(高→低)**:构造参数 > `appsettings.json` 的 `Star` 节 > 本机 StarAgent(UDP 5500)> `star.config` 文件;生产环境推荐用 `appsettings.json`,本地开发通过 StarAgent 自动获取服务端地址。
+3. **`star.Config` 返回 `IConfigProvider`,与本地配置统一接口**:配置中心的 key 通过 `star.Config["Key"]` 读取;可与 `appsettings.json` 组合使用(同一接口,星尘配置优先级可调整)。
+4. **APM 追踪调用 `DiagnosticListenerObserver.Install()` 一次即可**:安装后自动监听 ASP.NET Core/HttpClient/EF Core/SQL/gRPC 等,无需在每个调用点手动埋点;仅需在关键业务操作处用 `tracer.NewSpan()` 补充自定义 span。
+5. **服务发现消费返回地址列表,调用方负责负载均衡**:`registry.Consume("UserService")` 返回该服务全部可用实例地址;结合 `NewLife.Http.HttpClient` 或手动轮询实现客户端负载均衡。
+6. **StarAgent 是 NewLife.Agent 的增强版本**:`StarAgent` 基于 `ServiceBase` 构建,额外提供进程守护、远程发布、节点监控采集;部署 StarAgent 后,本地应用可通过 UDP 5500 自动获取星尘服务端地址(无需硬编码)。
+
+## 执行步骤
+
+### 一、控制台/Worker 应用接入
+
+```csharp
+using NewLife.Stardust;
+
+// 方式 1:显式参数(指定星尘服务端)
+var star = new StarFactory("http://stardust.example.com:6600", "MyApp", "secret");
+
+// 方式 2:自动发现(本机有 StarAgent 时自动获取地址)
+var star = new StarFactory();   // 从环境自动读取服务端地址
+
+// 获取各功能入口
+var registry = star.Registry;   // 服务注册发现
+var config   = star.Config;     // 配置中心
+var tracer   = star.Tracer;     // APM 性能追踪
+
+// 使用配置中心读取配置
+var dbConn = config["ConnectionStrings:Default"];
+
+// 发布自身服务(注册到注册中心)
+registry.Register("OrderService", "http://192.168.1.10:5001");
+
+// 应用退出时取消注册(建议放在 IDisposable 中)
+registry.Unregister("OrderService");
+```
+
+### 二、ASP.NET Core 集成(推荐)
+
+```csharp
+// Program.cs
+var builder = WebApplication.CreateBuilder(args);
+
+// 一行代码接入星尘(注册发现 + 配置中心 + APM)
+var star = builder.AddStardust("OrderService");
+
+// 自动完成:
+// 1. 将本服务注册到星尘注册中心
+// 2. 接入配置中心(star.Config 合并到 IConfiguration)
+// 3. 安装 APM 诊断监听器(自动追踪 HTTP/SQL/EF)
+// 4. 定期心跳上报 CPU/内存等运行指标
+
+builder.Services.AddControllers();
+var app = builder.Build();
+app.MapControllers();
+app.Run();
+```
+
+### 三、appsettings.json 配置
+
+```json
+{
+  "Star": {
+    "Server"        : "http://stardust.example.com:6600",
+    "AppKey"        : "OrderService",
+    "Secret"        : "your-secret",
+    "TracerPeriod"  : 60,
+    "MaxSamples"    : 1,
+    "MaxErrors"     : 10,
+    "Debug"         : false
+  }
+}
+```
+
+### 四、服务注册与发现
+
+```csharp
+// 发布(注册自身)
+var registry = star.Registry;
+registry.Register("OrderService", "http://192.168.1.10:5001");
+
+// 消费(发现其他服务)
+var addresses = await registry.ConsumeAsync("UserService");
+// addresses: ["http://192.168.1.20:5002", "http://192.168.1.21:5002"]
+
+// 客户端负载均衡(简单轮询)
+var idx  = Interlocked.Increment(ref _roundRobinIndex);
+var addr = addresses[idx % addresses.Length];
+
+// 使用 NewLife.Http 配合多地址自动故障切换
+var client = new HttpClient { BaseAddress = new Uri(addr) };
+var result = await client.GetStringAsync("/api/users/1");
+```
+
+### 五、配置中心使用
+
+```csharp
+// 读取配置(与 IConfiguration 接口一致)
+var config = star.Config;
+var dbConn  = config["DbConn"];
+var timeout = config["Timeout"].ToInt(30);
+
+// 订阅配置变更(热更新)
+config.OnChanged += (k, v) =>
+{
+    XTrace.WriteLine($"配置已更新: {k} = {v}");
+    // 重新应用配置
+};
+
+// 在 DI 中使用(IConfiguration 方式)
+builder.Configuration.AddStardust(star);
+
+// 然后通过标准 IConfiguration 读取
+var connStr = configuration.GetConnectionString("Default");
+```
+
+### 六、APM 性能追踪
+
+```csharp
+// 方式 1:自动安装(推荐,覆盖全部框架埋点)
+// 在 AddStardust 时自动调用,无需手动
+DiagnosticListenerObserver.Install();
+
+// 方式 2:手动自定义 Span(业务关键路径补充)
+var tracer = star.Tracer;
+using var span = tracer.NewSpan("ProcessOrder");
+span.Tag = orderId.ToString();
+try
+{
+    // 业务逻辑
+    ProcessOrderInternal(orderId);
+}
+catch (Exception ex)
+{
+    span.SetError(ex, null);  // 标记错误并记录异常
+    throw;
+}
+
+// 自动追踪的框架(安装 DiagnosticListenerObserver 后):
+// - ASP.NET Core 请求(入站 HTTP)
+// - HttpClient(出站 HTTP)
+// - EF Core / ADO.NET SQL 查询
+// - gRPC 调用
+// - MongoDB 操作
+// - DNS 查询 / TCP 连接
+```
+
+### 七、通过 StarAgent 接入(推荐生产部署)
+
+```bash
+# 1. 机器上部署 StarAgent(一次性)
+dotnet StarAgent.dll -install
+# StarAgent 监听 UDP 5500,应用自动发现星尘服务端地址
+
+# 2. 应用代码中直接用自动发现模式
+# var star = new StarFactory();  // 自动从本机 StarAgent 获取服务端地址
+# 无需在代码或配置文件中硬编码星尘服务端地址
+```
+
+### 八、StarAgent 作为应用守护进程
+
+```csharp
+// StarAgent 基于 NewLife.Agent 的 ServiceBase 构建
+// 应用只需要以 NewLife.Agent 方式打包,StarAgent 接管进程生命周期:
+// - 进程异常退出自动重启
+// - 远程触发发布新版本
+// - 采集 CPU/内存/网络指标上报到星尘平台
+// - 支持通过星尘平台 Web 界面远程查看日志和执行命令
+
+// 具体见 agent-service 技能中的 ServiceBase 用法
+```
+
+## StarFactory 功能入口速查
+
+| 属性 | 类型 | 说明 |
+|------|------|------|
+| `star.Registry` | `IRegistry` | 服务注册与发现 |
+| `star.Config` | `IConfigProvider` | 配置中心(与 IConfiguration 统一接口) |
+| `star.Tracer` | `ITracer` | APM 性能追踪(NewSpan/StartSpan) |
+| `star.Client` | `IApiClient` | 星尘原始 HTTP 客户端(低级 API) |
+| `star.Dust` | `AppClient` | 应用客户端(心跳、服务注册的底层实现) |
+
+## 配置参数速查
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `Server` | 星尘服务端地址 | 空(从 StarAgent/环境变量读取) |
+| `AppKey` | 应用唯一标识 | 入口程序集名称 |
+| `Secret` | 应用密钥(鉴权) | 空 |
+| `TracerPeriod` | 追踪数据上报间隔(秒) | `60` |
+| `MaxSamples` | 每周期最大正常采样数 | `1` |
+| `MaxErrors` | 每周期最大错误采样数 | `10` |
+| `Debug` | 调试模式(输出原始请求) | `false` |
+
+## 自动追踪覆盖范围
+
+| 组件 | 监听器类 | 追踪点 |
+|------|---------|--------|
+| ASP.NET Core | `AspNetCoreDiagnosticListener` | HTTP 入站请求 |
+| HttpClient | `HttpDiagnosticListener` | HTTP 出站调用 |
+| EF Core | `EfCoreDiagnosticListener` | SQL 查询 |
+| ADO.NET | `SqlClientDiagnosticListener` | SQL 执行 |
+| gRPC | `GrpcDiagnosticListener` | gRPC 调用 |
+| MongoDB | `MongoDbDiagnosticListener` | 数据库操作 |
+| Socket/TCP | `SocketEventListener` | 网络连接 |
+| DNS | `DnsEventListener` | 域名解析 |
+
+## 常见错误与注意事项
+
+- **`AppKey` 相同的多个实例会被视为同一服务的多个节点**:同一服务的所有实例使用相同 `AppKey`,注册中心自动识别为同一服务的不同副本,参与负载均衡。
+- **配置中心变更不是实时推送而是轮询**:默认 60 秒轮询一次;不要在热路径中假设配置值毫秒级同步,设计时应允许短暂的「旧值」窗口。
+- **StarAgent UDP 5500 端口需开放防火墙**:应用通过 UDP 5500 探测本机 StarAgent 获取服务端地址;容器化部署时需映射该端口或通过环境变量 `STAR_SERVER` 指定地址。
+- **`DiagnosticListenerObserver.Install()` 只调用一次**:重复调用会注册多个监听器,导致追踪数据重复上报,增加平台存储压力。
+- **配置中心 key 区分大小写**:星尘配置中心的 key 大小写敏感;读取 `dbConn` 与 `DbConn` 是两个不同的 key。
+- **`secret` 为空时所有应用可接入**:开发环境可省略 secret;生产环境必须配置密钥,防止未授权应用注册到注册中心污染服务列表。