docs(zh-CN): sync Chinese docs with latest upstream changes

This commit is contained in:
neo
2026-03-21 12:55:58 +08:00
parent 0af0fbf40b
commit e73c2ffa34
85 changed files with 11028 additions and 747 deletions

View File

@@ -4,7 +4,7 @@
位于 `~/.claude/agents/` 中:
| 智能体 | 用途 | 使用时机 |
| 代理 | 用途 | 使用时机 |
|-------|---------|-------------|
| planner | 实现规划 | 复杂功能、重构 |
| architect | 系统设计 | 架构决策 |
@@ -14,7 +14,8 @@
| build-error-resolver | 修复构建错误 | 构建失败时 |
| e2e-runner | 端到端测试 | 关键用户流程 |
| refactor-cleaner | 清理死代码 | 代码维护 |
| doc-updater | 文档 | 更新文档 |
| doc-updater | 文档 | 更新文档 |
| rust-reviewer | Rust 代码审查 | Rust 项目 |
## 即时智能体使用

View File

@@ -0,0 +1,45 @@
---
paths:
- "**/*.cpp"
- "**/*.hpp"
- "**/*.cc"
- "**/*.hh"
- "**/*.cxx"
- "**/*.h"
- "**/CMakeLists.txt"
---
# C++ 编码风格
> 本文档基于 [common/coding-style.md](../common/coding-style.md) 扩展了 C++ 特定内容。
## 现代 C++ (C++17/20/23)
* 优先使用**现代 C++ 特性**而非 C 风格结构
* 当类型可从上下文推断时,使用 `auto`
* 使用 `constexpr` 定义编译时常量
* 使用结构化绑定:`auto [key, value] = map_entry;`
## 资源管理
* **处处使用 RAII** — 避免手动 `new`/`delete`
* 使用 `std::unique_ptr` 表示独占所有权
* 仅在确实需要共享所有权时使用 `std::shared_ptr`
* 使用 `std::make_unique` / `std::make_shared` 替代原始 `new`
## 命名约定
* 类型/类:`PascalCase`
* 函数/方法:`snake_case``camelCase`(遵循项目约定)
* 常量:`kPascalCase``UPPER_SNAKE_CASE`
* 命名空间:`lowercase`
* 成员变量:`snake_case_`(尾随下划线)或 `m_` 前缀
## 格式化
* 使用 **clang-format** — 避免风格争论
* 提交前运行 `clang-format -i <file>`
## 参考
有关全面的 C++ 编码标准和指南,请参阅技能:`cpp-coding-standards`

View File

@@ -0,0 +1,40 @@
---
paths:
- "**/*.cpp"
- "**/*.hpp"
- "**/*.cc"
- "**/*.hh"
- "**/*.cxx"
- "**/*.h"
- "**/CMakeLists.txt"
---
# C++ 钩子
> 本文档基于 [common/hooks.md](../common/hooks.md) 扩展了 C++ 相关内容。
## 构建钩子
在提交 C++ 更改前运行以下检查:
```bash
# Format check
clang-format --dry-run --Werror src/*.cpp src/*.hpp
# Static analysis
clang-tidy src/*.cpp -- -std=c++17
# Build
cmake --build build
# Tests
ctest --test-dir build --output-on-failure
```
## 推荐的 CI 流水线
1. **clang-format** — 代码格式化检查
2. **clang-tidy** — 静态分析
3. **cppcheck** — 补充分析
4. **cmake build** — 编译
5. **ctest** — 使用清理器执行测试

View File

