diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md old mode 100644 new mode 100755 index 809b990b2..177c34dcf --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,428 +1,406 @@ -# NewLife Copilot 协作指令 - -本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 - -> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 - ---- - -## 1. 核心原则 - -| 原则 | 说明 | -|------|------| -| **提效** | 减少机械样板,聚焦业务/核心算法 | -| **一致** | 风格、结构、命名、API 行为稳定 | -| **可控** | 限制改动影响面,可审计,兼容友好 | -| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | -| **主动** | 发现问题主动修复,不回避合理优化 | - ---- - -## 2. 适用范围 - -- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 -- 不含纯前端/非 .NET/市场文案 -- 存在本文件 → 必须遵循 - ---- - -## 3. 组件专用指令索引(按需加载) - -以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 - -### 3.1 XCode / Cube(数据库 & Web 快速开发) - -当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: - -- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 -- 解决方案/项目中出现:`NewLife.XCode` 包引用 -- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) -- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 -- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 - -**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 - -未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 - ---- - -## 4. 工作流 - -``` -需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 -``` - -1. **需求分类**:功能/修复/性能/重构/文档 -2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) -3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** -4. **设计**:列出改动点 + 兼容/降级策略 -5. **实施**: - - 完成用户请求的核心任务 - - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) - - **顺带优化**可简化的重复代码 - - 保留原注释与结构,除非注释本身有误 -6. **验证**: - - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) - - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 -7. **说明**:变更摘要/影响范围/风险点 - -### 4.1 主动优化原则 - -当用户请求分析或优化代码时,**应主动**: - -| 类型 | 行动 | -|------|------| -| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | -| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | -| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | -| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | -| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | -| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | -| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | - -**架构调整策略**: -- **改动较小**:直接调整,完成后说明变更内容 -- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 - -**不应过度保守**: -- ❌ 仅添加注释而忽略明显的代码问题 -- ❌ 发现资源泄漏却不修复 -- ❌ 看到重复代码却不提取 -- ❌ 用户要求优化时只做表面工作 - -**保持谨慎的场景**: -- 公共 API 签名变更 → 需说明兼容性影响 -- 性能关键路径 → 需有依据或说明推理 -- 大范围重构 → 需先与用户确认范围 - -### 4.2 防御性注释规则 - -在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 - -**这些是防御性注释**: -- 记录了过去曾经踩过的坑 -- 目的是告诉后来人不要按照注释代码去写,否则会有问题 -- **禁止删除此类防御性注释**,用于警示后人 - -**识别特征**: -```csharp -// 曾经尝试过同步等待,但会导致线程池饥饿和死锁 -// var result = task.Result; - -// 不要使用 SendAsync 的无超时重载,否则会造成连接泄漏 -// await client.SendAsync(data); - -// 这里不能用 Flush,因为底层 SSL 流会抛出 ObjectDisposedException -// stream.Flush(); - -// 不要改成 ConfigureAwait(true),会导致 UI 线程死锁 -// await Task.Delay(100).ConfigureAwait(true); -``` - -**处理原则**: -- ✅ 保留这类带说明的注释代码 -- ✅ 可以补充更详细的说明,解释为什么不能这样做 -- ❌ 不要删除这类防御性注释 -- ❌ 不要尝试"恢复"这些被注释的代码 - ---- - -## 5. 编码规范 - -### 5.1 基础规范 - -| 项目 | 规范 | -|------|------| -| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | -| 命名空间 | file-scoped namespace | -| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | -| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | -| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | - -### 5.2 命名规范 - -| 成员类型 | 命名规则 | 示例 | -|---------|---------|------| -| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | -| 参数/局部变量 | camelCase | `userName`、`count` | -| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | -| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | -| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | - -### 5.3 代码风格 - -```csharp -// ✅ 单行 if:单语句且整行不过长时同行 -if (value == null) return; -if (key == null) throw new ArgumentNullException(nameof(key)); - -// ✅ 单行 if:语句较长时另起一行 -if (value == null) - throw new ArgumentNullException(nameof(value), "Value cannot be null"); - -// ✅ 多分支单语句:不加花括号 -if (count > 0) - DoSomething(); -else - DoOther(); - -// ✅ 循环必须保留花括号(即使单语句) -foreach (var item in list) -{ - Process(item); -} - -// ✅ using 语句:优先使用 using declaration(无花括号) -using var stream = File.OpenRead("file.txt"); -using var reader = new StreamReader(stream); -return reader.ReadToEnd(); - -// ✅ using 弃元:仅用于需要资源生命周期但不需要引用的场景 -// 例如:分布式锁、性能追踪 Span、临时文件锁等 -using var _ = _lock.AcquireLock(); -DoSomething(); -// 方法结束时自动释放锁 - -// ⚠️ using 语句:仅在需要提前结束作用域时使用花括号 -using (var connection = CreateConnection()) -{ - connection.Open(); - // ... 执行操作 -} -// 这里 connection 已释放,可以继续执行其他操作 -``` - -### 5.4 Region 组织结构 - -较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 - -**日志 Region 规则**: -- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 -- **必须放在类代码的最后** -- **必须用名为"日志"的 region 包裹** -- 不要放在"辅助" region 中,应单独作为"日志" region - -### 5.5 现代 C# 语法 - -优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 - -### 5.6 集合表达式 - -优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` - -### 5.7 Null 条件运算符 - -优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` - ---- - -## 6. 多目标框架 - -NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 - -新增 API 时需评估各框架兼容性,必要时提供降级实现。 - ---- - -## 7. 文档注释 - -| 规则 | 说明 | -|------|------| -| `` | **必须同一行闭合**,简短描述方法用途 | -| `` | **必须为每个参数添加**,无论方法可见性如何 | -| `` | 有返回值时必须添加 | -| `` | 复杂方法可增加详细说明(可多行) | -| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | -| `[Obsolete]` | 必须包含迁移建议 | - -**正确示例**:`/// 获取名称` `/// 编号` - -**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 - ---- - -## 8. 异步与性能 - -| 规范 | 说明 | -|------|------| -| 方法命名 | 异步方法后缀 `Async` | -| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | -| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | -| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | -| 池化资源 | 明确获取/归还;异常分支不遗失归还 | - -**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 - ---- - -## 9. 日志与追踪 - -规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 - ---- - -## 10. 错误处理 - -- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 -- **参数校验**:空/越界/格式 -- **TryXxx 模式**:不用异常作常规分支 -- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 -- **对外异常**:不暴露内部实现/路径 - ---- - -## 11. 测试规范 - -| 项目 | 规范 | -|------|------| -| 框架 | xUnit | -| 命名 | `{ClassName}Tests` | -| 描述 | `[DisplayName("中文描述意图")]` | -| IO | 使用临时目录;端口用 0/随机 | -| 覆盖 | 正常/边界/异常/并发(必要时) | - -### 测试执行策略 - -1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 -2. 未命中则查找 `{ClassName}Tests.cs` -3. **未发现相关测试需明确说明**,不自动创建测试项目 - ---- - -## 12. NuGet 发布规范 - -| 类型 | 命名规则 | 示例 | -|------|---------|------| -| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | -| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | - -- **正式版**:每月月初发布 -- **测试版**:提交代码到 GitHub 时自动发布 - ---- - -## 13. Markdown 文档规范 - -| 项目 | 规范 | -|------|------| -| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | -| 默认存放 | 代码库根目录下的 `Doc` 目录 | -| 文件命名 | 优先**中文文件名**,简洁描述内容 | - -**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 - ---- - -## 14. Copilot 行为守则 - -### 必须 - -- 简体中文回复 -- 输出前检索现有实现,**禁止重复造轮子** -- 先列方案再实现 -- 标记不确定上下文为"需查看文件" -- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) -- **用户要求优化时深入分析**,不做表面工作 - -### 鼓励 - -- 提取重复代码为公共方法 -- 简化冗余的条件判断 -- 使用现代 C# 语法改进可读性 -- 补充缺失的资源释放逻辑 -- 修正错误或过时的注释 - -### 禁止 - -- 虚构 API/文件/类型 -- 伪造测试结果/性能数据 -- 擅自删除公共/受保护成员 -- 擅自删除已有代码注释(除非注释本身错误) -- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) -- 仅删除空白行制造"格式优化"提交 -- 删除循环体的花括号 -- 将 `` 拆成多行 -- 将 `String`/`Int32` 改为 `string`/`int` -- 新增外部依赖(除非说明理由并给出权衡) -- 在热点路径添加未缓存反射/复杂 Linq -- 输出敏感凭据/内部地址 -- **发现问题却视而不见** -- **用户要求优化时仅做注释/测试等表面工作** - ---- - -## 15. 变更说明模板 - -提交或答复需包含: - -```markdown -## 概述 -做了什么 / 为什么 - -## 影响 -- 公共 API:是/否 -- 性能影响:无/有(说明) - -## 兼容性 -降级策略 / 条件编译点 - -## 风险 -潜在回归 / 性能开销 - -## 后续 -是否补测试 / 文档 -``` - ---- - -## 16. 术语说明 - -| 术语 | 定义 | -|------|------| -| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | -| **基线** | 变更前的功能/性能参考数据 | -| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | -| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | - ---- - -## 17. 代码优化检查清单 - -当进行代码优化时,按以下清单逐项检查: - -### 架构与结构 -- [ ] 代码架构是否清晰?是否需要重构? -- [ ] 类的职责是否单一?是否需要拆分? -- [ ] 是否有重复代码可以提取为公共方法? -- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? - -### 语法现代化 -- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) -- [ ] 集合初始化是否使用了集合表达式 `[]`? -- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? - -### 健壮性 -- [ ] 是否存在空引用风险? -- [ ] 资源是否正确释放?(IDisposable、流、连接等) -- [ ] 异常处理是否完善? -- [ ] 并发场景是否线程安全? - -### 性能 -- [ ] 是否存在可以缓存的重复计算? -- [ ] 是否有不必要的对象分配? -- [ ] 热点路径是否避免了反射和复杂 Linq? -- [ ] 是否使用了对象池/ArrayPool 等池化技术? - -### 注释与文档 -- [ ] 类、接口是否有 `` 注释? -- [ ] 公共方法是否有完整的参数和返回值注释? -- [ ] 方法内重要逻辑是否有注释说明? -- [ ] 防御性注释是否保留? - -### 日志 -- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? -- [ ] 是否用名为"日志"的 region 包裹? - ---- - -(完) +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git "a/Doc/InfluxDB\346\224\257\346\214\201\350\257\264\346\230\216.md" "b/Doc/InfluxDB\346\224\257\346\214\201\350\257\264\346\230\216.md" new file mode 100644 index 000000000..8e28c6bf6 --- /dev/null +++ "b/Doc/InfluxDB\346\224\257\346\214\201\350\257\264\346\230\216.md" @@ -0,0 +1,276 @@ +# XCode InfluxDB 时序数据库支持 + +## 概述 + +XCode 现已支持 InfluxDB 2.x 时序数据库,提供完整的添删改查(CRUD)功能。InfluxDB 是专为时序数据优化的开源数据库,广泛应用于物联网(IoT)、工业监控、实时分析等场景。 + +## 功能特性 + +- ✅ 支持 InfluxDB 2.x HTTP API +- ✅ 使用 Flux 查询语言进行数据查询 +- ✅ 使用 Line Protocol 格式进行数据写入 +- ✅ 支持 Token 认证 +- ✅ 支持 Organization 和 Bucket 管理 +- ✅ 批量数据写入 +- ✅ 自动处理 CSV 格式查询结果 +- ✅ 兼容 .NET Framework 4.5 到 .NET 10 + +## 快速开始 + +### 1. 安装 + +```bash +dotnet add package NewLife.XCode +``` + +### 2. 连接字符串配置 + +```csharp +// 方式一:在配置文件中配置 +{ + "ConnectionStrings": { + "InfluxDB": "Server=http://localhost:8086;Token=your-token;Organization=your-org;Bucket=your-bucket" + } +} + +// 方式二:代码中配置 +DAL.AddConnStr("InfluxDB", "Server=http://localhost:8086;Token=your-token;Organization=your-org;Bucket=your-bucket", null, "InfluxDB"); +``` + +#### 连接字符串参数说明 + +| 参数 | 必填 | 说明 | 示例 | +|------|------|------|------| +| Server | 是 | InfluxDB 服务器地址(包含协议) | `http://localhost:8086` 或 `https://influx.example.com` | +| Token | 是 | API Token(在 InfluxDB UI 中生成) | `your-influxdb-token` | +| Organization | 是 | 组织名称或ID | `my-org` | +| Bucket | 是 | Bucket 名称(相当于数据库) | `my-bucket` | + +### 3. 基本使用 + +#### 3.1 写入数据(Line Protocol) + +```csharp +var dal = DAL.Create("InfluxDB"); + +// 单条数据写入 +// 格式:measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp +var lineProtocol = $"temperature,location=room1,sensor=sensor1 value=23.5,humidity=45 {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1000000}"; +dal.Execute(lineProtocol); + +// 批量数据写入 +var lines = new[] +{ + "temperature,location=room1 value=21.0", + "temperature,location=room2 value=22.5", + "temperature,location=room3 value=23.0", +}; +var batchData = String.Join("\n", lines); +dal.Execute(batchData); +``` + +#### 3.2 查询数据(Flux) + +```csharp +var dal = DAL.Create("InfluxDB"); + +// Flux 查询 +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) + |> filter(fn: (r) => r._measurement == ""temperature"") + |> filter(fn: (r) => r.location == ""room1"") + |> limit(n: 100) +"; + +var dt = dal.Query(flux); +foreach (var row in dt) +{ + var time = row["_time"]; + var value = row["_value"]; + var location = row["location"]; + Console.WriteLine($"Time: {time}, Value: {value}, Location: {location}"); +} +``` + +#### 3.3 获取 Measurements(表) + +```csharp +var dal = DAL.Create("InfluxDB"); + +// 获取所有 measurement +var tables = dal.Tables; +foreach (var table in tables) +{ + Console.WriteLine($"Measurement: {table.TableName}"); +} +``` + +## Line Protocol 格式说明 + +Line Protocol 是 InfluxDB 的写入格式,语法如下: + +``` +measurement,tag_key1=tag_value1,tag_key2=tag_value2 field_key1=field_value1,field_key2=field_value2 timestamp +``` + +### 组成部分 + +1. **measurement**:测量名称(类似表名),必填 +2. **tags**:标签(索引字段),可选,多个用逗号分隔 +3. **fields**:字段值,必填,多个用逗号分隔 +4. **timestamp**:时间戳(纳秒),可选,不指定则使用当前时间 + +### 示例 + +```csharp +// 基本示例 +"temperature value=23.5" + +// 带标签 +"temperature,location=room1,sensor=sensor1 value=23.5" + +// 多字段 +"temperature,location=room1 value=23.5,humidity=45" + +// 指定时间戳(纳秒) +var nanos = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1000000; +$"temperature,location=room1 value=23.5 {nanos}" +``` + +## Flux 查询语言 + +Flux 是 InfluxDB 的查询语言,功能强大且灵活。 + +### 基本查询示例 + +```csharp +// 1. 查询最近1小时的数据 +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) +"; + +// 2. 按 measurement 过滤 +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) + |> filter(fn: (r) => r._measurement == ""temperature"") +"; + +// 3. 按 tag 过滤 +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) + |> filter(fn: (r) => r._measurement == ""temperature"") + |> filter(fn: (r) => r.location == ""room1"") +"; + +// 4. 聚合查询(平均值) +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) + |> filter(fn: (r) => r._measurement == ""temperature"") + |> aggregateWindow(every: 5m, fn: mean) +"; + +// 5. 限制返回数量 +var flux = @" +from(bucket: ""my-bucket"") + |> range(start: -1h) + |> limit(n: 100) +"; +``` + +## 使用场景 + +### 1. 物联网数据采集 + +```csharp +// 传感器数据写入 +var sensorId = "sensor_001"; +var location = "warehouse_A"; +var temperature = 25.3; +var humidity = 60.5; + +var lineProtocol = $"sensor_data,sensor_id={sensorId},location={location} temperature={temperature},humidity={humidity}"; +dal.Execute(lineProtocol); +``` + +### 2. 系统监控指标 + +```csharp +// 服务器性能指标 +var hostname = Environment.MachineName; +var cpuUsage = GetCpuUsage(); +var memoryUsage = GetMemoryUsage(); + +var lineProtocol = $"system_metrics,host={hostname} cpu_usage={cpuUsage},memory_usage={memoryUsage}"; +dal.Execute(lineProtocol); +``` + +### 3. 日志与事件追踪 + +```csharp +// 应用程序日志 +var appName = "MyApp"; +var level = "INFO"; +var message = "User logged in"; + +var lineProtocol = $"app_logs,app={appName},level={level} message=\"{message}\""; +dal.Execute(lineProtocol); +``` + +## 注意事项 + +1. **时间戳精度**:InfluxDB 使用纳秒级时间戳,需要将毫秒 × 1000000 +2. **字符串字段**:在 Line Protocol 中,字符串字段值需要用双引号包裹 +3. **特殊字符**:measurement、tag key/value、field key 中的空格、逗号、等号需要转义 +4. **Bucket 权限**:确保 Token 有对应 Bucket 的读写权限 +5. **查询性能**:合理使用 tag 进行索引,避免在 field 上频繁过滤 +6. **批量写入**:多条数据用换行符分隔,提高写入效率 + +## 与 XCode 实体的集成 + +InfluxDB 主要用于时序数据存储,与 XCode 的关系型实体模型有所不同。建议: + +- **时序数据**:使用 InfluxDB 的原生 Line Protocol 和 Flux 查询 +- **元数据**:使用 XCode 的其他数据库(如 MySQL、PostgreSQL)存储设备信息、用户信息等 +- **混合场景**:在一个应用中同时使用多个 DAL 连接,InfluxDB 负责时序数据,关系型数据库负责业务数据 + +## 常见问题 + +### Q1: 如何生成 InfluxDB Token? + +A: 在 InfluxDB UI 中: +1. 进入 `Load Data` > `API Tokens` +2. 点击 `Generate API Token` +3. 选择权限范围(读/写特定 Bucket) +4. 复制生成的 Token + +### Q2: 支持 InfluxDB 1.x 吗? + +A: 当前实现主要针对 InfluxDB 2.x。InfluxDB 1.x 使用不同的认证和查询方式(InfluxQL),暂不支持。 + +### Q3: 如何优化查询性能? + +A: +- 合理使用 tag 作为索引 +- 查询时指定明确的时间范围 +- 避免使用 `group()` 等高开销操作 +- 使用 `limit()` 限制返回数量 + +### Q4: 数据保留策略如何设置? + +A: 数据保留策略在 Bucket 级别设置,通过 InfluxDB UI 或 API 管理,XCode 不直接管理保留策略。 + +## 参考资源 + +- [InfluxDB 官方文档](https://docs.influxdata.com/influxdb/v2/) +- [Flux 查询语言](https://docs.influxdata.com/flux/v0/) +- [Line Protocol 参考](https://docs.influxdata.com/influxdb/v2/reference/syntax/line-protocol/) +- [XCode 文档](https://newlifex.com/xcode) + +## 开源协议 + +本实现遵循 NewLife 系列组件的开源协议,详见项目根目录 LICENSE 文件。 diff --git a/Readme.MD b/Readme.MD index c905d2a44..8c1c99ca8 100644 --- a/Readme.MD +++ b/Readme.MD @@ -6,7 +6,7 @@ ![Nuget](https://img.shields.io/nuget/v/newlife.xcode?logo=nuget) ![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/newlife.xcode?label=dev%20nuget&logo=nuget) -高性能 .NET 数据中间件,聚焦“添删改查 + 极致性能 + 海量数据”,内置多级缓存、自动建模/迁移、分表分库、强类型查询、跨库迁移,支持 MySQL / SQLite / SqlServer / Oracle / PostgreSQL / TDengine / 达梦 / 金仓 / 瀚高 / DB2 等。单表生产实践达百亿级,查询吞吐可达十亿级 QPS(配合缓存策略)。 +高性能 .NET 数据中间件,聚焦“添删改查 + 极致性能 + 海量数据”,内置多级缓存、自动建模/迁移、分表分库、强类型查询、跨库迁移,支持 MySQL / SQLite / SqlServer / Oracle / PostgreSQL / TDengine / InfluxDB / 达梦 / 金仓 / 瀚高 / DB2 等。单表生产实践达百亿级,查询吞吐可达十亿级 QPS(配合缓存策略)。 文档:https://newlifex.com/xcode 社区交流QQ群:1600800 / 1600838 diff --git a/XCode.DB2/.github/copilot-instructions.md b/XCode.DB2/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCode.DB2/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XCode.DaMeng/.github/copilot-instructions.md b/XCode.DaMeng/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCode.DaMeng/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XCode.HighGo/.github/copilot-instructions.md b/XCode.HighGo/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCode.HighGo/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XCode.KingBase/.github/copilot-instructions.md b/XCode.KingBase/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCode.KingBase/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XCode/.github/copilot-instructions.md b/XCode/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCode/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XCode/DataAccessLayer/Common/DatabaseType.cs b/XCode/DataAccessLayer/Common/DatabaseType.cs index 403c309b4..8d6116f3e 100644 --- a/XCode/DataAccessLayer/Common/DatabaseType.cs +++ b/XCode/DataAccessLayer/Common/DatabaseType.cs @@ -73,6 +73,10 @@ public enum DatabaseType [Description("VastBase数据库")] VastBase = 16, + /// InfluxDB时序数据库 + [Description("InfluxDB时序数据库")] + InfluxDB = 17, + ///// 网络虚拟数据库 //[Description("网络虚拟数据库")] //Network = 100, diff --git a/XCode/DataAccessLayer/Common/DbFactory.cs b/XCode/DataAccessLayer/Common/DbFactory.cs index c718ba07b..94cf6e65d 100644 --- a/XCode/DataAccessLayer/Common/DbFactory.cs +++ b/XCode/DataAccessLayer/Common/DbFactory.cs @@ -23,6 +23,7 @@ static DbFactory() Register(DatabaseType.HighGo); Register(DatabaseType.IRIS); Register(DatabaseType.VastBase); + Register(DatabaseType.InfluxDB); //Register(DatabaseType.Access); //Register(DatabaseType.SqlCe); //Register(DatabaseType.Network); diff --git a/XCode/DataAccessLayer/Database/InfluxDB.cs b/XCode/DataAccessLayer/Database/InfluxDB.cs new file mode 100644 index 000000000..2e2059e46 --- /dev/null +++ b/XCode/DataAccessLayer/Database/InfluxDB.cs @@ -0,0 +1,378 @@ +using System.Data; +using System.Data.Common; +using System.Text; +using NewLife.Collections; +using NewLife.Data; +using NewLife.Log; +using XCode.InfluxDB; + +namespace XCode.DataAccessLayer; + +class InfluxDB : RemoteDb +{ + #region 属性 + /// 返回数据库类型。 + public override DatabaseType Type => DatabaseType.InfluxDB; + + /// 创建工厂 + /// + protected override DbProviderFactory CreateFactory() => InfluxDBFactory.Instance; + + const String Server_Key = "Server"; + protected override void OnSetConnectionString(ConnectionStringBuilder builder) + { + base.OnSetConnectionString(builder); + + // 确保 Server 地址以 http:// 或 https:// 开头 + var server = builder[Server_Key]; + if (!String.IsNullOrEmpty(server) && !server.StartsWithIgnoreCase("http://", "https://")) + { + builder[Server_Key] = $"http://{server}"; + } + } + #endregion + + #region 方法 + /// 创建数据库会话 + /// + protected override IDbSession OnCreateSession() => new InfluxDBSession(this); + + /// 创建元数据对象 + /// + protected override IMetaData OnCreateMetaData() => new InfluxDBMetaData(); + + public override Boolean Support(String providerName) + { + providerName = providerName.ToLower(); + if (providerName.EqualIgnoreCase("InfluxDB", "Influx")) return true; + + return false; + } + #endregion + + #region 数据库特性 + protected override String ReservedWordsStr => "AND,OR,NOT,FROM,WHERE,SELECT,DELETE,DROP,SHOW,MEASUREMENT,TAG,FIELD,TIME"; + + /// 格式化关键字 + /// 关键字 + /// + public override String FormatKeyWord(String keyWord) + { + if (keyWord.IsNullOrEmpty()) return keyWord; + if (keyWord.StartsWith("\"") && keyWord.EndsWith("\"")) return keyWord; + return $"\"{keyWord}\""; + } + + /// 格式化数据为SQL数据 + /// 字段 + /// 数值 + /// + public override String FormatValue(IDataColumn field, Object? value) + { + var code = System.Type.GetTypeCode(field.DataType); + if (code == TypeCode.String) + { + if (value == null) + return field.Nullable ? "null" : "\"\""; + + return "\"" + value.ToString()?.Replace("\"", "\\\"") + "\""; + } + else if (code == TypeCode.Boolean) + { + return value.ToBoolean() ? "true" : "false"; + } + + return base.FormatValue(field, value); + } + + /// 格式化时间为SQL字符串 + /// 字段 + /// 时间值 + /// + public override String FormatDateTime(IDataColumn column, DateTime dateTime) + { + // InfluxDB 使用纳秒时间戳 + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var nanos = (dateTime.ToUniversalTime() - epoch).Ticks * 100; + return nanos.ToString(); + } + + /// 长文本长度 + public override Int32 LongTextLength => 65535; + + internal protected override String ParamPrefix => "@"; + + /// 系统数据库名 + public override String SystemDatabaseName => "_internal"; + + /// 字符串相加 + /// + /// + /// + public override String StringConcat(String left, String right) => $"{left} + {right}"; + #endregion +} + +/// InfluxDB数据库会话 +internal class InfluxDBSession : RemoteDbSession +{ + #region 构造函数 + public InfluxDBSession(IDatabase db) : base(db) { } + #endregion + + #region 基本方法 查询/执行 + /// 执行插入语句并返回新增行的自动编号 + /// SQL语句 + /// 命令类型,默认SQL文本 + /// 命令参数 + /// 新增行的自动编号 + public override Int64 InsertAndGetIdentity(String sql, CommandType type = CommandType.Text, params IDataParameter[]? ps) + { + // InfluxDB 是时序数据库,通常使用时间戳作为主键,不支持自增ID + Execute(sql, type, ps); + return 0; + } + + public override Task InsertAndGetIdentityAsync(String sql, CommandType type = CommandType.Text, params IDataParameter[]? ps) + { + // InfluxDB 是时序数据库,通常使用时间戳作为主键,不支持自增ID + ExecuteAsync(sql, type, ps).Wait(); + return Task.FromResult(0L); + } + #endregion + + #region 批量操作 + /// 批量插入 + /// 数据表 + /// 要插入的字段 + /// 实体列表 + /// + public override Int32 Insert(IDataTable table, IDataColumn[] columns, IEnumerable list) + { + var sb = Pool.StringBuilder.Get(); + var db = (Database as DbBase)!; + + // InfluxDB 使用 Line Protocol 格式写入 + // 格式: measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp + foreach (var entity in list) + { + // measurement 名称(表名) + sb.Append(db.FormatName(table)); + + // tags(索引字段,通常是维度) + var tags = columns.Where(c => c.PrimaryKey || c.Master).ToArray(); + if (tags.Length > 0) + { + sb.Append(','); + sb.Append(tags.Join(",", c => + { + var value = entity[c.Name]; + return $"{db.FormatName(c)}={value}"; + })); + } + + // fields(数据字段) + var fields = columns.Where(c => !c.PrimaryKey && !c.Master).ToArray(); + if (fields.Length > 0) + { + sb.Append(' '); + sb.Append(fields.Join(",", c => + { + var value = entity[c.Name]; + var strValue = value?.ToString() ?? ""; + // 字符串字段需要加引号 + if (c.DataType == typeof(String)) + strValue = $"\"{strValue}\""; + return $"{db.FormatName(c)}={strValue}"; + })); + } + + // timestamp(纳秒级时间戳) + var timeCol = columns.FirstOrDefault(c => c.Name.EqualIgnoreCase("Time", "CreateTime", "UpdateTime")); + if (timeCol != null) + { + var time = entity[timeCol.Name]; + if (time is DateTime dt) + { + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var nanos = (dt.ToUniversalTime() - epoch).Ticks * 100; + sb.Append($" {nanos}"); + } + } + + sb.AppendLine(); + } + + var lineProtocol = sb.Return(true); + return Execute(lineProtocol); + } + + /// 批量插入或更新 + /// 数据表 + /// 要插入的字段 + /// 主键已存在时,要更新的字段 + /// 主键已存在时,要累加更新的字段 + /// 实体列表 + /// + public override Int32 Upsert(IDataTable table, IDataColumn[] columns, ICollection? updateColumns, ICollection? addColumns, IEnumerable list) + { + // InfluxDB 自动处理相同 measurement + tags + timestamp 的写入,新值会覆盖旧值 + return Insert(table, columns, list); + } + #endregion + + #region 架构 + public override DataTable GetSchema(DbConnection? conn, String collectionName, String?[]? restrictionValues) => new DataTable(); + #endregion +} + +/// InfluxDB元数据 +class InfluxDBMetaData : RemoteDbMetaData +{ + public InfluxDBMetaData() => Types = _DataTypes; + + #region 数据类型 + /// 数据类型映射 + private static readonly Dictionary _DataTypes = new() + { + { typeof(Byte), new String[] { "INTEGER" } }, + { typeof(Int16), new String[] { "INTEGER" } }, + { typeof(Int32), new String[] { "INTEGER" } }, + { typeof(Int64), new String[] { "INTEGER" } }, + { typeof(Single), new String[] { "FLOAT" } }, + { typeof(Double), new String[] { "FLOAT" } }, + { typeof(Decimal), new String[] { "FLOAT" } }, + { typeof(DateTime), new String[] { "TIMESTAMP" } }, + { typeof(String), new String[] { "STRING" } }, + { typeof(Boolean), new String[] { "BOOLEAN" } }, + }; + #endregion + + #region 架构 + protected override List OnGetTables(String[]? names) + { + var ss = Database.CreateSession(); + var list = new List(); + + var old = ss.ShowSQL; + ss.ShowSQL = false; + try + { + // InfluxDB Flux 查询获取所有 measurement + var flux = @" +import ""influxdata/influxdb/schema"" +schema.measurements(bucket: v.bucket) +"; + var dt = ss.Query(flux, null); + if (dt.Rows.Count == 0) return []; + + var hs = new HashSet(names ?? [], StringComparer.OrdinalIgnoreCase); + + // 所有表(measurement) + foreach (var dr in dt) + { + var name = dr["_value"] + ""; + if (name.IsNullOrEmpty() || hs.Count > 0 && !hs.Contains(name)) continue; + + var table = DAL.CreateTable(); + table.TableName = name; + table.DbType = Database.Type; + + // InfluxDB 的 schema 需要通过查询数据来推断 + // 这里简化处理,不详细查询字段信息 + #region 字段 + // 默认添加 time 字段 + var timeField = table.CreateColumn(); + timeField.ColumnName = "time"; + timeField.DataType = typeof(DateTime); + timeField.PrimaryKey = true; + table.Columns.Add(timeField); + #endregion + + // 修正关系数据 + table.Fix(); + + list.Add(table); + } + } + finally + { + ss.ShowSQL = old; + } + + return list; + } + + /// 快速取得所有表名 + /// + public override IList GetTableNames() + { + var list = new List(); + + var flux = @" +import ""influxdata/influxdb/schema"" +schema.measurements(bucket: v.bucket) +"; + var dt = base.Database.CreateSession().Query(flux, null); + if (dt.Rows.Count == 0) return list; + + foreach (var dr in dt) + { + var name = dr["_value"] + ""; + if (!name.IsNullOrEmpty()) list.Add(name); + } + + return list; + } + + public override String FieldClause(IDataColumn field, Boolean onlyDefine) + { + var sb = new StringBuilder(); + sb.AppendFormat("{0} ", FormatName(field)); + + String? typeName = null; + if (Database.Type == field.Table.DbType && !field.Identity) typeName = field.RawType; + if (String.IsNullOrEmpty(typeName)) typeName = GetFieldType(field); + + sb.Append(typeName); + return sb.ToString(); + } + #endregion + + #region 反向工程 + protected override Boolean DatabaseExist(String databaseName) + { + // InfluxDB 2.x 使用 bucket 概念 + var flux = @"buckets() |> filter(fn: (r) => r.name == """ + databaseName + @""")"; + var dt = Database.CreateSession().Query(flux, null); + return dt != null && dt.Rows != null && dt.Rows.Count > 0; + } + + public override String CreateDatabaseSQL(String dbname, String? file) + { + // InfluxDB 2.x 不支持通过 SQL/Flux 创建 bucket,需要使用 HTTP API + throw new NotSupportedException("InfluxDB does not support creating buckets via SQL. Use HTTP API or CLI."); + } + + public override String DropDatabaseSQL(String dbname) + { + throw new NotSupportedException("InfluxDB does not support dropping buckets via SQL. Use HTTP API or CLI."); + } + + public override String CreateTableSQL(IDataTable table) + { + // InfluxDB 不需要显式创建表(measurement),写入数据时自动创建 + return String.Empty; + } + + public override String AddTableDescriptionSQL(IDataTable table) => String.Empty; + + public override String AlterColumnSQL(IDataColumn field, IDataColumn? oldfield) + { + // InfluxDB 不支持修改字段 + throw new NotSupportedException("InfluxDB does not support altering columns."); + } + + public override String AddColumnDescriptionSQL(IDataColumn field) => String.Empty; + #endregion +} diff --git a/XCode/InfluxDB/InfluxDBCommand.cs b/XCode/InfluxDB/InfluxDBCommand.cs new file mode 100644 index 000000000..d7947ebd3 --- /dev/null +++ b/XCode/InfluxDB/InfluxDBCommand.cs @@ -0,0 +1,134 @@ +using System.Data; +using System.Data.Common; +using System.Net.Http; + +namespace XCode.InfluxDB; + +/// InfluxDB命令 +public class InfluxDBCommand : DbCommand +{ + #region 属性 + /// 命令文本 + public override String CommandText { get; set; } = String.Empty; + + /// 命令超时时间 + public override Int32 CommandTimeout { get; set; } = 30; + + /// 命令类型 + public override CommandType CommandType { get; set; } = CommandType.Text; + + /// 是否已设计时更新 + public override Boolean DesignTimeVisible { get; set; } + + /// 更新行为来源 + public override UpdateRowSource UpdatedRowSource { get; set; } + + /// 连接 + protected override DbConnection? DbConnection { get; set; } + + /// 参数集合 + protected override DbParameterCollection DbParameterCollection { get; } = new InfluxDBParameterCollection(); + + /// 事务 + protected override DbTransaction? DbTransaction { get; set; } + + /// 连接(强类型) + public new InfluxDBConnection? Connection + { + get => DbConnection as InfluxDBConnection; + set => DbConnection = value; + } + #endregion + + #region 方法 + /// 取消命令 + public override void Cancel() { } + + /// 执行非查询 + /// + public override Int32 ExecuteNonQuery() + { + var conn = Connection; + if (conn == null || conn.State != ConnectionState.Open) + throw new InvalidOperationException("Connection must be open."); + + // InfluxDB 写入操作使用 Line Protocol + var httpClient = conn.HttpClient; + if (httpClient == null) + throw new InvalidOperationException("HttpClient is not initialized."); + + var baseUrl = conn.DataSource.TrimEnd('/'); + var url = $"{baseUrl}/api/v2/write?org={conn.Organization}&bucket={conn.Bucket}&precision=ns"; + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Add("Authorization", $"Token {conn.Token}"); + request.Content = new StringContent(CommandText, System.Text.Encoding.UTF8, "text/plain"); + + var response = httpClient.SendAsync(request).Result; + if (!response.IsSuccessStatusCode) + { + var error = response.Content.ReadAsStringAsync().Result; + throw new Exception($"InfluxDB write failed: {error}"); + } + + return 1; // 假设写入成功 + } + + /// 执行查询 + /// + public override Object? ExecuteScalar() + { + using var reader = ExecuteReader(); + if (reader.Read()) + return reader.GetValue(0); + return null; + } + + /// 准备命令 + public override void Prepare() { } + + /// 创建参数 + /// + protected override DbParameter CreateDbParameter() => new InfluxDBParameter(); + + /// 执行读取器 + /// 行为 + /// + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + var conn = Connection; + if (conn == null || conn.State != ConnectionState.Open) + throw new InvalidOperationException("Connection must be open."); + + var httpClient = conn.HttpClient; + if (httpClient == null) + throw new InvalidOperationException("HttpClient is not initialized."); + + // InfluxDB 查询使用 Flux 语言 + var baseUrl = conn.DataSource.TrimEnd('/'); + var url = $"{baseUrl}/api/v2/query?org={conn.Organization}"; + var fluxQuery = CommandText; + + // 确保查询中包含 bucket 信息 + if (!fluxQuery.Contains("from(bucket:")) + { + fluxQuery = $"from(bucket:\"{conn.Bucket}\") |> {fluxQuery}"; + } + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Add("Authorization", $"Token {conn.Token}"); + request.Headers.Add("Accept", "application/csv"); + request.Content = new StringContent(fluxQuery, System.Text.Encoding.UTF8, "application/vnd.flux"); + + var response = httpClient.SendAsync(request).Result; + if (!response.IsSuccessStatusCode) + { + var error = response.Content.ReadAsStringAsync().Result; + throw new Exception($"InfluxDB query failed: {error}"); + } + + var csv = response.Content.ReadAsStringAsync().Result; + return new InfluxDBDataReader(csv); + } + #endregion +} diff --git a/XCode/InfluxDB/InfluxDBConnection.cs b/XCode/InfluxDB/InfluxDBConnection.cs new file mode 100644 index 000000000..6ce8350d4 --- /dev/null +++ b/XCode/InfluxDB/InfluxDBConnection.cs @@ -0,0 +1,155 @@ +using System.Data; +using System.Data.Common; +using System.Net.Http; +using System.Text; +using XCode.DataAccessLayer; + +namespace XCode.InfluxDB; + +/// InfluxDB连接 +public class InfluxDBConnection : DbConnection +{ + #region 属性 + private String _connectionString = String.Empty; + private ConnectionState _state = ConnectionState.Closed; + private static HttpClient? _sharedHttpClient; + private HttpClient? _httpClient; + private String _database = String.Empty; + private String _dataSource = String.Empty; + + /// 连接字符串 + public override String ConnectionString + { + get => _connectionString; + set => _connectionString = value; + } + + /// 数据库名 + public override String Database => _database; + + /// 数据源 + public override String DataSource => _dataSource; + + /// 服务器版本 + public override String ServerVersion => "InfluxDB 2.x"; + + /// 连接状态 + public override ConnectionState State => _state; + + /// InfluxDB Token + public String Token { get; private set; } = String.Empty; + + /// InfluxDB Organization + public String Organization { get; private set; } = String.Empty; + + /// InfluxDB Bucket(相当于数据库) + public String Bucket { get; private set; } = String.Empty; + + /// HTTP客户端 + internal HttpClient? HttpClient => _httpClient; + #endregion + + #region 构造函数 + /// 实例化 + public InfluxDBConnection() { } + + /// 实例化 + /// 连接字符串 + public InfluxDBConnection(String connectionString) + { + ConnectionString = connectionString; + } + #endregion + + #region 方法 + /// 打开连接 + public override void Open() + { + if (_state == ConnectionState.Open) return; + + // 解析连接字符串 + ParseConnectionString(); + + // 使用共享 HttpClient 以避免 socket 耗尽 + // 为每个连接创建独立的 headers + if (_sharedHttpClient == null) + { + _sharedHttpClient = new HttpClient + { + Timeout = TimeSpan.FromSeconds(30) + }; + } + + _httpClient = _sharedHttpClient; + _state = ConnectionState.Open; + } + + /// 关闭连接 + public override void Close() + { + if (_state == ConnectionState.Closed) return; + + // 不要释放共享的 HttpClient + _httpClient = null; + _state = ConnectionState.Closed; + } + + /// 开始事务 + /// 隔离级别 + /// + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + { + throw new NotSupportedException("InfluxDB does not support transactions."); + } + + /// 改变数据库 + /// 数据库名 + public override void ChangeDatabase(String databaseName) + { + Bucket = databaseName; + _database = databaseName; + } + + /// 创建命令 + /// + protected override DbCommand CreateDbCommand() + { + return new InfluxDBCommand { Connection = this }; + } + + /// 释放资源 + /// + protected override void Dispose(Boolean disposing) + { + if (disposing) + { + Close(); + } + base.Dispose(disposing); + } + + private void ParseConnectionString() + { + var builder = new ConnectionStringBuilder(ConnectionString); + + // Server=http://localhost:8086;Token=mytoken;Organization=myorg;Bucket=mybucket;Database=mybucket + _dataSource = builder["Server"] ?? "http://localhost:8086"; + Token = builder["Token"] ?? String.Empty; + Organization = builder["Organization"] ?? builder["Org"] ?? String.Empty; + Bucket = builder["Bucket"] ?? builder["Database"] ?? String.Empty; + _database = Bucket; + + if (String.IsNullOrEmpty(Token)) + throw new ArgumentException("Token is required in connection string."); + if (String.IsNullOrEmpty(Organization)) + throw new ArgumentException("Organization is required in connection string."); + if (String.IsNullOrEmpty(Bucket)) + throw new ArgumentException("Bucket is required in connection string."); + } + #endregion +} + +/// 连接字符串构建器 +public class InfluxDBConnectionStringBuilder : DbConnectionStringBuilder +{ +} diff --git a/XCode/InfluxDB/InfluxDBDataAdapter.cs b/XCode/InfluxDB/InfluxDBDataAdapter.cs new file mode 100644 index 000000000..1f8a0220e --- /dev/null +++ b/XCode/InfluxDB/InfluxDBDataAdapter.cs @@ -0,0 +1,20 @@ +using System.Data; +using System.Data.Common; + +namespace XCode.InfluxDB; + +/// InfluxDB数据适配器 +public class InfluxDBDataAdapter : DbDataAdapter +{ + /// 删除命令 + public new InfluxDBCommand? DeleteCommand { get; set; } + + /// 插入命令 + public new InfluxDBCommand? InsertCommand { get; set; } + + /// 选择命令 + public new InfluxDBCommand? SelectCommand { get; set; } + + /// 更新命令 + public new InfluxDBCommand? UpdateCommand { get; set; } +} diff --git a/XCode/InfluxDB/InfluxDBDataReader.cs b/XCode/InfluxDB/InfluxDBDataReader.cs new file mode 100644 index 000000000..12331ef15 --- /dev/null +++ b/XCode/InfluxDB/InfluxDBDataReader.cs @@ -0,0 +1,256 @@ +using System.Collections; +using System.Data; +using System.Data.Common; + +namespace XCode.InfluxDB; + +/// InfluxDB数据读取器 +public class InfluxDBDataReader : DbDataReader +{ + private readonly String _csv; + private readonly String[][] _rows; + private readonly String[] _headers; + private Int32 _currentRow = -1; + private Boolean _isClosed; + + /// 实例化 + /// CSV数据 + public InfluxDBDataReader(String csv) + { + _csv = csv; + var lines = csv.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + // InfluxDB 返回的 CSV 格式:第一行是注释(以#开头),第二行是列名,第三行开始是数据 + var dataLines = lines.Where(l => !l.StartsWith("#")).ToArray(); + if (dataLines.Length > 0) + { + _headers = dataLines[0].Split(','); + _rows = dataLines.Skip(1).Select(line => line.Split(',')).ToArray(); + } + else + { + _headers = []; + _rows = []; + } + } + + /// 字段数量 + public override Int32 FieldCount => _headers.Length; + + /// 是否有行 + public override Boolean HasRows => _rows.Length > 0; + + /// 是否已关闭 + public override Boolean IsClosed => _isClosed; + + /// 受影响行数 + public override Int32 RecordsAffected => -1; + + /// 深度 + public override Int32 Depth => 0; + + /// 索引器 + /// 索引 + /// + public override Object this[Int32 ordinal] => GetValue(ordinal); + + /// 索引器 + /// 列名 + /// + public override Object this[String name] => GetValue(GetOrdinal(name)); + + /// 读取下一行 + /// + public override Boolean Read() + { + if (_currentRow + 1 < _rows.Length) + { + _currentRow++; + return true; + } + return false; + } + + /// 关闭读取器 + public override void Close() => _isClosed = true; + + /// 获取列名 + /// 索引 + /// + public override String GetName(Int32 ordinal) => _headers[ordinal]; + + /// 获取列索引 + /// 列名 + /// + public override Int32 GetOrdinal(String name) + { + for (var i = 0; i < _headers.Length; i++) + { + if (_headers[i].Equals(name, StringComparison.OrdinalIgnoreCase)) + return i; + } + return -1; + } + + /// 获取值 + /// 索引 + /// + public override Object GetValue(Int32 ordinal) + { + if (_currentRow < 0 || _currentRow >= _rows.Length) + throw new InvalidOperationException("Invalid row position."); + + var value = _rows[_currentRow][ordinal]; + return String.IsNullOrEmpty(value) ? DBNull.Value : value; + } + + /// 获取所有值 + /// 值数组 + /// + public override Int32 GetValues(Object[] values) + { + var count = Math.Min(values.Length, FieldCount); + for (var i = 0; i < count; i++) + { + values[i] = GetValue(i); + } + return count; + } + + /// 是否为空值 + /// 索引 + /// + public override Boolean IsDBNull(Int32 ordinal) => GetValue(ordinal) == DBNull.Value; + + /// 获取字段类型 + /// 索引 + /// + public override Type GetFieldType(Int32 ordinal) => typeof(String); + + /// 获取数据类型名称 + /// 索引 + /// + public override String GetDataTypeName(Int32 ordinal) => "String"; + + /// 获取布尔值 + /// 索引 + /// + public override Boolean GetBoolean(Int32 ordinal) + { + var value = GetValue(ordinal); + if (value == DBNull.Value) return false; + return Boolean.TryParse(value.ToString(), out var result) ? result : false; + } + + /// 获取字节 + /// 索引 + /// + public override Byte GetByte(Int32 ordinal) + { + var value = GetValue(ordinal); + if (value == DBNull.Value) return 0; + return Byte.TryParse(value.ToString(), out var result) ? result : (Byte)0; + } + + /// 获取字节数组 + /// 索引 + /// 数据偏移 + /// 缓冲区 + /// 缓冲区偏移 + /// 长度 + /// + public override Int64 GetBytes(Int32 ordinal, Int64 dataOffset, Byte[]? buffer, Int32 bufferOffset, Int32 length) => 0; + + /// 获取字符 + /// 索引 + /// + public override Char GetChar(Int32 ordinal) + { + var value = GetValue(ordinal); + if (value == DBNull.Value) return '\0'; + return Char.TryParse(value.ToString(), out var result) ? result : '\0'; + } + + /// 获取字符数组 + /// 索引 + /// 数据偏移 + /// 缓冲区 + /// 缓冲区偏移 + /// 长度 + /// + public override Int64 GetChars(Int32 ordinal, Int64 dataOffset, Char[]? buffer, Int32 bufferOffset, Int32 length) => 0; + + /// 获取日期时间 + /// 索引 + /// + public override DateTime GetDateTime(Int32 ordinal) => DateTime.Parse(GetValue(ordinal).ToString()!); + + /// 获取十进制数 + /// 索引 + /// + public override Decimal GetDecimal(Int32 ordinal) => Decimal.Parse(GetValue(ordinal).ToString()!); + + /// 获取双精度浮点数 + /// 索引 + /// + public override Double GetDouble(Int32 ordinal) => Double.Parse(GetValue(ordinal).ToString()!); + + /// 获取单精度浮点数 + /// 索引 + /// + public override Single GetFloat(Int32 ordinal) => Single.Parse(GetValue(ordinal).ToString()!); + + /// 获取GUID + /// 索引 + /// + public override Guid GetGuid(Int32 ordinal) => Guid.Parse(GetValue(ordinal).ToString()!); + + /// 获取16位整数 + /// 索引 + /// + public override Int16 GetInt16(Int32 ordinal) => Int16.Parse(GetValue(ordinal).ToString()!); + + /// 获取32位整数 + /// 索引 + /// + public override Int32 GetInt32(Int32 ordinal) => Int32.Parse(GetValue(ordinal).ToString()!); + + /// 获取64位整数 + /// 索引 + /// + public override Int64 GetInt64(Int32 ordinal) => Int64.Parse(GetValue(ordinal).ToString()!); + + /// 获取字符串 + /// 索引 + /// + public override String GetString(Int32 ordinal) => GetValue(ordinal).ToString()!; + + /// 获取枚举器 + /// + public override IEnumerator GetEnumerator() => new DbEnumerator(this); + + /// 获取模式表 + /// + public override DataTable GetSchemaTable() + { + var table = new DataTable("SchemaTable"); + table.Columns.Add("ColumnName", typeof(String)); + table.Columns.Add("ColumnOrdinal", typeof(Int32)); + table.Columns.Add("DataType", typeof(Type)); + + for (var i = 0; i < _headers.Length; i++) + { + var row = table.NewRow(); + row["ColumnName"] = _headers[i]; + row["ColumnOrdinal"] = i; + row["DataType"] = typeof(String); + table.Rows.Add(row); + } + + return table; + } + + /// 下一个结果集 + /// + public override Boolean NextResult() => false; +} diff --git a/XCode/InfluxDB/InfluxDBFactory.cs b/XCode/InfluxDB/InfluxDBFactory.cs new file mode 100644 index 000000000..7b665db7b --- /dev/null +++ b/XCode/InfluxDB/InfluxDBFactory.cs @@ -0,0 +1,30 @@ +using System.Data.Common; + +namespace XCode.InfluxDB; + +/// InfluxDB数据库工厂 +public class InfluxDBFactory : DbProviderFactory +{ + /// 实例 + public static readonly InfluxDBFactory Instance = new(); + + /// 创建连接 + /// + public override DbConnection CreateConnection() => new InfluxDBConnection(); + + /// 创建命令 + /// + public override DbCommand CreateCommand() => new InfluxDBCommand(); + + /// 创建参数 + /// + public override DbParameter CreateParameter() => new InfluxDBParameter(); + + /// 创建数据适配器 + /// + public override DbDataAdapter CreateDataAdapter() => new InfluxDBDataAdapter(); + + /// 创建连接字符串生成器 + /// + public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new InfluxDBConnectionStringBuilder(); +} diff --git a/XCode/InfluxDB/InfluxDBParameter.cs b/XCode/InfluxDB/InfluxDBParameter.cs new file mode 100644 index 000000000..fb2644ee6 --- /dev/null +++ b/XCode/InfluxDB/InfluxDBParameter.cs @@ -0,0 +1,177 @@ +using System.Collections; +using System.Data; +using System.Data.Common; + +namespace XCode.InfluxDB; + +/// InfluxDB参数 +public class InfluxDBParameter : DbParameter +{ + /// 参数名称 + public override String ParameterName { get; set; } = String.Empty; + + /// 参数值 + public override Object? Value { get; set; } + + /// 数据库类型 + public override DbType DbType { get; set; } + + /// 参数方向 + public override ParameterDirection Direction { get; set; } + + /// 是否可空 + public override Boolean IsNullable { get; set; } + + /// 参数大小 + public override Int32 Size { get; set; } + + /// 源列 + public override String SourceColumn { get; set; } = String.Empty; + + /// 源列是否可空 + public override Boolean SourceColumnNullMapping { get; set; } + + /// 源版本 + public override DataRowVersion SourceVersion { get; set; } = DataRowVersion.Current; + + /// 重置数据库类型 + public override void ResetDbType() + { + DbType = DbType.String; + } +} + +/// InfluxDB参数集合 +public class InfluxDBParameterCollection : DbParameterCollection +{ + private readonly List _parameters = []; + + /// 参数数量 + public override Int32 Count => _parameters.Count; + + /// 同步根 + public override Object SyncRoot => ((ICollection)_parameters).SyncRoot; + + /// 固定大小 + public override Boolean IsFixedSize => false; + + /// 只读 + public override Boolean IsReadOnly => false; + + /// 同步 + public override Boolean IsSynchronized => false; + + /// 获取或设置参数 + /// 索引 + /// + public new InfluxDBParameter this[Int32 index] + { + get => _parameters[index]; + set => _parameters[index] = value; + } + + /// 获取或设置参数 + /// 参数名 + /// + public new InfluxDBParameter this[String parameterName] + { + get => _parameters[IndexOf(parameterName)]; + set => _parameters[IndexOf(parameterName)] = value; + } + + /// 添加参数 + /// 参数 + /// + public override Int32 Add(Object value) + { + _parameters.Add((InfluxDBParameter)value); + return _parameters.Count - 1; + } + + /// 添加参数范围 + /// 参数数组 + public override void AddRange(Array values) + { + foreach (InfluxDBParameter param in values) + { + _parameters.Add(param); + } + } + + /// 清空参数 + public override void Clear() => _parameters.Clear(); + + /// 是否包含参数 + /// 参数 + /// + public override Boolean Contains(Object value) => _parameters.Contains((InfluxDBParameter)value); + + /// 是否包含参数 + /// 参数名 + /// + public override Boolean Contains(String value) => IndexOf(value) != -1; + + /// 复制到数组 + /// 目标数组 + /// 起始索引 + public override void CopyTo(Array array, Int32 index) => ((ICollection)_parameters).CopyTo(array, index); + + /// 获取枚举器 + /// + public override IEnumerator GetEnumerator() => _parameters.GetEnumerator(); + + /// 获取参数 + /// 参数名 + /// + protected override DbParameter GetParameter(String parameterName) => _parameters[IndexOf(parameterName)]; + + /// 获取参数 + /// 索引 + /// + protected override DbParameter GetParameter(Int32 index) => _parameters[index]; + + /// 获取参数索引 + /// 参数名 + /// + public override Int32 IndexOf(String parameterName) + { + for (var i = 0; i < _parameters.Count; i++) + { + if (_parameters[i].ParameterName.Equals(parameterName, StringComparison.OrdinalIgnoreCase)) + return i; + } + return -1; + } + + /// 获取参数索引 + /// 参数 + /// + public override Int32 IndexOf(Object value) => _parameters.IndexOf((InfluxDBParameter)value); + + /// 插入参数 + /// 索引 + /// 参数 + public override void Insert(Int32 index, Object value) => _parameters.Insert(index, (InfluxDBParameter)value); + + /// 移除参数 + /// 参数 + public override void Remove(Object value) => _parameters.Remove((InfluxDBParameter)value); + + /// 移除参数 + /// 索引 + public override void RemoveAt(Int32 index) => _parameters.RemoveAt(index); + + /// 移除参数 + /// 参数名 + public override void RemoveAt(String parameterName) => RemoveAt(IndexOf(parameterName)); + + /// 设置参数 + /// 参数名 + /// 参数 + protected override void SetParameter(String parameterName, DbParameter value) => _parameters[IndexOf(parameterName)] = (InfluxDBParameter)value; + + /// 设置参数 + /// 索引 + /// 参数 + protected override void SetParameter(Int32 index, DbParameter value) => _parameters[index] = (InfluxDBParameter)value; +} diff --git a/XCodeTool/.github/copilot-instructions.md b/XCodeTool/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XCodeTool/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XUnitTest.XCode/.github/copilot-instructions.md b/XUnitTest.XCode/.github/copilot-instructions.md new file mode 100755 index 000000000..177c34dcf --- /dev/null +++ b/XUnitTest.XCode/.github/copilot-instructions.md @@ -0,0 +1,406 @@ +# NewLife Copilot 协作指令 + +本说明适用于新生命团队(NewLife)及其全部开源/衍生项目,规范 Copilot 及类似智能助手在 C#/.NET 项目中的协作行为。 + +> 目标:把"每次请求必须携带的通用规则"控制在可接受体积;组件/业务专项流程放在 `.github/instructions/`,按需读取。 + +--- + +## 1. 核心原则 + +| 原则 | 说明 | +|------|------| +| **提效** | 减少机械样板,聚焦业务/核心算法 | +| **一致** | 风格、结构、命名、API 行为稳定 | +| **可控** | 限制改动影响面,可审计,兼容友好 | +| **可靠** | 先检索再生成,不虚构,不破坏现有合约 | +| **主动** | 发现问题主动修复,不回避合理优化 | + +--- + +## 2. 适用范围 + +- 含 NewLife 组件或衍生的全部 C#/.NET 仓库 +- 不含纯前端/非 .NET/市场文案 +- 存在本文件 → 必须遵循 + +--- + +## 3. 组件专用指令索引(按需加载) + +以下专用指令**仅在相关任务时**才需要读取,避免每次请求都携带大段流程/示例。 + +### 3.1 XCode / Cube(数据库 & Web 快速开发) + +当任务涉及以下任一信号时,请**先搜索并检查当前仓库** `.github/instructions/xcode.instructions.md` **是否存在**,若存在则读取并遵循: + +- 需求包含:XCode/Cube/魔方/实体生成/模型 XML/数据类库/数据库 CRUD/Controller 生成/`xcodetool`/`xcode` 命令 +- 解决方案/项目中出现:`NewLife.XCode` 包引用 +- 存在:`Model.xml`、`*.xcode.xml`、`*.Data.csproj`(或项目名以 `.Data` 结尾) +- 代码出现命名空间/类型:`XCode.*`、`Entity`(XCode 实体基类)、XCode 相关特性/接口 +- **用户提到修改任意 `.xml` 文件**(如 `member.xml`、`area.xml` 等配置文件),应**主动搜索** `xcode.instructions.md` 判断是否需要引入 + +**主动检测策略**:当用户提及 XML 文件修改时,即使未明确提到 XCode 关键字,也应先用 `file_search` 搜索 `xcode.instructions.md`,若存在则读取,以确定该 XML 文件是否属于 XCode/Cube 体系。 + +未满足以上条件时,**不要**引入 XCode/Cube 初始化流程,避免干扰其它仓库的常规开发。 + +--- + +## 4. 工作流 + +``` +需求分类 → 检索 → 评估 → 设计 → 实施 → 验证 → 说明 +``` + +1. **需求分类**:功能/修复/性能/重构/文档 +2. **检索**:相关类型、目录、方法、已有扩展/工具(**优先复用**) +3. **评估**:是否公共 API?是否性能热点?**是否存在潜在问题?** +4. **设计**:列出改动点 + 兼容/降级策略 +5. **实施**: + - 完成用户请求的核心任务 + - **顺带修复**发现的明显缺陷(资源泄漏、空引用、逻辑错误) + - **顺带优化**可简化的重复代码 + - 保留原注释与结构,除非注释本身有误 +6. **验证**: + - 代码变更:必须编译通过;运行相关单元测试(未找到需说明) + - 仅文档变更(未修改任何代码文件):可跳过编译与单元测试 +7. **说明**:变更摘要/影响范围/风险点 + +### 4.1 主动优化原则 + +当用户请求分析或优化代码时,**应主动**: + +| 类型 | 行动 | +|------|------| +| **架构梳理** | 梳理代码架构并进行重构,让代码结构更清晰易懂 | +| **语法现代化** | 使用最新的 C# 语法来简化代码,提升可读性 | +| **缺陷修复** | 资源泄漏、空引用风险、并发问题、逻辑错误 → 直接修复,让代码更健壮 | +| **性能优化** | 无用分配、重复计算、可池化资源 → 通过缓存减少耗时的重复计算 | +| **代码简化** | 重复代码提取、冗余判断合并、现代语法替换 → 在不影响可读性前提下简化 | +| **注释完善** | 补充类、接口、属性、方法头部的注释,以及方法内部重要代码的注释 | +| **架构参考** | 参考网络上同类功能的优秀架构,给出架构调整建议 | + +**架构调整策略**: +- **改动较小**:直接调整,完成后说明变更内容 +- **改动较大**:先列出调整方案,询问用户意见,待确认后再修改 + +**不应过度保守**: +- ❌ 仅添加注释而忽略明显的代码问题 +- ❌ 发现资源泄漏却不修复 +- ❌ 看到重复代码却不提取 +- ❌ 用户要求优化时只做表面工作 + +**保持谨慎的场景**: +- 公共 API 签名变更 → 需说明兼容性影响 +- 性能关键路径 → 需有依据或说明推理 +- 大范围重构 → 需先与用户确认范围 + +### 4.2 防御性注释规则 + +在旧有代码中,经常可以看到**被注释掉的代码**,这些注释代码前面通常带有说明文字。 + +**这些是防御性注释**: +- 记录了过去曾经踩过的坑 +- 目的是告诉后来人不要按照注释代码去写,否则会有问题 +- **禁止删除此类防御性注释**,用于警示后人 + +**识别特征**: +```csharp +// 曾经尝试过 xxx 方案,但会导致 yyy 问题 +// var result = DoSomethingWrong(); + +// 不要使用 xxx,否则会造成 yyy +// await client.SendAsync(data); + +// 这里不能用 xxx,因为 yyy +// stream.Flush(); +``` + +**处理原则**: +- ✅ 保留这类带说明的注释代码 +- ✅ 可以补充更详细的说明,解释为什么不能这样做 +- ❌ 不要删除这类防御性注释 +- ❌ 不要尝试"恢复"这些被注释的代码 + +--- + +## 5. 编码规范 + +### 5.1 基础规范 + +| 项目 | 规范 | +|------|------| +| 语言版本 | `latest`,所有目标框架均使用最新 C# 语法 | +| 命名空间 | file-scoped namespace | +| 类型名 | **必须**使用 .NET 正式名 `String`/`Int32`/`Boolean` 等,避免 `string`/`int`/`bool` | +| 兼容性 | 代码需兼容 .NET 4.5+;**禁止**使用 `ArgumentNullException.ThrowIfNull`,改用 `if (value == null) throw new ArgumentNullException(nameof(value));` | +| 单文件 | 每文件一个主要公共类型;较大平台差异使用 `partial` | + +### 5.2 命名规范 + +| 成员类型 | 命名规则 | 示例 | +|---------|---------|------| +| 类型/公共成员 | PascalCase | `UserService`、`GetName()` | +| 参数/局部变量 | camelCase | `userName`、`count` | +| 私有字段(实例/静态) | `_camelCase` | `_cache`、`_instance` | +| 属性/方法(实例/静态) | PascalCase | `Name`、`Default`、`Create()` | +| 扩展方法类 | `xxxHelper` 或 `xxxExtensions` | `StringHelper`、`CollectionExtensions` | + +### 5.3 代码风格 + +```csharp +// ✅ 单行 if:单语句且整行不过长时同行 +if (value == null) return; +if (key == null) throw new ArgumentNullException(nameof(key)); + +// ✅ 单行 if:语句较长时另起一行 +if (value == null) + throw new ArgumentNullException(nameof(value), "Value cannot be null"); + +// ✅ 多分支单语句:不加花括号 +if (count > 0) + DoSomething(); +else + DoOther(); + +// ✅ 循环必须保留花括号(即使单语句) +foreach (var item in list) +{ + Process(item); +} +``` + +### 5.4 Region 组织结构 + +较长的类使用 `#region` 分段组织,顺序为:`属性` → `静态`(如有)→ `构造` → `方法` → `辅助`(如有)→ `日志`。 + +**日志 Region 规则**: +- 类代码中如果带有 `ILog Log { get; set; }` 和 `WriteLog` 方法 +- **必须放在类代码的最后** +- **必须用名为"日志"的 region 包裹** +- 不要放在"辅助" region 中,应单独作为"日志" region + +### 5.5 现代 C# 语法 + +优先使用最新语法(switch 表达式、模式匹配、目标类型 `new`、record 等),即使目标框架是 net45。 + +### 5.6 集合表达式 + +优先使用集合表达式 `[]` 初始化集合:`List Tags { get; set; } = [];` + +### 5.7 Null 条件运算符 + +优先使用 `?.` / `??` 简化空值检查:`span?.AppendTag("test");` `var name = user?.Profile?.Name ?? "";` + +--- + +## 6. 多目标框架 + +NewLife 支持 `net45` 到 `net10`,常用条件符号:`NETFRAMEWORK`、`NETSTANDARD2_0`、`NETCOREAPP`、`NET5_0_OR_GREATER`、`NET6_0_OR_GREATER`、`NET8_0_OR_GREATER`。 + +新增 API 时需评估各框架兼容性,必要时提供降级实现。 + +--- + +## 7. 文档注释 + +| 规则 | 说明 | +|------|------| +| `` | **必须同一行闭合**,简短描述方法用途 | +| `` | **必须为每个参数添加**,无论方法可见性如何 | +| `` | 有返回值时必须添加 | +| `` | 复杂方法可增加详细说明(可多行) | +| 覆盖范围 | `public`/`protected` 成员必须注释;`private`/`internal` 建议添加 | +| `[Obsolete]` | 必须包含迁移建议 | + +**正确示例**:`/// 获取名称` `/// 编号` + +**禁止**:`` 拆成多行;缺少 ``;有参数但无 param 标签。 + +--- + +## 8. 异步与性能 + +| 规范 | 说明 | +|------|------| +| 方法命名 | 异步方法后缀 `Async` | +| ConfigureAwait | 库内部默认 `ConfigureAwait(false)` | +| 高频路径 | 优先对象池/`ArrayPool`/`Span`,避免多余分配 | +| 反射/Linq | 仅用于非热点路径;热点使用手写循环/缓存 | +| 池化资源 | 明确获取/归还;异常分支不遗失归还 | + +**内置工具优先**:`Pool.StringBuilder`、`Runtime.TickCount64`、`ToInt()`/`ToBoolean()` 等扩展方法。 + +--- + +## 9. 日志与追踪 + +规则:若类包含 `ILog Log` 与 `WriteLog`,必须放在类末尾,并用名为"日志"的 `#region` 包裹;关键过程可使用 `Tracer?.NewSpan()` 埋点。 + +--- + +## 10. 错误处理 + +- **精准异常类型**:`ArgumentNullException`/`InvalidOperationException` 等 +- **参数校验**:空/越界/格式 +- **TryXxx 模式**:不用异常作常规分支 +- **类型转换**:优先使用 `ToInt()`/`ToBoolean()` 等扩展方法 +- **对外异常**:不暴露内部实现/路径 + +--- + +## 11. 测试规范 + +| 项目 | 规范 | +|------|------| +| 框架 | xUnit | +| 命名 | `{ClassName}Tests` | +| 描述 | `[DisplayName("中文描述意图")]` | +| IO | 使用临时目录;端口用 0/随机 | +| 覆盖 | 正常/边界/异常/并发(必要时) | + +### 测试执行策略 + +1. 优先检索 `{ClassName}` 引用,若落入测试项目则运行 +2. 未命中则查找 `{ClassName}Tests.cs` +3. **未发现相关测试需明确说明**,不自动创建测试项目 + +--- + +## 12. NuGet 发布规范 + +| 类型 | 命名规则 | 示例 | +|------|---------|------| +| 正式版 | `{主版本}.{子版本}.{年}.{月日}` | `11.9.2025.0701` | +| 测试版 | `{主版本}.{子版本}.{年}.{月日}-beta{时分}` | `11.9.2025.0701-beta0906` | + +- **正式版**:每月月初发布 +- **测试版**:提交代码到 GitHub 时自动发布 + +--- + +## 13. Markdown 文档规范 + +| 项目 | 规范 | +|------|------| +| 文件编码 | **必须** UTF-8,**禁止** GB2312/GBK/UTF-8 BOM | +| 默认存放 | 代码库根目录下的 `Doc` 目录 | +| 文件命名 | 优先**中文文件名**,简洁描述内容 | + +**注意**:已有文件**必须先读取**再增量修改,**禁止直接覆盖**。 + +--- + +## 14. Copilot 行为守则 + +### 必须 + +- 简体中文回复 +- 输出前检索现有实现,**禁止重复造轮子** +- 先列方案再实现 +- 标记不确定上下文为"需查看文件" +- **发现明显缺陷时主动修复**(资源泄漏、空引用、逻辑错误) +- **用户要求优化时深入分析**,不做表面工作 + +### 鼓励 + +- 提取重复代码为公共方法 +- 简化冗余的条件判断 +- 使用现代 C# 语法改进可读性 +- 补充缺失的资源释放逻辑 +- 修正错误或过时的注释 + +### 禁止 + +- 虚构 API/文件/类型 +- 伪造测试结果/性能数据 +- 擅自删除公共/受保护成员 +- 擅自删除已有代码注释(除非注释本身错误) +- **删除防御性注释**(带说明的注释代码,记录历史踩坑经验) +- 仅删除空白行制造"格式优化"提交 +- 删除循环体的花括号 +- 将 `` 拆成多行 +- 将 `String`/`Int32` 改为 `string`/`int` +- 新增外部依赖(除非说明理由并给出权衡) +- 在热点路径添加未缓存反射/复杂 Linq +- 输出敏感凭据/内部地址 +- **发现问题却视而不见** +- **用户要求优化时仅做注释/测试等表面工作** + +--- + +## 15. 变更说明模板 + +提交或答复需包含: + +```markdown +## 概述 +做了什么 / 为什么 + +## 影响 +- 公共 API:是/否 +- 性能影响:无/有(说明) + +## 兼容性 +降级策略 / 条件编译点 + +## 风险 +潜在回归 / 性能开销 + +## 后续 +是否补测试 / 文档 +``` + +--- + +## 16. 术语说明 + +| 术语 | 定义 | +|------|------| +| **热点路径** | 经性能分析或高频调用栈确认的关键执行段 | +| **基线** | 变更前的功能/性能参考数据 | +| **顺带修复** | 在完成主任务过程中,修复发现的相关问题 | +| **防御性注释** | 被注释掉的代码,前面带有说明,记录历史踩坑经验,用于警示后人 | + +--- + +## 17. 代码优化检查清单 + +当进行代码优化时,按以下清单逐项检查: + +### 架构与结构 +- [ ] 代码架构是否清晰?是否需要重构? +- [ ] 类的职责是否单一?是否需要拆分? +- [ ] 是否有重复代码可以提取为公共方法? +- [ ] Region 组织是否符合规范(属性→静态→构造→方法→辅助→日志)? + +### 语法现代化 +- [ ] 是否可以使用更简洁的 C# 语法?(switch 表达式、模式匹配等) +- [ ] 集合初始化是否使用了集合表达式 `[]`? +- [ ] 是否可以使用 null 条件运算符 `?.` 简化代码? + +### 健壮性 +- [ ] 是否存在空引用风险? +- [ ] 资源是否正确释放?(IDisposable、流、连接等) +- [ ] 异常处理是否完善? +- [ ] 并发场景是否线程安全? + +### 性能 +- [ ] 是否存在可以缓存的重复计算? +- [ ] 是否有不必要的对象分配? +- [ ] 热点路径是否避免了反射和复杂 Linq? +- [ ] 是否使用了对象池/ArrayPool 等池化技术? + +### 注释与文档 +- [ ] 类、接口是否有 `` 注释? +- [ ] 公共方法是否有完整的参数和返回值注释? +- [ ] 方法内重要逻辑是否有注释说明? +- [ ] 防御性注释是否保留? + +### 日志 +- [ ] `ILog Log` 和 `WriteLog` 是否放在类的最后? +- [ ] 是否用名为"日志"的 region 包裹? + +--- + +(完) diff --git a/XUnitTest.XCode/DataAccessLayer/InfluxDBTests.cs b/XUnitTest.XCode/DataAccessLayer/InfluxDBTests.cs new file mode 100644 index 000000000..e14efc940 --- /dev/null +++ b/XUnitTest.XCode/DataAccessLayer/InfluxDBTests.cs @@ -0,0 +1,194 @@ +using System; +using System.IO; +using NewLife; +using NewLife.Log; +using NewLife.UnitTest; +using XCode; +using XCode.DataAccessLayer; +using XCode.InfluxDB; +using Xunit; + +namespace XUnitTest.XCode.DataAccessLayer; + +[TestCaseOrderer("NewLife.UnitTest.PriorityOrderer", "NewLife.UnitTest")] +public class InfluxDBTests +{ + // InfluxDB 2.x 连接字符串格式: + // Server=http://localhost:8086;Token=your-token;Organization=your-org;Bucket=your-bucket + private static String _ConnStr = "Server=http://localhost:8086;Token=your-influxdb-token;Organization=your-org;Bucket=test"; + + public InfluxDBTests() + { + var f = "Config\\influxdb.config".GetFullPath(); + if (File.Exists(f)) + _ConnStr = File.ReadAllText(f); + else + File.WriteAllText(f.EnsureDirectory(true), _ConnStr); + } + + [TestOrder(0)] + [Fact(Skip = "跳过")] + public void InitTest() + { + var db = DbFactory.Create(DatabaseType.InfluxDB); + Assert.NotNull(db); + + var factory = db.Factory; + Assert.NotNull(factory); + + var conn = factory.CreateConnection(); + Assert.NotNull(conn); + + var cmd = factory.CreateCommand(); + Assert.NotNull(cmd); + + var adp = factory.CreateDataAdapter(); + Assert.NotNull(adp); + + var dp = factory.CreateParameter(); + Assert.NotNull(dp); + } + + [TestOrder(10)] + [Fact(Skip = "跳过")] + public void ConnectTest() + { + var db = DbFactory.Create(DatabaseType.InfluxDB); + var factory = db.Factory; + + var conn = factory.CreateConnection() as InfluxDBConnection; + Assert.NotNull(conn); + + conn.ConnectionString = _ConnStr; + conn.Open(); + + Assert.NotEmpty(conn.ServerVersion); + XTrace.WriteLine("ServerVersion={0}", conn.ServerVersion); + + conn.Close(); + } + + [TestOrder(20)] + [Fact(Skip = "跳过")] + public void DALTest() + { + DAL.AddConnStr("sysInfluxDB", _ConnStr, null, "InfluxDB"); + var dal = DAL.Create("sysInfluxDB"); + Assert.NotNull(dal); + Assert.Equal("sysInfluxDB", dal.ConnName); + Assert.Equal(DatabaseType.InfluxDB, dal.DbType); + + var db = dal.Db; + var connstr = db.ConnectionString; + + var ver = db.ServerVersion; + Assert.NotEmpty(ver); + } + + [TestOrder(30)] + [Fact(Skip = "跳过")] + public void WriteDataTest() + { + DAL.AddConnStr("sysInfluxDB", _ConnStr, null, "InfluxDB"); + var dal = DAL.Create("sysInfluxDB"); + + // InfluxDB Line Protocol 格式写入 + // measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp + var lineProtocol = "temperature,location=room1,sensor=sensor1 value=23.5,humidity=45 " + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1000000; + + var rs = dal.Execute(lineProtocol); + Assert.Equal(1, rs); + + XTrace.WriteLine("Write data success"); + } + + [TestOrder(40)] + [Fact(Skip = "跳过")] + public void QueryDataTest() + { + DAL.AddConnStr("sysInfluxDB", _ConnStr, null, "InfluxDB"); + var dal = DAL.Create("sysInfluxDB"); + + // Flux 查询语句 + var flux = @" +from(bucket: ""test"") + |> range(start: -1h) + |> filter(fn: (r) => r._measurement == ""temperature"") + |> limit(n: 10) +"; + + var dt = dal.Query(flux); + Assert.NotNull(dt); + Assert.True(dt.Rows.Count >= 0); + + XTrace.WriteLine("Query returned {0} rows", dt.Rows.Count); + } + + [TestOrder(50)] + [Fact(Skip = "跳过")] + public void GetTablesTest() + { + DAL.AddConnStr("sysInfluxDB", _ConnStr, null, "InfluxDB"); + var dal = DAL.Create("sysInfluxDB"); + + var tables = dal.Tables; + Assert.NotNull(tables); + XTrace.WriteLine("Found {0} measurements", tables.Count); + + foreach (var table in tables) + { + XTrace.WriteLine("Measurement: {0}", table.TableName); + } + } + + [TestOrder(60)] + [Fact(Skip = "跳过")] + public void SupportTest() + { + var db = DbFactory.Create(DatabaseType.InfluxDB); + Assert.NotNull(db); + + Assert.True(db.Support("InfluxDB")); + Assert.True(db.Support("Influx")); + Assert.False(db.Support("MySQL")); + } + + [TestOrder(70)] + [Fact(Skip = "跳过")] + public void ConnectionStringBuilderTest() + { + var conn = new InfluxDBConnection(); + conn.ConnectionString = "Server=http://localhost:8086;Token=mytoken;Organization=myorg;Bucket=mybucket"; + + conn.Open(); + + Assert.Equal("mytoken", conn.Token); + Assert.Equal("myorg", conn.Organization); + Assert.Equal("mybucket", conn.Bucket); + + conn.Close(); + } + + [TestOrder(80)] + [Fact(Skip = "跳过")] + public void BatchWriteTest() + { + DAL.AddConnStr("sysInfluxDB", _ConnStr, null, "InfluxDB"); + var dal = DAL.Create("sysInfluxDB"); + + // 批量写入多条数据 + var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1000000; + var lines = new[] + { + $"temperature,location=room1 value=21.0 {timestamp}", + $"temperature,location=room2 value=22.5 {timestamp + 1000000}", + $"temperature,location=room3 value=23.0 {timestamp + 2000000}", + }; + + var lineProtocol = String.Join("\n", lines); + var rs = dal.Execute(lineProtocol); + Assert.Equal(1, rs); + + XTrace.WriteLine("Batch write success"); + } +}