14 changed files with 2531 additions and 109 deletions
@ -0,0 +1,181 @@ |
|||
# LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper |
|||
|
|||
Dapr Actors ASP.NET Core wrapper module for handling Actor method call response wrapping and unwrapping. |
|||
|
|||
## Features |
|||
|
|||
* Automatic Actor response result wrapping/unwrapping |
|||
* Integration with ABP's wrapper system |
|||
* Error handling for Actor method calls |
|||
* Support for success/error code configuration |
|||
* Integration with Dapr.Actors.AspNetCore |
|||
* Support for custom response wrapper format |
|||
* Flexible wrapping control options |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(AbpDaprActorsAspNetCoreModule), |
|||
typeof(AbpWrapperModule) |
|||
)] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
## Configuration Options |
|||
|
|||
The module uses `AbpWrapperOptions` from the `LINGYUN.Abp.Wrapper` package for configuration: |
|||
|
|||
```json |
|||
{ |
|||
"Wrapper": { |
|||
"IsEnabled": true, // Enable/disable response wrapping |
|||
"CodeWithSuccess": "0", // Success code in wrapped response |
|||
"HttpStatusCode": 200, // Default HTTP status code for wrapped responses |
|||
"WrapOnError": true, // Whether to wrap error responses |
|||
"WrapOnSuccess": true // Whether to wrap success responses |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Example |
|||
|
|||
1. Actor Interface Definition |
|||
|
|||
```csharp |
|||
public interface ICounterActor : IActor |
|||
{ |
|||
Task<int> GetCountAsync(); |
|||
Task IncrementCountAsync(); |
|||
} |
|||
``` |
|||
|
|||
2. Actor Implementation |
|||
|
|||
```csharp |
|||
public class CounterActor : Actor, ICounterActor |
|||
{ |
|||
private const string CountStateName = "count"; |
|||
|
|||
public CounterActor(ActorHost host) : base(host) |
|||
{ |
|||
} |
|||
|
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
|
|||
public async Task IncrementCountAsync() |
|||
{ |
|||
var currentCount = await GetCountAsync(); |
|||
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Response Format |
|||
|
|||
When wrapping is enabled, Actor method responses will be in the following format: |
|||
|
|||
```json |
|||
{ |
|||
"code": "0", // Response code, "0" indicates success by default |
|||
"message": "Success", // Response message |
|||
"details": null, // Additional details (optional) |
|||
"result": { // Actual response data |
|||
// ... Actor method return value |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Error Handling |
|||
|
|||
The module automatically handles Actor method call errors: |
|||
* For wrapped responses: |
|||
* Unwraps the response and checks the code |
|||
* If code doesn't match `CodeWithSuccess`, throws `AbpRemoteCallException` |
|||
* Includes error message, details, and code in the exception |
|||
* Supports custom error code mapping |
|||
* For Actor runtime errors: |
|||
* Automatically wraps as standard error response |
|||
* Preserves original exception information |
|||
* Includes Actor-related context information |
|||
|
|||
### Error Response Example |
|||
|
|||
```json |
|||
{ |
|||
"code": "ERROR_001", |
|||
"message": "Actor method call failed", |
|||
"details": "Failed to access state for actor 'counter'", |
|||
"result": null |
|||
} |
|||
``` |
|||
|
|||
## Advanced Usage |
|||
|
|||
### 1. Controlling Response Wrapping |
|||
|
|||
Response wrapping can be controlled per Actor call using HTTP headers: |
|||
|
|||
```csharp |
|||
// Add to request headers |
|||
var headers = new Dictionary<string, string> |
|||
{ |
|||
{ "X-Abp-Wrap-Result", "true" }, // Force enable wrapping |
|||
// or |
|||
{ "X-Abp-Dont-Wrap-Result", "true" } // Force disable wrapping |
|||
}; |
|||
|
|||
// Use in Actor method |
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var context = ActorContext.GetContext(); |
|||
context.Headers.Add("X-Abp-Wrap-Result", "true"); |
|||
|
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
``` |
|||
|
|||
### 2. Custom Error Handling |
|||
|
|||
```csharp |
|||
public class CustomActorErrorHandler : IAbpWrapperErrorHandler |
|||
{ |
|||
public Task HandleAsync(AbpWrapperErrorContext context) |
|||
{ |
|||
if (context.Exception is ActorMethodInvocationException actorException) |
|||
{ |
|||
// Custom Actor error handling logic |
|||
context.Response = new WrapperResponse |
|||
{ |
|||
Code = "ACTOR_ERROR", |
|||
Message = actorException.Message, |
|||
Details = actorException.ActorId |
|||
}; |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* Response wrapping can be controlled through: |
|||
* Global settings in configuration |
|||
* HTTP headers for individual requests |
|||
* Dynamic control in Actor methods |
|||
* Error responses maintain original error structure for Actor methods |
|||
* The module integrates with ABP's remote service error handling system |
|||
* Recommended to use response wrapping consistently in microservices architecture |
|||
* Wrapper format can be customized by implementing `IAbpWrapperResponseBuilder` |
|||
* Actor state operation errors are properly wrapped and handled |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,181 @@ |
|||
# LINGYUN.Abp.Dapr.Actors.AspNetCore.Wrapper |
|||
|
|||
Dapr Actors ASP.NET Core响应包装模块,用于处理Actor方法调用的响应包装和解包。 |
|||
|
|||
## 功能特性 |
|||
|
|||
* Actor响应结果自动包装/解包 |
|||
* 与ABP包装系统集成 |
|||
* 支持Actor方法调用的错误处理 |
|||
* 支持成功/错误代码配置 |
|||
* 与Dapr.Actors.AspNetCore集成 |
|||
* 支持自定义响应包装格式 |
|||
* 灵活的包装控制选项 |
|||
|
|||
## 配置使用 |
|||
|
|||
模块按需引用: |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(AbpDaprActorsAspNetCoreModule), |
|||
typeof(AbpWrapperModule) |
|||
)] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
## 配置选项 |
|||
|
|||
模块使用来自`LINGYUN.Abp.Wrapper`包的`AbpWrapperOptions`进行配置: |
|||
|
|||
```json |
|||
{ |
|||
"Wrapper": { |
|||
"IsEnabled": true, // 启用/禁用响应包装 |
|||
"CodeWithSuccess": "0", // 包装响应中的成功代码 |
|||
"HttpStatusCode": 200, // 包装响应的默认HTTP状态码 |
|||
"WrapOnError": true, // 是否包装错误响应 |
|||
"WrapOnSuccess": true // 是否包装成功响应 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 实现示例 |
|||
|
|||
1. Actor接口定义 |
|||
|
|||
```csharp |
|||
public interface ICounterActor : IActor |
|||
{ |
|||
Task<int> GetCountAsync(); |
|||
Task IncrementCountAsync(); |
|||
} |
|||
``` |
|||
|
|||
2. Actor实现 |
|||
|
|||
```csharp |
|||
public class CounterActor : Actor, ICounterActor |
|||
{ |
|||
private const string CountStateName = "count"; |
|||
|
|||
public CounterActor(ActorHost host) : base(host) |
|||
{ |
|||
} |
|||
|
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
|
|||
public async Task IncrementCountAsync() |
|||
{ |
|||
var currentCount = await GetCountAsync(); |
|||
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 响应格式 |
|||
|
|||
当启用包装时,Actor方法的响应将采用以下格式: |
|||
|
|||
```json |
|||
{ |
|||
"code": "0", // 响应代码,默认"0"表示成功 |
|||
"message": "Success", // 响应消息 |
|||
"details": null, // 附加详情(可选) |
|||
"result": { // 实际响应数据 |
|||
// ... Actor方法的返回值 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 错误处理 |
|||
|
|||
模块自动处理Actor方法调用的错误: |
|||
* 对于包装的响应: |
|||
* 解包响应并检查代码 |
|||
* 如果代码与`CodeWithSuccess`不匹配,抛出`AbpRemoteCallException` |
|||
* 在异常中包含错误消息、详情和代码 |
|||
* 支持自定义错误代码映射 |
|||
* 对于Actor运行时错误: |
|||
* 自动包装为标准错误响应 |
|||
* 保留原始异常信息 |
|||
* 包含Actor相关的上下文信息 |
|||
|
|||
### 错误响应示例 |
|||
|
|||
```json |
|||
{ |
|||
"code": "ERROR_001", |
|||
"message": "Actor方法调用失败", |
|||
"details": "Actor 'counter' 的状态访问失败", |
|||
"result": null |
|||
} |
|||
``` |
|||
|
|||
## 高级用法 |
|||
|
|||
### 1. 控制响应包装 |
|||
|
|||
可以通过HTTP头控制单个Actor调用的响应包装: |
|||
|
|||
```csharp |
|||
// 在请求头中添加 |
|||
var headers = new Dictionary<string, string> |
|||
{ |
|||
{ "X-Abp-Wrap-Result", "true" }, // 强制启用包装 |
|||
// 或 |
|||
{ "X-Abp-Dont-Wrap-Result", "true" } // 强制禁用包装 |
|||
}; |
|||
|
|||
// 在Actor方法中使用 |
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var context = ActorContext.GetContext(); |
|||
context.Headers.Add("X-Abp-Wrap-Result", "true"); |
|||
|
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
``` |
|||
|
|||
### 2. 自定义错误处理 |
|||
|
|||
```csharp |
|||
public class CustomActorErrorHandler : IAbpWrapperErrorHandler |
|||
{ |
|||
public Task HandleAsync(AbpWrapperErrorContext context) |
|||
{ |
|||
if (context.Exception is ActorMethodInvocationException actorException) |
|||
{ |
|||
// 自定义Actor错误处理逻辑 |
|||
context.Response = new WrapperResponse |
|||
{ |
|||
Code = "ACTOR_ERROR", |
|||
Message = actorException.Message, |
|||
Details = actorException.ActorId |
|||
}; |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 注意事项 |
|||
|
|||
* 响应包装可以通过以下方式控制: |
|||
* 配置文件中的全局设置 |
|||
* HTTP头控制单个请求 |
|||
* Actor方法中的动态控制 |
|||
* Actor方法的错误响应会保持原始错误结构 |
|||
* 模块与ABP的远程服务错误处理系统集成 |
|||
* 建议在微服务架构中统一使用响应包装 |
|||
* 包装格式可以通过继承`IAbpWrapperResponseBuilder`自定义 |
|||
* Actor状态操作的错误会被正确包装和处理 |
|||
|
|||
[查看英文](README.EN.md) |
|||
@ -0,0 +1,123 @@ |
|||
# LINGYUN.Abp.Dapr.Actors.AspNetCore |
|||
|
|||
Integration of Dapr.Actors with ASP.NET Core in the ABP framework. This module automatically scans and registers Actor services defined within assemblies as Dapr.Actors. |
|||
|
|||
## Features |
|||
|
|||
* Automatic Actor service registration |
|||
* Integration with ABP's dependency injection system |
|||
* Support for custom Actor type names through `RemoteServiceAttribute` |
|||
* Actor runtime configuration through `ActorRuntimeOptions` |
|||
* Automatic Actor endpoint mapping |
|||
* Actor interface validation |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprActorsAspNetCoreModule))] |
|||
public class YourProjectModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Configure Actor runtime options |
|||
PreConfigure<ActorRuntimeOptions>(options => |
|||
{ |
|||
options.ActorIdleTimeout = TimeSpan.FromMinutes(60); |
|||
options.ActorScanInterval = TimeSpan.FromSeconds(30); |
|||
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(30); |
|||
options.DrainRebalancedActors = true; |
|||
options.RemindersStoragePartitions = 7; |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Example |
|||
|
|||
1. Define Actor Interface |
|||
|
|||
```csharp |
|||
[RemoteService("counter")] // Optional: customize Actor type name |
|||
public interface ICounterActor : IActor |
|||
{ |
|||
Task<int> GetCountAsync(); |
|||
Task IncrementCountAsync(); |
|||
} |
|||
``` |
|||
|
|||
2. Implement Actor |
|||
|
|||
```csharp |
|||
public class CounterActor : Actor, ICounterActor |
|||
{ |
|||
private const string CountStateName = "count"; |
|||
|
|||
public CounterActor(ActorHost host) : base(host) |
|||
{ |
|||
} |
|||
|
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
|
|||
public async Task IncrementCountAsync() |
|||
{ |
|||
var currentCount = await GetCountAsync(); |
|||
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The module will automatically: |
|||
1. Detect the `CounterActor` implementation |
|||
2. Register it with Dapr.Actors |
|||
3. Configure the Actor runtime |
|||
4. Map the Actor endpoints |
|||
|
|||
## Actor Runtime Configuration |
|||
|
|||
The module supports all standard Dapr Actor runtime configurations through `ActorRuntimeOptions`: |
|||
|
|||
```csharp |
|||
PreConfigure<ActorRuntimeOptions>(options => |
|||
{ |
|||
// Actor timeout settings |
|||
options.ActorIdleTimeout = TimeSpan.FromMinutes(60); |
|||
options.ActorScanInterval = TimeSpan.FromSeconds(30); |
|||
|
|||
// Draining settings |
|||
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(30); |
|||
options.DrainRebalancedActors = true; |
|||
|
|||
// Reminders settings |
|||
options.RemindersStoragePartitions = 7; |
|||
|
|||
// Custom serialization settings |
|||
options.JsonSerializerOptions = new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase |
|||
}; |
|||
}); |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* Actor implementations must be registered in the dependency injection container |
|||
* Actor interfaces must inherit from `IActor` |
|||
* Actor type names can be customized using the `RemoteServiceAttribute` |
|||
* The module automatically maps Actor endpoints using ABP's endpoint routing system |
|||
* Actor runtime options should be configured in the `PreConfigureServices` phase |
|||
|
|||
## Endpoint Mapping |
|||
|
|||
The module automatically maps the following Actor endpoints: |
|||
* `/dapr/actors/{actorType}/{actorId}/method/{methodName}` |
|||
* `/dapr/actors/{actorType}/{actorId}/state` |
|||
* `/dapr/actors/{actorType}/{actorId}/reminders/{reminderName}` |
|||
* `/dapr/actors/{actorType}/{actorId}/timers/{timerName}` |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,129 @@ |
|||
# LINGYUN.Abp.Dapr.Actors |
|||
|
|||
Dapr.IActor client proxy module |
|||
|
|||
## Features |
|||
|
|||
* Dynamic proxy generation for Dapr Actors |
|||
* Integration with ABP's remote service system |
|||
* Support for Actor authentication and authorization |
|||
* Multi-tenant support |
|||
* Automatic request/response handling |
|||
* Custom error handling |
|||
* Culture and language header support |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprActorsModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Register proxies similar to Volo.Abp.Http.Client module |
|||
context.Services.AddDaprActorProxies( |
|||
typeof(YouProjectActorInterfaceModule).Assembly, // Search for IActor definitions in YouProjectActorInterfaceModule |
|||
RemoteServiceName |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Configuration Options |
|||
|
|||
```json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"BaseUrl": "http://localhost:3500", // Required, Dapr HTTP endpoint |
|||
"DaprApiToken": "your-api-token", // Optional, Dapr API Token |
|||
"RequestTimeout": "30000" // Optional, request timeout in milliseconds (default: 30000) |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Example |
|||
|
|||
1. Actor Interface Definition |
|||
|
|||
```csharp |
|||
public interface ICounterActor : IActor |
|||
{ |
|||
Task<int> GetCountAsync(); |
|||
Task IncrementCountAsync(); |
|||
} |
|||
``` |
|||
|
|||
2. Actor Implementation |
|||
|
|||
```csharp |
|||
public class CounterActor : Actor, ICounterActor |
|||
{ |
|||
private const string CountStateName = "count"; |
|||
|
|||
public CounterActor(ActorHost host) : base(host) |
|||
{ |
|||
} |
|||
|
|||
public async Task<int> GetCountAsync() |
|||
{ |
|||
var count = await StateManager.TryGetStateAsync<int>(CountStateName); |
|||
return count.HasValue ? count.Value : 0; |
|||
} |
|||
|
|||
public async Task IncrementCountAsync() |
|||
{ |
|||
var currentCount = await GetCountAsync(); |
|||
await StateManager.SetStateAsync(CountStateName, currentCount + 1); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
3. Client Usage |
|||
|
|||
```csharp |
|||
public class CounterService |
|||
{ |
|||
private readonly ICounterActor _counterActor; |
|||
|
|||
public CounterService(ICounterActor counterActor) |
|||
{ |
|||
_counterActor = counterActor; |
|||
} |
|||
|
|||
public async Task<int> GetAndIncrementCountAsync() |
|||
{ |
|||
var count = await _counterActor.GetCountAsync(); |
|||
await _counterActor.IncrementCountAsync(); |
|||
return count; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* Actor methods must return `Task` or `Task<T>` |
|||
* Actor methods can have at most one parameter |
|||
* Actor instances are single-threaded, processing one request at a time |
|||
* Actor state is managed by the Dapr runtime |
|||
* The module automatically handles: |
|||
* Authentication headers |
|||
* Tenant context |
|||
* Culture information |
|||
* Request timeouts |
|||
* Error handling |
|||
|
|||
## Error Handling |
|||
|
|||
The module provides custom error handling for Actor calls: |
|||
* `AbpDaprActorCallException`: Thrown when an Actor method call fails |
|||
* `ActorMethodInvocationException`: Contains detailed information about the failure |
|||
* Error responses include: |
|||
* Error message |
|||
* Error code |
|||
* Original exception type |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,189 @@ |
|||
# LINGYUN.Abp.Dapr.Client.Wrapper |
|||
|
|||
Dapr service-to-service invocation module that handles wrapped response result unpacking. |
|||
|
|||
## Features |
|||
|
|||
* Automatic response result wrapping/unwrapping |
|||
* Integration with ABP's wrapper system |
|||
* Custom error handling for wrapped responses |
|||
* Support for success/error code configuration |
|||
* HTTP status code mapping |
|||
* Support for custom response wrapper format |
|||
* Flexible wrapping control options |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprClientModule))] |
|||
public class AbpDaprClientWrapperModule : AbpModule |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
## Configuration Options |
|||
|
|||
The module uses `AbpWrapperOptions` from the `LINGYUN.Abp.Wrapper` package for configuration: |
|||
|
|||
```json |
|||
{ |
|||
"Wrapper": { |
|||
"IsEnabled": true, // Enable/disable response wrapping |
|||
"CodeWithSuccess": "0", // Success code in wrapped response |
|||
"HttpStatusCode": 200, // Default HTTP status code for wrapped responses |
|||
"WrapOnError": true, // Whether to wrap error responses |
|||
"WrapOnSuccess": true // Whether to wrap success responses |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Example |
|||
|
|||
1. Service Definition |
|||
|
|||
```csharp |
|||
public interface IProductService |
|||
{ |
|||
Task<ProductDto> GetAsync(string id); |
|||
Task<List<ProductDto>> GetListAsync(); |
|||
Task<ProductDto> CreateAsync(CreateProductDto input); |
|||
} |
|||
``` |
|||
|
|||
2. Service Implementation |
|||
|
|||
```csharp |
|||
public class ProductService : IProductService |
|||
{ |
|||
private readonly DaprClient _daprClient; |
|||
|
|||
public ProductService(DaprClient daprClient) |
|||
{ |
|||
_daprClient = daprClient; |
|||
} |
|||
|
|||
public async Task<ProductDto> GetAsync(string id) |
|||
{ |
|||
// Response wrapping is handled automatically |
|||
return await _daprClient.InvokeMethodAsync<ProductDto>( |
|||
"product-service", // Target service ID |
|||
$"api/products/{id}", // Method path |
|||
HttpMethod.Get |
|||
); |
|||
} |
|||
|
|||
public async Task<List<ProductDto>> GetListAsync() |
|||
{ |
|||
return await _daprClient.InvokeMethodAsync<List<ProductDto>>( |
|||
"product-service", |
|||
"api/products", |
|||
HttpMethod.Get |
|||
); |
|||
} |
|||
|
|||
public async Task<ProductDto> CreateAsync(CreateProductDto input) |
|||
{ |
|||
return await _daprClient.InvokeMethodAsync<ProductDto>( |
|||
"product-service", |
|||
"api/products", |
|||
HttpMethod.Post, |
|||
input |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Response Format |
|||
|
|||
When wrapping is enabled, the response will be in the following format: |
|||
|
|||
```json |
|||
{ |
|||
"code": "0", // Response code, "0" indicates success by default |
|||
"message": "Success", // Response message |
|||
"details": null, // Additional details (optional) |
|||
"result": { // Actual response data |
|||
// ... response content |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Error Handling |
|||
|
|||
The module automatically handles error responses: |
|||
* For wrapped responses (with `AbpWrapResult` header): |
|||
* Unwraps the response and checks the code |
|||
* If code doesn't match `CodeWithSuccess`, throws `AbpRemoteCallException` |
|||
* Includes error message, details, and code in the exception |
|||
* Supports custom error code mapping |
|||
* For unwrapped responses: |
|||
* Passes through the original response |
|||
* Uses standard HTTP error handling |
|||
* Maintains original error information |
|||
|
|||
### Error Response Example |
|||
|
|||
```json |
|||
{ |
|||
"code": "ERROR_001", |
|||
"message": "Product not found", |
|||
"details": "Product with ID '123' does not exist", |
|||
"result": null |
|||
} |
|||
``` |
|||
|
|||
## Advanced Usage |
|||
|
|||
### 1. Controlling Response Wrapping |
|||
|
|||
Response wrapping can be controlled per request using HTTP headers: |
|||
|
|||
```csharp |
|||
// Add to request headers |
|||
var headers = new Dictionary<string, string> |
|||
{ |
|||
{ "X-Abp-Wrap-Result", "true" }, // Force enable wrapping |
|||
// or |
|||
{ "X-Abp-Dont-Wrap-Result", "true" } // Force disable wrapping |
|||
}; |
|||
|
|||
await _daprClient.InvokeMethodAsync<ProductDto>( |
|||
"product-service", |
|||
"api/products", |
|||
HttpMethod.Get, |
|||
null, |
|||
headers |
|||
); |
|||
``` |
|||
|
|||
### 2. Custom Error Handling |
|||
|
|||
```csharp |
|||
public class CustomErrorHandler : IAbpWrapperErrorHandler |
|||
{ |
|||
public Task HandleAsync(AbpWrapperErrorContext context) |
|||
{ |
|||
// Custom error handling logic |
|||
if (context.Response.Code == "CUSTOM_ERROR") |
|||
{ |
|||
// Special handling |
|||
} |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* Response wrapping can be controlled through: |
|||
* Global settings in configuration |
|||
* HTTP headers for individual requests |
|||
* Dynamic control in code |
|||
* Error responses maintain original error structure when possible |
|||
* The module integrates with ABP's remote service error handling system |
|||
* Recommended to use response wrapping consistently in microservices architecture |
|||
* Wrapper format can be customized by implementing `IAbpWrapperResponseBuilder` |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,277 @@ |
|||
[Actors](../README.md) | Dapr.Client Documentation |
|||
|
|||
# LINGYUN.Abp.Dapr.Client |
|||
|
|||
Implements service-to-service invocation as described in the Dapr documentation. The project design is consistent with Volo.Abp.Http.Client and can seamlessly replace Volo.Abp.Http.Client through configuration. |
|||
|
|||
For configuration reference, see [AbpRemoteServiceOptions](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients#abpremoteserviceoptions) |
|||
|
|||
## Features |
|||
|
|||
* Integration with ABP remote service system |
|||
* Dynamic proxy generation |
|||
* Service discovery and load balancing |
|||
* Custom request and response handling |
|||
* Error handling and formatting |
|||
* Multiple service endpoint configuration |
|||
* Request/response interceptors |
|||
* Custom DaprClient behavior support |
|||
|
|||
## Configuration Options |
|||
|
|||
```json |
|||
{ |
|||
"RemoteServices": { |
|||
"Default": { |
|||
"AppId": "default-app", // Dapr application ID |
|||
"BaseUrl": "http://localhost:3500", // Dapr HTTP endpoint |
|||
"HealthCheckUrl": "/health", // Health check endpoint |
|||
"RequestTimeout": 30000, // Request timeout in milliseconds |
|||
"RetryCount": 3, // Number of retry attempts |
|||
"RetryWaitTime": 1000 // Retry wait time in milliseconds |
|||
}, |
|||
"System": { |
|||
"AppId": "system-app", |
|||
"BaseUrl": "http://localhost:50000", |
|||
"Headers": { // Custom request headers |
|||
"Tenant": "Default", |
|||
"Culture": "en-US" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprClientModule))] |
|||
public class YourProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Register proxies similar to Volo.Abp.Http.Client module |
|||
context.Services.AddDaprClientProxies( |
|||
typeof(YourProjectInterfaceModule).Assembly, // Search for remote service definitions |
|||
RemoteServiceName |
|||
); |
|||
|
|||
// Configure proxy options |
|||
Configure<AbpDaprClientProxyOptions>(options => |
|||
{ |
|||
// Configure request interceptor |
|||
options.ProxyRequestActions.Add((appId, request) => |
|||
{ |
|||
request.Headers.Add("Custom-Header", "Value"); |
|||
}); |
|||
|
|||
// Configure response handling |
|||
options.OnResponse(async (response, serviceProvider) => |
|||
{ |
|||
return await response.Content.ReadAsStringAsync(); |
|||
}); |
|||
|
|||
// Configure error handling |
|||
options.OnError(async (response, serviceProvider) => |
|||
{ |
|||
var error = await response.Content.ReadAsStringAsync(); |
|||
return new RemoteServiceErrorInfo |
|||
{ |
|||
Code = response.StatusCode.ToString(), |
|||
Message = error |
|||
}; |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Example |
|||
|
|||
### 1. Interface Definition |
|||
|
|||
```csharp |
|||
// IApplicationService implements IRemoteService |
|||
public interface ISystemAppService : IApplicationService |
|||
{ |
|||
Task<string> GetAsync(); |
|||
Task<SystemDto> CreateAsync(CreateSystemDto input); |
|||
Task<List<SystemDto>> GetListAsync(); |
|||
Task DeleteAsync(string id); |
|||
} |
|||
|
|||
public class SystemInterfaceModule : AbpModule |
|||
{ |
|||
} |
|||
``` |
|||
|
|||
### 2. Server Implementation |
|||
|
|||
```csharp |
|||
[DependsOn( |
|||
typeof(SystemInterfaceModule), |
|||
typeof(AbpAspNetCoreMvcModule) |
|||
)] |
|||
public class SystemServerModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<IMvcBuilder>(mvcBuilder => |
|||
{ |
|||
mvcBuilder.AddApplicationPartIfNotExists(typeof(SystemServerModule).Assembly); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class SystemAppService : ApplicationService, ISystemAppService |
|||
{ |
|||
private readonly ISystemRepository _systemRepository; |
|||
|
|||
public SystemAppService(ISystemRepository systemRepository) |
|||
{ |
|||
_systemRepository = systemRepository; |
|||
} |
|||
|
|||
public async Task<string> GetAsync() |
|||
{ |
|||
return "System"; |
|||
} |
|||
|
|||
public async Task<SystemDto> CreateAsync(CreateSystemDto input) |
|||
{ |
|||
var system = await _systemRepository.CreateAsync( |
|||
new System |
|||
{ |
|||
Name = input.Name, |
|||
Description = input.Description |
|||
} |
|||
); |
|||
return ObjectMapper.Map<System, SystemDto>(system); |
|||
} |
|||
|
|||
public async Task<List<SystemDto>> GetListAsync() |
|||
{ |
|||
var systems = await _systemRepository.GetListAsync(); |
|||
return ObjectMapper.Map<List<System>, List<SystemDto>>(systems); |
|||
} |
|||
|
|||
public async Task DeleteAsync(string id) |
|||
{ |
|||
await _systemRepository.DeleteAsync(id); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. Client Usage |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprClientModule))] |
|||
public class SystemClientModule : AbpModule |
|||
{ |
|||
private const string RemoteServiceName = "System"; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Register proxies |
|||
context.Services.AddDaprClientProxies( |
|||
typeof(SystemInterfaceModule).Assembly, |
|||
RemoteServiceName |
|||
); |
|||
|
|||
// Configure retry policy |
|||
context.Services.AddDaprClientBuilder(builder => |
|||
{ |
|||
builder.ConfigureHttpClient((sp, client) => |
|||
{ |
|||
client.Timeout = TimeSpan.FromSeconds(30); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public class SystemService |
|||
{ |
|||
private readonly ISystemAppService _systemAppService; |
|||
|
|||
public SystemService(ISystemAppService systemAppService) |
|||
{ |
|||
_systemAppService = systemAppService; |
|||
} |
|||
|
|||
public async Task<List<SystemDto>> GetSystemsAsync() |
|||
{ |
|||
try |
|||
{ |
|||
return await _systemAppService.GetListAsync(); |
|||
} |
|||
catch (AbpRemoteCallException ex) |
|||
{ |
|||
// Handle remote call exception |
|||
_logger.LogError(ex, "Failed to get systems"); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Advanced Usage |
|||
|
|||
### 1. Custom Request Handling |
|||
|
|||
```csharp |
|||
public class CustomRequestHandler |
|||
{ |
|||
public void Configure(HttpRequestMessage request) |
|||
{ |
|||
request.Headers.Add("Correlation-Id", Guid.NewGuid().ToString()); |
|||
request.Headers.Add("Client-Version", "1.0.0"); |
|||
} |
|||
} |
|||
|
|||
// Register in module |
|||
Configure<AbpDaprClientProxyOptions>(options => |
|||
{ |
|||
options.ProxyRequestActions.Add((appId, request) => |
|||
{ |
|||
new CustomRequestHandler().Configure(request); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
### 2. Custom Response Handling |
|||
|
|||
```csharp |
|||
public class CustomResponseHandler |
|||
{ |
|||
public async Task<string> HandleAsync(HttpResponseMessage response) |
|||
{ |
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
// Custom response handling logic |
|||
return content; |
|||
} |
|||
} |
|||
|
|||
// Register in module |
|||
Configure<AbpDaprClientProxyOptions>(options => |
|||
{ |
|||
options.OnResponse(async (response, sp) => |
|||
{ |
|||
return await new CustomResponseHandler().HandleAsync(response); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* Remote service interfaces must inherit `IRemoteService` |
|||
* Configuration changes require recreating proxy instances |
|||
* Configure appropriate timeout and retry policies |
|||
* Error handling should consider network exceptions and service unavailability |
|||
* Enable service discovery in production environments |
|||
* Use health checks to ensure service availability |
|||
* Request header configuration should consider security and authentication requirements |
|||
* Logging is important for problem diagnosis |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,204 @@ |
|||
# LINGYUN.Abp.Dapr |
|||
|
|||
Dapr integration base module, implementing the named singleton DaprClient as described in the Dapr documentation. |
|||
|
|||
See: https://docs.dapr.io/developing-applications/sdks/dotnet/dotnet-client/dotnet-daprclient-usage |
|||
|
|||
## Features |
|||
|
|||
* Support for creating default and named DaprClient instances |
|||
* Support for configuring HTTP and gRPC endpoints |
|||
* Support for custom JSON serialization options |
|||
* Support for Dapr API Token authentication |
|||
* Support for gRPC channel configuration |
|||
* Support for DaprClient instance configuration and builder configuration extensions |
|||
* Support for multiple Dapr Sidecar connections |
|||
* Support for custom DaprClient behaviors |
|||
|
|||
## Configuration Options |
|||
|
|||
```json |
|||
{ |
|||
"Dapr": { |
|||
"Client": { |
|||
"DaprApiToken": "your-api-token", // Optional, Dapr API Token |
|||
"HttpEndpoint": "http://localhost:3500", // Optional, HTTP endpoint |
|||
"GrpcEndpoint": "http://localhost:50001", // Optional, gRPC endpoint |
|||
"JsonSerializerOptions": { // Optional, JSON serialization options |
|||
"PropertyNamingPolicy": "CamelCase", |
|||
"PropertyNameCaseInsensitive": true, |
|||
"WriteIndented": true, |
|||
"DefaultIgnoreCondition": "WhenWritingNull" |
|||
}, |
|||
"GrpcChannelOptions": { // Optional, gRPC channel options |
|||
"Credentials": "Insecure", |
|||
"MaxReceiveMessageSize": 1048576, |
|||
"MaxSendMessageSize": 1048576 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Basic Usage |
|||
|
|||
Module reference as needed: |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDaprModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Create a DaprClient |
|||
context.Services.AddDaprClient(); |
|||
|
|||
// Create a named DaprClient |
|||
context.Services.AddDaprClient("__DaprClient"); |
|||
|
|||
// Configure DaprClient options |
|||
Configure<DaprClientFactoryOptions>(options => |
|||
{ |
|||
options.HttpEndpoint = "http://localhost:3500"; |
|||
options.GrpcEndpoint = "http://localhost:50001"; |
|||
options.DaprApiToken = "your-api-token"; |
|||
|
|||
// Add DaprClient configuration actions |
|||
options.DaprClientActions.Add(client => |
|||
{ |
|||
// Configure DaprClient instance |
|||
}); |
|||
|
|||
// Add DaprClientBuilder configuration actions |
|||
options.DaprClientBuilderActions.Add(builder => |
|||
{ |
|||
// Configure DaprClientBuilder |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Advanced Usage |
|||
|
|||
### 1. Configure DaprClient |
|||
|
|||
```csharp |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Configure named DaprClient |
|||
context.Services.AddDaprClient("CustomClient", builder => |
|||
{ |
|||
// Configure HTTP endpoint |
|||
builder.UseHttpEndpoint("http://localhost:3500"); |
|||
|
|||
// Configure gRPC endpoint |
|||
builder.UseGrpcEndpoint("http://localhost:50001"); |
|||
|
|||
// Configure API Token |
|||
builder.UseDaprApiToken("your-api-token"); |
|||
|
|||
// Configure JSON serialization options |
|||
builder.UseJsonSerializerOptions(new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
|||
PropertyNameCaseInsensitive = true |
|||
}); |
|||
|
|||
// Configure gRPC channel options |
|||
builder.UseGrpcChannelOptions(new GrpcChannelOptions |
|||
{ |
|||
MaxReceiveMessageSize = 1024 * 1024, |
|||
MaxSendMessageSize = 1024 * 1024 |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
### 2. Using DaprClient |
|||
|
|||
```csharp |
|||
public class YourService |
|||
{ |
|||
private readonly IDaprClientFactory _daprClientFactory; |
|||
|
|||
public YourService(IDaprClientFactory daprClientFactory) |
|||
{ |
|||
_daprClientFactory = daprClientFactory; |
|||
} |
|||
|
|||
public async Task InvokeMethodAsync() |
|||
{ |
|||
// Use default client |
|||
var defaultClient = _daprClientFactory.CreateClient(); |
|||
|
|||
// Use named client |
|||
var namedClient = _daprClientFactory.CreateClient("CustomClient"); |
|||
|
|||
// Invoke service method |
|||
var response = await defaultClient.InvokeMethodAsync<OrderDto>( |
|||
HttpMethod.Get, |
|||
"order-service", // Target service ID |
|||
"api/orders/1", // Method path |
|||
new { id = 1 } // Request parameters |
|||
); |
|||
|
|||
// Publish event |
|||
await defaultClient.PublishEventAsync( |
|||
"pubsub", // Pub/sub component name |
|||
"order-created", // Topic name |
|||
response // Event data |
|||
); |
|||
|
|||
// Save state |
|||
await defaultClient.SaveStateAsync( |
|||
"statestore", // State store component name |
|||
"order-1", // Key |
|||
response // Value |
|||
); |
|||
|
|||
// Get state |
|||
var state = await defaultClient.GetStateAsync<OrderDto>( |
|||
"statestore", // State store component name |
|||
"order-1" // Key |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. Custom DaprClient Behavior |
|||
|
|||
```csharp |
|||
public class CustomDaprClientBehavior |
|||
{ |
|||
public void Configure(DaprClient client) |
|||
{ |
|||
// Configure custom behavior |
|||
} |
|||
} |
|||
|
|||
// Register in module |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<DaprClientFactoryOptions>(options => |
|||
{ |
|||
options.DaprClientActions.Add(client => |
|||
{ |
|||
new CustomDaprClientBehavior().Configure(client); |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
## Important Notes |
|||
|
|||
* DaprClient instances are thread-safe, singleton pattern is recommended |
|||
* Named DaprClients can have different configurations, suitable for scenarios requiring connections to different Dapr Sidecars |
|||
* Configuration changes require recreating the DaprClient instance to take effect |
|||
* Pay attention to performance and resource consumption when configuring gRPC channels |
|||
* JSON serialization options affect all requests using that DaprClient |
|||
* API Tokens should be managed through secure configuration management systems |
|||
* Recommended to use different named DaprClients for different microservices |
|||
* Configure appropriate timeout and retry policies in production environments |
|||
|
|||
[查看中文](README.md) |
|||
@ -0,0 +1,256 @@ |
|||
# LINGYUN.Abp.DistributedLocking.Dapr |
|||
|
|||
An ABP distributed locking implementation based on the Dapr distributed lock API. This module provides seamless integration with Dapr's distributed locking service, supporting cross-service and cross-instance locking capabilities. |
|||
|
|||
Reference: [Dapr Distributed Lock API](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/) |
|||
|
|||
## Features |
|||
|
|||
* Integration with ABP distributed locking system |
|||
* Support for custom lock resource owner identification |
|||
* Configurable lock timeout duration |
|||
* Automatic lock release support |
|||
* Multiple lock storage component support |
|||
* Lock acquisition and release event notifications |
|||
* Distributed lock health check support |
|||
|
|||
## Configuration Options |
|||
|
|||
```json |
|||
{ |
|||
"DistributedLocking": { |
|||
"Dapr": { |
|||
"StoreName": "lockstore", // Storage name defined in Dapr component |
|||
"DefaultIdentifier": "dapr-lock-owner", // Default lock resource owner identifier |
|||
"DefaultTimeout": "00:00:30" // Default lock timeout (30 seconds) |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Basic Usage |
|||
|
|||
### 1. Module Configuration |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDistributedLockingDaprModule))] |
|||
public class YourProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Basic configuration |
|||
Configure<AbpDistributedLockingDaprOptions>(options => |
|||
{ |
|||
options.StoreName = "redis-lock"; // Use Redis as lock storage |
|||
options.DefaultIdentifier = "my-service"; // Custom lock owner identifier |
|||
options.DefaultTimeout = TimeSpan.FromMinutes(1); // Set default timeout to 1 minute |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 2. Basic Usage |
|||
|
|||
```csharp |
|||
public class OrderService |
|||
{ |
|||
private readonly IDistributedLockProvider _lockProvider; |
|||
|
|||
public OrderService(IDistributedLockProvider lockProvider) |
|||
{ |
|||
_lockProvider = lockProvider; |
|||
} |
|||
|
|||
public async Task ProcessOrderAsync(string orderId) |
|||
{ |
|||
// Try to acquire lock |
|||
using (var handle = await _lockProvider.TryAcquireAsync($"order:{orderId}")) |
|||
{ |
|||
if (handle != null) |
|||
{ |
|||
try |
|||
{ |
|||
// Execute business logic that requires locking |
|||
await ProcessOrderInternalAsync(orderId); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
// Handle exception |
|||
_logger.LogError(ex, "Error occurred while processing order"); |
|||
throw; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new ConcurrencyException("Order is being processed by another process"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. Advanced Usage |
|||
|
|||
```csharp |
|||
public class InventoryService |
|||
{ |
|||
private readonly IDistributedLockProvider _lockProvider; |
|||
private readonly ILogger<InventoryService> _logger; |
|||
|
|||
public InventoryService( |
|||
IDistributedLockProvider lockProvider, |
|||
ILogger<InventoryService> logger) |
|||
{ |
|||
_lockProvider = lockProvider; |
|||
_logger = logger; |
|||
} |
|||
|
|||
public async Task UpdateInventoryAsync(string productId, int quantity) |
|||
{ |
|||
// Custom lock configuration |
|||
var lockOptions = new DistributedLockOptions |
|||
{ |
|||
Timeout = TimeSpan.FromSeconds(10), // Custom timeout |
|||
RetryDelay = TimeSpan.FromMilliseconds(100) // Retry delay |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
using (var handle = await _lockProvider.TryAcquireAsync( |
|||
$"inventory:{productId}", |
|||
lockOptions)) |
|||
{ |
|||
if (handle == null) |
|||
{ |
|||
_logger.LogWarning("Unable to acquire inventory lock for product ID: {ProductId}", productId); |
|||
throw new ConcurrencyException("Unable to acquire inventory lock"); |
|||
} |
|||
|
|||
// Execute inventory update operation |
|||
await UpdateInventoryInternalAsync(productId, quantity); |
|||
} |
|||
} |
|||
catch (Exception ex) when (ex is not ConcurrencyException) |
|||
{ |
|||
_logger.LogError(ex, "Error occurred while updating inventory"); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Component Configuration |
|||
|
|||
### Redis Lock Store Configuration Example |
|||
|
|||
```yaml |
|||
apiVersion: dapr.io/v1alpha1 |
|||
kind: Component |
|||
metadata: |
|||
name: redis-lock # Corresponds to StoreName configuration |
|||
spec: |
|||
type: lock.redis |
|||
version: v1 |
|||
metadata: |
|||
- name: redisHost |
|||
value: localhost:6379 |
|||
- name: redisPassword |
|||
value: "" |
|||
- name: enableTLS |
|||
value: false |
|||
- name: maxRetries |
|||
value: 5 |
|||
- name: maxRetryBackoff |
|||
value: 5s |
|||
``` |
|||
|
|||
### Consul Lock Store Configuration Example |
|||
|
|||
```yaml |
|||
apiVersion: dapr.io/v1alpha1 |
|||
kind: Component |
|||
metadata: |
|||
name: consul-lock |
|||
spec: |
|||
type: lock.consul |
|||
version: v1 |
|||
metadata: |
|||
- name: host |
|||
value: localhost:8500 |
|||
- name: sessionTTL |
|||
value: 10 |
|||
- name: scheme |
|||
value: http |
|||
``` |
|||
|
|||
## Core Interfaces |
|||
|
|||
### ILockOwnerFinder |
|||
|
|||
Interface for providing lock resource owner identification. |
|||
|
|||
```csharp |
|||
public interface ILockOwnerFinder |
|||
{ |
|||
string GetOwner(); |
|||
} |
|||
``` |
|||
|
|||
The default implementation `LockOwnerFinder`: |
|||
1. Primarily uses the current user ID as the lock owner identifier |
|||
2. Falls back to the configured `DefaultIdentifier` if no user is logged in |
|||
|
|||
### Custom Lock Owner Identifier Implementation |
|||
|
|||
```csharp |
|||
public class CustomLockOwnerFinder : ILockOwnerFinder |
|||
{ |
|||
private readonly ICurrentTenant _currentTenant; |
|||
|
|||
public CustomLockOwnerFinder(ICurrentTenant currentTenant) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
} |
|||
|
|||
public string GetOwner() |
|||
{ |
|||
// Use combination of tenant ID and machine name as lock owner identifier |
|||
return $"{_currentTenant.Id ?? "host"}-{Environment.MachineName}"; |
|||
} |
|||
} |
|||
|
|||
// Register custom implementation |
|||
context.Services.AddTransient<ILockOwnerFinder, CustomLockOwnerFinder>(); |
|||
``` |
|||
|
|||
## Best Practices |
|||
|
|||
1. **Set Appropriate Timeout Duration** |
|||
- Set timeout based on expected execution time of business operations |
|||
- Avoid setting excessively long timeouts to prevent deadlocks |
|||
|
|||
2. **Proper Lock Granularity** |
|||
- Keep lock scope as small as possible, only lock necessary resources |
|||
- Avoid holding locks for extended periods, release promptly |
|||
|
|||
3. **Exception Handling** |
|||
- Always use locks within using blocks |
|||
- Handle lock acquisition failures appropriately |
|||
- Log critical lock operations |
|||
|
|||
4. **Performance Optimization** |
|||
- Use appropriate storage components |
|||
- Configure suitable retry policies |
|||
- Monitor lock usage |
|||
|
|||
## Important Notes |
|||
|
|||
* Ensure Dapr Sidecar is properly configured and running |
|||
* Distributed lock component must be correctly defined in Dapr configuration |
|||
* Set appropriate lock timeouts to avoid deadlocks |
|||
* Handle lock acquisition failures properly |
|||
* Consider performance impact in high-concurrency scenarios |
|||
* Configure health checks for lock components |
|||
* Add logging for important operations |
|||
|
|||
[查看中文](README.md) |
|||
@ -1,38 +1,256 @@ |
|||
# LINGYUN.Abp.DistributedLocking.Dapr |
|||
|
|||
Abp分布式锁的Dapr实现 |
|||
基于Dapr分布式锁API的ABP分布式锁实现。该模块提供了与Dapr分布式锁服务的无缝集成,支持跨服务、跨实例的分布式锁定功能。 |
|||
|
|||
See: https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/ |
|||
参考文档: [Dapr Distributed Lock API](https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/) |
|||
|
|||
## 配置使用 |
|||
## 功能特性 |
|||
|
|||
模块按需引用 |
|||
* 与ABP分布式锁系统集成 |
|||
* 支持自定义锁资源拥有者标识 |
|||
* 支持可配置的锁定超时时间 |
|||
* 支持锁资源自动释放 |
|||
* 支持多种锁存储组件 |
|||
* 支持锁获取和释放的事件通知 |
|||
* 支持分布式锁的健康检查 |
|||
|
|||
## 配置选项 |
|||
|
|||
```json |
|||
{ |
|||
"DistributedLocking": { |
|||
"Dapr": { |
|||
"StoreName": "lockstore", // Dapr组件中定义的存储名称 |
|||
"DefaultIdentifier": "dapr-lock-owner", // 默认锁资源拥有者标识 |
|||
"DefaultTimeout": "00:00:30" // 默认锁定超时时间(30秒) |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 基础使用 |
|||
|
|||
### 1. 模块配置 |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpDistributedLockingDaprModule))] |
|||
public class YouProjectModule : AbpModule |
|||
public class YourProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// 基础配置 |
|||
Configure<AbpDistributedLockingDaprOptions>(options => |
|||
{ |
|||
options.StoreName = "store-name"; |
|||
options.DefaultIdentifier = "default-owner-id"; |
|||
options.DefaultTimeout = TimeSpan.FromSeconds(30); |
|||
options.StoreName = "redis-lock"; // 使用Redis作为锁存储 |
|||
options.DefaultIdentifier = "my-service"; // 自定义锁拥有者标识 |
|||
options.DefaultTimeout = TimeSpan.FromMinutes(1); // 设置默认超时时间为1分钟 |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 配置说明 |
|||
### 2. 基本用法 |
|||
|
|||
```csharp |
|||
public class OrderService |
|||
{ |
|||
private readonly IDistributedLockProvider _lockProvider; |
|||
|
|||
public OrderService(IDistributedLockProvider lockProvider) |
|||
{ |
|||
_lockProvider = lockProvider; |
|||
} |
|||
|
|||
public async Task ProcessOrderAsync(string orderId) |
|||
{ |
|||
// 尝试获取锁 |
|||
using (var handle = await _lockProvider.TryAcquireAsync($"order:{orderId}")) |
|||
{ |
|||
if (handle != null) |
|||
{ |
|||
try |
|||
{ |
|||
// 执行需要加锁的业务逻辑 |
|||
await ProcessOrderInternalAsync(orderId); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
// 处理异常 |
|||
_logger.LogError(ex, "处理订单时发生错误"); |
|||
throw; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
throw new ConcurrencyException("订单正在被其他进程处理"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. 高级用法 |
|||
|
|||
```csharp |
|||
public class InventoryService |
|||
{ |
|||
private readonly IDistributedLockProvider _lockProvider; |
|||
private readonly ILogger<InventoryService> _logger; |
|||
|
|||
public InventoryService( |
|||
IDistributedLockProvider lockProvider, |
|||
ILogger<InventoryService> logger) |
|||
{ |
|||
_lockProvider = lockProvider; |
|||
_logger = logger; |
|||
} |
|||
|
|||
public async Task UpdateInventoryAsync(string productId, int quantity) |
|||
{ |
|||
// 自定义锁配置 |
|||
var lockOptions = new DistributedLockOptions |
|||
{ |
|||
Timeout = TimeSpan.FromSeconds(10), // 自定义超时时间 |
|||
RetryDelay = TimeSpan.FromMilliseconds(100) // 重试延迟 |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
using (var handle = await _lockProvider.TryAcquireAsync( |
|||
$"inventory:{productId}", |
|||
lockOptions)) |
|||
{ |
|||
if (handle == null) |
|||
{ |
|||
_logger.LogWarning("无法获取库存锁,产品ID: {ProductId}", productId); |
|||
throw new ConcurrencyException("无法获取库存锁"); |
|||
} |
|||
|
|||
// 执行库存更新操作 |
|||
await UpdateInventoryInternalAsync(productId, quantity); |
|||
} |
|||
} |
|||
catch (Exception ex) when (ex is not ConcurrencyException) |
|||
{ |
|||
_logger.LogError(ex, "更新库存时发生错误"); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 组件配置 |
|||
|
|||
### Redis锁存储配置示例 |
|||
|
|||
```yaml |
|||
apiVersion: dapr.io/v1alpha1 |
|||
kind: Component |
|||
metadata: |
|||
name: redis-lock # 对应StoreName配置 |
|||
spec: |
|||
type: lock.redis |
|||
version: v1 |
|||
metadata: |
|||
- name: redisHost |
|||
value: localhost:6379 |
|||
- name: redisPassword |
|||
value: "" |
|||
- name: enableTLS |
|||
value: false |
|||
- name: maxRetries |
|||
value: 5 |
|||
- name: maxRetryBackoff |
|||
value: 5s |
|||
``` |
|||
|
|||
### Consul锁存储配置示例 |
|||
|
|||
```yaml |
|||
apiVersion: dapr.io/v1alpha1 |
|||
kind: Component |
|||
metadata: |
|||
name: consul-lock |
|||
spec: |
|||
type: lock.consul |
|||
version: v1 |
|||
metadata: |
|||
- name: host |
|||
value: localhost:8500 |
|||
- name: sessionTTL |
|||
value: 10 |
|||
- name: scheme |
|||
value: http |
|||
``` |
|||
|
|||
## 核心接口 |
|||
|
|||
### ILockOwnerFinder |
|||
|
|||
提供锁资源持有者标识的接口。 |
|||
|
|||
```csharp |
|||
public interface ILockOwnerFinder |
|||
{ |
|||
string GetOwner(); |
|||
} |
|||
``` |
|||
|
|||
默认实现 `LockOwnerFinder` 会: |
|||
1. 优先使用当前用户ID作为锁拥有者标识 |
|||
2. 如果用户未登录,则使用配置的 `DefaultIdentifier` |
|||
|
|||
### 自定义锁拥有者标识实现 |
|||
|
|||
```csharp |
|||
public class CustomLockOwnerFinder : ILockOwnerFinder |
|||
{ |
|||
private readonly ICurrentTenant _currentTenant; |
|||
|
|||
public CustomLockOwnerFinder(ICurrentTenant currentTenant) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
} |
|||
|
|||
public string GetOwner() |
|||
{ |
|||
// 使用租户ID和机器名称组合作为锁拥有者标识 |
|||
return $"{_currentTenant.Id ?? "host"}-{Environment.MachineName}"; |
|||
} |
|||
} |
|||
|
|||
// 注册自定义实现 |
|||
context.Services.AddTransient<ILockOwnerFinder, CustomLockOwnerFinder>(); |
|||
``` |
|||
|
|||
## 最佳实践 |
|||
|
|||
1. **合理设置超时时间** |
|||
- 根据业务操作的预期执行时间设置合适的超时时间 |
|||
- 避免设置过长的超时时间,以防止死锁 |
|||
|
|||
2. **正确的锁粒度** |
|||
- 锁的范围应该尽可能小,只锁定必要的资源 |
|||
- 避免长时间持有锁,及时释放 |
|||
|
|||
3. **异常处理** |
|||
- 始终在 using 块中使用锁 |
|||
- 妥善处理锁获取失败的情况 |
|||
- 记录关键的锁操作日志 |
|||
|
|||
* AbpDistributedLockingDaprOptions.StoreName 在dapr component文件中定义的metadata name,默认: lockstore; |
|||
* AbpDistributedLockingDaprOptions.DefaultIdentifier 默认锁资源拥有者标识,默认: dapr-lock-owner; |
|||
* AbpDistributedLockingDaprOptions.DefaultTimeout 默认锁定超时时间,默认: 30s. |
|||
4. **性能优化** |
|||
- 使用合适的存储组件 |
|||
- 配置适当的重试策略 |
|||
- 监控锁的使用情况 |
|||
|
|||
## 接口说明 |
|||
## 注意事项 |
|||
|
|||
[ILockOwnerFinder](./LINGYUN/Abp/DistributedLocking/Dapr/ILockOwnerFinder), 提供锁资源持有者标识 |
|||
默认实现 [LockOwnerFinder](./LINGYUN/Abp/DistributedLocking/Dapr/LockOwnerFinder), 获取用户标识,如果不存在,返回DefaultIdentifier |
|||
* 确保Dapr Sidecar已正确配置并运行 |
|||
* 分布式锁组件需要在Dapr配置中正确定义 |
|||
* 合理设置锁的超时时间,避免死锁 |
|||
* 正确处理锁获取失败的情况 |
|||
* 在高并发场景下注意性能影响 |
|||
* 建议配置锁组件的健康检查 |
|||
* 重要操作建议添加日志记录 |
|||
|
|||
## 其他 |
|||
[查看英文](README.EN.md) |
|||
|
|||
Loading…
Reference in new issue