@@ -0,0 +1,52 @@
---
paths:
- "**/*.cpp"
- "**/*.hpp"
- "**/*.cc"
- "**/*.hh"
- "**/*.cxx"
- "**/*.h"
- "**/CMakeLists.txt"
---
# C++ 模式
> 本文档基于 [common/patterns.md](../common/patterns.md) 扩展了 C++ 特定内容。
## RAII资源获取即初始化
将资源生命周期与对象生命周期绑定:
```cpp
class FileHandle {
public:
explicit FileHandle(const std::string& path) : file_(std::fopen(path.c_str(), "r")) {}
~FileHandle() { if (file_) std::fclose(file_); }
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
private:
std::FILE* file_;
};
```
## 三五法则/零法则
* **零法则**:优先使用不需要自定义析构函数、拷贝/移动构造函数或赋值运算符的类。
* **五法则**:如果你定义了析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数或移动赋值运算符中的任何一个,那么就需要定义全部五个。
## 值语义
* 按值传递小型/平凡类型。
*`const&` 传递大型类型。
* 按值返回(依赖 RVO/NRVO
* 对于接收后即被消耗的参数,使用移动语义。
## 错误处理
* 使用异常处理异常情况。
* 对于可能不存在的值,使用 `std::optional`
* 对于预期的失败,使用 `std::expected`C++23或结果类型。
## 参考
有关全面的 C++ 模式和反模式,请参阅技能:`cpp-coding-standards`

View File

@@ -0,0 +1,52 @@
---
paths:
- "**/*.cpp"
- "**/*.hpp"
- "**/*.cc"
- "**/*.hh"
- "**/*.cxx"
- "**/*.h"
- "**/CMakeLists.txt"
---
# C++ 安全
> 本文档扩展了 [common/security.md](../common/security.md),增加了 C++ 特有的内容。
## 内存安全
* 绝不使用原始的 `new`/`delete` — 使用智能指针
* 绝不使用 C 风格数组 — 使用 `std::array``std::vector`
* 绝不使用 `malloc`/`free` — 使用 C++ 分配方式
* 除非绝对必要,避免使用 `reinterpret_cast`
## 缓冲区溢出
* 使用 `std::string` 而非 `char*`
* 当安全性重要时,使用 `.at()` 进行边界检查访问
* 绝不使用 `strcpy``strcat``sprintf` — 使用 `std::string``fmt::format`
## 未定义行为
* 始终初始化变量
* 避免有符号整数溢出
* 绝不解引用空指针或悬垂指针
* 在 CI 中使用消毒剂:
```bash
cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" ..
```
## 静态分析
* 使用 **clang-tidy** 进行自动化检查:
```bash
clang-tidy --checks='*' src/*.cpp
```
* 使用 **cppcheck** 进行额外分析:
```bash
cppcheck --enable=all src/
```
## 参考
查看技能:`cpp-coding-standards` 以获取详细的安全指南。

View File

@@ -0,0 +1,45 @@
---
paths:
- "**/*.cpp"
- "**/*.hpp"
- "**/*.cc"
- "**/*.hh"
- "**/*.cxx"
- "**/*.h"
- "**/CMakeLists.txt"
---
# C++ 测试
> 本文档扩展了 [common/testing.md](../common/testing.md) 中关于 C++ 的特定内容。
## 框架
使用 **GoogleTest** (gtest/gmock) 配合 **CMake/CTest**
## 运行测试
```bash
cmake --build build && ctest --test-dir build --output-on-failure
```
## 覆盖率
```bash
cmake -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" ..
cmake --build .
ctest --output-on-failure
lcov --capture --directory . --output-file coverage.info
```
## 内存消毒工具
在 CI 中应始终使用内存消毒工具运行测试:
```bash
cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" ..
```
## 参考
查看技能:`cpp-testing` 以获取详细的 C++ 测试模式、TDD 工作流以及 GoogleTest/GMock 使用指南。

View File

@@ -0,0 +1,73 @@
---
paths:
- "**/*.cs"
- "**/*.csx"
---
# C# 编码风格
> 本文档扩展了 [common/coding-style.md](../common/coding-style.md) 中关于 C# 的特定内容。
## 标准
* 遵循当前的 .NET 约定并启用可为空引用类型
* 在公共和内部 API 上优先使用显式访问修饰符
* 保持文件与其定义的主要类型对齐
## 类型与模型
* 对于不可变的值类型模型,优先使用 `record``record struct`
* 对于具有标识和生命周期的实体或类型,使用 `class`
* 对于服务边界和抽象,使用 `interface`
* 避免在应用程序代码中使用 `dynamic`;优先使用泛型或显式模型
```csharp
public sealed record UserDto(Guid Id, string Email);
public interface IUserRepository
{
Task<UserDto?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
}
```
## 不可变性
* 对于共享状态,优先使用 `init` 设置器、构造函数参数和不可变集合
* 在生成更新状态时,不要原地修改输入模型
```csharp
public sealed record UserProfile(string Name, string Email);
public static UserProfile Rename(UserProfile profile, string name) =>
profile with { Name = name };
```
## 异步与错误处理
* 优先使用 `async`/`await`,而非阻塞调用如 `.Result``.Wait()`
* 通过公共异步 API 传递 `CancellationToken`
* 抛出特定异常并使用结构化属性进行日志记录
```csharp
public async Task<Order> LoadOrderAsync(
Guid orderId,
CancellationToken cancellationToken)
{
try
{
return await repository.FindAsync(orderId, cancellationToken)
?? throw new InvalidOperationException($"Order {orderId} was not found.");
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to load order {OrderId}", orderId);
throw;
}
}
```
## 格式化
* 使用 `dotnet format` 进行格式化和分析器修复
* 保持 `using` 指令有序,并移除未使用的导入
* 仅当表达式体成员保持可读性时才优先使用

View File

@@ -0,0 +1,26 @@
---
paths:
- "**/*.cs"
- "**/*.csx"
- "**/*.csproj"
- "**/*.sln"
- "**/Directory.Build.props"
- "**/Directory.Build.targets"
---
# C# 钩子
> 本文档基于 [common/hooks.md](../common/hooks.md) 扩展了 C# 相关的具体内容。
## PostToolUse 钩子
`~/.claude/settings.json` 中配置:
* **dotnet format**:自动格式化编辑过的 C# 文件并应用分析器修复
* **dotnet build**:验证编辑后解决方案或项目是否仍能编译
* **dotnet test --no-build**:在行为更改后重新运行最近相关的测试项目
## Stop 钩子
* 在结束涉及广泛 C# 更改的会话前,运行一次最终的 `dotnet build`
*`appsettings*.json` 文件被修改时发出警告,以防敏感信息被提交

View File

@@ -0,0 +1,51 @@
---
paths:
- "**/*.cs"
- "**/*.csx"
---
# C# 模式
> 本文档在 [common/patterns.md](../common/patterns.md) 的基础上扩展了 C# 相关内容。
## API 响应模式
```csharp
public sealed record ApiResponse<T>(
bool Success,
T? Data = default,
string? Error = null,
object? Meta = null);
```
## 仓储模式
```csharp
public interface IRepository<T>
{
Task<IReadOnlyList<T>> FindAllAsync(CancellationToken cancellationToken);
Task<T?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
Task<T> CreateAsync(T entity, CancellationToken cancellationToken);
Task<T> UpdateAsync(T entity, CancellationToken cancellationToken);
Task DeleteAsync(Guid id, CancellationToken cancellationToken);
}
```
## 选项模式
使用强类型选项进行配置,而不是在整个代码库中读取原始字符串。
```csharp
public sealed class PaymentsOptions
{
public const string SectionName = "Payments";
public required string BaseUrl { get; init; }
public required string ApiKeySecretName { get; init; }
}
```
## 依赖注入
* 在服务边界上依赖于接口
* 保持构造函数专注;如果某个服务需要太多依赖项,请拆分其职责
* 有意识地注册生命周期:无状态/共享服务使用单例,请求数据使用作用域,轻量级纯工作者使用瞬时

View File

@@ -0,0 +1,59 @@
---
paths:
- "**/*.cs"
- "**/*.csx"
- "**/*.csproj"
- "**/appsettings*.json"
---
# C# 安全性
> 本文档在 [common/security.md](../common/security.md) 的基础上补充了 C# 特有的内容。
## 密钥管理
* 切勿在源代码中硬编码 API 密钥、令牌或连接字符串
* 在本地开发环境中使用环境变量或用户密钥,在生产环境中使用密钥管理器
* 确保 `appsettings.*.json` 中不包含真实的凭证信息
```csharp
// BAD
const string ApiKey = "sk-live-123";
// GOOD
var apiKey = builder.Configuration["OpenAI:ApiKey"]
?? throw new InvalidOperationException("OpenAI:ApiKey is not configured.");
```
## SQL 注入防范
* 始终使用 ADO.NET、Dapper 或 EF Core 的参数化查询
* 切勿将用户输入直接拼接到 SQL 字符串中
* 在使用动态查询构建时,先对排序字段和筛选操作符进行验证
```csharp
const string sql = "SELECT * FROM Orders WHERE CustomerId = @customerId";
await connection.QueryAsync<Order>(sql, new { customerId });
```
## 输入验证
* 在应用程序边界处验证 DTO
* 使用数据注解、FluentValidation 或显式的守卫子句
* 在执行业务逻辑之前拒绝无效的模型状态
## 身份验证与授权
* 优先使用框架提供的身份验证处理器,而非自定义的令牌解析逻辑
* 在端点或处理器边界强制执行授权策略
* 切勿记录原始令牌、密码或个人身份信息 (PII)
## 错误处理
* 返回面向客户端的、安全的错误信息
* 在服务器端记录包含结构化上下文的详细异常信息
* 切勿在 API 响应中暴露堆栈跟踪、SQL 语句或文件系统路径
## 参考资料
有关更广泛的应用安全审查清单,请参阅技能:`security-review`

View File

@@ -0,0 +1,47 @@
---
paths:
- "**/*.cs"
- "**/*.csx"
- "**/*.csproj"
---
# C# 测试
> 本文档扩展了 [common/testing.md](../common/testing.md) 中关于 C# 的特定内容。
## 测试框架
* 单元测试和集成测试首选 **xUnit**
* 使用 **FluentAssertions** 编写可读性强的断言
* 使用 **Moq****NSubstitute** 来模拟依赖项
* 当集成测试需要真实基础设施时,使用 **Testcontainers**
## 测试组织
*`tests/` 下镜像 `src/` 的结构
* 明确区分单元测试、集成测试和端到端测试的覆盖范围
* 根据行为而非实现细节来命名测试
```csharp
public sealed class OrderServiceTests
{
[Fact]
public async Task FindByIdAsync_ReturnsOrder_WhenOrderExists()
{
// Arrange
// Act
// Assert
}
}
```
## ASP.NET Core 集成测试
* 使用 `WebApplicationFactory<TEntryPoint>` 进行 API 集成测试覆盖
* 通过 HTTP 测试身份验证、验证和序列化,而不是绕过中间件
## 覆盖率
* 目标行覆盖率 80% 以上
* 将覆盖率重点放在领域逻辑、验证、身份验证和失败路径上
* 在 CI 中运行 `dotnet test` 并启用覆盖率收集(在可用的情况下)

View File

@@ -0,0 +1,117 @@
---
paths:
- "**/*.java"
---
# Java 编码风格
> 本文档基于 [common/coding-style.md](../common/coding-style.md),补充了 Java 特有的内容。
## 格式
* 使用 **google-java-format****Checkstyle**Google 或 Sun 风格)进行强制规范
* 每个文件只包含一个顶层的公共类型
* 保持一致的缩进2 或 4 个空格(遵循项目标准)
* 成员顺序:常量、字段、构造函数、公共方法、受保护方法、私有方法
## 不可变性
* 对于值类型,优先使用 `record`Java 16+
* 默认将字段标记为 `final` —— 仅在需要时才使用可变状态
* 从公共 API 返回防御性副本:`List.copyOf()``Map.copyOf()``Set.copyOf()`
* 写时复制:返回新实例,而不是修改现有实例
```java
// GOOD — immutable value type
public record OrderSummary(Long id, String customerName, BigDecimal total) {}
// GOOD — final fields, no setters
public class Order {
private final Long id;
private final List<LineItem> items;
public List<LineItem> getItems() {
return List.copyOf(items);
}
}
```
## 命名
遵循标准的 Java 命名约定:
* `PascalCase` 用于类、接口、记录、枚举
* `camelCase` 用于方法、字段、参数、局部变量
* `SCREAMING_SNAKE_CASE` 用于 `static final` 常量
* 包名:全小写,使用反向域名(`com.example.app.service`
## 现代 Java 特性
在能提高代码清晰度的地方使用现代语言特性:
* **记录** 用于 DTO 和值类型Java 16+
* **密封类** 用于封闭的类型层次结构Java 17+
* 使用 `instanceof` 进行**模式匹配** —— 避免显式类型转换Java 16+
* **文本块** 用于多行字符串 —— SQL、JSON 模板Java 15+
* 使用箭头语法的**Switch 表达式**Java 14+
* **Switch 中的模式匹配** —— 用于处理密封类型的穷举情况Java 21+
```java
// Pattern matching instanceof
if (shape instanceof Circle c) {
return Math.PI * c.radius() * c.radius();
}
// Sealed type hierarchy
public sealed interface PaymentMethod permits CreditCard, BankTransfer, Wallet {}
// Switch expression
String label = switch (status) {
case ACTIVE -> "Active";
case SUSPENDED -> "Suspended";
case CLOSED -> "Closed";
};
```
## Optional 的使用
* 从可能没有结果的查找方法中返回 `Optional<T>`
* 使用 `map()``flatMap()``orElseThrow()` —— 绝不直接调用 `get()` 而不先检查 `isPresent()`
* 绝不将 `Optional` 用作字段类型或方法参数
```java
// GOOD
return repository.findById(id)
.map(ResponseDto::from)
.orElseThrow(() -> new OrderNotFoundException(id));
// BAD — Optional as parameter
public void process(Optional<String> name) {}
```
## 错误处理
* 对于领域错误,优先使用非受检异常
* 创建扩展自 `RuntimeException` 的领域特定异常
* 避免宽泛的 `catch (Exception e)`,除非在最顶层的处理器中
* 在异常消息中包含上下文信息
```java
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(Long id) {
super("Order not found: id=" + id);
}
}
```
## 流
* 使用流进行转换;保持流水线简短(最多 3-4 个操作)
* 在可读性好的情况下,优先使用方法引用:`.map(Order::getTotal)`
* 避免在流操作中产生副作用
* 对于复杂逻辑,优先使用循环而不是难以理解的流流水线
## 参考
完整编码标准及示例,请参阅技能:`java-coding-standards`
JPA/Hibernate 实体设计模式,请参阅技能:`jpa-patterns`

View File

@@ -0,0 +1,19 @@
---
paths:
- "**/*.java"
- "**/pom.xml"
- "**/build.gradle"
- "**/build.gradle.kts"
---
# Java 钩子
> 本文件在[common/hooks.md](../common/hooks.md)的基础上扩展了Java相关的内容。
## PostToolUse 钩子
`~/.claude/settings.json` 中配置:
* **google-java-format**:编辑后自动格式化 `.java` 文件
* **checkstyle**编辑Java文件后运行样式检查
* **./mvnw compile** 或 **./gradlew compileJava**:变更后验证编译

View File

@@ -0,0 +1,147 @@
---
paths:
- "**/*.java"
---
# Java 模式
> 本文档扩展了 [common/patterns.md](../common/patterns.md) 中的内容,增加了 Java 特有的部分。
## 仓储模式
将数据访问封装在接口之后:
```java
public interface OrderRepository {
Optional<Order> findById(Long id);
List<Order> findAll();
Order save(Order order);
void deleteById(Long id);
}
```
具体的实现类处理存储细节JPA、JDBC、用于测试的内存存储等
## 服务层
业务逻辑放在服务类中;保持控制器和仓储层的精简:
```java
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentGateway paymentGateway;
public OrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) {
this.orderRepository = orderRepository;
this.paymentGateway = paymentGateway;
}
public OrderSummary placeOrder(CreateOrderRequest request) {
var order = Order.from(request);
paymentGateway.charge(order.total());
var saved = orderRepository.save(order);
return OrderSummary.from(saved);
}
}
```
## 构造函数注入
始终使用构造函数注入 —— 绝不使用字段注入:
```java
// GOOD — constructor injection (testable, immutable)
public class NotificationService {
private final EmailSender emailSender;
public NotificationService(EmailSender emailSender) {
this.emailSender = emailSender;
}
}
// BAD — field injection (untestable without reflection, requires framework magic)
public class NotificationService {
@Inject // or @Autowired
private EmailSender emailSender;
}
```
## DTO 映射
使用记录record作为 DTO。在服务层/控制器边界进行映射:
```java
public record OrderResponse(Long id, String customer, BigDecimal total) {
public static OrderResponse from(Order order) {
return new OrderResponse(order.getId(), order.getCustomerName(), order.getTotal());
}
}
```
## 建造者模式
用于具有多个可选参数的对象:
```java
public class SearchCriteria {
private final String query;
private final int page;
private final int size;
private final String sortBy;
private SearchCriteria(Builder builder) {
this.query = builder.query;
this.page = builder.page;
this.size = builder.size;
this.sortBy = builder.sortBy;
}
public static class Builder {
private String query = "";
private int page = 0;
private int size = 20;
private String sortBy = "id";
public Builder query(String query) { this.query = query; return this; }
public Builder page(int page) { this.page = page; return this; }
public Builder size(int size) { this.size = size; return this; }
public Builder sortBy(String sortBy) { this.sortBy = sortBy; return this; }
public SearchCriteria build() { return new SearchCriteria(this); }
}
}
```
## 使用密封类型构建领域模型
```java
public sealed interface PaymentResult permits PaymentSuccess, PaymentFailure {
record PaymentSuccess(String transactionId, BigDecimal amount) implements PaymentResult {}
record PaymentFailure(String errorCode, String message) implements PaymentResult {}
}
// Exhaustive handling (Java 21+)
String message = switch (result) {
case PaymentSuccess s -> "Paid: " + s.transactionId();
case PaymentFailure f -> "Failed: " + f.errorCode();
};
```
## API 响应封装
统一的 API 响应格式:
```java
public record ApiResponse<T>(boolean success, T data, String error) {
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, data, null);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, null, message);
}
}
```
## 参考
有关 Spring Boot 架构模式,请参见技能:`springboot-patterns`
有关实体设计和查询优化,请参见技能:`jpa-patterns`

View File

@@ -0,0 +1,101 @@
---
paths:
- "**/*.java"
---
# Java 安全
> 本文档在 [common/security.md](../common/security.md) 的基础上,补充了 Java 相关的内容。
## 密钥管理
* 切勿在源代码中硬编码 API 密钥、令牌或凭据
* 使用环境变量:`System.getenv("API_KEY")`
* 生产环境密钥请使用密钥管理器(如 Vault、AWS Secrets Manager
* 包含密钥的本地配置文件应放在 `.gitignore`
```java
// BAD
private static final String API_KEY = "sk-abc123...";
// GOOD — environment variable
String apiKey = System.getenv("PAYMENT_API_KEY");
Objects.requireNonNull(apiKey, "PAYMENT_API_KEY must be set");
```
## SQL 注入防护
* 始终使用参数化查询——切勿将用户输入拼接到 SQL 语句中
* 使用 `PreparedStatement` 或你所使用框架的参数化查询 API
* 对用于原生查询的任何输入进行验证和清理
```java
// BAD — SQL injection via string concatenation
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM orders WHERE name = '" + name + "'";
stmt.executeQuery(sql);
// GOOD — PreparedStatement with parameterized query
PreparedStatement ps = conn.prepareStatement("SELECT * FROM orders WHERE name = ?");
ps.setString(1, name);
// GOOD — JDBC template
jdbcTemplate.query("SELECT * FROM orders WHERE name = ?", mapper, name);
```
## 输入验证
* 在处理前,于系统边界处验证所有用户输入
* 使用验证框架时,在 DTO 上使用 Bean 验证(`@NotNull`, `@NotBlank`, `@Size`
* 在使用文件路径和用户提供的字符串前,对其进行清理
* 对于验证失败的输入,应拒绝并提供清晰的错误信息
```java
// Validate manually in plain Java
public Order createOrder(String customerName, BigDecimal amount) {
if (customerName == null || customerName.isBlank()) {
throw new IllegalArgumentException("Customer name is required");
}
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
return new Order(customerName, amount);
}
```
## 认证与授权
* 切勿自行实现认证加密逻辑——请使用成熟的库
* 使用 bcrypt 或 Argon2 存储密码,切勿使用 MD5/SHA1
* 在服务边界强制执行授权检查
* 清理日志中的敏感数据——切勿记录密码、令牌或个人身份信息
## 依赖项安全
* 运行 `mvn dependency:tree``./gradlew dependencies` 来审计传递依赖项
* 使用 OWASP Dependency-Check 或 Snyk 扫描已知的 CVE
* 保持依赖项更新——设置 Dependabot 或 Renovate
## 错误信息
* 切勿在 API 响应中暴露堆栈跟踪、内部路径或 SQL 错误
* 在处理器边界将异常映射为安全、通用的客户端消息
* 在服务器端记录详细错误;向客户端返回通用消息
```java
// Log the detail, return a generic message
try {
return orderService.findById(id);
} catch (OrderNotFoundException ex) {
log.warn("Order not found: id={}", id);
return ApiResponse.error("Resource not found"); // generic, no internals
} catch (Exception ex) {
log.error("Unexpected error processing order id={}", id, ex);
return ApiResponse.error("Internal server error"); // never expose ex.getMessage()
}
```
## 参考
关于 Spring Security 认证与授权模式,请参见技能:`springboot-security`
关于通用安全检查清单,请参见技能:`security-review`

View File

@@ -0,0 +1,133 @@
---
paths:
- "**/*.java"
---
# Java 测试
> 本文档扩展了 [common/testing.md](../common/testing.md) 中与 Java 相关的内容。
## 测试框架
* **JUnit 5** (`@Test`, `@ParameterizedTest`, `@Nested`, `@DisplayName`)
* **AssertJ** 用于流式断言 (`assertThat(result).isEqualTo(expected)`)
* **Mockito** 用于模拟依赖
* **Testcontainers** 用于需要数据库或服务的集成测试
## 测试组织
```
src/test/java/com/example/app/
service/ # Unit tests for service layer
controller/ # Web layer / API tests
repository/ # Data access tests
integration/ # Cross-layer integration tests
```
`src/test/java` 中镜像 `src/main/java` 的包结构。
## 单元测试模式
```java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
private OrderService orderService;
@BeforeEach
void setUp() {
orderService = new OrderService(orderRepository);
}
@Test
@DisplayName("findById returns order when exists")
void findById_existingOrder_returnsOrder() {
var order = new Order(1L, "Alice", BigDecimal.TEN);
when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
var result = orderService.findById(1L);
assertThat(result.customerName()).isEqualTo("Alice");
verify(orderRepository).findById(1L);
}
@Test
@DisplayName("findById throws when order not found")
void findById_missingOrder_throws() {
when(orderRepository.findById(99L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> orderService.findById(99L))
.isInstanceOf(OrderNotFoundException.class)
.hasMessageContaining("99");
}
}
```
## 参数化测试
```java
@ParameterizedTest
@CsvSource({
"100.00, 10, 90.00",
"50.00, 0, 50.00",
"200.00, 25, 150.00"
})
@DisplayName("discount applied correctly")
void applyDiscount(BigDecimal price, int pct, BigDecimal expected) {
assertThat(PricingUtils.discount(price, pct)).isEqualByComparingTo(expected);
}
```
## 集成测试
使用 Testcontainers 进行真实的数据库集成:
```java
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
private OrderRepository repository;
@BeforeEach
void setUp() {
var dataSource = new PGSimpleDataSource();
dataSource.setUrl(postgres.getJdbcUrl());
dataSource.setUser(postgres.getUsername());
dataSource.setPassword(postgres.getPassword());
repository = new JdbcOrderRepository(dataSource);
}
@Test
void save_and_findById() {
var saved = repository.save(new Order(null, "Bob", BigDecimal.ONE));
var found = repository.findById(saved.getId());
assertThat(found).isPresent();
}
}
```
关于 Spring Boot 集成测试,请参阅技能:`springboot-tdd`
## 测试命名
使用带有 `@DisplayName` 的描述性名称:
* `methodName_scenario_expectedBehavior()` 用于方法名
* `@DisplayName("human-readable description")` 用于报告
## 覆盖率
* 目标为 80%+ 的行覆盖率
* 使用 JaCoCo 生成覆盖率报告
* 重点关注服务和领域逻辑 — 跳过简单的 getter/配置类
## 参考
关于使用 MockMvc 和 Testcontainers 的 Spring Boot TDD 模式,请参阅技能:`springboot-tdd`
关于测试期望,请参阅技能:`java-coding-standards`

View File

@@ -26,6 +26,11 @@ paths:
* 使用 **PHPStan****Psalm** 进行静态分析。
* 将 Composer 脚本纳入版本控制,以便在本地和 CI 中运行相同的命令。
## 导入
* 为所有引用的类、接口和特征添加 `use` 语句。
* 避免依赖全局命名空间,除非项目明确偏好使用完全限定名称。
## 错误处理
* 对于异常状态抛出异常;避免在新代码中返回 `false`/`null` 作为隐藏的错误通道。

View File

@@ -30,4 +30,5 @@ paths:
## 参考
关于端点约定和响应格式的指导,请参见技能:`api-design`
参见技能:`api-design` 了解端点约定和响应格式指导。
参见技能:`laravel-patterns` 了解 Laravel 特定架构指导。

View File

@@ -32,3 +32,7 @@ paths:
* 使用 `password_hash()` / `password_verify()` 存储密码。
* 在身份验证和权限变更后重新生成会话标识符。
* 对状态变更的 Web 请求强制实施 CSRF 保护。
## 参考
有关 Laravel 特定安全指南,请参阅技能:`laravel-security`

View File

@@ -12,7 +12,7 @@ paths:
## 测试框架
默认使用 **PHPUnit** 作为测试框架。如果项目已在使用 **Pest**也是可以接受的
使用 **PHPUnit** 作为默认测试框架。如果项目中配置了 **Pest**则新测试优先使用 Pest并避免混合使用框架
## 覆盖率
@@ -30,6 +30,11 @@ vendor/bin/pest --coverage
* 使用工厂/构建器来生成测试数据,而不是手动编写大量的数组。
* 保持 HTTP/控制器测试专注于传输和验证;将业务规则移到服务层级的测试中。
## Inertia
如果项目使用了 Inertia.js优先使用 `assertInertia` 搭配 `AssertableInertia` 来验证组件名称和属性,而不是原始的 JSON 断言。
## 参考
关于整个仓库范围内的 RED -> GREEN -> REFACTOR 循环,请参见技能:`tdd-workflow`
查看技能:`tdd-workflow` 以了解项目范围内的 RED -> GREEN -> REFACTOR 循环。
查看技能:`laravel-tdd` 以了解 Laravel 特定的测试模式PHPUnit 和 Pest

View File

@@ -0,0 +1,153 @@
---
paths:
- "**/*.rs"
---
# Rust 编码风格
> 本文档扩展了 [common/coding-style.md](../common/coding-style.md) 中关于 Rust 的特定内容。
## 格式化
* **rustfmt** 用于强制执行 — 提交前务必运行 `cargo fmt`
* **clippy** 用于代码检查 — `cargo clippy -- -D warnings`(将警告视为错误)
* 4 空格缩进rustfmt 默认)
* 最大行宽100 个字符rustfmt 默认)
## 不可变性
Rust 变量默认是不可变的 — 请遵循此原则:
* 默认使用 `let`;仅在需要修改时才使用 `let mut`
* 优先返回新值,而非原地修改
* 当函数可能分配内存也可能不分配时,使用 `Cow<'_, T>`
```rust
use std::borrow::Cow;
// GOOD — immutable by default, new value returned
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') {
Cow::Owned(input.replace(' ', "_"))
} else {
Cow::Borrowed(input)
}
}
// BAD — unnecessary mutation
fn normalize_bad(input: &mut String) {
*input = input.replace(' ', "_");
}
```
## 命名
遵循标准的 Rust 约定:
* `snake_case` 用于函数、方法、变量、模块、crate
* `PascalCase`(大驼峰式)用于类型、特征、枚举、类型参数
* `SCREAMING_SNAKE_CASE` 用于常量和静态变量
* 生命周期:简短的小写字母(`'a``'de`)— 复杂情况使用描述性名称(`'input`
## 所有权与借用
* 默认借用(`&T`);仅在需要存储或消耗时再获取所有权
* 切勿在不理解根本原因的情况下,为了满足借用检查器而克隆数据
* 在函数参数中,优先接受 `&str` 而非 `String`,优先接受 `&[T]` 而非 `Vec<T>`
* 对于需要拥有 `String` 的构造函数,使用 `impl Into<String>`
```rust
// GOOD — borrows when ownership isn't needed
fn word_count(text: &str) -> usize {
text.split_whitespace().count()
}
// GOOD — takes ownership in constructor via Into
fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
// BAD — takes String when &str suffices
fn word_count_bad(text: String) -> usize {
text.split_whitespace().count()
}
```
## 错误处理
* 使用 `Result<T, E>``?` 进行传播 — 切勿在生产代码中使用 `unwrap()`
* **库**:使用 `thiserror` 定义类型化错误
* **应用程序**:使用 `anyhow` 以获取灵活的错误上下文
* 使用 `.with_context(|| format!("failed to ..."))?` 添加上下文
*`unwrap()` / `expect()` 保留用于测试和真正无法到达的状态
```rust
// GOOD — library error with thiserror
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("failed to read config: {0}")]
Io(#[from] std::io::Error),
#[error("invalid config format: {0}")]
Parse(String),
}
// GOOD — application error with anyhow
use anyhow::Context;
fn load_config(path: &str) -> anyhow::Result<Config> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("failed to read {path}"))?;
toml::from_str(&content)
.with_context(|| format!("failed to parse {path}"))
}
```
## 迭代器优于循环
对于转换操作,优先使用迭代器链;对于复杂的控制流,使用循环:
```rust
// GOOD — declarative and composable
let active_emails: Vec<&str> = users.iter()
.filter(|u| u.is_active)
.map(|u| u.email.as_str())
.collect();
// GOOD — loop for complex logic with early returns
for user in &users {
if let Some(verified) = verify_email(&user.email)? {
send_welcome(&verified)?;
}
}
```
## 模块组织
按领域而非类型组织:
```text
src/
├── main.rs
├── lib.rs
├── auth/ # Domain module
│ ├── mod.rs
│ ├── token.rs
│ └── middleware.rs
├── orders/ # Domain module
│ ├── mod.rs
│ ├── model.rs
│ └── service.rs
└── db/ # Infrastructure
├── mod.rs
└── pool.rs
```
## 可见性
* 默认为私有;使用 `pub(crate)` 进行内部共享
* 仅将属于 crate 公共 API 的部分标记为 `pub`
*`lib.rs` 重新导出公共 API
## 参考
有关全面的 Rust 惯用法和模式,请参阅技能:`rust-patterns`

View File

@@ -0,0 +1,17 @@
---
paths:
- "**/*.rs"
- "**/Cargo.toml"
---
# Rust 钩子
> 此文件扩展了 [common/hooks.md](../common/hooks.md),包含 Rust 特定内容。
## PostToolUse 钩子
`~/.claude/settings.json` 中配置:
* **cargo fmt**:编辑后自动格式化 `.rs` 文件
* **cargo clippy**:编辑 Rust 文件后运行 lint 检查
* **cargo check**:更改后验证编译(比 `cargo build` 更快)

View File

@@ -0,0 +1,169 @@
---
paths:
- "**/*.rs"
---
# Rust 设计模式
> 本文档在 [common/patterns.md](../common/patterns.md) 的基础上,补充了 Rust 特有的内容。
## 基于 Trait 的 Repository 模式
将数据访问封装在 trait 之后:
```rust
pub trait OrderRepository: Send + Sync {
fn find_by_id(&self, id: u64) -> Result<Option<Order>, StorageError>;
fn find_all(&self) -> Result<Vec<Order>, StorageError>;
fn save(&self, order: &Order) -> Result<Order, StorageError>;
fn delete(&self, id: u64) -> Result<(), StorageError>;
}
```
具体的实现负责处理存储细节(如 Postgres、SQLite或用于测试的内存存储
## 服务层
业务逻辑位于服务结构体中;通过构造函数注入依赖:
```rust
pub struct OrderService {
repo: Box<dyn OrderRepository>,
payment: Box<dyn PaymentGateway>,
}
impl OrderService {
pub fn new(repo: Box<dyn OrderRepository>, payment: Box<dyn PaymentGateway>) -> Self {
Self { repo, payment }
}
pub fn place_order(&self, request: CreateOrderRequest) -> anyhow::Result<OrderSummary> {
let order = Order::from(request);
self.payment.charge(order.total())?;
let saved = self.repo.save(&order)?;
Ok(OrderSummary::from(saved))
}
}
```
## 为类型安全使用 Newtype 模式
使用不同的包装类型防止参数混淆:
```rust
struct UserId(u64);
struct OrderId(u64);
fn get_order(user: UserId, order: OrderId) -> anyhow::Result<Order> {
// Can't accidentally swap user and order IDs at call sites
todo!()
}
```
## 枚举状态机
将状态建模为枚举 —— 使非法状态无法表示:
```rust
enum ConnectionState {
Disconnected,
Connecting { attempt: u32 },
Connected { session_id: String },
Failed { reason: String, retries: u32 },
}
fn handle(state: &ConnectionState) {
match state {
ConnectionState::Disconnected => connect(),
ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),
ConnectionState::Connecting { .. } => wait(),
ConnectionState::Connected { session_id } => use_session(session_id),
ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),
ConnectionState::Failed { reason, .. } => log_failure(reason),
}
}
```
始终进行穷尽匹配 —— 对于业务关键的枚举,不要使用通配符 `_`
## 建造者模式
适用于具有多个可选参数的结构体:
```rust
pub struct ServerConfig {
host: String,
port: u16,
max_connections: usize,
}
impl ServerConfig {
pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
ServerConfigBuilder {
host: host.into(),
port,
max_connections: 100,
}
}
}
pub struct ServerConfigBuilder {
host: String,
port: u16,
max_connections: usize,
}
impl ServerConfigBuilder {
pub fn max_connections(mut self, n: usize) -> Self {
self.max_connections = n;
self
}
pub fn build(self) -> ServerConfig {
ServerConfig {
host: self.host,
port: self.port,
max_connections: self.max_connections,
}
}
}
```
## 密封 Trait 以控制扩展性
使用私有模块来密封一个 trait防止外部实现
```rust
mod private {
pub trait Sealed {}
}
pub trait Format: private::Sealed {
fn encode(&self, data: &[u8]) -> Vec<u8>;
}
pub struct Json;
impl private::Sealed for Json {}
impl Format for Json {
fn encode(&self, data: &[u8]) -> Vec<u8> { todo!() }
}
```
## API 响应包装器
使用泛型枚举实现一致的 API 响应:
```rust
#[derive(Debug, serde::Serialize)]
#[serde(tag = "status")]
pub enum ApiResponse<T: serde::Serialize> {
#[serde(rename = "ok")]
Ok { data: T },
#[serde(rename = "error")]
Error { message: String },
}
```
## 参考资料
参见技能:`rust-patterns`其中包含全面的模式涵盖所有权、trait、泛型、并发和异步。

View File

@@ -0,0 +1,142 @@
---
paths:
- "**/*.rs"
---
# Rust 安全
> 本文档在 [common/security.md](../common/security.md) 的基础上扩展了 Rust 相关的内容。
## 密钥管理
* 切勿在源代码中硬编码 API 密钥、令牌或凭证
* 使用环境变量:`std::env::var("API_KEY")`
* 如果启动时缺少必需的密钥,应快速失败
*`.env` 文件保存在 `.gitignore`
```rust
// BAD
const API_KEY: &str = "sk-abc123...";
// GOOD — environment variable with early validation
fn load_api_key() -> anyhow::Result<String> {
std::env::var("PAYMENT_API_KEY")
.context("PAYMENT_API_KEY must be set")
}
```
## SQL 注入防护
* 始终使用参数化查询 —— 切勿将用户输入格式化到 SQL 字符串中
* 使用支持绑定参数的查询构建器或 ORMsqlx, diesel, sea-orm
```rust
// BAD — SQL injection via format string
let query = format!("SELECT * FROM users WHERE name = '{name}'");
sqlx::query(&query).fetch_one(&pool).await?;
// GOOD — parameterized query with sqlx
// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1
sqlx::query("SELECT * FROM users WHERE name = $1")
.bind(&name)
.fetch_one(&pool)
.await?;
```
## 输入验证
* 在处理之前,在系统边界处验证所有用户输入
* 利用类型系统来强制约束newtype 模式)
* 进行解析,而非验证 —— 在边界处将非结构化数据转换为有类型的结构体
* 以清晰的错误信息拒绝无效输入
```rust
// Parse, don't validate — invalid states are unrepresentable
pub struct Email(String);
impl Email {
pub fn parse(input: &str) -> Result<Self, ValidationError> {
let trimmed = input.trim();
let at_pos = trimmed.find('@')
.filter(|&p| p > 0 && p < trimmed.len() - 1)
.ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;
let domain = &trimmed[at_pos + 1..];
if trimmed.len() > 254 || !domain.contains('.') {
return Err(ValidationError::InvalidEmail(input.to_string()));
}
// For production use, prefer a validated email crate (e.g., `email_address`)
Ok(Self(trimmed.to_string()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
```
## 不安全代码
* 尽量减少 `unsafe` 块 —— 优先使用安全的抽象
* 每个 `unsafe` 块必须附带一个 `// SAFETY:` 注释来解释其不变量
* 切勿为了方便而使用 `unsafe` 来绕过借用检查器
* 在代码审查时审核所有 `unsafe` 代码 —— 若无合理解释,应视为危险信号
* 优先使用 `safe` 作为 C 库的 FFI 包装器
```rust
// GOOD — safety comment documents ALL required invariants
let widget: &Widget = {
// SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,
// and no mutable references or mutations exist for its lifetime.
unsafe { &*ptr }
};
// BAD — no safety justification
unsafe { &*ptr }
```
## 依赖项安全
* 运行 `cargo audit` 以扫描依赖项中已知的 CVE
* 运行 `cargo deny check` 以确保许可证和公告合规
* 使用 `cargo tree` 来审计传递依赖项
* 保持依赖项更新 —— 设置 Dependabot 或 Renovate
* 最小化依赖项数量 —— 添加新 crate 前进行评估
```bash
# Security audit
cargo audit
# Deny advisories, duplicate versions, and restricted licenses
cargo deny check
# Inspect dependency tree
cargo tree
cargo tree -d # Show duplicates only
```
## 错误信息
* 切勿在 API 响应中暴露内部路径、堆栈跟踪或数据库错误
* 在服务器端记录详细错误;向客户端返回通用消息
* 使用 `tracing``log` 进行结构化的服务器端日志记录
```rust
// Map errors to appropriate status codes and generic messages
// (Example uses axum; adapt the response type to your framework)
match order_service.find_by_id(id) {
Ok(order) => Ok((StatusCode::OK, Json(order))),
Err(ServiceError::NotFound(_)) => {
tracing::info!(order_id = id, "order not found");
Err((StatusCode::NOT_FOUND, "Resource not found"))
}
Err(e) => {
tracing::error!(order_id = id, error = %e, "unexpected error");
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
}
}
```
## 参考资料
关于不安全代码指南和所有权模式,请参见技能:`rust-patterns`
关于通用安全检查清单,请参见技能:`security-review`

View File

@@ -0,0 +1,156 @@
---
paths:
- "**/*.rs"
---
# Rust 测试
> 本文件扩展了 [common/testing.md](../common/testing.md) 中关于 Rust 的特定内容。
## 测试框架
* **`#[test]`** 配合 `#[cfg(test)]` 模块进行单元测试
* **rstest** 用于参数化测试和夹具
* **proptest** 用于基于属性的测试
* **mockall** 用于基于特征的模拟
* **`#[tokio::test]`** 用于异步测试
## 测试组织
```text
my_crate/
├── src/
│ ├── lib.rs # Unit tests in #[cfg(test)] modules
│ ├── auth/
│ │ └── mod.rs # #[cfg(test)] mod tests { ... }
│ └── orders/
│ └── service.rs # #[cfg(test)] mod tests { ... }
├── tests/ # Integration tests (each file = separate binary)
│ ├── api_test.rs
│ ├── db_test.rs
│ └── common/ # Shared test utilities
│ └── mod.rs
└── benches/ # Criterion benchmarks
└── benchmark.rs
```
单元测试放在同一文件的 `#[cfg(test)]` 模块内。集成测试放在 `tests/` 目录中。
## 单元测试模式
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_user_with_valid_email() {
let user = User::new("Alice", "alice@example.com").unwrap();
assert_eq!(user.name, "Alice");
}
#[test]
fn rejects_invalid_email() {
let result = User::new("Bob", "not-an-email");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid email"));
}
}
```
## 参数化测试
```rust
use rstest::rstest;
#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_string_length(#[case] input: &str, #[case] expected: usize) {
assert_eq!(input.len(), expected);
}
```
## 异步测试
```rust
#[tokio::test]
async fn fetches_data_successfully() {
let client = TestClient::new().await;
let result = client.get("/data").await;
assert!(result.is_ok());
}
```
## 使用 mockall 进行模拟
在生产代码中定义特征;在测试模块中生成模拟对象:
```rust
// Production trait — pub so integration tests can import it
pub trait UserRepository {
fn find_by_id(&self, id: u64) -> Option<User>;
}
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::eq;
mockall::mock! {
pub Repo {}
impl UserRepository for Repo {
fn find_by_id(&self, id: u64) -> Option<User>;
}
}
#[test]
fn service_returns_user_when_found() {
let mut mock = MockRepo::new();
mock.expect_find_by_id()
.with(eq(42))
.times(1)
.returning(|_| Some(User { id: 42, name: "Alice".into() }));
let service = UserService::new(Box::new(mock));
let user = service.get_user(42).unwrap();
assert_eq!(user.name, "Alice");
}
}
```
## 测试命名
使用描述性的名称来解释场景:
* `creates_user_with_valid_email()`
* `rejects_order_when_insufficient_stock()`
* `returns_none_when_not_found()`
## 覆盖率
* 目标为 80%+ 的行覆盖率
* 使用 **cargo-llvm-cov** 生成覆盖率报告
* 关注业务逻辑 —— 排除生成的代码和 FFI 绑定
```bash
cargo llvm-cov # Summary
cargo llvm-cov --html # HTML report
cargo llvm-cov --fail-under-lines 80 # Fail if below threshold
```
## 测试命令
```bash
cargo test # Run all tests
cargo test -- --nocapture # Show println output
cargo test test_name # Run tests matching pattern
cargo test --lib # Unit tests only
cargo test --test api_test # Specific integration test (tests/api_test.rs)
cargo test --doc # Doc tests only
```
## 参考
有关全面的测试模式(包括基于属性的测试、夹具以及使用 Criterion 进行基准测试),请参阅技能:`rust-testing`