zhuge-workflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/index.js +801 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +61 -0
  4. package/templates/claude/CLAUDE-ccg.md +258 -0
  5. package/templates/claude/CLAUDE.md +106 -0
  6. package/templates/claude/commands/ccg/_context.md +152 -0
  7. package/templates/claude/commands/ccg/spec-impl.md +161 -0
  8. package/templates/claude/commands/ccg/spec-plan-trellis.md +239 -0
  9. package/templates/claude/commands/ccg/spec-plan.md +225 -0
  10. package/templates/claude/commands/ccg/spec-research.md +113 -0
  11. package/templates/claude/commands/ccg/spec-review.md +127 -0
  12. package/templates/claude/commands/developer/brainstorm.md +5 -0
  13. package/templates/claude/commands/developer/design-checklist.md +81 -0
  14. package/templates/claude/commands/developer/design-doc.md +188 -0
  15. package/templates/claude/commands/developer/requirement-doc.md +150 -0
  16. package/templates/claude/commands/developer/requirement-interrogate.md +71 -0
  17. package/templates/claude/commands/developer/status.md +55 -0
  18. package/templates/claude/rules/bash-style.md +46 -0
  19. package/templates/claude/rules/claude-code-defensive.md +99 -0
  20. package/templates/claude/rules/doc-sync.md +49 -0
  21. package/templates/claude/rules/ops-safety.md +32 -0
  22. package/templates/claude/skills/bash-style/SKILL.md +244 -0
  23. package/templates/claude/skills/brainstorming/SKILL.md +48 -0
  24. package/templates/claude/skills/dotnet-dev/SKILL.md +250 -0
  25. package/templates/claude/skills/dotnet-dev/references/dotnet-style.md +991 -0
  26. package/templates/claude/skills/mcp-builder/LICENSE.txt +202 -0
  27. package/templates/claude/skills/mcp-builder/SKILL.md +328 -0
  28. package/templates/claude/skills/mcp-builder/reference/evaluation.md +602 -0
  29. package/templates/claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  30. package/templates/claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  31. package/templates/claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  32. package/templates/claude/skills/mcp-builder/scripts/connections.py +151 -0
  33. package/templates/claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  34. package/templates/claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  35. package/templates/claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  36. package/templates/claude/skills/ops-safety/SKILL.md +130 -0
  37. package/templates/claude/skills/python-dev/SKILL.md +281 -0
  38. package/templates/claude/skills/skill-creator/LICENSE.txt +202 -0
  39. package/templates/claude/skills/skill-creator/SKILL.md +209 -0
  40. package/templates/claude/skills/skill-creator/scripts/init_skill.py +303 -0
  41. package/templates/claude/skills/skill-creator/scripts/package_skill.py +110 -0
  42. package/templates/claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  43. package/templates/claude/skills/sqlserver-executor/SKILL.md +201 -0
  44. package/templates/claude/skills/sqlserver-executor/assets/config-example.json +26 -0
  45. package/templates/claude/skills/sqlserver-executor/config.json +12 -0
  46. package/templates/claude/skills/sqlserver-executor/scripts/sql_executor.py +404 -0
  47. package/templates/claude/skills/ui-ux-pro-max/SKILL.md +228 -0
  48. package/templates/claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  49. package/templates/claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  50. package/templates/claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  51. package/templates/claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  52. package/templates/claude/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  53. package/templates/claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  54. package/templates/claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  55. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  56. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  57. package/templates/claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  58. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  59. package/templates/claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  60. package/templates/claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  61. package/templates/claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  62. package/templates/claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  63. package/templates/claude/skills/ui-ux-pro-max/data/styles.csv +59 -0
  64. package/templates/claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  65. package/templates/claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  66. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  67. package/templates/claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  68. package/templates/claude/skills/ui-ux-pro-max/scripts/core.py +238 -0
  69. package/templates/claude/skills/ui-ux-pro-max/scripts/search.py +61 -0
  70. package/templates/claude/skills/webapp-testing/LICENSE.txt +202 -0
  71. package/templates/claude/skills/webapp-testing/SKILL.md +96 -0
  72. package/templates/claude/skills/webapp-testing/examples/console_logging.py +35 -0
  73. package/templates/claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  74. package/templates/claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  75. package/templates/claude/skills/webapp-testing/scripts/with_server.py +106 -0
  76. package/templates/init/claude-agents/ccg-impl.md +199 -0
  77. package/templates/init/claude-agents/ccg-review.md +146 -0
  78. package/templates/init/claude-agents/dispatch.md +253 -0
  79. package/templates/init/claude-hooks/inject-subagent-context.py +964 -0
  80. package/templates/init/trellis-scripts/task.sh +1326 -0
@@ -0,0 +1,991 @@
1
+ # .NET Core 开发规范
2
+
3
+ 作者:wwj
4
+ 版本:v1.0
5
+ 日期:2026-01-13
6
+ 状态:草稿
7
+
8
+ > **部署位置**: `~/.claude/skills/dotnet-dev/`
9
+ > **作用范围**: 所有 .NET Core 项目
10
+ > **参考来源**: Microsoft C# 编码约定、Furion 官方文档、SqlSugar 官方文档
11
+
12
+ ---
13
+ paths:
14
+ - "**/*.cs"
15
+ - "**/*.csproj"
16
+ - "**/*.sln"
17
+ - "**/appsettings.json"
18
+ ---
19
+
20
+ ## 工具链
21
+
22
+ <!-- [注释] 可根据项目调整 -->
23
+
24
+ - 格式化: dotnet format / IDE 内置格式化
25
+ - 静态检查: Roslyn Analyzers、StyleCop.Analyzers
26
+ - 构建工具: dotnet CLI / MSBuild
27
+ - 测试: xUnit + Moq
28
+
29
+ ```bash
30
+ # dotnet CLI 常用命令
31
+ dotnet build # 编译
32
+ dotnet test # 运行测试
33
+ dotnet run # 运行项目
34
+ dotnet publish -c Release # 发布
35
+ dotnet format # 格式化代码
36
+
37
+ # 带分析器的构建
38
+ dotnet build /p:TreatWarningsAsErrors=true
39
+ dotnet build /p:EnforceCodeStyleInBuild=true
40
+ ```
41
+
42
+ ## 命名约定
43
+
44
+ <!-- [注释] 遵循 Microsoft 官方规范 -->
45
+
46
+ ### 命名空间
47
+ - PascalCase,公司.产品.模块: `MyCompany.Project.Services`
48
+ - 避免与类名冲突
49
+
50
+ ```csharp
51
+ // ✅ 好
52
+ namespace MyCompany.ECommerce.Services;
53
+ namespace MyCompany.ECommerce.Models;
54
+
55
+ // ❌ 差
56
+ namespace mycompany.ecommerce; // 应使用 PascalCase
57
+ namespace My_Company.E_Commerce; // 不要用下划线
58
+ ```
59
+
60
+ ### 类命名
61
+ - PascalCase: `UserService`、`HttpClient`
62
+ - 类名应是名词或名词短语
63
+ - 接口以 I 前缀: `IUserRepository`、`IDisposable`
64
+
65
+ ```csharp
66
+ // ✅ 好
67
+ public class UserService { }
68
+ public class HttpRequestHandler { }
69
+ public interface IUserRepository { }
70
+
71
+ // ❌ 差
72
+ public class userService { } // 应大写开头
73
+ public class Do_Something { } // 不要用下划线
74
+ public interface UserRepository { } // 接口应以 I 开头
75
+ ```
76
+
77
+ ### 方法命名
78
+ - PascalCase: `GetUserById`、`IsValid`
79
+ - 动词或动词短语开头
80
+ - 异步方法以 Async 后缀: `GetUserByIdAsync`
81
+
82
+ ```csharp
83
+ // ✅ 好
84
+ public User FindById(long id) { }
85
+ public async Task<User> FindByIdAsync(long id) { }
86
+ public bool IsActive() { }
87
+ public bool HasPermission(string role) { }
88
+
89
+ // ❌ 差
90
+ public User findById(long id) { } // 应 PascalCase
91
+ public async Task<User> FindById() { } // 异步应加 Async 后缀
92
+ ```
93
+
94
+ ### 字段与属性命名
95
+ - 公共属性: PascalCase `UserId`
96
+ - 私有字段: _camelCase `_userId`
97
+ - 常量: PascalCase `MaxRetryCount`
98
+ - 静态只读: PascalCase `DefaultTimeout`
99
+
100
+ ```csharp
101
+ // ✅ 好
102
+ private readonly long _userId;
103
+ private readonly IUserRepository _userRepository;
104
+ public long UserId { get; set; }
105
+ public const int MaxRetryCount = 3;
106
+ public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
107
+
108
+ // ❌ 差
109
+ private long userId; // 私有字段应加下划线前缀
110
+ public long userId { get; } // 属性应 PascalCase
111
+ private const int MAX_RETRY; // 常量不用全大写
112
+ ```
113
+
114
+ ### 泛型类型参数
115
+ - 单个大写字母或描述性名称: `T`、`TEntity`、`TKey`、`TValue`
116
+
117
+ ```csharp
118
+ // ✅ 好
119
+ public class Repository<TEntity> where TEntity : class { }
120
+ public interface IDictionary<TKey, TValue> { }
121
+ public T Find<T>(int id) where T : class { }
122
+ ```
123
+
124
+ ## 代码组织
125
+
126
+ ### 类成员顺序
127
+
128
+ <!-- [注释] 建议顺序,可根据团队习惯调整 -->
129
+
130
+ ```csharp
131
+ public class Example
132
+ {
133
+ // 1. 常量
134
+ public const string DefaultName = "value";
135
+
136
+ // 2. 静态只读字段
137
+ private static readonly ILogger<Example> _logger;
138
+
139
+ // 3. 静态字段
140
+ private static int _instanceCount;
141
+
142
+ // 4. 实例只读字段
143
+ private readonly IUserRepository _userRepository;
144
+
145
+ // 5. 实例字段
146
+ private long _id;
147
+
148
+ // 6. 构造函数
149
+ public Example() { }
150
+ public Example(IUserRepository userRepository)
151
+ {
152
+ _userRepository = userRepository;
153
+ }
154
+
155
+ // 7. 属性
156
+ public long Id { get; set; }
157
+ public string Name { get; init; }
158
+
159
+ // 8. 公共方法
160
+ public void DoSomething() { }
161
+ public async Task DoSomethingAsync() { }
162
+
163
+ // 9. 私有方法
164
+ private void HelperMethod() { }
165
+ }
166
+ ```
167
+
168
+ ### using 规范
169
+ - System 命名空间优先
170
+ - 按字母顺序排列
171
+ - 使用 global using 减少重复
172
+
173
+ ```csharp
174
+ // ✅ 好 - 文件顶部
175
+ using System;
176
+ using System.Collections.Generic;
177
+ using System.Threading.Tasks;
178
+
179
+ using Microsoft.AspNetCore.Mvc;
180
+ using Microsoft.Extensions.Logging;
181
+
182
+ using MyCompany.Project.Models;
183
+ using MyCompany.Project.Services;
184
+
185
+ // ✅ 好 - GlobalUsings.cs
186
+ global using System;
187
+ global using System.Collections.Generic;
188
+ global using Microsoft.Extensions.Logging;
189
+ ```
190
+
191
+ ### 项目结构
192
+
193
+ <!-- [注释] 遵循 Clean Architecture 或分层架构 -->
194
+
195
+ ```
196
+ Project/
197
+ ├── src/
198
+ │ ├── Project.Api/ # Web API 层
199
+ │ │ ├── Controllers/
200
+ │ │ ├── Filters/
201
+ │ │ ├── Middleware/
202
+ │ │ └── Program.cs
203
+ │ ├── Project.Application/ # 应用层
204
+ │ │ ├── Services/
205
+ │ │ ├── DTOs/
206
+ │ │ └── Interfaces/
207
+ │ ├── Project.Domain/ # 领域层
208
+ │ │ ├── Entities/
209
+ │ │ ├── ValueObjects/
210
+ │ │ └── Interfaces/
211
+ │ └── Project.Infrastructure/ # 基础设施层
212
+ │ ├── Data/
213
+ │ │ ├── DbContext.cs
214
+ │ │ └── Repositories/
215
+ │ └── Services/
216
+ ├── tests/
217
+ │ ├── Project.UnitTests/
218
+ │ └── Project.IntegrationTests/
219
+ └── Project.sln
220
+ ```
221
+
222
+ ## 异常处理
223
+
224
+ <!-- [注释] 异常处理是 .NET 开发的重点 -->
225
+
226
+ ### 基本原则
227
+ - 优先使用内置异常类型
228
+ - 不要捕获 `Exception`(除非在最顶层)
229
+ - 不要忽略异常(空 catch 块)
230
+ - 异常信息要有意义
231
+
232
+ ```csharp
233
+ // ✅ 好:捕获具体异常,添加上下文
234
+ try
235
+ {
236
+ user = await _userRepository.FindByIdAsync(id);
237
+ }
238
+ catch (DbUpdateException ex)
239
+ {
240
+ throw new ServiceException($"Failed to find user: {id}", ex);
241
+ }
242
+
243
+ // ✅ 好:资源自动释放
244
+ await using var stream = File.OpenRead(filePath);
245
+ await using var connection = new SqlConnection(connectionString);
246
+
247
+ // ❌ 差:捕获过宽
248
+ try
249
+ {
250
+ DoSomething();
251
+ }
252
+ catch (Exception ex) // 太宽泛
253
+ {
254
+ Console.WriteLine(ex); // 不要用 Console.WriteLine
255
+ }
256
+
257
+ // ❌ 差:忽略异常
258
+ try
259
+ {
260
+ DoSomething();
261
+ }
262
+ catch (IOException)
263
+ {
264
+ // 空的 catch 块,异常被吞掉
265
+ }
266
+ ```
267
+
268
+ ### 自定义异常
269
+ - 业务异常继承 `Exception`
270
+ - 必须提供有意义的消息
271
+ - 实现序列化构造函数(如需跨进程传递)
272
+
273
+ ```csharp
274
+ public class BusinessException : Exception
275
+ {
276
+ public string ErrorCode { get; }
277
+
278
+ public BusinessException(string errorCode, string message)
279
+ : base(message)
280
+ {
281
+ ErrorCode = errorCode;
282
+ }
283
+
284
+ public BusinessException(string errorCode, string message, Exception innerException)
285
+ : base(message, innerException)
286
+ {
287
+ ErrorCode = errorCode;
288
+ }
289
+ }
290
+
291
+ // 使用
292
+ throw new BusinessException("USER_NOT_FOUND", $"User with id {id} not found");
293
+ ```
294
+
295
+ ## 空值处理
296
+
297
+ <!-- [注释] NRE 是最常见的错误,使用 Nullable Reference Types -->
298
+
299
+ ### 基本原则
300
+ - 启用 Nullable Reference Types (`<Nullable>enable</Nullable>`)
301
+ - 使用 `?` 标记可空类型
302
+ - 参数校验放在方法开头
303
+
304
+ ```csharp
305
+ // ✅ 好:启用 Nullable Reference Types
306
+ public User? FindById(long id)
307
+ {
308
+ return _context.Users.FirstOrDefault(u => u.Id == id);
309
+ }
310
+
311
+ // ✅ 好:参数校验
312
+ public void UpdateUser(User user)
313
+ {
314
+ ArgumentNullException.ThrowIfNull(user);
315
+ ArgumentNullException.ThrowIfNull(user.Id);
316
+ // ...
317
+ }
318
+
319
+ // ✅ 好:安全的空值处理
320
+ var name = user?.Name ?? "Unknown";
321
+ var length = user?.Name?.Length ?? 0;
322
+
323
+ // ✅ 好:模式匹配
324
+ if (user is { Name: var name, Age: > 18 })
325
+ {
326
+ Console.WriteLine($"Adult user: {name}");
327
+ }
328
+
329
+ // ❌ 差:返回 null 且未标记可空
330
+ public User FindById(long id) // 应该是 User?
331
+ {
332
+ return _context.Users.FirstOrDefault(u => u.Id == id);
333
+ }
334
+ ```
335
+
336
+ ## 注释规范
337
+
338
+ <!-- [注释] XML 文档注释是 .NET 文档的标准方式 -->
339
+
340
+ ### XML 文档注释
341
+ - 所有公共 API 应有文档注释
342
+ - 描述"做什么"而非"怎么做"
343
+
344
+ ```csharp
345
+ /// <summary>
346
+ /// Finds a user by their unique identifier.
347
+ /// </summary>
348
+ /// <param name="id">The user's unique identifier.</param>
349
+ /// <returns>The user if found; otherwise, null.</returns>
350
+ /// <exception cref="ArgumentException">Thrown when id is less than 1.</exception>
351
+ public async Task<User?> FindByIdAsync(long id)
352
+ {
353
+ if (id < 1)
354
+ throw new ArgumentException("Id must be positive", nameof(id));
355
+
356
+ return await _context.Users.FindAsync(id);
357
+ }
358
+ ```
359
+
360
+ ### 行内注释
361
+ - 解释"为什么"而非"是什么"
362
+ - 避免废话注释
363
+
364
+ ```csharp
365
+ // ✅ 好:解释原因
366
+ // 使用 lock 而非 ConcurrentDictionary,因为需要原子地检查并更新多个字段
367
+ lock (_syncLock)
368
+ {
369
+ // ...
370
+ }
371
+
372
+ // ❌ 差:废话注释
373
+ // 获取用户 ID
374
+ var userId = user.Id; // 代码已经很清楚了
375
+ ```
376
+
377
+ ## 异步编程
378
+
379
+ <!-- [注释] async/await 是 .NET 异步编程的核心 -->
380
+
381
+ ### 基本原则
382
+ - IO 操作使用 async/await
383
+ - 不要阻塞异步调用(`.Result`、`.Wait()`)
384
+ - 正确传递 CancellationToken
385
+
386
+ ```csharp
387
+ // ✅ 好:使用 async/await
388
+ public async Task<User?> GetUserAsync(long id, CancellationToken cancellationToken = default)
389
+ {
390
+ return await _context.Users
391
+ .FirstOrDefaultAsync(u => u.Id == id, cancellationToken);
392
+ }
393
+
394
+ // ✅ 好:并行执行
395
+ var tasks = userIds.Select(id => GetUserAsync(id));
396
+ var users = await Task.WhenAll(tasks);
397
+
398
+ // ✅ 好:带超时的操作
399
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
400
+ var result = await GetDataAsync(cts.Token);
401
+
402
+ // ❌ 差:阻塞异步调用(可能死锁)
403
+ var user = GetUserAsync(id).Result;
404
+ GetUserAsync(id).Wait();
405
+
406
+ // ❌ 差:async void(除了事件处理器)
407
+ public async void DoSomething() { } // 应该返回 Task
408
+ ```
409
+
410
+ ### 并发控制
411
+
412
+ ```csharp
413
+ // ✅ 使用 SemaphoreSlim 限制并发
414
+ private readonly SemaphoreSlim _semaphore = new(10);
415
+
416
+ public async Task ProcessAsync()
417
+ {
418
+ await _semaphore.WaitAsync();
419
+ try
420
+ {
421
+ await DoWorkAsync();
422
+ }
423
+ finally
424
+ {
425
+ _semaphore.Release();
426
+ }
427
+ }
428
+
429
+ // ✅ 使用 Channel 进行生产者-消费者
430
+ var channel = Channel.CreateBounded<WorkItem>(100);
431
+
432
+ // Producer
433
+ await channel.Writer.WriteAsync(item);
434
+
435
+ // Consumer
436
+ await foreach (var item in channel.Reader.ReadAllAsync())
437
+ {
438
+ await ProcessItemAsync(item);
439
+ }
440
+ ```
441
+
442
+ ## 测试规范
443
+
444
+ <!-- [注释] 使用 xUnit + Moq -->
445
+
446
+ ### 测试方法命名
447
+ - 描述测试场景和预期结果
448
+ - 格式: `MethodName_Scenario_ExpectedResult`
449
+
450
+ ```csharp
451
+ public class UserServiceTests
452
+ {
453
+ private readonly Mock<IUserRepository> _mockRepository;
454
+ private readonly UserService _sut; // System Under Test
455
+
456
+ public UserServiceTests()
457
+ {
458
+ _mockRepository = new Mock<IUserRepository>();
459
+ _sut = new UserService(_mockRepository.Object);
460
+ }
461
+
462
+ [Fact]
463
+ public async Task FindByIdAsync_WhenUserExists_ReturnsUser()
464
+ {
465
+ // Arrange
466
+ var expected = new User { Id = 1, Name = "test" };
467
+ _mockRepository
468
+ .Setup(r => r.FindByIdAsync(1))
469
+ .ReturnsAsync(expected);
470
+
471
+ // Act
472
+ var result = await _sut.FindByIdAsync(1);
473
+
474
+ // Assert
475
+ Assert.NotNull(result);
476
+ Assert.Equal("test", result.Name);
477
+ }
478
+
479
+ [Fact]
480
+ public async Task FindByIdAsync_WhenUserNotExists_ReturnsNull()
481
+ {
482
+ // Arrange
483
+ _mockRepository
484
+ .Setup(r => r.FindByIdAsync(It.IsAny<long>()))
485
+ .ReturnsAsync((User?)null);
486
+
487
+ // Act
488
+ var result = await _sut.FindByIdAsync(999);
489
+
490
+ // Assert
491
+ Assert.Null(result);
492
+ }
493
+
494
+ [Theory]
495
+ [InlineData(0)]
496
+ [InlineData(-1)]
497
+ public async Task FindByIdAsync_WhenIdInvalid_ThrowsArgumentException(long id)
498
+ {
499
+ // Act & Assert
500
+ await Assert.ThrowsAsync<ArgumentException>(
501
+ () => _sut.FindByIdAsync(id));
502
+ }
503
+ }
504
+ ```
505
+
506
+ ### 测试结构
507
+ - 使用 Arrange-Act-Assert 模式
508
+ - 每个测试只验证一个行为
509
+
510
+ ```csharp
511
+ [Fact]
512
+ public async Task CreateOrder_WithValidData_CreatesAndReturnsOrder()
513
+ {
514
+ // Arrange
515
+ var request = new CreateOrderRequest { /* ... */ };
516
+ _mockProductService
517
+ .Setup(s => s.CheckStockAsync(It.IsAny<long>()))
518
+ .ReturnsAsync(true);
519
+
520
+ // Act
521
+ var result = await _sut.CreateOrderAsync(request);
522
+
523
+ // Assert
524
+ Assert.NotNull(result);
525
+ Assert.Equal(OrderStatus.Created, result.Status);
526
+ _mockOrderRepository.Verify(r => r.AddAsync(It.IsAny<Order>()), Times.Once);
527
+ }
528
+ ```
529
+
530
+ ## 日志规范
531
+
532
+ <!-- [注释] 使用 Microsoft.Extensions.Logging 或 Serilog -->
533
+
534
+ ### 基本原则
535
+ - 使用结构化日志
536
+ - 使用消息模板,避免字符串拼接
537
+ - 选择合适的日志级别
538
+
539
+ ```csharp
540
+ // ✅ 好:结构化日志
541
+ private readonly ILogger<UserService> _logger;
542
+
543
+ _logger.LogDebug("Finding user by id: {UserId}", userId);
544
+ _logger.LogInformation("User {Username} logged in from {IpAddress}", username, ip);
545
+ _logger.LogWarning("Failed to send email to {Email}, will retry", email);
546
+ _logger.LogError(exception, "Failed to process order {OrderId}", orderId);
547
+
548
+ // ✅ 好:高性能日志(.NET 6+)
549
+ [LoggerMessage(Level = LogLevel.Information, Message = "User {UserId} logged in")]
550
+ partial void LogUserLogin(long userId);
551
+
552
+ // ❌ 差:字符串拼接(即使不输出也会执行拼接)
553
+ _logger.LogDebug("Finding user by id: " + userId);
554
+ _logger.LogDebug($"Finding user by id: {userId}");
555
+ ```
556
+
557
+ ### 日志级别
558
+ - `Critical`: 系统崩溃,需要立即处理
559
+ - `Error`: 操作失败,需要关注
560
+ - `Warning`: 警告,可能的问题
561
+ - `Information`: 重要业务事件
562
+ - `Debug`: 调试信息
563
+ - `Trace`: 详细追踪信息
564
+
565
+ ## Furion 框架规范
566
+
567
+ <!-- [注释] Furion 最佳实践 -->
568
+
569
+ ### 动态 API
570
+ - 使用 `IDynamicApiController` 自动生成 RESTful API
571
+ - 方法名自动映射为 HTTP 动词
572
+
573
+ ```csharp
574
+ // ✅ 好:动态 API(自动生成路由)
575
+ [DynamicApiController]
576
+ public class UserService : IDynamicApiController
577
+ {
578
+ private readonly ISqlSugarRepository<User> _repository;
579
+
580
+ public UserService(ISqlSugarRepository<User> repository)
581
+ {
582
+ _repository = repository;
583
+ }
584
+
585
+ // GET /api/user/{id}
586
+ public async Task<User> GetAsync(long id)
587
+ {
588
+ return await _repository.GetByIdAsync(id);
589
+ }
590
+
591
+ // GET /api/user/list
592
+ public async Task<List<User>> GetListAsync()
593
+ {
594
+ return await _repository.GetListAsync();
595
+ }
596
+
597
+ // POST /api/user
598
+ public async Task<long> AddAsync(CreateUserDto dto)
599
+ {
600
+ var user = dto.Adapt<User>();
601
+ return await _repository.InsertReturnIdentityAsync(user);
602
+ }
603
+
604
+ // PUT /api/user
605
+ public async Task UpdateAsync(UpdateUserDto dto)
606
+ {
607
+ var user = dto.Adapt<User>();
608
+ await _repository.UpdateAsync(user);
609
+ }
610
+
611
+ // DELETE /api/user/{id}
612
+ public async Task DeleteAsync(long id)
613
+ {
614
+ await _repository.DeleteByIdAsync(id);
615
+ }
616
+ }
617
+ ```
618
+
619
+ ### 依赖注入
620
+ - Furion 自动扫描并注册服务
621
+ - 使用接口约定:`ITransient`、`IScoped`、`ISingleton`
622
+
623
+ ```csharp
624
+ // ✅ 好:使用接口约定自动注册
625
+ public class UserService : IUserService, ITransient
626
+ {
627
+ private readonly ISqlSugarRepository<User> _repository;
628
+
629
+ public UserService(ISqlSugarRepository<User> repository)
630
+ {
631
+ _repository = repository;
632
+ }
633
+ }
634
+
635
+ // ✅ 好:手动注册(需要更多控制时)
636
+ services.AddScoped<IUserService, UserService>();
637
+ ```
638
+
639
+ ### 统一返回与异常处理
640
+
641
+ ```csharp
642
+ // ✅ 好:使用 Furion 统一返回格式
643
+ [DynamicApiController]
644
+ public class UserService : IDynamicApiController
645
+ {
646
+ // 自动包装为 { code: 200, data: {...}, message: "success" }
647
+ public async Task<User> GetAsync(long id)
648
+ {
649
+ var user = await _repository.GetByIdAsync(id);
650
+ return user ?? throw Oops.Oh("用户不存在");
651
+ }
652
+ }
653
+
654
+ // ✅ 好:友好异常
655
+ throw Oops.Oh(ErrorCodes.UserNotFound);
656
+ throw Oops.Bah("业务异常提示");
657
+
658
+ // ✅ 好:自定义错误码
659
+ [ErrorCodeType]
660
+ public enum ErrorCodes
661
+ {
662
+ [ErrorCodeItemMetadata("用户不存在")]
663
+ UserNotFound,
664
+
665
+ [ErrorCodeItemMetadata("用户名已存在")]
666
+ UserNameExists
667
+ }
668
+ ```
669
+
670
+ ### 数据验证
671
+
672
+ ```csharp
673
+ // ✅ 好:使用 DataAnnotations
674
+ public class CreateUserDto
675
+ {
676
+ [Required(ErrorMessage = "用户名不能为空")]
677
+ [MaxLength(50, ErrorMessage = "用户名最长50个字符")]
678
+ public string Name { get; set; }
679
+
680
+ [Required, EmailAddress]
681
+ public string Email { get; set; }
682
+
683
+ [Range(1, 150, ErrorMessage = "年龄范围1-150")]
684
+ public int Age { get; set; }
685
+ }
686
+
687
+ // ✅ 好:使用 FluentValidation
688
+ public class CreateUserDtoValidator : AbstractValidator<CreateUserDto>
689
+ {
690
+ public CreateUserDtoValidator()
691
+ {
692
+ RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
693
+ RuleFor(x => x.Email).NotEmpty().EmailAddress();
694
+ }
695
+ }
696
+ ```
697
+
698
+ ### 传统 Controller(需要更多控制时)
699
+
700
+ ```csharp
701
+ [ApiController]
702
+ [Route("api/[controller]")]
703
+ public class UsersController : ControllerBase
704
+ {
705
+ private readonly IUserService _userService;
706
+
707
+ public UsersController(IUserService userService)
708
+ {
709
+ _userService = userService;
710
+ }
711
+
712
+ [HttpGet("{id:long}")]
713
+ public async Task<ActionResult<UserDto>> GetById(long id)
714
+ {
715
+ var user = await _userService.GetAsync(id);
716
+ return user is null ? NotFound() : Ok(user);
717
+ }
718
+ }
719
+
720
+ ## SqlSugar ORM
721
+
722
+ <!-- [注释] SqlSugar 最佳实践 -->
723
+
724
+ ### 基础配置
725
+
726
+ ```csharp
727
+ // ✅ Program.cs 配置
728
+ builder.Services.AddSqlSugar(new ConnectionConfig
729
+ {
730
+ ConnectionString = builder.Configuration.GetConnectionString("Default"),
731
+ DbType = DbType.MySql,
732
+ IsAutoCloseConnection = true,
733
+ InitKeyType = InitKeyType.Attribute
734
+ });
735
+
736
+ // ✅ 实体定义
737
+ [SugarTable("user")]
738
+ public class User
739
+ {
740
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
741
+ public long Id { get; set; }
742
+
743
+ [SugarColumn(Length = 50)]
744
+ public string Name { get; set; }
745
+
746
+ [SugarColumn(IsNullable = true)]
747
+ public string? Email { get; set; }
748
+
749
+ [SugarColumn(IsIgnore = true)] // 忽略映射
750
+ public string FullName => $"{Name}";
751
+
752
+ // 导航属性
753
+ [Navigate(NavigateType.OneToMany, nameof(Order.UserId))]
754
+ public List<Order> Orders { get; set; }
755
+ }
756
+ ```
757
+
758
+ ### 仓储模式
759
+
760
+ ```csharp
761
+ // ✅ 使用内置仓储
762
+ public class UserService : ITransient
763
+ {
764
+ private readonly ISqlSugarRepository<User> _repository;
765
+
766
+ public UserService(ISqlSugarRepository<User> repository)
767
+ {
768
+ _repository = repository;
769
+ }
770
+
771
+ public async Task<User?> GetByIdAsync(long id)
772
+ {
773
+ return await _repository.GetByIdAsync(id);
774
+ }
775
+
776
+ public async Task<List<User>> GetListAsync(UserQueryDto query)
777
+ {
778
+ return await _repository.AsQueryable()
779
+ .WhereIF(!string.IsNullOrEmpty(query.Name), u => u.Name.Contains(query.Name))
780
+ .WhereIF(query.Status.HasValue, u => u.Status == query.Status)
781
+ .OrderByDescending(u => u.CreateTime)
782
+ .ToPageListAsync(query.PageIndex, query.PageSize);
783
+ }
784
+ }
785
+ ```
786
+
787
+ ### 查询优化
788
+
789
+ ```csharp
790
+ // ❌ N+1 查询问题
791
+ var users = await _db.Queryable<User>().ToListAsync();
792
+ foreach (var user in users)
793
+ {
794
+ var orders = await _db.Queryable<Order>().Where(o => o.UserId == user.Id).ToListAsync();
795
+ }
796
+
797
+ // ✅ 使用 Includes 导航查询
798
+ var users = await _db.Queryable<User>()
799
+ .Includes(u => u.Orders)
800
+ .ToListAsync();
801
+
802
+ // ✅ 多级导航
803
+ var users = await _db.Queryable<User>()
804
+ .Includes(u => u.Orders, o => o.OrderItems)
805
+ .ToListAsync();
806
+
807
+ // ✅ 只查询需要的字段
808
+ var userDtos = await _db.Queryable<User>()
809
+ .Select(u => new UserDto
810
+ {
811
+ Id = u.Id,
812
+ Name = u.Name,
813
+ OrderCount = SqlFunc.Subqueryable<Order>().Where(o => o.UserId == u.Id).Count()
814
+ })
815
+ .ToListAsync();
816
+
817
+ // ✅ 分页查询
818
+ var (list, total) = await _db.Queryable<User>()
819
+ .Where(u => u.Status == 1)
820
+ .OrderByDescending(u => u.CreateTime)
821
+ .ToPageListAsync(pageIndex, pageSize);
822
+ ```
823
+
824
+ ### 事务处理
825
+
826
+ ```csharp
827
+ // ✅ 使用 UnitOfWork
828
+ public class OrderService : ITransient
829
+ {
830
+ private readonly ISqlSugarRepository<Order> _orderRepo;
831
+ private readonly ISqlSugarRepository<OrderItem> _itemRepo;
832
+
833
+ public async Task CreateOrderAsync(CreateOrderDto dto)
834
+ {
835
+ try
836
+ {
837
+ _orderRepo.Ado.BeginTran();
838
+
839
+ var order = dto.Adapt<Order>();
840
+ var orderId = await _orderRepo.InsertReturnIdentityAsync(order);
841
+
842
+ var items = dto.Items.Select(i => new OrderItem
843
+ {
844
+ OrderId = orderId,
845
+ ProductId = i.ProductId,
846
+ Quantity = i.Quantity
847
+ }).ToList();
848
+
849
+ await _itemRepo.InsertRangeAsync(items);
850
+
851
+ _orderRepo.Ado.CommitTran();
852
+ }
853
+ catch
854
+ {
855
+ _orderRepo.Ado.RollbackTran();
856
+ throw;
857
+ }
858
+ }
859
+ }
860
+
861
+ // ✅ 使用 Furion 工作单元
862
+ [UnitOfWork]
863
+ public async Task CreateOrderAsync(CreateOrderDto dto)
864
+ {
865
+ // 自动开启事务,方法结束自动提交,异常自动回滚
866
+ var order = dto.Adapt<Order>();
867
+ await _orderRepo.InsertAsync(order);
868
+ await _itemRepo.InsertRangeAsync(dto.Items);
869
+ }
870
+ ```
871
+
872
+ ### 批量操作
873
+
874
+ ```csharp
875
+ // ✅ 批量插入
876
+ await _db.Insertable(users).ExecuteCommandAsync();
877
+
878
+ // ✅ 批量更新
879
+ await _db.Updateable(users).ExecuteCommandAsync();
880
+
881
+ // ✅ 批量删除
882
+ await _db.Deleteable<User>().In(ids).ExecuteCommandAsync();
883
+
884
+ // ✅ 条件更新(只更新指定字段)
885
+ await _db.Updateable<User>()
886
+ .SetColumns(u => u.Status == 0)
887
+ .Where(u => u.ExpireTime < DateTime.Now)
888
+ .ExecuteCommandAsync();
889
+ ```
890
+
891
+ ## 性能考虑
892
+
893
+ <!-- [注释] 先写正确的代码,再优化性能 -->
894
+
895
+ ### 核心原则
896
+
897
+ | 原则 | 说明 |
898
+ |------|------|
899
+ | **先正确后优化** | 先确保功能正确,再考虑性能 |
900
+ | **先测量后优化** | 用 BenchmarkDotNet / dotTrace 定位瓶颈 |
901
+ | **避免过早优化** | 可读性优先,除非有明确的性能需求 |
902
+
903
+ ### 避免常见陷阱
904
+
905
+ | 陷阱 | 解决方案 |
906
+ |------|---------|
907
+ | N+1 查询 | 使用 Includes 导航查询 |
908
+ | 循环中拼接字符串 | 使用 `StringBuilder` |
909
+ | 频繁分配临时对象 | 使用 `Span<T>`、`ArrayPool<T>` |
910
+ | 阻塞异步调用 | 使用 async/await |
911
+ | 未使用只读查询 | 只查询不修改时避免跟踪 |
912
+ | 加载过多数据 | 使用分页、投影 |
913
+
914
+ ### 字符串处理
915
+
916
+ ```csharp
917
+ // ❌ 差:循环拼接字符串
918
+ var result = "";
919
+ foreach (var s in strings)
920
+ {
921
+ result += s; // 每次创建新对象
922
+ }
923
+
924
+ // ✅ 好:使用 StringBuilder
925
+ var sb = new StringBuilder(estimatedSize);
926
+ foreach (var s in strings)
927
+ {
928
+ sb.Append(s);
929
+ }
930
+ var result = sb.ToString();
931
+
932
+ // ✅ 好:使用 string.Join
933
+ var result = string.Join(",", strings);
934
+
935
+ // ✅ 好:使用字符串插值(少量拼接)
936
+ var message = $"User {name} created at {DateTime.Now}";
937
+ ```
938
+
939
+ ### 集合与 LINQ 优化
940
+
941
+ ```csharp
942
+ // ✅ 预分配容量
943
+ var list = new List<User>(expectedSize);
944
+ var dict = new Dictionary<long, User>(expectedSize);
945
+
946
+ // ✅ 使用 HashSet 进行包含检查
947
+ var ids = userIds.ToHashSet();
948
+ var filtered = allUsers.Where(u => ids.Contains(u.Id));
949
+
950
+ // ❌ 差:多次枚举 IEnumerable
951
+ var query = GetUsers(); // IEnumerable
952
+ var count = query.Count(); // 第一次枚举
953
+ var first = query.First(); // 第二次枚举
954
+
955
+ // ✅ 好:先具体化
956
+ var users = GetUsers().ToList();
957
+ var count = users.Count;
958
+ var first = users[0];
959
+ ```
960
+
961
+ ### 性能分析工具
962
+
963
+ ```bash
964
+ # 使用 BenchmarkDotNet
965
+ dotnet add package BenchmarkDotNet
966
+
967
+ # 运行基准测试
968
+ dotnet run -c Release
969
+
970
+ # 使用 dotnet-counters 监控
971
+ dotnet counters monitor --process-id <pid>
972
+
973
+ # 使用 dotnet-trace 采集性能数据
974
+ dotnet trace collect --process-id <pid>
975
+ ```
976
+
977
+ ## 规则溯源要求
978
+
979
+ 当回复明确受到本规则约束时,在回复末尾声明:
980
+
981
+ ```
982
+ > 📋 本回复遵循规则:`dotnet-style.md` - [具体章节]
983
+ ```
984
+
985
+ ---
986
+
987
+ ## 参考资料
988
+
989
+ - [Microsoft C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
990
+ - [Furion 官方文档](https://furion.baiqian.ltd/)
991
+ - [SqlSugar 官方文档](https://www.donet5.com/Home/Doc)