Compare commits
79 Commits
master
...
refactor/r
Author | SHA1 | Date |
---|---|---|
|
2c0d6c9d09 | 2 months ago |
|
fcb169e667 | 2 months ago |
|
d161bccab2 | 2 months ago |
|
3bf83be5e9 | 2 months ago |
|
00933ee3ee | 2 months ago |
|
01bf2e727c | 2 months ago |
|
f572287401 | 2 months ago |
|
b3e8943169 | 2 months ago |
|
35c339418d | 2 months ago |
|
9f50481ebd | 2 months ago |
|
c484d3aa23 | 2 months ago |
|
47b4f6e0e8 | 2 months ago |
|
0e321f828b | 2 months ago |
|
b7982d530e | 2 months ago |
|
67076199b0 | 2 months ago |
|
716637c03b | 2 months ago |
|
5227ebfcf4 | 2 months ago |
|
fd1d2b1e70 | 3 months ago |
|
3d2ab3a071 | 3 months ago |
|
ff634ad4aa | 3 months ago |
|
1a2c88c6d8 | 3 months ago |
|
128f3be343 | 3 months ago |
|
6114cb226d | 3 months ago |
|
dc74694c68 | 3 months ago |
|
85930db010 | 3 months ago |
|
a551bda406 | 3 months ago |
|
b0a379f9b2 | 3 months ago |
|
3d41e2e275 | 3 months ago |
|
2a75722a4d | 3 months ago |
|
14bc6be277 | 3 months ago |
|
09eaa7efbe | 3 months ago |
|
986487be47 | 3 months ago |
|
492eaa169d | 3 months ago |
|
0a4eff6cf6 | 3 months ago |
|
29dd65651f | 3 months ago |
|
423f5335be | 3 months ago |
|
c4cdd28411 | 3 months ago |
|
845357e59b | 3 months ago |
|
6a17a8fb0a | 3 months ago |
|
7cc1fd7a09 | 3 months ago |
|
83a6cc88ce | 3 months ago |
|
042ed88bc0 | 3 months ago |
|
2fe019001f | 3 months ago |
|
ea307a2077 | 3 months ago |
|
fe78db0f53 | 3 months ago |
|
75f24121d1 | 3 months ago |
|
81d5f2243e | 3 months ago |
|
2ef5a02eca | 3 months ago |
|
3b237ed510 | 3 months ago |
|
fd14a6acfc | 3 months ago |
|
d18b2bb245 | 3 months ago |
|
2339a4273c | 3 months ago |
|
4dcb222883 | 3 months ago |
|
2c72231a05 | 3 months ago |
|
71953e6277 | 3 months ago |
|
6e5a8eb8a7 | 3 months ago |
|
b300a64521 | 3 months ago |
|
219261ef93 | 3 months ago |
|
50802175b9 | 3 months ago |
|
eb970f1004 | 3 months ago |
|
bb79ab6b8b | 3 months ago |
|
da9345dbad | 3 months ago |
|
efcb2882a1 | 3 months ago |
|
65e729867e | 3 months ago |
|
aec7c755bc | 3 months ago |
|
f57dc0e001 | 3 months ago |
|
bb9cd356c9 | 3 months ago |
|
0363255029 | 3 months ago |
|
85cecfb653 | 3 months ago |
|
3dafc526f5 | 3 months ago |
|
884dd79538 | 3 months ago |
|
414f68bbe4 | 3 months ago |
|
5084ae78ff | 3 months ago |
|
68f47da591 | 3 months ago |
|
57292385ad | 3 months ago |
|
e0bb742fbe | 3 months ago |
|
554ca522f6 | 3 months ago |
|
6916280258 | 3 months ago |
|
f7aff85d75 | 3 months ago |
572 changed files with 33924 additions and 21085 deletions
@ -0,0 +1,211 @@ |
|||
# JWT 实现指南 |
|||
|
|||
## 1. JwtOptions(配置类) |
|||
|
|||
### 1.1 主要职责 |
|||
- 存储和管理 JWT 相关的所有配置项 |
|||
- 提供配置验证功能 |
|||
- 支持从配置文件加载配置 |
|||
|
|||
### 1.2 关键配置项 |
|||
```csharp |
|||
public class JwtOptions |
|||
{ |
|||
public string SecretKey { get; set; } // JWT 密钥 |
|||
public string Issuer { get; set; } // 颁发者 |
|||
public string Audience { get; set; } // 受众 |
|||
public int ExpiryMinutes { get; set; } // 访问令牌过期时间 |
|||
public int RefreshTokenExpiryDays { get; set; } // 刷新令牌过期时间 |
|||
public int ClockSkewMinutes { get; set; } // 时钟偏差 |
|||
public int KeyRotationDays { get; set; } // 密钥轮换间隔 |
|||
public int MinKeyLength { get; set; } // 密钥最小长度 |
|||
} |
|||
``` |
|||
|
|||
### 1.3 配置验证 |
|||
- 验证所有必需字段不为空 |
|||
- 验证数值字段的有效性 |
|||
- 验证密钥格式和长度 |
|||
|
|||
## 2. JwtOptionsExtensions(扩展方法类) |
|||
|
|||
### 2.1 主要职责 |
|||
- 提供 JwtOptions 的扩展功能 |
|||
- 实现密钥强度验证 |
|||
- 提供熵值计算功能 |
|||
|
|||
### 2.2 关键方法 |
|||
```csharp |
|||
public static class JwtOptionsExtensions |
|||
{ |
|||
// 验证密钥强度 |
|||
public static void ValidateKeyStrength(this JwtOptions options) |
|||
|
|||
// 计算字符串熵值 |
|||
private static double CalculateEntropy(string input) |
|||
} |
|||
``` |
|||
|
|||
### 2.3 验证标准 |
|||
- 密钥不能为空 |
|||
- 密钥必须是有效的 Base64 字符串 |
|||
- 密钥长度必须满足最小长度要求 |
|||
- 密钥熵值必须大于 3.5(确保足够的随机性) |
|||
|
|||
## 3. KeyRotationService(密钥管理服务) |
|||
|
|||
### 3.1 主要职责 |
|||
- 管理 JWT 密钥的生命周期 |
|||
- 实现密钥自动轮换 |
|||
- 提供密钥生成和验证功能 |
|||
|
|||
### 3.2 关键功能 |
|||
- 密钥初始化 |
|||
- 密钥轮换 |
|||
- 密钥强度验证 |
|||
- 密钥缓存管理 |
|||
|
|||
### 3.3 安全特性 |
|||
- 定期自动轮换密钥 |
|||
- 支持密钥预热 |
|||
- 防止密钥泄露 |
|||
- 密钥强度保证 |
|||
|
|||
## 4. JwtProvider(JWT 令牌提供者) |
|||
|
|||
### 4.1 主要职责 |
|||
- 生成 JWT 访问令牌和刷新令牌 |
|||
- 验证 JWT 令牌 |
|||
- 管理令牌黑名单 |
|||
- 提供令牌解析功能 |
|||
|
|||
### 4.2 关键方法 |
|||
```csharp |
|||
public interface IJwtProvider |
|||
{ |
|||
string GenerateAccessToken(IEnumerable<Claim> claims); |
|||
string GenerateRefreshToken(IEnumerable<Claim> claims); |
|||
bool ValidateToken(string token); |
|||
void RevokeToken(string token); |
|||
void AddToBlacklist(string token); |
|||
IEnumerable<Claim> GetClaimsFromToken(string token); |
|||
} |
|||
``` |
|||
|
|||
### 4.3 安全特性 |
|||
- 令牌撤销机制 |
|||
- 令牌黑名单 |
|||
- 完整的令牌验证 |
|||
- 支持自定义声明 |
|||
|
|||
## 5. JwtBearerOptionsSetup(JWT Bearer 认证配置) |
|||
|
|||
### 5.1 主要职责 |
|||
- 配置 ASP.NET Core JWT Bearer 认证 |
|||
- 设置令牌验证参数 |
|||
- 配置认证事件处理 |
|||
|
|||
### 5.2 关键配置 |
|||
```csharp |
|||
public class JwtBearerOptionsSetup : IConfigureOptions<JwtBearerOptions> |
|||
{ |
|||
public void Configure(JwtBearerOptions options) |
|||
{ |
|||
// 配置令牌验证参数 |
|||
options.TokenValidationParameters = new TokenValidationParameters |
|||
{ |
|||
ValidateIssuer = true, |
|||
ValidIssuer = _jwtOptions.Issuer, |
|||
ValidateAudience = true, |
|||
ValidAudience = _jwtOptions.Audience, |
|||
ValidateLifetime = true, |
|||
// ... 其他配置 |
|||
}; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 5.3 安全特性 |
|||
- 强制使用 HTTPS |
|||
- 完整的令牌验证 |
|||
- 详细的日志记录 |
|||
- 异常处理机制 |
|||
|
|||
## 安全最佳实践 |
|||
|
|||
1. **密钥管理** |
|||
- 使用足够长的随机密钥 |
|||
- 定期轮换密钥 |
|||
- 使用安全的密钥存储机制 |
|||
|
|||
2. **令牌安全** |
|||
- 设置合理的过期时间 |
|||
- 实现令牌撤销机制 |
|||
- 使用令牌黑名单 |
|||
|
|||
3. **传输安全** |
|||
- 强制使用 HTTPS |
|||
- 验证令牌签名 |
|||
- 验证令牌来源 |
|||
|
|||
4. **配置安全** |
|||
- 验证所有配置项 |
|||
- 使用强类型配置 |
|||
- 避免硬编码敏感信息 |
|||
|
|||
## 使用示例 |
|||
|
|||
### 1. 配置 JWT 选项 |
|||
```csharp |
|||
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName)); |
|||
``` |
|||
|
|||
### 2. 注册服务 |
|||
```csharp |
|||
services.AddScoped<IJwtProvider, JwtProvider>(); |
|||
services.AddScoped<IKeyRotationService, KeyRotationService>(); |
|||
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>(); |
|||
``` |
|||
|
|||
### 3. 使用 JWT 提供者 |
|||
```csharp |
|||
public class AuthController : ControllerBase |
|||
{ |
|||
private readonly IJwtProvider _jwtProvider; |
|||
|
|||
public AuthController(IJwtProvider jwtProvider) |
|||
{ |
|||
_jwtProvider = jwtProvider; |
|||
} |
|||
|
|||
public IActionResult Login(LoginRequest request) |
|||
{ |
|||
// 验证用户 |
|||
var claims = GetUserClaims(user); |
|||
var token = _jwtProvider.GenerateAccessToken(claims); |
|||
return Ok(new { token }); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 注意事项 |
|||
|
|||
1. **密钥管理** |
|||
- 不要在代码中硬编码密钥 |
|||
- 使用环境变量或密钥管理服务 |
|||
- 定期轮换密钥 |
|||
|
|||
2. **令牌配置** |
|||
- 设置合理的过期时间 |
|||
- 启用所有安全验证 |
|||
- 使用 HTTPS |
|||
|
|||
3. **错误处理** |
|||
- 实现完整的错误处理 |
|||
- 记录详细的日志 |
|||
- 返回适当的错误信息 |
|||
|
|||
4. **性能考虑** |
|||
- 使用缓存机制 |
|||
- 优化令牌验证 |
|||
- 控制令牌大小 |
@ -0,0 +1,217 @@ |
|||
# JWT 服务注册指南 |
|||
|
|||
## 服务注册概览 |
|||
|
|||
JWT 相关的服务注册主要分布在两个位置: |
|||
1. `Program.cs` - Web API 层的服务注册 |
|||
2. `DependencyInjection.cs` - 基础设施层的服务注册 |
|||
|
|||
## 1. 基础设施层注册 (DependencyInjection.cs) |
|||
|
|||
### 1.1 JWT 配置注册 |
|||
```csharp |
|||
// 配置 JWT 选项 |
|||
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName)); |
|||
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>(); |
|||
services.AddScoped<IJwtProvider, JwtProvider>(); |
|||
``` |
|||
|
|||
说明: |
|||
- `Configure<JwtOptions>`: 从配置文件加载 JWT 配置 |
|||
- `JwtBearerOptionsSetup`: 配置 JWT Bearer 认证选项 |
|||
- `JwtProvider`: 实现 JWT 令牌的生成和验证 |
|||
|
|||
### 1.2 密钥管理服务注册 |
|||
```csharp |
|||
// 注册密钥轮换服务 |
|||
services.AddSingleton<IKeyRotationService, KeyRotationService>(); |
|||
services.AddHostedService<KeyRotationBackgroundService>(); |
|||
``` |
|||
|
|||
说明: |
|||
- `KeyRotationService`: 管理 JWT 密钥的生命周期 |
|||
- `KeyRotationBackgroundService`: 后台服务,定期执行密钥轮换 |
|||
|
|||
### 1.3 认证服务注册 |
|||
```csharp |
|||
// 配置JWT认证 |
|||
services.AddAuthentication(options => |
|||
{ |
|||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
}) |
|||
.AddJwtBearer(); |
|||
|
|||
services.AddAuthorization(); |
|||
``` |
|||
|
|||
说明: |
|||
- 设置默认认证方案为 JWT Bearer |
|||
- 启用授权服务 |
|||
|
|||
## 2. Web API 层注册 (Program.cs) |
|||
|
|||
### 2.1 JWT 配置注册 |
|||
```csharp |
|||
// 配置JWT认证 |
|||
builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("JwtOptions")); |
|||
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>(); |
|||
``` |
|||
|
|||
### 2.2 认证服务注册 |
|||
```csharp |
|||
builder.Services.AddAuthentication(options => |
|||
{ |
|||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
}) |
|||
.AddJwtBearer(); |
|||
``` |
|||
|
|||
### 2.3 Swagger 配置 |
|||
```csharp |
|||
builder.Services.AddSwaggerGen(options => |
|||
{ |
|||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme |
|||
{ |
|||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", |
|||
Name = "Authorization", |
|||
In = ParameterLocation.Header, |
|||
Type = SecuritySchemeType.ApiKey, |
|||
Scheme = "Bearer" |
|||
}); |
|||
|
|||
options.AddSecurityRequirement(new OpenApiSecurityRequirement |
|||
{ |
|||
{ |
|||
new OpenApiSecurityScheme |
|||
{ |
|||
Reference = new OpenApiReference |
|||
{ |
|||
Type = ReferenceType.SecurityScheme, |
|||
Id = "Bearer" |
|||
} |
|||
}, |
|||
Array.Empty<string>() |
|||
} |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
## 3. 服务注册优化建议 |
|||
|
|||
### 3.1 避免重复注册 |
|||
目前存在重复注册的问题: |
|||
1. JWT 配置在两个地方都进行了注册 |
|||
2. 认证服务在两个地方都进行了配置 |
|||
|
|||
建议优化方案: |
|||
```csharp |
|||
// 在 DependencyInjection.cs 中统一注册 |
|||
public static IServiceCollection AddJwtServices( |
|||
this IServiceCollection services, |
|||
IConfiguration configuration) |
|||
{ |
|||
// 配置 JWT 选项 |
|||
services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName)); |
|||
|
|||
// 注册 JWT 服务 |
|||
services.AddSingleton<IConfigureOptions<JwtBearerOptions>, JwtBearerOptionsSetup>(); |
|||
services.AddScoped<IJwtProvider, JwtProvider>(); |
|||
services.AddSingleton<IKeyRotationService, KeyRotationService>(); |
|||
services.AddHostedService<KeyRotationBackgroundService>(); |
|||
|
|||
// 配置认证 |
|||
services.AddAuthentication(options => |
|||
{ |
|||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
}) |
|||
.AddJwtBearer(); |
|||
|
|||
services.AddAuthorization(); |
|||
|
|||
return services; |
|||
} |
|||
``` |
|||
|
|||
### 3.2 配置文件结构 |
|||
建议的 JWT 配置结构: |
|||
```json |
|||
{ |
|||
"JwtOptions": { |
|||
"SecretKey": "your-secret-key", |
|||
"Issuer": "your-issuer", |
|||
"Audience": "your-audience", |
|||
"ExpiryMinutes": 15, |
|||
"RefreshTokenExpiryDays": 7, |
|||
"ClockSkewMinutes": 5, |
|||
"KeyRotationDays": 30, |
|||
"MinKeyLength": 64 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 4. 中间件配置 |
|||
|
|||
### 4.1 认证中间件 |
|||
```csharp |
|||
// 启用认证中间件 |
|||
app.UseAuthentication(); |
|||
|
|||
// 启用授权中间件 |
|||
app.UseAuthorization(); |
|||
``` |
|||
|
|||
### 4.2 HTTPS 重定向 |
|||
```csharp |
|||
// 启用 HTTPS 重定向 |
|||
app.UseHttpsRedirection(); |
|||
``` |
|||
|
|||
## 5. 使用建议 |
|||
|
|||
1. **配置管理** |
|||
- 使用强类型配置 |
|||
- 集中管理配置项 |
|||
- 避免硬编码敏感信息 |
|||
|
|||
2. **服务注册** |
|||
- 使用扩展方法组织服务注册 |
|||
- 避免重复注册 |
|||
- 遵循依赖注入最佳实践 |
|||
|
|||
3. **安全配置** |
|||
- 启用 HTTPS |
|||
- 配置适当的 CORS 策略 |
|||
- 实现完整的认证和授权 |
|||
|
|||
4. **开发体验** |
|||
- 配置 Swagger 文档 |
|||
- 提供详细的错误信息 |
|||
- 实现适当的日志记录 |
|||
|
|||
## 6. 常见问题 |
|||
|
|||
1. **配置加载失败** |
|||
- 检查配置文件路径 |
|||
- 验证配置节点名称 |
|||
- 确保配置值格式正确 |
|||
|
|||
2. **认证失败** |
|||
- 检查令牌格式 |
|||
- 验证密钥配置 |
|||
- 确认过期时间设置 |
|||
|
|||
3. **密钥轮换问题** |
|||
- 检查轮换间隔设置 |
|||
- 验证密钥生成逻辑 |
|||
- 确保缓存正确更新 |
|||
|
|||
4. **性能问题** |
|||
- 优化令牌验证 |
|||
- 使用适当的缓存策略 |
|||
- 控制令牌大小 |
@ -0,0 +1,25 @@ |
|||
# 设置错误时停止执行 |
|||
$ErrorActionPreference = "Stop" |
|||
|
|||
# 设置项目路径 |
|||
$projectPath = "src/CellularManagement.Infrastructure" |
|||
$startupProjectPath = "src/CellularManagement.WebAPI" |
|||
|
|||
# 设置迁移名称 |
|||
$migrationName = "AddLoginLogs" |
|||
|
|||
# 检查是否已存在迁移 |
|||
$migrations = dotnet ef migrations list --project $projectPath --startup-project $startupProjectPath |
|||
if ($migrations -match $migrationName) { |
|||
Write-Host "迁移 '$migrationName' 已存在,跳过创建迁移步骤" |
|||
} else { |
|||
# 创建迁移 |
|||
Write-Host "正在创建迁移 '$migrationName'..." |
|||
dotnet ef migrations add $migrationName --project $projectPath --startup-project $startupProjectPath |
|||
} |
|||
|
|||
# 更新数据库 |
|||
Write-Host "正在更新数据库..." |
|||
dotnet ef database update --project $projectPath --startup-project $startupProjectPath |
|||
|
|||
Write-Host "数据库更新完成!" |
@ -0,0 +1,73 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using FluentValidation; |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Application.Behaviours |
|||
{ |
|||
/// <summary>
|
|||
/// 请求验证行为类
|
|||
/// </summary>
|
|||
/// <typeparam name="TRequest">请求类型</typeparam>
|
|||
/// <typeparam name="TResponse">响应类型</typeparam>
|
|||
public sealed class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> |
|||
where TRequest : class, IRequest<TResponse> |
|||
{ |
|||
private readonly IEnumerable<IValidator<TRequest>> _validators; |
|||
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger; |
|||
|
|||
public ValidationBehaviour( |
|||
IEnumerable<IValidator<TRequest>> validators, |
|||
ILogger<ValidationBehaviour<TRequest, TResponse>> logger) |
|||
{ |
|||
_validators = validators ?? throw new ArgumentNullException(nameof(validators)); |
|||
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理请求验证
|
|||
/// </summary>
|
|||
public async Task<TResponse> Handle( |
|||
TRequest request, |
|||
RequestHandlerDelegate<TResponse> next, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
if (!_validators.Any()) |
|||
{ |
|||
return await next(); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var context = new ValidationContext<TRequest>(request); |
|||
var validationResults = await Task.WhenAll( |
|||
_validators.Select(v => v.ValidateAsync(context, cancellationToken))); |
|||
|
|||
var failures = validationResults |
|||
.SelectMany(result => result.Errors) |
|||
.Where(failure => failure != null) |
|||
.ToList(); |
|||
|
|||
if (failures.Any()) |
|||
{ |
|||
_logger.LogWarning( |
|||
"验证失败 - {RequestType} - 错误: {@Errors}", |
|||
typeof(TRequest).Name, |
|||
failures); |
|||
throw new ValidationException(failures); |
|||
} |
|||
|
|||
return await next(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "验证过程中发生错误 - {RequestType}", typeof(TRequest).Name); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace CellularManagement.Application.Common.Extensions; |
|||
|
|||
/// <summary>
|
|||
/// 邮箱验证扩展方法
|
|||
/// 提供邮箱地址格式验证的功能
|
|||
/// </summary>
|
|||
public static class EmailValidationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// RFC 5322 标准的邮箱验证正则表达式
|
|||
/// </summary>
|
|||
private static readonly Regex EmailRegex = new( |
|||
@"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$", |
|||
RegexOptions.Compiled | RegexOptions.IgnoreCase); |
|||
|
|||
/// <summary>
|
|||
/// 验证邮箱地址格式是否有效
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 验证规则:
|
|||
/// 1. 不能为空或空白字符
|
|||
/// 2. 必须符合 RFC 5322 标准格式
|
|||
/// 3. 本地部分(@前)长度不超过64个字符
|
|||
/// 4. 域名部分(@后)长度不超过255个字符
|
|||
/// </remarks>
|
|||
/// <param name="email">要验证的邮箱地址</param>
|
|||
/// <returns>如果邮箱地址格式有效返回 true,否则返回 false</returns>
|
|||
public static bool IsValidEmail(this string email) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(email)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// 检查邮箱长度
|
|||
if (email.Length > 254) // RFC 5321 规定的最大长度
|
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// 检查本地部分和域名部分的长度
|
|||
var parts = email.Split('@'); |
|||
if (parts.Length != 2) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (parts[0].Length > 64 || parts[1].Length > 255) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return EmailRegex.IsMatch(email); |
|||
} |
|||
} |
@ -1,19 +1,47 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using MediatR; |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Behaviours; |
|||
using CellularManagement.Domain.Services; |
|||
using Microsoft.Extensions.Configuration; |
|||
using CellularManagement.Domain.Options; |
|||
|
|||
namespace CellularManagement.Application; |
|||
|
|||
/// <summary>
|
|||
/// 应用层依赖注入扩展
|
|||
/// 负责注册应用层所需的所有服务
|
|||
/// </summary>
|
|||
public static class DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// 添加应用层服务
|
|||
/// 包括命令/查询处理、验证等服务
|
|||
/// </summary>
|
|||
/// <param name="services">服务集合</param>
|
|||
/// <param name="configuration">配置</param>
|
|||
/// <returns>服务集合</returns>
|
|||
public static IServiceCollection AddApplicationServices(this IServiceCollection services) |
|||
public static IServiceCollection AddApplication( |
|||
this IServiceCollection services, |
|||
IConfiguration configuration) |
|||
{ |
|||
ArgumentNullException.ThrowIfNull(services); |
|||
ArgumentNullException.ThrowIfNull(configuration); |
|||
|
|||
// 获取当前程序集
|
|||
var assembly = typeof(DependencyInjection).Assembly; |
|||
|
|||
// 配置 MediatR 服务
|
|||
// 1. 注册程序集中的所有处理器
|
|||
// 2. 添加验证行为中间件,用于自动验证请求
|
|||
services.AddMediatR(configuration => |
|||
configuration |
|||
.RegisterServicesFromAssembly(assembly) |
|||
.AddOpenBehavior(typeof(ValidationBehaviour<,>))); |
|||
|
|||
// 注册验证器
|
|||
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); |
|||
|
|||
return services; |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证命令
|
|||
/// </summary>
|
|||
public sealed record AuthenticateUserCommand( |
|||
/// <summary>
|
|||
/// 账号
|
|||
/// </summary>
|
|||
string UserName, |
|||
|
|||
/// <summary>
|
|||
/// 密码
|
|||
/// </summary>
|
|||
string Password) : IRequest<OperationResult<AuthenticateUserResponse>>; |
@ -0,0 +1,78 @@ |
|||
using System.Security.Claims; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Application.Common.Extensions; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Services; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
using CellularManagement.Domain.Common; |
|||
using Microsoft.AspNetCore.Http; |
|||
using UAParser; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 账号登录命令处理器
|
|||
/// </summary>
|
|||
public sealed class AuthenticateUserCommandHandler : BaseLoginCommandHandler<AuthenticateUserCommand, AuthenticateUserResponse> |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public AuthenticateUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
IJwtProvider jwtProvider, |
|||
ILogger<AuthenticateUserCommandHandler> logger, |
|||
IUserRoleRepository userRoleRepository, |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
IUnitOfWork unitOfWork, |
|||
ILoginLogRepository loginLogRepository, |
|||
IHttpContextAccessor httpContextAccessor) |
|||
: base(userManager, jwtProvider, logger, userRoleRepository, rolePermissionRepository, unitOfWork, loginLogRepository, httpContextAccessor) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理认证请求
|
|||
/// </summary>
|
|||
public override Task<OperationResult<AuthenticateUserResponse>> Handle( |
|||
AuthenticateUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
return HandleLoginAsync(request, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 查找用户
|
|||
/// </summary>
|
|||
protected override Task<AppUser?> FindUserAsync(AuthenticateUserCommand request) |
|||
{ |
|||
return _userManager.FindByNameAsync(request.UserName); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取用户标识
|
|||
/// </summary>
|
|||
protected override string GetUserIdentifier(AuthenticateUserCommand request) |
|||
{ |
|||
return request.UserName; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取密码
|
|||
/// </summary>
|
|||
protected override string GetPassword(AuthenticateUserCommand request) |
|||
{ |
|||
return request.Password; |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Application.Common.Extensions; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证命令验证器
|
|||
/// </summary>
|
|||
public sealed class AuthenticateUserCommandValidator : AbstractValidator<AuthenticateUserCommand> |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化验证器
|
|||
/// </summary>
|
|||
public AuthenticateUserCommandValidator() |
|||
{ |
|||
// 验证账号
|
|||
RuleFor(x => x.UserName) |
|||
.NotEmpty().WithMessage("账号不能为空") |
|||
.MaximumLength(256).WithMessage("账号长度不能超过256个字符"); |
|||
|
|||
// 验证密码
|
|||
RuleFor(x => x.Password) |
|||
.NotEmpty().WithMessage("密码不能为空") |
|||
.MinimumLength(6).WithMessage("密码长度不能少于6个字符") |
|||
.MaximumLength(50).WithMessage("密码长度不能超过50个字符"); |
|||
} |
|||
} |
@ -0,0 +1,290 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Services; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using CellularManagement.Domain.Common; |
|||
using Microsoft.AspNetCore.Http; |
|||
using System.Security.Claims; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
using CellularManagement.Domain.Entities.Logging; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands; |
|||
|
|||
/// <summary>
|
|||
/// 登录命令处理器基类
|
|||
/// </summary>
|
|||
public abstract class BaseLoginCommandHandler<TCommand, TResponse> : IRequestHandler<TCommand, OperationResult<TResponse>> |
|||
where TCommand : IRequest<OperationResult<TResponse>> |
|||
where TResponse : class |
|||
{ |
|||
protected readonly UserManager<AppUser> _userManager; |
|||
protected readonly IJwtProvider _jwtProvider; |
|||
protected readonly ILogger _logger; |
|||
protected readonly IUserRoleRepository _userRoleRepository; |
|||
protected readonly IRolePermissionRepository _rolePermissionRepository; |
|||
protected readonly IUnitOfWork _unitOfWork; |
|||
protected readonly ILoginLogRepository _loginLogRepository; |
|||
protected readonly IHttpContextAccessor _httpContextAccessor; |
|||
private const int MaxRetryAttempts = 3; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
protected BaseLoginCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
IJwtProvider jwtProvider, |
|||
ILogger logger, |
|||
IUserRoleRepository userRoleRepository, |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
IUnitOfWork unitOfWork, |
|||
ILoginLogRepository loginLogRepository, |
|||
IHttpContextAccessor httpContextAccessor) |
|||
{ |
|||
_userManager = userManager; |
|||
_jwtProvider = jwtProvider; |
|||
_logger = logger; |
|||
_userRoleRepository = userRoleRepository; |
|||
_rolePermissionRepository = rolePermissionRepository; |
|||
_unitOfWork = unitOfWork; |
|||
_loginLogRepository = loginLogRepository; |
|||
_httpContextAccessor = httpContextAccessor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理登录请求
|
|||
/// </summary>
|
|||
public abstract Task<OperationResult<TResponse>> Handle(TCommand request, CancellationToken cancellationToken); |
|||
|
|||
/// <summary>
|
|||
/// 查找用户
|
|||
/// </summary>
|
|||
protected abstract Task<AppUser?> FindUserAsync(TCommand request); |
|||
|
|||
/// <summary>
|
|||
/// 获取用户标识
|
|||
/// </summary>
|
|||
protected abstract string GetUserIdentifier(TCommand request); |
|||
|
|||
/// <summary>
|
|||
/// 获取密码
|
|||
/// </summary>
|
|||
protected abstract string GetPassword(TCommand request); |
|||
|
|||
/// <summary>
|
|||
/// 验证凭据
|
|||
/// </summary>
|
|||
protected virtual async Task<bool> ValidateCredentialsAsync(TCommand request, AppUser user) |
|||
{ |
|||
return await _userManager.CheckPasswordAsync(user, GetPassword(request)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理登录逻辑
|
|||
/// </summary>
|
|||
protected async Task<OperationResult<TResponse>> HandleLoginAsync(TCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
var httpContext = _httpContextAccessor.HttpContext; |
|||
var ipAddress = httpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown"; |
|||
var userAgent = httpContext?.Request.Headers["User-Agent"].ToString() ?? "Unknown"; |
|||
|
|||
// 检查IP是否被限制
|
|||
if (await _loginLogRepository.IsIpRestrictedAsync(ipAddress, cancellationToken)) |
|||
{ |
|||
_logger.LogWarning("IP {IpAddress} 已被限制登录", ipAddress); |
|||
return OperationResult<TResponse>.CreateFailure("登录尝试次数过多,请稍后再试"); |
|||
} |
|||
|
|||
// 解析设备信息
|
|||
var parser = UAParser.Parser.GetDefault(); |
|||
var clientInfo = parser.Parse(userAgent); |
|||
|
|||
// 查找用户
|
|||
var user = await FindUserAsync(request); |
|||
var userIdentifier = GetUserIdentifier(request); |
|||
|
|||
// 创建登录日志
|
|||
var loginLog = LoginLog.Create( |
|||
userId: user?.Id ?? "Unknown", |
|||
ipAddress: ipAddress, |
|||
userAgent: userAgent, |
|||
isSuccess: false, |
|||
loginType: "Password", |
|||
loginSource: "Web", |
|||
browser: clientInfo.UA.ToString(), |
|||
operatingSystem: clientInfo.OS.ToString()); |
|||
|
|||
if (user == null) |
|||
{ |
|||
loginLog.UpdateFailureReason("用户不存在"); |
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
_logger.LogWarning("用户 {UserIdentifier} 不存在", userIdentifier); |
|||
return OperationResult<TResponse>.CreateFailure("账号或密码错误"); |
|||
} |
|||
|
|||
// 检查用户是否已删除
|
|||
if (user.IsDeleted) |
|||
{ |
|||
loginLog.UpdateFailureReason("用户已被删除"); |
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
_logger.LogWarning("用户 {UserIdentifier} 已被删除", userIdentifier); |
|||
return OperationResult<TResponse>.CreateFailure("用户已被删除"); |
|||
} |
|||
|
|||
// 检查用户是否已禁用
|
|||
if (!user.IsActive) |
|||
{ |
|||
loginLog.UpdateFailureReason("用户已被禁用"); |
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
_logger.LogWarning("用户 {UserIdentifier} 已被禁用", userIdentifier); |
|||
return OperationResult<TResponse>.CreateFailure("用户已被禁用"); |
|||
} |
|||
|
|||
// 验证凭据
|
|||
var isValidCredentials = await ValidateCredentialsAsync(request, user); |
|||
if (!isValidCredentials) |
|||
{ |
|||
loginLog.UpdateFailureReason("验证失败"); |
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
_logger.LogWarning("用户 {UserIdentifier} 验证失败", userIdentifier); |
|||
return OperationResult<TResponse>.CreateFailure("验证失败"); |
|||
} |
|||
|
|||
// 更新最后登录时间(带并发控制)
|
|||
var retryCount = 0; |
|||
var updateSuccess = false; |
|||
|
|||
while (!updateSuccess && retryCount < MaxRetryAttempts) |
|||
{ |
|||
try |
|||
{ |
|||
await _unitOfWork.ExecuteTransactionAsync(async () => |
|||
{ |
|||
user.LastLoginTime = DateTime.UtcNow; |
|||
var result = await _userManager.UpdateAsync(user); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("更新用户最后登录时间失败: {Errors}", string.Join(", ", errors)); |
|||
throw new InvalidOperationException(string.Join(", ", errors)); |
|||
} |
|||
}, cancellationToken: cancellationToken); |
|||
updateSuccess = true; |
|||
} |
|||
catch (DbUpdateConcurrencyException) |
|||
{ |
|||
retryCount++; |
|||
_logger.LogWarning("用户 {UserIdentifier} 更新时发生并发冲突,重试次数: {RetryCount}", |
|||
userIdentifier, retryCount); |
|||
|
|||
if (retryCount >= MaxRetryAttempts) |
|||
{ |
|||
_logger.LogError("用户 {UserIdentifier} 更新失败,超过最大重试次数", userIdentifier); |
|||
return OperationResult<TResponse>.CreateFailure("系统繁忙,请稍后重试"); |
|||
} |
|||
|
|||
// 重新获取最新数据
|
|||
user = await _userManager.FindByIdAsync(user.Id); |
|||
if (user == null) |
|||
{ |
|||
return OperationResult<TResponse>.CreateFailure("用户不存在"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 获取用户角色
|
|||
var roles = await _userRoleRepository.GetUserRolesAsync(user.Id, cancellationToken); |
|||
|
|||
// 创建用户声明
|
|||
var claims = new List<Claim> |
|||
{ |
|||
new(ClaimTypes.NameIdentifier, user.Id), |
|||
new(ClaimTypes.Name, user.UserName!), |
|||
new(ClaimTypes.Email, user.Email!) |
|||
}; |
|||
|
|||
// 添加最后登录时间声明(如果存在)
|
|||
if (user.LastLoginTime.HasValue) |
|||
{ |
|||
claims.Add(new Claim("LastLoginTime", user.LastLoginTime.Value.ToUniversalTime().ToString("o"))); |
|||
} |
|||
|
|||
// 添加角色声明
|
|||
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); |
|||
|
|||
// 获取所有角色的权限
|
|||
var permissions = new Dictionary<string, bool>(); |
|||
foreach (var role in roles) |
|||
{ |
|||
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken); |
|||
foreach (var rolePermission in rolePermissions) |
|||
{ |
|||
if (!permissions.ContainsKey(rolePermission.Permission.Code)) |
|||
{ |
|||
permissions[rolePermission.Permission.Code] = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 生成访问令牌
|
|||
var accessToken = _jwtProvider.GenerateAccessToken(claims); |
|||
|
|||
// 生成刷新令牌
|
|||
var refreshToken = _jwtProvider.GenerateRefreshToken(claims); |
|||
|
|||
// 获取令牌过期时间
|
|||
var expiresAt = _jwtProvider.GetTokenExpiration(accessToken); |
|||
|
|||
// 创建用户信息
|
|||
var userInfo = new UserInfo( |
|||
user.Id, |
|||
user.UserName!, |
|||
user.RealName, |
|||
user.Email!, |
|||
user.PhoneNumber, |
|||
roles.ToList().AsReadOnly(), |
|||
permissions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); |
|||
|
|||
// 记录成功的登录日志
|
|||
loginLog = LoginLog.Create( |
|||
userId: user.Id, |
|||
ipAddress: ipAddress, |
|||
userAgent: userAgent, |
|||
isSuccess: true, |
|||
loginType: "Password", |
|||
loginSource: "Web", |
|||
browser: clientInfo.UA.ToString(), |
|||
operatingSystem: clientInfo.OS.ToString()); |
|||
|
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
await _unitOfWork.SaveChangesAsync(cancellationToken); |
|||
_logger.LogInformation("用户 {UserIdentifier} 认证成功", userIdentifier); |
|||
|
|||
// 返回认证结果
|
|||
var response = (TResponse)Activator.CreateInstance( |
|||
typeof(TResponse), |
|||
accessToken, |
|||
refreshToken, |
|||
expiresAt, |
|||
userInfo)!; |
|||
|
|||
return OperationResult<TResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "用户认证失败"); |
|||
return OperationResult<TResponse>.CreateFailure("认证失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin; |
|||
|
|||
/// <summary>
|
|||
/// 邮箱登录命令
|
|||
/// </summary>
|
|||
public sealed record EmailLoginCommand( |
|||
/// <summary>
|
|||
/// 邮箱
|
|||
/// </summary>
|
|||
string Email, |
|||
|
|||
/// <summary>
|
|||
/// 验证码
|
|||
/// </summary>
|
|||
string VerificationCode) : IRequest<OperationResult<EmailLoginResponse>>; |
@ -0,0 +1,84 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Services; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using CellularManagement.Domain.Common; |
|||
using Microsoft.AspNetCore.Http; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin; |
|||
|
|||
/// <summary>
|
|||
/// 邮箱登录命令处理器
|
|||
/// </summary>
|
|||
public sealed class EmailLoginCommandHandler : BaseLoginCommandHandler<EmailLoginCommand, EmailLoginResponse> |
|||
{ |
|||
private readonly IEmailVerificationService _emailVerificationService; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public EmailLoginCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
IJwtProvider jwtProvider, |
|||
ILogger<EmailLoginCommandHandler> logger, |
|||
IUserRoleRepository userRoleRepository, |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
IUnitOfWork unitOfWork, |
|||
ILoginLogRepository loginLogRepository, |
|||
IHttpContextAccessor httpContextAccessor, |
|||
IEmailVerificationService emailVerificationService) |
|||
: base(userManager, jwtProvider, logger, userRoleRepository, rolePermissionRepository, unitOfWork, loginLogRepository, httpContextAccessor) |
|||
{ |
|||
_emailVerificationService = emailVerificationService; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理认证请求
|
|||
/// </summary>
|
|||
public override Task<OperationResult<EmailLoginResponse>> Handle( |
|||
EmailLoginCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
return HandleLoginAsync(request, cancellationToken); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 查找用户
|
|||
/// </summary>
|
|||
protected override Task<AppUser?> FindUserAsync(EmailLoginCommand request) |
|||
{ |
|||
return _userManager.FindByEmailAsync(request.Email); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取用户标识
|
|||
/// </summary>
|
|||
protected override string GetUserIdentifier(EmailLoginCommand request) |
|||
{ |
|||
return request.Email; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 获取密码
|
|||
/// </summary>
|
|||
protected override string GetPassword(EmailLoginCommand request) |
|||
{ |
|||
return request.VerificationCode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证凭据
|
|||
/// </summary>
|
|||
protected override async Task<bool> ValidateCredentialsAsync(EmailLoginCommand request, AppUser user) |
|||
{ |
|||
// 验证邮箱验证码
|
|||
return await _emailVerificationService.VerifyCodeAsync(request.Email, request.VerificationCode); |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common.Extensions; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.EmailLogin; |
|||
|
|||
/// <summary>
|
|||
/// 邮箱登录命令验证器
|
|||
/// </summary>
|
|||
public sealed class EmailLoginCommandValidator : AbstractValidator<EmailLoginCommand> |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化验证器
|
|||
/// </summary>
|
|||
public EmailLoginCommandValidator() |
|||
{ |
|||
// 验证邮箱
|
|||
RuleFor(x => x.Email) |
|||
.NotEmpty().WithMessage("邮箱不能为空") |
|||
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符") |
|||
.Must(x => x.IsValidEmail()) |
|||
.WithMessage("邮箱格式不正确"); |
|||
|
|||
// 验证验证码
|
|||
RuleFor(x => x.VerificationCode) |
|||
.NotEmpty().WithMessage("验证码不能为空") |
|||
.Length(6).WithMessage("验证码长度必须为6位") |
|||
.Matches("^[0-9]+$").WithMessage("验证码只能包含数字"); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha; |
|||
|
|||
public class GenerateCaptchaCommand : IRequest<OperationResult<GenerateCaptchaResponse>> |
|||
{ |
|||
public int Length { get; set; } = 4; |
|||
public string? ClientId { get; set; } |
|||
} |
@ -0,0 +1,79 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Services; |
|||
using CellularManagement.Application.Common; |
|||
using System.Text; |
|||
using CellularManagement.Domain.Common; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha; |
|||
|
|||
public class GenerateCaptchaCommandHandler : IRequestHandler<GenerateCaptchaCommand, OperationResult<GenerateCaptchaResponse>> |
|||
{ |
|||
private readonly ICaptchaService _captchaService; |
|||
private readonly ICacheService _cacheService; |
|||
private const string RATE_LIMIT_KEY_PREFIX = "captcha_rate_limit:"; |
|||
private const int MAX_REQUESTS_PER_MINUTE = 30; |
|||
|
|||
public GenerateCaptchaCommandHandler( |
|||
ICaptchaService captchaService, |
|||
ICacheService cacheService) |
|||
{ |
|||
_captchaService = captchaService; |
|||
_cacheService = cacheService; |
|||
} |
|||
|
|||
public async Task<OperationResult<GenerateCaptchaResponse>> Handle( |
|||
GenerateCaptchaCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 获取客户端IP或标识
|
|||
var clientId = request.ClientId ?? "anonymous"; |
|||
var rateLimitKey = $"{RATE_LIMIT_KEY_PREFIX}{clientId}"; |
|||
|
|||
// 检查请求频率
|
|||
var requestCount = _cacheService.Get<int>(rateLimitKey); |
|||
if (requestCount >= MAX_REQUESTS_PER_MINUTE) |
|||
{ |
|||
var remainingTime = _cacheService.Get<DateTime>($"{rateLimitKey}:expiry"); |
|||
var seconds = remainingTime != default |
|||
? (int)(remainingTime - DateTime.UtcNow).TotalSeconds |
|||
: 60; |
|||
|
|||
return OperationResult<GenerateCaptchaResponse>.CreateFailure( |
|||
$"请求过于频繁,请{seconds}秒后再试"); |
|||
} |
|||
|
|||
// 更新请求计数
|
|||
var rateLimitOptions = new MemoryCacheEntryOptions() |
|||
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1)); |
|||
_cacheService.Set(rateLimitKey, requestCount + 1, rateLimitOptions); |
|||
_cacheService.Set($"{rateLimitKey}:expiry", DateTime.UtcNow.AddMinutes(1), rateLimitOptions); |
|||
|
|||
// 生成验证码
|
|||
var (code, imageBytes) = _captchaService.GenerateCaptcha(request.Length); |
|||
|
|||
// 生成唯一ID
|
|||
var captchaId = Guid.NewGuid().ToString(); |
|||
|
|||
// 将验证码存入缓存,设置5分钟过期
|
|||
var cacheOptions = new MemoryCacheEntryOptions() |
|||
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5)); |
|||
_cacheService.Set($"captcha:{captchaId}", code, cacheOptions); |
|||
|
|||
// 将图片转换为Base64
|
|||
var base64Image = Convert.ToBase64String(imageBytes); |
|||
|
|||
return await Task.FromResult(OperationResult<GenerateCaptchaResponse>.CreateSuccess(new GenerateCaptchaResponse |
|||
{ |
|||
CaptchaId = captchaId, |
|||
ImageBase64 = base64Image |
|||
})); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return await Task.FromResult(OperationResult<GenerateCaptchaResponse>.CreateFailure("生成验证码失败")); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
namespace CellularManagement.Application.Features.Auth.Commands.GenerateCaptcha; |
|||
|
|||
public class GenerateCaptchaResponse |
|||
{ |
|||
public string CaptchaId { get; set; } |
|||
public string ImageBase64 { get; set; } |
|||
} |
@ -0,0 +1,21 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.Login; |
|||
|
|||
/// <summary>
|
|||
/// 登录请求
|
|||
/// </summary>
|
|||
public class LoginRequest |
|||
{ |
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "账号不能为空")] |
|||
public string UserName { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 密码
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "密码不能为空")] |
|||
public string Password { get; set; } = string.Empty; |
|||
} |
@ -0,0 +1,45 @@ |
|||
using Swashbuckle.AspNetCore.Filters; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.Login; |
|||
|
|||
/// <summary>
|
|||
/// 登录请求示例
|
|||
/// </summary>
|
|||
public class LoginRequestExample : IExamplesProvider<LoginRequest> |
|||
{ |
|||
/// <summary>
|
|||
/// 获取示例
|
|||
/// </summary>
|
|||
/// <returns>登录请求示例</returns>
|
|||
public LoginRequest GetExamples() |
|||
{ |
|||
return new LoginRequest |
|||
{ |
|||
UserName = "hyh", |
|||
Password = "Hyh@123456" |
|||
}; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 登录请求示例提供者
|
|||
/// </summary>
|
|||
public class LoginRequestExamples : IMultipleExamplesProvider<LoginRequest> |
|||
{ |
|||
/// <summary>
|
|||
/// 获取示例
|
|||
/// </summary>
|
|||
/// <returns>登录请求示例集合</returns>
|
|||
public IEnumerable<SwaggerExample<LoginRequest>> GetExamples() |
|||
{ |
|||
yield return SwaggerExample.Create( |
|||
"默认用户", |
|||
"使用默认测试账号", |
|||
new LoginRequest |
|||
{ |
|||
UserName = "hyh", |
|||
Password = "Hyh@123456" |
|||
} |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.Logout; |
|||
|
|||
/// <summary>
|
|||
/// 登出命令
|
|||
/// </summary>
|
|||
public class LogoutCommand : IRequest<OperationResult<bool>> |
|||
{ |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
public string AccessToken { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌
|
|||
/// </summary>
|
|||
public string RefreshToken { get; set; } = string.Empty; |
|||
} |
@ -0,0 +1,260 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Services; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Entities; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using System.Security.Claims; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using CellularManagement.Domain.Entities.Logging; |
|||
using Microsoft.AspNetCore.Http; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.Logout; |
|||
|
|||
/// <summary>
|
|||
/// 登出命令处理器
|
|||
/// </summary>
|
|||
public class LogoutCommandHandler : IRequestHandler<LogoutCommand, OperationResult<bool>> |
|||
{ |
|||
private readonly IJwtProvider _jwtProvider; |
|||
private readonly ILogger<LogoutCommandHandler> _logger; |
|||
private readonly ILoginLogRepository _loginLogRepository; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
private readonly IHttpContextAccessor _httpContextAccessor; |
|||
private static readonly Action<ILogger, string, Exception?> LogTokenValidationError = |
|||
LoggerMessage.Define<string>(LogLevel.Warning, new EventId(1, "TokenValidationError"), "令牌验证错误: {Message}"); |
|||
private static readonly Action<ILogger, string, string, Exception?> LogTokenProcessingError = |
|||
LoggerMessage.Define<string, string>(LogLevel.Error, new EventId(2, "TokenProcessingError"), "处理令牌 {TokenType} 时发生错误: {Message}"); |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public LogoutCommandHandler( |
|||
IJwtProvider jwtProvider, |
|||
ILogger<LogoutCommandHandler> logger, |
|||
ILoginLogRepository loginLogRepository, |
|||
IUnitOfWork unitOfWork, |
|||
IHttpContextAccessor httpContextAccessor) |
|||
{ |
|||
_jwtProvider = jwtProvider; |
|||
_logger = logger; |
|||
_loginLogRepository = loginLogRepository; |
|||
_unitOfWork = unitOfWork; |
|||
_httpContextAccessor = httpContextAccessor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理登出请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<bool>> Handle(LogoutCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
var sw = Stopwatch.StartNew(); |
|||
var correlationId = Guid.NewGuid().ToString(); |
|||
_logger.LogInformation("[{CorrelationId}] 开始处理登出请求", correlationId); |
|||
|
|||
try |
|||
{ |
|||
// 快速验证令牌存在性
|
|||
if (string.IsNullOrEmpty(request.AccessToken) || string.IsNullOrEmpty(request.RefreshToken)) |
|||
{ |
|||
_logger.LogWarning("[{CorrelationId}] 令牌为空", correlationId); |
|||
return OperationResult<bool>.CreateFailure("无效的请求"); |
|||
} |
|||
|
|||
_logger.LogDebug("[{CorrelationId}] 开始验证令牌", correlationId); |
|||
string? userId = null; |
|||
bool isAccessTokenValid = false; |
|||
bool isRefreshTokenValid = false; |
|||
var tokenValidationSw = Stopwatch.StartNew(); |
|||
|
|||
try |
|||
{ |
|||
// 验证访问令牌
|
|||
_logger.LogDebug("[{CorrelationId}] 开始验证访问令牌", correlationId); |
|||
var accessTokenClaims = _jwtProvider.GetClaimsFromToken(request.AccessToken); |
|||
if (accessTokenClaims.Any()) |
|||
{ |
|||
var tokenType = accessTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value; |
|||
_logger.LogDebug("[{CorrelationId}] 访问令牌类型: {TokenType}", correlationId, tokenType); |
|||
|
|||
if (tokenType == "access_token") |
|||
{ |
|||
userId = accessTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; |
|||
isAccessTokenValid = !string.IsNullOrEmpty(userId); |
|||
_logger.LogDebug("[{CorrelationId}] 访问令牌验证结果: {IsValid}, 用户ID: {UserId}", |
|||
correlationId, isAccessTokenValid, userId); |
|||
} |
|||
else |
|||
{ |
|||
_logger.LogWarning("[{CorrelationId}] 访问令牌类型不匹配: {TokenType}", correlationId, tokenType); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
LogTokenValidationError(_logger, "访问令牌验证失败", ex); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
// 验证刷新令牌
|
|||
_logger.LogDebug("[{CorrelationId}] 开始验证刷新令牌", correlationId); |
|||
var refreshTokenClaims = _jwtProvider.GetClaimsFromToken(request.RefreshToken); |
|||
if (refreshTokenClaims.Any()) |
|||
{ |
|||
var tokenType = refreshTokenClaims.FirstOrDefault(c => c.Type == "token_type")?.Value; |
|||
_logger.LogDebug("[{CorrelationId}] 刷新令牌类型: {TokenType}", correlationId, tokenType); |
|||
|
|||
if (tokenType == "refresh_token") |
|||
{ |
|||
var refreshTokenUserId = refreshTokenClaims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; |
|||
isRefreshTokenValid = !string.IsNullOrEmpty(refreshTokenUserId) && |
|||
(string.IsNullOrEmpty(userId) || userId == refreshTokenUserId); |
|||
|
|||
if (isRefreshTokenValid && string.IsNullOrEmpty(userId)) |
|||
{ |
|||
userId = refreshTokenUserId; |
|||
} |
|||
_logger.LogDebug("[{CorrelationId}] 刷新令牌验证结果: {IsValid}, 用户ID: {UserId}", |
|||
correlationId, isRefreshTokenValid, refreshTokenUserId); |
|||
} |
|||
else |
|||
{ |
|||
_logger.LogWarning("[{CorrelationId}] 刷新令牌类型不匹配: {TokenType}", correlationId, tokenType); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
LogTokenValidationError(_logger, "刷新令牌验证失败", ex); |
|||
} |
|||
|
|||
tokenValidationSw.Stop(); |
|||
_logger.LogInformation("[{CorrelationId}] 令牌验证完成,耗时: {ElapsedMs}ms", |
|||
correlationId, tokenValidationSw.ElapsedMilliseconds); |
|||
|
|||
// 如果两个令牌都无效,直接返回成功
|
|||
if (!isAccessTokenValid && !isRefreshTokenValid) |
|||
{ |
|||
_logger.LogInformation("[{CorrelationId}] 所有令牌无效,直接返回成功", correlationId); |
|||
return OperationResult<bool>.CreateSuccess(true); |
|||
} |
|||
|
|||
// 记录登出日志
|
|||
if (!string.IsNullOrEmpty(userId)) |
|||
{ |
|||
_logger.LogDebug("[{CorrelationId}] 开始记录登出日志,用户ID: {UserId}", correlationId, userId); |
|||
var logSw = Stopwatch.StartNew(); |
|||
await RecordLogoutLogAsync(userId, correlationId, cancellationToken); |
|||
logSw.Stop(); |
|||
_logger.LogInformation("[{CorrelationId}] 登出日志记录完成,耗时: {ElapsedMs}ms", |
|||
correlationId, logSw.ElapsedMilliseconds); |
|||
} |
|||
|
|||
// 异步处理令牌撤销和黑名单
|
|||
_ = Task.Run(async () => |
|||
{ |
|||
var tokenProcessingSw = Stopwatch.StartNew(); |
|||
try |
|||
{ |
|||
if (isAccessTokenValid) |
|||
{ |
|||
_logger.LogDebug("[{CorrelationId}] 开始处理访问令牌撤销和黑名单", correlationId); |
|||
await ProcessTokenAsync(request.AccessToken, "访问令牌", correlationId); |
|||
} |
|||
|
|||
if (isRefreshTokenValid) |
|||
{ |
|||
_logger.LogDebug("[{CorrelationId}] 开始处理刷新令牌撤销和黑名单", correlationId); |
|||
await ProcessTokenAsync(request.RefreshToken, "刷新令牌", correlationId); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
LogTokenProcessingError(_logger, "令牌处理", "处理令牌撤销和黑名单时发生异常", ex); |
|||
} |
|||
finally |
|||
{ |
|||
tokenProcessingSw.Stop(); |
|||
_logger.LogInformation("[{CorrelationId}] 令牌处理完成,耗时: {ElapsedMs}ms", |
|||
correlationId, tokenProcessingSw.ElapsedMilliseconds); |
|||
} |
|||
}); |
|||
|
|||
sw.Stop(); |
|||
_logger.LogInformation("[{CorrelationId}] 登出请求处理完成,总耗时: {ElapsedMs}ms", |
|||
correlationId, sw.ElapsedMilliseconds); |
|||
return OperationResult<bool>.CreateSuccess(true); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
sw.Stop(); |
|||
_logger.LogError(ex, "[{CorrelationId}] 处理登出请求时发生异常,耗时: {ElapsedMs}ms", |
|||
correlationId, sw.ElapsedMilliseconds); |
|||
return OperationResult<bool>.CreateFailure("系统错误"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 记录登出日志
|
|||
/// </summary>
|
|||
private async Task RecordLogoutLogAsync(string userId, string correlationId, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
var httpContext = _httpContextAccessor.HttpContext; |
|||
var ipAddress = httpContext?.Connection.RemoteIpAddress?.ToString() ?? "Unknown"; |
|||
var userAgent = httpContext?.Request.Headers["User-Agent"].ToString() ?? "Unknown"; |
|||
|
|||
// 解析设备信息
|
|||
var parser = UAParser.Parser.GetDefault(); |
|||
var clientInfo = parser.Parse(userAgent); |
|||
|
|||
var loginLog = LoginLog.Create( |
|||
userId: userId, |
|||
ipAddress: ipAddress, |
|||
userAgent: userAgent, |
|||
isSuccess: true, |
|||
loginType: "Logout", |
|||
loginSource: "Web", |
|||
browser: clientInfo.UA.ToString(), |
|||
operatingSystem: clientInfo.OS.ToString()); |
|||
|
|||
await _loginLogRepository.AddAsync(loginLog, cancellationToken); |
|||
await _unitOfWork.SaveChangesAsync(cancellationToken); |
|||
_logger.LogDebug("[{CorrelationId}] 登出日志记录成功", correlationId); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "[{CorrelationId}] 记录登出日志时发生异常", correlationId); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理令牌撤销和黑名单
|
|||
/// </summary>
|
|||
private async Task ProcessTokenAsync(string token, string tokenType, string correlationId) |
|||
{ |
|||
try |
|||
{ |
|||
var sw = Stopwatch.StartNew(); |
|||
_jwtProvider.RevokeToken(token); |
|||
_jwtProvider.AddToBlacklist(token); |
|||
sw.Stop(); |
|||
_logger.LogDebug("[{CorrelationId}] {TokenType}处理完成,耗时: {ElapsedMs}ms", |
|||
correlationId, tokenType, sw.ElapsedMilliseconds); |
|||
await Task.CompletedTask; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
LogTokenProcessingError(_logger, tokenType, "处理令牌时发生异常", ex); |
|||
throw; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken; |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌命令
|
|||
/// </summary>
|
|||
public class RefreshTokenCommand : IRequest<OperationResult<AuthenticateUserResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 刷新令牌
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "刷新令牌不能为空")] |
|||
public string RefreshToken { get; set; } = string.Empty; |
|||
} |
@ -0,0 +1,149 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Application.Common; |
|||
using System.Security.Claims; |
|||
using CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Services; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using System; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Application.Features.Auth.Models; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken; |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌命令处理器
|
|||
/// </summary>
|
|||
public sealed class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, OperationResult<AuthenticateUserResponse>> |
|||
{ |
|||
private readonly IJwtProvider _jwtProvider; |
|||
private readonly ILogger<RefreshTokenCommandHandler> _logger; |
|||
private readonly IUserRoleRepository _userRoleRepository; |
|||
private readonly IRolePermissionRepository _rolePermissionRepository; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="jwtProvider">JWT提供者</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
/// <param name="userRoleRepository">用户角色仓储</param>
|
|||
/// <param name="rolePermissionRepository">角色权限仓储</param>
|
|||
public RefreshTokenCommandHandler( |
|||
IJwtProvider jwtProvider, |
|||
ILogger<RefreshTokenCommandHandler> logger, |
|||
IUserRoleRepository userRoleRepository, |
|||
IRolePermissionRepository rolePermissionRepository) |
|||
{ |
|||
_jwtProvider = jwtProvider; |
|||
_logger = logger; |
|||
_userRoleRepository = userRoleRepository; |
|||
_rolePermissionRepository = rolePermissionRepository; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理刷新令牌请求
|
|||
/// </summary>
|
|||
/// <param name="request">刷新令牌命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>刷新令牌结果</returns>
|
|||
public async Task<OperationResult<AuthenticateUserResponse>> Handle( |
|||
RefreshTokenCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
_logger.LogInformation("开始处理刷新令牌请求"); |
|||
|
|||
// 验证刷新令牌
|
|||
if (!_jwtProvider.ValidateToken(request.RefreshToken)) |
|||
{ |
|||
_logger.LogWarning("刷新令牌验证失败"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌"); |
|||
} |
|||
|
|||
// 获取令牌中的声明
|
|||
var claims = _jwtProvider.GetClaimsFromToken(request.RefreshToken); |
|||
if (!claims.Any()) |
|||
{ |
|||
_logger.LogWarning("刷新令牌中未找到声明"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌"); |
|||
} |
|||
|
|||
// 检查令牌类型
|
|||
var tokenType = _jwtProvider.GetTokenType(request.RefreshToken); |
|||
if (tokenType != "refresh_token") |
|||
{ |
|||
_logger.LogWarning("令牌类型不是刷新令牌"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌"); |
|||
} |
|||
|
|||
// 检查令牌是否在黑名单中
|
|||
if (_jwtProvider.GetAllClaims(request.RefreshToken).ContainsKey("blacklisted")) |
|||
{ |
|||
_logger.LogWarning("刷新令牌已被列入黑名单"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("刷新令牌已被撤销"); |
|||
} |
|||
|
|||
// 获取用户ID
|
|||
var userId = claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; |
|||
if (string.IsNullOrEmpty(userId)) |
|||
{ |
|||
_logger.LogWarning("刷新令牌中未找到用户ID"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("无效的刷新令牌"); |
|||
} |
|||
|
|||
// 获取用户角色
|
|||
var roles = await _userRoleRepository.GetUserRolesAsync(userId, cancellationToken); |
|||
|
|||
// 获取所有角色的权限
|
|||
var permissions = new Dictionary<string, bool>(); |
|||
foreach (var role in roles) |
|||
{ |
|||
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken); |
|||
foreach (var rolePermission in rolePermissions) |
|||
{ |
|||
if (!permissions.ContainsKey(rolePermission.Permission.Code)) |
|||
{ |
|||
permissions[rolePermission.Permission.Code] = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 生成新的访问令牌
|
|||
var accessToken = _jwtProvider.GenerateAccessToken(claims); |
|||
|
|||
// 生成新的刷新令牌
|
|||
var refreshToken = _jwtProvider.GenerateRefreshToken(claims); |
|||
|
|||
// 撤销旧的刷新令牌
|
|||
_jwtProvider.RevokeToken(request.RefreshToken); |
|||
|
|||
// 获取令牌过期时间
|
|||
var expiresAt = _jwtProvider.GetTokenExpiration(accessToken); |
|||
|
|||
// 创建用户信息
|
|||
var userInfo = new UserInfo( |
|||
userId, |
|||
claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? string.Empty, |
|||
claims.FirstOrDefault(c => c.Type == "RealName")?.Value, |
|||
claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty, |
|||
claims.FirstOrDefault(c => c.Type == ClaimTypes.MobilePhone)?.Value, |
|||
roles.ToList().AsReadOnly(), |
|||
permissions); |
|||
|
|||
_logger.LogInformation("刷新令牌成功"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateSuccess( |
|||
new AuthenticateUserResponse(accessToken, refreshToken, expiresAt, userInfo)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "刷新令牌时发生异常"); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("刷新令牌失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
namespace CellularManagement.Application.Features.Auth.Commands.RefreshToken; |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌响应
|
|||
/// </summary>
|
|||
public class RefreshTokenResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
public string AccessToken { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌
|
|||
/// </summary>
|
|||
public string RefreshToken { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 过期时间(分钟)
|
|||
/// </summary>
|
|||
public int ExpiresIn { get; set; } |
|||
} |
@ -0,0 +1,48 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册命令
|
|||
/// </summary>
|
|||
public sealed record RegisterUserCommand( |
|||
/// <summary>
|
|||
/// 账号
|
|||
/// </summary>
|
|||
string UserName, |
|||
|
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
string RealName, |
|||
|
|||
/// <summary>
|
|||
/// 邮箱
|
|||
/// </summary>
|
|||
string Email, |
|||
|
|||
/// <summary>
|
|||
/// 密码
|
|||
/// </summary>
|
|||
string Password, |
|||
|
|||
/// <summary>
|
|||
/// 确认密码
|
|||
/// </summary>
|
|||
string ConfirmPassword, |
|||
|
|||
/// <summary>
|
|||
/// 电话号码
|
|||
/// </summary>
|
|||
string? PhoneNumber, |
|||
|
|||
/// <summary>
|
|||
/// 验证码ID
|
|||
/// </summary>
|
|||
string CaptchaId, |
|||
|
|||
/// <summary>
|
|||
/// 验证码
|
|||
/// </summary>
|
|||
string CaptchaCode) : IRequest<OperationResult<RegisterUserResponse>>; |
@ -0,0 +1,115 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Services; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Exceptions; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册命令处理器
|
|||
/// </summary>
|
|||
public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, OperationResult<RegisterUserResponse>> |
|||
{ |
|||
private readonly IUserRegistrationService _userRegistrationService; |
|||
private readonly ICaptchaVerificationService _captchaVerificationService; |
|||
private readonly ILogger<RegisterUserCommandHandler> _logger; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
|
|||
public RegisterUserCommandHandler( |
|||
IUserRegistrationService userRegistrationService, |
|||
ICaptchaVerificationService captchaVerificationService, |
|||
ILogger<RegisterUserCommandHandler> logger, |
|||
IUnitOfWork unitOfWork) |
|||
{ |
|||
_userRegistrationService = userRegistrationService; |
|||
_captchaVerificationService = captchaVerificationService; |
|||
_logger = logger; |
|||
_unitOfWork = unitOfWork; |
|||
} |
|||
|
|||
public async Task<OperationResult<RegisterUserResponse>> Handle( |
|||
RegisterUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 验证图形验证码
|
|||
var(IsSuccess, errorMessage) = await _captchaVerificationService.VerifyCaptchaAsync(request.CaptchaId, request.CaptchaCode); |
|||
if (!IsSuccess) |
|||
{ |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(errorMessage?? "验证码验证失败"); |
|||
} |
|||
|
|||
// 创建用户实体
|
|||
var user = new AppUser |
|||
{ |
|||
UserName = request.UserName, |
|||
RealName = request.RealName, |
|||
Email = request.Email, |
|||
PhoneNumber = request.PhoneNumber |
|||
}; |
|||
|
|||
// 在事务中执行用户注册和角色分配
|
|||
await _unitOfWork.ExecuteTransactionAsync(async () => |
|||
{ |
|||
// 注册用户
|
|||
var (success, errorMessage) = await _userRegistrationService.RegisterUserAsync(user, request.Password); |
|||
if (!success) |
|||
{ |
|||
_logger.LogWarning("注册用户失败: {Error}", errorMessage); |
|||
throw new UserRegistrationException(errorMessage!); |
|||
} |
|||
|
|||
// 分配角色
|
|||
(success, errorMessage) = await _userRegistrationService.AssignUserRoleAsync(user); |
|||
if (!success) |
|||
{ |
|||
_logger.LogWarning("分配角色失败: {Error}", errorMessage); |
|||
throw new RoleAssignmentException(errorMessage!); |
|||
} |
|||
await _unitOfWork.SaveChangesAsync(cancellationToken); |
|||
}); |
|||
|
|||
_logger.LogInformation("用户 {UserName} 注册成功", request.UserName); |
|||
|
|||
return OperationResult<RegisterUserResponse>.CreateSuccess( |
|||
new RegisterUserResponse( |
|||
Id: user.Id, |
|||
UserName: user.UserName, |
|||
RealName: user.RealName ?? user.UserName)); |
|||
} |
|||
catch (UserNameAlreadyExistsException ex) |
|||
{ |
|||
_logger.LogWarning(ex, "账号已存在"); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message); |
|||
} |
|||
catch (EmailAlreadyExistsException ex) |
|||
{ |
|||
_logger.LogWarning(ex, "邮箱已存在"); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message); |
|||
} |
|||
catch (RoleAssignmentException ex) |
|||
{ |
|||
_logger.LogError(ex, "角色分配失败"); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message); |
|||
} |
|||
catch (UserRegistrationException ex) |
|||
{ |
|||
_logger.LogError(ex, "用户注册失败"); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(ex.Message); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "用户 {UserName} 注册失败", request.UserName); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure("系统错误,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common.Extensions; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册命令验证器
|
|||
/// </summary>
|
|||
public sealed class RegisterUserCommandValidator : AbstractValidator<RegisterUserCommand> |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化验证器
|
|||
/// </summary>
|
|||
public RegisterUserCommandValidator() |
|||
{ |
|||
// 验证账号
|
|||
RuleFor(x => x.UserName) |
|||
.NotEmpty().WithMessage("账号不能为空") |
|||
.MinimumLength(3).WithMessage("账号长度不能少于3个字符") |
|||
.MaximumLength(50).WithMessage("账号长度不能超过50个字符") |
|||
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线"); |
|||
|
|||
// 验证用户名
|
|||
RuleFor(x => x.RealName) |
|||
.NotEmpty().WithMessage("用户名不能为空") |
|||
.MaximumLength(50).WithMessage("用户名长度不能超过50个字符"); |
|||
|
|||
// 验证邮箱
|
|||
RuleFor(x => x.Email) |
|||
.NotEmpty().WithMessage("邮箱不能为空") |
|||
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符") |
|||
.Must(x => x.IsValidEmail()).WithMessage("邮箱格式不正确"); |
|||
|
|||
// 验证密码
|
|||
RuleFor(x => x.Password) |
|||
.NotEmpty().WithMessage("密码不能为空") |
|||
.MinimumLength(6).WithMessage("密码长度不能少于6个字符") |
|||
.MaximumLength(50).WithMessage("密码长度不能超过50个字符") |
|||
.Matches("[A-Z]").WithMessage("密码必须包含至少一个大写字母") |
|||
.Matches("[a-z]").WithMessage("密码必须包含至少一个小写字母") |
|||
.Matches("[0-9]").WithMessage("密码必须包含至少一个数字") |
|||
.Matches("[^a-zA-Z0-9]").WithMessage("密码必须包含至少一个特殊字符"); |
|||
|
|||
// 验证确认密码
|
|||
RuleFor(x => x.ConfirmPassword) |
|||
.NotEmpty().WithMessage("确认密码不能为空") |
|||
.Equal(x => x.Password).WithMessage("两次输入的密码不一致"); |
|||
|
|||
// 验证验证码ID
|
|||
RuleFor(x => x.CaptchaId) |
|||
.NotEmpty().WithMessage("验证码ID不能为空"); |
|||
|
|||
// 验证验证码
|
|||
RuleFor(x => x.CaptchaCode) |
|||
.NotEmpty().WithMessage("验证码不能为空") |
|||
.Length(4, 6).WithMessage("验证码长度必须在4-6个字符之间") |
|||
.Matches("^[A-Z0-9]+$").WithMessage("验证码只能包含大写字母和数字"); |
|||
|
|||
// 验证电话号码
|
|||
RuleFor(x => x.PhoneNumber) |
|||
.Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber)) |
|||
.WithMessage("电话号码格式不正确"); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
namespace CellularManagement.Application.Features.Auth.Commands.RegisterUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册响应
|
|||
/// </summary>
|
|||
public sealed record RegisterUserResponse( |
|||
/// <summary>
|
|||
/// 用户ID
|
|||
/// </summary>
|
|||
string Id, |
|||
|
|||
/// <summary>
|
|||
/// 账号
|
|||
/// </summary>
|
|||
string UserName, |
|||
|
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
string RealName); |
@ -0,0 +1,43 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.SendVerificationCode; |
|||
|
|||
/// <summary>
|
|||
/// 发送验证码命令
|
|||
/// 用于请求向指定邮箱发送验证码
|
|||
/// </summary>
|
|||
public sealed record SendVerificationCodeCommand : IRequest<OperationResult<SendVerificationCodeResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 目标邮箱地址
|
|||
/// </summary>
|
|||
public string Email { get; init; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 图形验证码ID
|
|||
/// </summary>
|
|||
public string CaptchaId { get; init; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 图形验证码
|
|||
/// </summary>
|
|||
public string CaptchaCode { get; init; } = string.Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 发送验证码响应
|
|||
/// 包含验证码发送的结果信息
|
|||
/// </summary>
|
|||
public sealed record SendVerificationCodeResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 是否发送成功
|
|||
/// </summary>
|
|||
public bool Success { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// 验证码有效期(分钟)
|
|||
/// </summary>
|
|||
public int ExpirationMinutes { get; init; } |
|||
} |
@ -0,0 +1,92 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using CellularManagement.Domain.Services; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Options; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using System.Linq; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.SendVerificationCode; |
|||
|
|||
/// <summary>
|
|||
/// 发送验证码命令处理器
|
|||
/// 处理发送验证码的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class SendVerificationCodeCommandHandler : IRequestHandler<SendVerificationCodeCommand, OperationResult<SendVerificationCodeResponse>> |
|||
{ |
|||
private readonly IEmailVerificationService _emailVerificationService; |
|||
private readonly ICaptchaVerificationService _captchaVerificationService; |
|||
private readonly ILogger<SendVerificationCodeCommandHandler> _logger; |
|||
private readonly EmailVerificationOptions _options; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="emailVerificationService">邮箱验证服务</param>
|
|||
/// <param name="captchaVerificationService">验证码验证服务</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
/// <param name="options">邮箱验证码配置选项</param>
|
|||
public SendVerificationCodeCommandHandler( |
|||
IEmailVerificationService emailVerificationService, |
|||
ICaptchaVerificationService captchaVerificationService, |
|||
ILogger<SendVerificationCodeCommandHandler> logger, |
|||
IOptions<EmailVerificationOptions> options) |
|||
{ |
|||
_emailVerificationService = emailVerificationService; |
|||
_captchaVerificationService = captchaVerificationService; |
|||
_logger = logger; |
|||
_options = options.Value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理发送验证码请求
|
|||
/// </summary>
|
|||
/// <param name="request">发送验证码命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>发送结果</returns>
|
|||
public async Task<OperationResult<SendVerificationCodeResponse>> Handle( |
|||
SendVerificationCodeCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 验证图形验证码
|
|||
var(IsSuccess, errorMessage) = await _captchaVerificationService.VerifyCaptchaAsync(request.CaptchaId, request.CaptchaCode); |
|||
if (!IsSuccess) |
|||
{ |
|||
return OperationResult<SendVerificationCodeResponse>.CreateFailure(errorMessage ?? "验证码验证失败"); |
|||
} |
|||
|
|||
// 记录开始发送验证码
|
|||
_logger.LogInformation("开始向邮箱 {Email} 发送验证码", request.Email); |
|||
|
|||
// 调用服务发送验证码
|
|||
var success = await _emailVerificationService.GenerateAndSendVerificationCodeAsync(request.Email); |
|||
|
|||
if (!success) |
|||
{ |
|||
_logger.LogWarning("向邮箱 {Email} 发送验证码失败", request.Email); |
|||
return OperationResult<SendVerificationCodeResponse>.CreateFailure("发送验证码失败"); |
|||
} |
|||
|
|||
// 记录发送成功
|
|||
_logger.LogInformation("成功向邮箱 {Email} 发送验证码", request.Email); |
|||
|
|||
// 返回成功响应
|
|||
return OperationResult<SendVerificationCodeResponse>.CreateSuccess( |
|||
new SendVerificationCodeResponse |
|||
{ |
|||
Success = true, |
|||
ExpirationMinutes = _options.VerificationCodeExpirationMinutes |
|||
}); |
|||
} |
|||
catch (System.Exception ex) |
|||
{ |
|||
// 记录异常
|
|||
_logger.LogError(ex, "向邮箱 {Email} 发送验证码时发生异常", request.Email); |
|||
return OperationResult<SendVerificationCodeResponse>.CreateFailure("发送验证码时发生错误"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.VerifyCode; |
|||
|
|||
/// <summary>
|
|||
/// 验证验证码命令
|
|||
/// 用于验证用户输入的验证码是否正确
|
|||
/// </summary>
|
|||
public sealed record VerifyCodeCommand : IRequest<OperationResult<VerifyCodeResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 邮箱地址
|
|||
/// </summary>
|
|||
public string Email { get; init; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 用户输入的验证码
|
|||
/// </summary>
|
|||
public string Code { get; init; } = string.Empty; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 验证验证码响应
|
|||
/// 包含验证结果信息
|
|||
/// </summary>
|
|||
public sealed record VerifyCodeResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 验证是否成功
|
|||
/// </summary>
|
|||
public bool Success { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// 验证失败的原因(如果验证失败)
|
|||
/// </summary>
|
|||
public string? FailureReason { get; init; } |
|||
} |
@ -0,0 +1,78 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Services; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.VerifyCode; |
|||
|
|||
/// <summary>
|
|||
/// 验证验证码命令处理器
|
|||
/// 处理验证码验证的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class VerifyCodeCommandHandler : IRequestHandler<VerifyCodeCommand, OperationResult<VerifyCodeResponse>> |
|||
{ |
|||
private readonly IEmailVerificationService _emailVerificationService; |
|||
private readonly ILogger<VerifyCodeCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="emailVerificationService">邮箱验证服务</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public VerifyCodeCommandHandler( |
|||
IEmailVerificationService emailVerificationService, |
|||
ILogger<VerifyCodeCommandHandler> logger) |
|||
{ |
|||
_emailVerificationService = emailVerificationService; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理验证验证码请求
|
|||
/// </summary>
|
|||
/// <param name="request">验证验证码命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>验证结果</returns>
|
|||
public async Task<OperationResult<VerifyCodeResponse>> Handle( |
|||
VerifyCodeCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 记录开始验证
|
|||
_logger.LogInformation("开始验证邮箱 {Email} 的验证码", request.Email); |
|||
|
|||
// 调用服务验证验证码
|
|||
var isValid = await _emailVerificationService.VerifyCodeAsync(request.Email, request.Code); |
|||
|
|||
if (!isValid) |
|||
{ |
|||
_logger.LogWarning("邮箱 {Email} 的验证码验证失败", request.Email); |
|||
return OperationResult<VerifyCodeResponse>.CreateSuccess( |
|||
new VerifyCodeResponse |
|||
{ |
|||
Success = false, |
|||
FailureReason = "验证码无效或已过期" |
|||
}); |
|||
} |
|||
|
|||
// 记录验证成功
|
|||
_logger.LogInformation("邮箱 {Email} 的验证码验证成功", request.Email); |
|||
|
|||
// 返回成功响应
|
|||
return OperationResult<VerifyCodeResponse>.CreateSuccess( |
|||
new VerifyCodeResponse |
|||
{ |
|||
Success = true |
|||
}); |
|||
} |
|||
catch (System.Exception ex) |
|||
{ |
|||
// 记录异常
|
|||
_logger.LogError(ex, "验证邮箱 {Email} 的验证码时发生异常", request.Email); |
|||
return OperationResult<VerifyCodeResponse>.CreateFailure("验证验证码时发生错误"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
/// <summary>
|
|||
/// 账号登录响应
|
|||
/// </summary>
|
|||
public class AuthenticateUserResponse : LoginResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化响应
|
|||
/// </summary>
|
|||
public AuthenticateUserResponse( |
|||
string accessToken, |
|||
string refreshToken, |
|||
DateTime expiresAt, |
|||
UserInfo user) |
|||
: base(accessToken, refreshToken, expiresAt, user) |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
/// <summary>
|
|||
/// 邮箱登录响应
|
|||
/// </summary>
|
|||
public class EmailLoginResponse : LoginResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化响应
|
|||
/// </summary>
|
|||
public EmailLoginResponse( |
|||
string accessToken, |
|||
string refreshToken, |
|||
DateTime expiresAt, |
|||
UserInfo user) |
|||
: base(accessToken, refreshToken, expiresAt, user) |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
/// <summary>
|
|||
/// 登录响应基类
|
|||
/// </summary>
|
|||
public abstract class LoginResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
public string AccessToken { get; } |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌
|
|||
/// </summary>
|
|||
public string RefreshToken { get; } |
|||
|
|||
/// <summary>
|
|||
/// 过期时间
|
|||
/// </summary>
|
|||
public DateTime ExpiresAt { get; } |
|||
|
|||
/// <summary>
|
|||
/// 用户信息
|
|||
/// </summary>
|
|||
public UserInfo User { get; } |
|||
|
|||
/// <summary>
|
|||
/// 初始化响应
|
|||
/// </summary>
|
|||
protected LoginResponse( |
|||
string accessToken, |
|||
string refreshToken, |
|||
DateTime expiresAt, |
|||
UserInfo user) |
|||
{ |
|||
AccessToken = accessToken; |
|||
RefreshToken = refreshToken; |
|||
ExpiresAt = expiresAt; |
|||
User = user; |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Models; |
|||
|
|||
/// <summary>
|
|||
/// 用户信息
|
|||
/// </summary>
|
|||
public class UserInfo |
|||
{ |
|||
/// <summary>
|
|||
/// 用户ID
|
|||
/// </summary>
|
|||
public string Id { get; } |
|||
|
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
public string UserName { get; } |
|||
|
|||
/// <summary>
|
|||
/// 真实姓名
|
|||
/// </summary>
|
|||
public string? RealName { get; } |
|||
|
|||
/// <summary>
|
|||
/// 邮箱
|
|||
/// </summary>
|
|||
public string Email { get; } |
|||
|
|||
/// <summary>
|
|||
/// 手机号
|
|||
/// </summary>
|
|||
public string? PhoneNumber { get; } |
|||
|
|||
/// <summary>
|
|||
/// 角色列表
|
|||
/// </summary>
|
|||
public IReadOnlyList<string> Roles { get; } |
|||
|
|||
/// <summary>
|
|||
/// 权限字典
|
|||
/// </summary>
|
|||
public IReadOnlyDictionary<string, bool> Permissions { get; } |
|||
|
|||
/// <summary>
|
|||
/// 初始化用户信息
|
|||
/// </summary>
|
|||
public UserInfo( |
|||
string id, |
|||
string userName, |
|||
string? realName, |
|||
string email, |
|||
string? phoneNumber, |
|||
IReadOnlyList<string> roles, |
|||
IReadOnlyDictionary<string, bool> permissions) |
|||
{ |
|||
Id = id; |
|||
UserName = userName; |
|||
RealName = realName; |
|||
Email = email; |
|||
PhoneNumber = phoneNumber; |
|||
Roles = roles; |
|||
Permissions = permissions; |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 创建设备命令
|
|||
/// </summary>
|
|||
public class CreateDeviceCommand : IRequest<OperationResult<CreateDeviceResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 设备名称
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "设备名称不能为空")] |
|||
[MaxLength(100, ErrorMessage = "设备名称不能超过100个字符")] |
|||
public string DeviceName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 序列号
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "序列号不能为空")] |
|||
[MaxLength(50, ErrorMessage = "序列号不能超过50个字符")] |
|||
public string SerialNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 备注
|
|||
/// </summary>
|
|||
[MaxLength(500, ErrorMessage = "备注不能超过500个字符")] |
|||
public string Comment { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备类型ID
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "设备类型不能为空")] |
|||
public string DeviceTypeId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 状态ID
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "设备状态不能为空")] |
|||
public string StatusId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 协议版本ID
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "协议版本不能为空")] |
|||
public string ProtocolVersionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 硬件版本
|
|||
/// </summary>
|
|||
[MaxLength(50, ErrorMessage = "硬件版本不能超过50个字符")] |
|||
public string HardwareVersion { get; set; } |
|||
} |
@ -0,0 +1,86 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Repositories.Device; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 创建设备命令处理器
|
|||
/// </summary>
|
|||
public class CreateDeviceCommandHandler : IRequestHandler<CreateDeviceCommand, OperationResult<CreateDeviceResponse>> |
|||
{ |
|||
private readonly ICellularDeviceRepository _deviceRepository; |
|||
private readonly ILogger<CreateDeviceCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化命令处理器
|
|||
/// </summary>
|
|||
public CreateDeviceCommandHandler( |
|||
ICellularDeviceRepository deviceRepository, |
|||
ILogger<CreateDeviceCommandHandler> logger) |
|||
{ |
|||
_deviceRepository = deviceRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理创建设备命令
|
|||
/// </summary>
|
|||
public async Task<OperationResult<CreateDeviceResponse>> Handle(CreateDeviceCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
_logger.LogInformation("开始创建设备,设备名称: {DeviceName}, 序列号: {SerialNumber}", |
|||
request.DeviceName, request.SerialNumber); |
|||
|
|||
// 检查序列号是否已存在
|
|||
if (await _deviceRepository.SerialNumberExistsAsync(request.SerialNumber, cancellationToken)) |
|||
{ |
|||
_logger.LogWarning("设备序列号已存在: {SerialNumber}", request.SerialNumber); |
|||
return OperationResult<CreateDeviceResponse>.CreateFailure($"设备序列号 {request.SerialNumber} 已存在"); |
|||
} |
|||
|
|||
// 创建设备实体
|
|||
var device = CellularDevice.Create( |
|||
name: request.DeviceName, |
|||
serialNumber: request.SerialNumber, |
|||
description: request.Comment, |
|||
deviceTypeId: request.DeviceTypeId, |
|||
statusId: request.StatusId, |
|||
protocolVersionId: request.ProtocolVersionId, |
|||
firmwareVersion: request.HardwareVersion); |
|||
|
|||
// 保存设备
|
|||
await _deviceRepository.AddDeviceAsync(device, cancellationToken); |
|||
|
|||
// 加载导航属性
|
|||
//await _deviceRepository.LoadNavigationPropertiesAsync(device, cancellationToken);
|
|||
|
|||
// 构建响应
|
|||
var response = new CreateDeviceResponse |
|||
{ |
|||
DeviceId = device.Id, |
|||
DeviceName = device.Name, |
|||
SerialNumber = device.SerialNumber, |
|||
DeviceType = device.DeviceType?.Name ?? "未知", |
|||
Status = device.Status?.Name ?? "未知", |
|||
ProtocolVersion = device.ProtocolVersion?.Version ?? "未知", |
|||
FirmwareVersion = device.FirmwareVersion, |
|||
CreatedAt = device.CreatedAt |
|||
}; |
|||
|
|||
_logger.LogInformation("设备创建成功,设备ID: {DeviceId}, 设备名称: {DeviceName}, 序列号: {SerialNumber}", |
|||
device.Id, device.Name, device.SerialNumber); |
|||
return OperationResult<CreateDeviceResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建设备时发生错误,设备名称: {DeviceName}, 序列号: {SerialNumber}", |
|||
request.DeviceName, request.SerialNumber); |
|||
return OperationResult<CreateDeviceResponse>.CreateFailure($"创建设备时发生错误: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
namespace CellularManagement.Application.Features.Devices.Commands.CreateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 创建设备响应
|
|||
/// </summary>
|
|||
public class CreateDeviceResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
public string DeviceId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备名称
|
|||
/// </summary>
|
|||
public string DeviceName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 序列号
|
|||
/// </summary>
|
|||
public string SerialNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备类型
|
|||
/// </summary>
|
|||
public string DeviceType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备状态
|
|||
/// </summary>
|
|||
public string Status { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 协议版本
|
|||
/// </summary>
|
|||
public string ProtocolVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 固件版本
|
|||
/// </summary>
|
|||
public string FirmwareVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreatedAt { get; set; } |
|||
} |
@ -0,0 +1,25 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice; |
|||
|
|||
/// <summary>
|
|||
/// 删除设备命令
|
|||
/// </summary>
|
|||
public class DeleteDeviceCommand : IRequest<OperationResult<bool>> |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "设备ID不能为空")] |
|||
public string DeviceId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 初始化删除设备命令
|
|||
/// </summary>
|
|||
public DeleteDeviceCommand(string deviceId) |
|||
{ |
|||
DeviceId = deviceId; |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Repositories.Device; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.DeleteDevice; |
|||
|
|||
/// <summary>
|
|||
/// 删除设备命令处理器
|
|||
/// </summary>
|
|||
public class DeleteDeviceCommandHandler : IRequestHandler<DeleteDeviceCommand, OperationResult<bool>> |
|||
{ |
|||
private readonly ICellularDeviceRepository _deviceRepository; |
|||
private readonly ILogger<DeleteDeviceCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化删除设备命令处理器
|
|||
/// </summary>
|
|||
public DeleteDeviceCommandHandler( |
|||
ICellularDeviceRepository deviceRepository, |
|||
ILogger<DeleteDeviceCommandHandler> logger) |
|||
{ |
|||
_deviceRepository = deviceRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理删除设备命令
|
|||
/// </summary>
|
|||
public async Task<OperationResult<bool>> Handle(DeleteDeviceCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
_logger.LogInformation("开始处理删除设备命令,设备ID: {DeviceId}", request.DeviceId); |
|||
|
|||
// 检查设备是否存在
|
|||
var device = await _deviceRepository.GetDeviceByIdAsync(request.DeviceId, cancellationToken); |
|||
if (device == null) |
|||
{ |
|||
_logger.LogWarning("未找到设备,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<bool>.CreateFailure($"未找到ID为 {request.DeviceId} 的设备"); |
|||
} |
|||
|
|||
// 删除设备
|
|||
await _deviceRepository.DeleteDeviceAsync(request.DeviceId, cancellationToken); |
|||
|
|||
_logger.LogInformation("设备删除成功,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<bool>.CreateSuccess("设备删除成功", true); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "删除设备时发生错误,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<bool>.CreateFailure($"删除设备时发生错误: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,63 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 更新设备命令
|
|||
/// </summary>
|
|||
public class UpdateDeviceCommand : IRequest<OperationResult<UpdateDeviceResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
[Required] |
|||
public string DeviceId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备名称
|
|||
/// </summary>
|
|||
[Required] |
|||
[MaxLength(100)] |
|||
public string DeviceName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 序列号
|
|||
/// </summary>
|
|||
[Required] |
|||
[MaxLength(50)] |
|||
public string SerialNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 备注
|
|||
/// </summary>
|
|||
[MaxLength(500)] |
|||
public string Comment { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备类型ID
|
|||
/// </summary>
|
|||
[Required] |
|||
public string DeviceTypeId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备状态ID
|
|||
/// </summary>
|
|||
[Required] |
|||
public string StatusId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 协议版本ID
|
|||
/// </summary>
|
|||
[Required] |
|||
public string ProtocolVersionId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 硬件版本
|
|||
/// </summary>
|
|||
[Required] |
|||
[MaxLength(50)] |
|||
public string HardwareVersion { get; set; } |
|||
} |
@ -0,0 +1,98 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Application.Features.Devices.Commands.UpdateDevice; |
|||
using CellularManagement.Domain.Repositories.Device; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 更新设备命令处理器
|
|||
/// </summary>
|
|||
public class UpdateDeviceCommandHandler : IRequestHandler<UpdateDeviceCommand, OperationResult<UpdateDeviceResponse>> |
|||
{ |
|||
private readonly ICellularDeviceRepository _deviceRepository; |
|||
private readonly ILogger<UpdateDeviceCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化命令处理器
|
|||
/// </summary>
|
|||
public UpdateDeviceCommandHandler( |
|||
ICellularDeviceRepository deviceRepository, |
|||
ILogger<UpdateDeviceCommandHandler> logger) |
|||
{ |
|||
_deviceRepository = deviceRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理更新设备命令
|
|||
/// </summary>
|
|||
public async Task<OperationResult<UpdateDeviceResponse>> Handle(UpdateDeviceCommand request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
_logger.LogInformation("开始更新设备,设备ID: {DeviceId}, 设备名称: {DeviceName}", |
|||
request.DeviceId, request.DeviceName); |
|||
|
|||
// 检查设备是否存在
|
|||
var existingDevice = await _deviceRepository.GetDeviceByIdAsync(request.DeviceId, cancellationToken); |
|||
if (existingDevice == null) |
|||
{ |
|||
_logger.LogWarning("未找到设备,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<UpdateDeviceResponse>.CreateFailure($"未找到ID为 {request.DeviceId} 的设备"); |
|||
} |
|||
|
|||
// 如果序列号发生变化,检查新序列号是否已存在
|
|||
if (existingDevice.SerialNumber != request.SerialNumber) |
|||
{ |
|||
if (await _deviceRepository.SerialNumberExistsAsync(request.SerialNumber, cancellationToken)) |
|||
{ |
|||
_logger.LogWarning("设备序列号已存在: {SerialNumber}", request.SerialNumber); |
|||
return OperationResult<UpdateDeviceResponse>.CreateFailure($"设备序列号 {request.SerialNumber} 已存在"); |
|||
} |
|||
} |
|||
|
|||
// 更新设备属性
|
|||
existingDevice.Update( |
|||
name: request.DeviceName, |
|||
serialNumber: request.SerialNumber, |
|||
description: request.Comment, |
|||
deviceTypeId: request.DeviceTypeId, |
|||
statusId: request.StatusId, |
|||
protocolVersionId: request.ProtocolVersionId, |
|||
firmwareVersion: request.HardwareVersion); |
|||
|
|||
// 保存更新
|
|||
await _deviceRepository.UpdateDeviceAsync(existingDevice, cancellationToken); |
|||
|
|||
// 加载导航属性
|
|||
//await _deviceRepository.LoadNavigationPropertiesAsync(existingDevice, cancellationToken);
|
|||
|
|||
// 构建响应
|
|||
var response = new UpdateDeviceResponse |
|||
{ |
|||
DeviceId = existingDevice.Id, |
|||
DeviceName = existingDevice.Name, |
|||
SerialNumber = existingDevice.SerialNumber, |
|||
DeviceType = existingDevice.DeviceType?.Name ?? "未知", |
|||
Status = existingDevice.Status?.Name ?? "未知", |
|||
ProtocolVersion = existingDevice.ProtocolVersion?.Version ?? "未知", |
|||
FirmwareVersion = existingDevice.FirmwareVersion, |
|||
UpdatedAt = existingDevice.UpdatedAt |
|||
}; |
|||
|
|||
_logger.LogInformation("设备更新成功,设备ID: {DeviceId}, 设备名称: {DeviceName}", |
|||
existingDevice.Id, existingDevice.Name); |
|||
return OperationResult<UpdateDeviceResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "更新设备时发生错误,设备ID: {DeviceId}, 设备名称: {DeviceName}", |
|||
request.DeviceId, request.DeviceName); |
|||
return OperationResult<UpdateDeviceResponse>.CreateFailure($"更新设备时发生错误: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
namespace CellularManagement.Application.Features.Devices.Commands.UpdateDevice; |
|||
|
|||
/// <summary>
|
|||
/// 更新设备响应
|
|||
/// </summary>
|
|||
public class UpdateDeviceResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
public string DeviceId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备名称
|
|||
/// </summary>
|
|||
public string DeviceName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 序列号
|
|||
/// </summary>
|
|||
public string SerialNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备类型
|
|||
/// </summary>
|
|||
public string DeviceType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备状态
|
|||
/// </summary>
|
|||
public string Status { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 协议版本
|
|||
/// </summary>
|
|||
public string ProtocolVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 固件版本
|
|||
/// </summary>
|
|||
public string FirmwareVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 更新时间
|
|||
/// </summary>
|
|||
public DateTime UpdatedAt { get; set; } |
|||
} |
@ -0,0 +1,26 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById; |
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取设备查询
|
|||
/// </summary>
|
|||
public class GetDeviceByIdQuery : IRequest<OperationResult<GetDeviceByIdResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
[Required(ErrorMessage = "设备ID不能为空")] |
|||
public string DeviceId { get; } |
|||
|
|||
/// <summary>
|
|||
/// 初始化查询
|
|||
/// </summary>
|
|||
public GetDeviceByIdQuery(string deviceId) |
|||
{ |
|||
DeviceId = deviceId; |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Repositories.Device; |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById; |
|||
|
|||
/// <summary>
|
|||
/// 根据ID获取设备查询处理器
|
|||
/// </summary>
|
|||
public class GetDeviceByIdQueryHandler : IRequestHandler<GetDeviceByIdQuery, OperationResult<GetDeviceByIdResponse>> |
|||
{ |
|||
private readonly ICellularDeviceRepository _deviceRepository; |
|||
private readonly ILogger<GetDeviceByIdQueryHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化查询处理器
|
|||
/// </summary>
|
|||
public GetDeviceByIdQueryHandler( |
|||
ICellularDeviceRepository deviceRepository, |
|||
ILogger<GetDeviceByIdQueryHandler> logger) |
|||
{ |
|||
_deviceRepository = deviceRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理查询请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<GetDeviceByIdResponse>> Handle(GetDeviceByIdQuery request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
_logger.LogInformation("开始查询设备,设备ID: {DeviceId}", request.DeviceId); |
|||
|
|||
var device = await _deviceRepository.GetDeviceByIdAsync(request.DeviceId, cancellationToken); |
|||
|
|||
if (device == null) |
|||
{ |
|||
_logger.LogWarning("未找到设备,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<GetDeviceByIdResponse>.CreateFailure($"未找到ID为 {request.DeviceId} 的设备"); |
|||
} |
|||
|
|||
var response = new GetDeviceByIdResponse |
|||
{ |
|||
DeviceId = device.Id, |
|||
DeviceName = device.Name, |
|||
SerialNumber = device.SerialNumber, |
|||
DeviceType = device.DeviceType?.Name ?? "未知", |
|||
Status = device.Status?.Name ?? "未知", |
|||
ProtocolVersion = device.ProtocolVersion?.Version ?? "未知", |
|||
FirmwareVersion = device.FirmwareVersion, |
|||
CreatedAt = device.CreatedAt |
|||
}; |
|||
|
|||
_logger.LogInformation("成功查询到设备,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<GetDeviceByIdResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "查询设备时发生错误,设备ID: {DeviceId}", request.DeviceId); |
|||
return OperationResult<GetDeviceByIdResponse>.CreateFailure($"查询设备时发生错误: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDeviceById; |
|||
|
|||
/// <summary>
|
|||
/// 获取设备详情响应
|
|||
/// </summary>
|
|||
public class GetDeviceByIdResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 设备ID
|
|||
/// </summary>
|
|||
public string DeviceId { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备名称
|
|||
/// </summary>
|
|||
public string DeviceName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 序列号
|
|||
/// </summary>
|
|||
public string SerialNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备类型
|
|||
/// </summary>
|
|||
public string DeviceType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备状态
|
|||
/// </summary>
|
|||
public string Status { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 协议版本
|
|||
/// </summary>
|
|||
public string ProtocolVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 固件版本
|
|||
/// </summary>
|
|||
public string FirmwareVersion { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
public DateTime CreatedAt { get; set; } |
|||
} |
@ -0,0 +1,34 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; |
|||
|
|||
/// <summary>
|
|||
/// 获取设备列表查询
|
|||
/// </summary>
|
|||
public class GetDevicesQuery : IRequest<OperationResult<GetDevicesResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 页码,从1开始
|
|||
/// </summary>
|
|||
[Range(1, int.MaxValue, ErrorMessage = "页码必须大于0")] |
|||
public int PageNumber { get; set; } = 1; |
|||
|
|||
/// <summary>
|
|||
/// 每页记录数
|
|||
/// </summary>
|
|||
[Range(1, 100, ErrorMessage = "每页记录数必须在1-100之间")] |
|||
public int PageSize { get; set; } = 10; |
|||
|
|||
/// <summary>
|
|||
/// 搜索关键词
|
|||
/// </summary>
|
|||
[MaxLength(100, ErrorMessage = "搜索关键词不能超过100个字符")] |
|||
public string? SearchTerm { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否禁用
|
|||
/// </summary>
|
|||
public bool? IsDisabled { get; set; } |
|||
} |
@ -0,0 +1,97 @@ |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using CellularManagement.Domain.Entities.Device; |
|||
using CellularManagement.Domain.Repositories; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using CellularManagement.Application.Features.Devices.Queries.GetDeviceById; |
|||
using CellularManagement.Domain.Repositories.Device; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; |
|||
|
|||
/// <summary>
|
|||
/// 获取设备列表查询处理器
|
|||
/// </summary>
|
|||
public class GetDevicesQueryHandler : IRequestHandler<GetDevicesQuery, OperationResult<GetDevicesResponse>> |
|||
{ |
|||
private readonly ICellularDeviceRepository _deviceRepository; |
|||
private readonly ILogger<GetDevicesQueryHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化查询处理器
|
|||
/// </summary>
|
|||
public GetDevicesQueryHandler( |
|||
ICellularDeviceRepository deviceRepository, |
|||
ILogger<GetDevicesQueryHandler> logger) |
|||
{ |
|||
_deviceRepository = deviceRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理获取设备列表查询
|
|||
/// </summary>
|
|||
public async Task<OperationResult<GetDevicesResponse>> Handle(GetDevicesQuery request, CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 验证请求参数
|
|||
var validationContext = new ValidationContext(request); |
|||
var validationResults = new List<ValidationResult>(); |
|||
if (!Validator.TryValidateObject(request, validationContext, validationResults, true)) |
|||
{ |
|||
var errorMessages = validationResults.Select(r => r.ErrorMessage).ToList(); |
|||
_logger.LogWarning("请求参数无效: {Errors}", string.Join(", ", errorMessages)); |
|||
return OperationResult<GetDevicesResponse>.CreateFailure(errorMessages); |
|||
} |
|||
|
|||
_logger.LogInformation("开始获取设备列表,页码: {PageNumber}, 每页数量: {PageSize}, 搜索关键词: {SearchTerm}", |
|||
request.PageNumber, request.PageSize, request.SearchTerm); |
|||
|
|||
// 获取分页数据
|
|||
var devices = await _deviceRepository.SearchDevicesAsync( |
|||
request.SearchTerm, |
|||
null, // deviceTypeId
|
|||
null, // statusId
|
|||
cancellationToken); |
|||
|
|||
// 计算分页
|
|||
int totalCount = devices.Count(); |
|||
var items = devices |
|||
.Skip((request.PageNumber - 1) * request.PageSize) |
|||
.Take(request.PageSize) |
|||
.ToList(); |
|||
|
|||
// 构建响应
|
|||
var response = new GetDevicesResponse |
|||
{ |
|||
TotalCount = totalCount, |
|||
PageNumber = request.PageNumber, |
|||
PageSize = request.PageSize, |
|||
TotalPages = (int)Math.Ceiling(totalCount / (double)request.PageSize), |
|||
HasPreviousPage = request.PageNumber > 1, |
|||
HasNextPage = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize), |
|||
Items = items.Select(d => new GetDeviceByIdResponse |
|||
{ |
|||
DeviceId = d.Id, |
|||
DeviceName = d.Name, |
|||
SerialNumber = d.SerialNumber, |
|||
DeviceType = d.DeviceType?.Name ?? "未知", |
|||
Status = d.Status?.Name ?? "未知", |
|||
ProtocolVersion = d.ProtocolVersion?.Version ?? "未知", |
|||
FirmwareVersion = d.FirmwareVersion, |
|||
CreatedAt = d.CreatedAt |
|||
}).ToList() |
|||
}; |
|||
|
|||
_logger.LogInformation("成功获取设备列表,共 {Count} 条记录", items.Count); |
|||
return OperationResult<GetDevicesResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "获取设备列表时发生错误"); |
|||
return OperationResult<GetDevicesResponse>.CreateFailure($"获取设备列表时发生错误: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
using CellularManagement.Application.Features.Devices.Queries.GetDeviceById; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace CellularManagement.Application.Features.Devices.Queries.GetDevices; |
|||
|
|||
/// <summary>
|
|||
/// 设备列表响应
|
|||
/// </summary>
|
|||
public class GetDevicesResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 总记录数
|
|||
/// </summary>
|
|||
public int TotalCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 当前页码
|
|||
/// </summary>
|
|||
public int PageNumber { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 每页记录数
|
|||
/// </summary>
|
|||
public int PageSize { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 总页数
|
|||
/// </summary>
|
|||
public int TotalPages { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否有上一页
|
|||
/// </summary>
|
|||
public bool HasPreviousPage { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 是否有下一页
|
|||
/// </summary>
|
|||
public bool HasNextPage { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 设备列表
|
|||
/// </summary>
|
|||
public List<GetDeviceByIdResponse> Items { get; set; } = new(); |
|||
} |
@ -0,0 +1,19 @@ |
|||
using MediatR; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Domain.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限命令
|
|||
/// </summary>
|
|||
public sealed record CreatePermissionCommand( |
|||
/// <summary>
|
|||
/// 权限名称
|
|||
/// </summary>
|
|||
string Name, |
|||
|
|||
/// <summary>
|
|||
/// 权限描述
|
|||
/// </summary>
|
|||
string? Description) : IRequest<OperationResult<CreatePermissionResponse>>; |
@ -0,0 +1,65 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MediatR; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Common; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限命令处理器
|
|||
/// </summary>
|
|||
public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermissionCommand, OperationResult<CreatePermissionResponse>> |
|||
{ |
|||
private readonly IPermissionRepository _permissionRepository; |
|||
private readonly ILogger<CreatePermissionCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public CreatePermissionCommandHandler( |
|||
IPermissionRepository permissionRepository, |
|||
ILogger<CreatePermissionCommandHandler> logger) |
|||
{ |
|||
_permissionRepository = permissionRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理创建权限请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<CreatePermissionResponse>> Handle( |
|||
CreatePermissionCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 检查权限是否已存在
|
|||
var existingPermission = await _permissionRepository.GetByNameAsync(request.Name, cancellationToken); |
|||
if (existingPermission != null) |
|||
{ |
|||
_logger.LogWarning("权限 {PermissionName} 已存在", request.Name); |
|||
return OperationResult<CreatePermissionResponse>.CreateFailure("权限已存在"); |
|||
} |
|||
|
|||
// 创建权限
|
|||
var permission = Permission.Create(request.Name, request.Description); |
|||
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken); |
|||
|
|||
_logger.LogInformation("权限 {PermissionName} 创建成功", request.Name); |
|||
|
|||
return OperationResult<CreatePermissionResponse>.CreateSuccess( |
|||
new CreatePermissionResponse(createdPermission.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建权限 {PermissionName} 失败", request.Name); |
|||
return OperationResult<CreatePermissionResponse>.CreateFailure("创建权限失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
namespace CellularManagement.Application.Features.Permissions.Commands.CreatePermission; |
|||
|
|||
/// <summary>
|
|||
/// 创建权限响应
|
|||
/// </summary>
|
|||
public sealed record CreatePermissionResponse( |
|||
/// <summary>
|
|||
/// 权限ID
|
|||
/// </summary>
|
|||
string PermissionId); |
@ -0,0 +1,21 @@ |
|||
using System.Collections.Generic; |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 添加角色权限命令
|
|||
/// </summary>
|
|||
public class AddRolePermissionsCommand : IRequest<OperationResult<AddRolePermissionsResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
public string RoleId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 权限ID列表
|
|||
/// </summary>
|
|||
public IEnumerable<string> PermissionIds { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,77 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Repositories; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System; |
|||
using System.Linq; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 添加角色权限命令处理器
|
|||
/// </summary>
|
|||
public class AddRolePermissionsCommandHandler : IRequestHandler<AddRolePermissionsCommand, OperationResult<AddRolePermissionsResponse>> |
|||
{ |
|||
private readonly IRolePermissionRepository _rolePermissionRepository; |
|||
private readonly ILogger<AddRolePermissionsCommandHandler> _logger; |
|||
|
|||
public AddRolePermissionsCommandHandler( |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
ILogger<AddRolePermissionsCommandHandler> logger) |
|||
{ |
|||
_rolePermissionRepository = rolePermissionRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
public async Task<OperationResult<AddRolePermissionsResponse>> Handle( |
|||
AddRolePermissionsCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 获取角色当前已有的权限
|
|||
var existingPermissions = await _rolePermissionRepository.GetPermissionsByRoleIdAsync(request.RoleId, cancellationToken); |
|||
var existingPermissionIds = existingPermissions.Select(p => p.Id).ToList(); |
|||
|
|||
// 过滤出需要新增的权限ID
|
|||
var newPermissionIds = request.PermissionIds.Except(existingPermissionIds).ToList(); |
|||
|
|||
if (!newPermissionIds.Any()) |
|||
{ |
|||
return OperationResult<AddRolePermissionsResponse>.CreateFailure("所有权限已存在,无需添加"); |
|||
} |
|||
|
|||
// 添加新的角色权限
|
|||
var addedRolePermissions = await _rolePermissionRepository.AddRolePermissionsAsync( |
|||
request.RoleId, |
|||
newPermissionIds, |
|||
cancellationToken); |
|||
|
|||
var response = new AddRolePermissionsResponse |
|||
{ |
|||
AddedPermissionIds = newPermissionIds, |
|||
FailedPermissionIds = request.PermissionIds.Except(newPermissionIds) |
|||
}; |
|||
|
|||
_logger.LogInformation( |
|||
"成功为角色 {RoleId} 添加 {Count} 个权限", |
|||
request.RoleId, |
|||
newPermissionIds.Count); |
|||
|
|||
return OperationResult<AddRolePermissionsResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError( |
|||
ex, |
|||
"为角色 {RoleId} 添加权限时发生错误", |
|||
request.RoleId); |
|||
|
|||
return OperationResult<AddRolePermissionsResponse>.CreateFailure( |
|||
"添加角色权限失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using FluentValidation; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 添加角色权限命令验证器
|
|||
/// </summary>
|
|||
public class AddRolePermissionsCommandValidator : AbstractValidator<AddRolePermissionsCommand> |
|||
{ |
|||
public AddRolePermissionsCommandValidator() |
|||
{ |
|||
RuleFor(x => x.RoleId) |
|||
.NotEmpty().WithMessage("角色ID不能为空"); |
|||
|
|||
RuleFor(x => x.PermissionIds) |
|||
.NotEmpty().WithMessage("权限ID列表不能为空") |
|||
.Must(x => x.Any()).WithMessage("至少需要指定一个权限ID"); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.AddRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 添加角色权限响应
|
|||
/// </summary>
|
|||
public class AddRolePermissionsResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 添加成功的权限ID列表
|
|||
/// </summary>
|
|||
public IEnumerable<string> AddedPermissionIds { get; set; } = new List<string>(); |
|||
|
|||
/// <summary>
|
|||
/// 添加失败的权限ID列表
|
|||
/// </summary>
|
|||
public IEnumerable<string> FailedPermissionIds { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,21 @@ |
|||
using System.Collections.Generic; |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限命令
|
|||
/// </summary>
|
|||
public class DeleteRolePermissionsCommand : IRequest<OperationResult<DeleteRolePermissionsResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
public string RoleId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 要删除的权限ID列表
|
|||
/// </summary>
|
|||
public IEnumerable<string> PermissionIds { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,77 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Repositories; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限命令处理器
|
|||
/// </summary>
|
|||
public class DeleteRolePermissionsCommandHandler : IRequestHandler<DeleteRolePermissionsCommand, OperationResult<DeleteRolePermissionsResponse>> |
|||
{ |
|||
private readonly IRolePermissionRepository _rolePermissionRepository; |
|||
private readonly ILogger<DeleteRolePermissionsCommandHandler> _logger; |
|||
|
|||
public DeleteRolePermissionsCommandHandler( |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
ILogger<DeleteRolePermissionsCommandHandler> logger) |
|||
{ |
|||
_rolePermissionRepository = rolePermissionRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
public async Task<OperationResult<DeleteRolePermissionsResponse>> Handle( |
|||
DeleteRolePermissionsCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 获取角色当前已有的权限
|
|||
var existingPermissions = await _rolePermissionRepository.GetPermissionsByRoleIdAsync(request.RoleId, cancellationToken); |
|||
var existingPermissionIds = existingPermissions.Select(p => p.Id).ToList(); |
|||
|
|||
// 过滤出实际存在的权限ID
|
|||
var validPermissionIds = request.PermissionIds.Intersect(existingPermissionIds).ToList(); |
|||
|
|||
if (!validPermissionIds.Any()) |
|||
{ |
|||
return OperationResult<DeleteRolePermissionsResponse>.CreateFailure("指定的权限不存在,无需删除"); |
|||
} |
|||
|
|||
// 删除角色权限
|
|||
var deletedCount = await _rolePermissionRepository.DeleteRolePermissionsAsync( |
|||
request.RoleId, |
|||
validPermissionIds, |
|||
cancellationToken); |
|||
|
|||
var response = new DeleteRolePermissionsResponse |
|||
{ |
|||
DeletedCount = deletedCount, |
|||
FailedPermissionIds = request.PermissionIds.Except(validPermissionIds) |
|||
}; |
|||
|
|||
_logger.LogInformation( |
|||
"成功从角色 {RoleId} 删除 {Count} 个权限", |
|||
request.RoleId, |
|||
deletedCount); |
|||
|
|||
return OperationResult<DeleteRolePermissionsResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError( |
|||
ex, |
|||
"从角色 {RoleId} 删除权限时发生错误", |
|||
request.RoleId); |
|||
|
|||
return OperationResult<DeleteRolePermissionsResponse>.CreateFailure( |
|||
"删除角色权限失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
using FluentValidation; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限命令验证器
|
|||
/// </summary>
|
|||
public class DeleteRolePermissionsCommandValidator : AbstractValidator<DeleteRolePermissionsCommand> |
|||
{ |
|||
public DeleteRolePermissionsCommandValidator() |
|||
{ |
|||
RuleFor(x => x.RoleId) |
|||
.NotEmpty().WithMessage("角色ID不能为空"); |
|||
|
|||
RuleFor(x => x.PermissionIds) |
|||
.NotEmpty().WithMessage("权限ID列表不能为空") |
|||
.Must(x => x.Any()).WithMessage("至少需要指定一个要删除的权限ID"); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
namespace CellularManagement.Application.Features.RolePermissions.Commands.DeleteRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色权限响应
|
|||
/// </summary>
|
|||
public class DeleteRolePermissionsResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 成功删除的权限数量
|
|||
/// </summary>
|
|||
public int DeletedCount { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// 删除失败的权限ID列表
|
|||
/// </summary>
|
|||
public IEnumerable<string> FailedPermissionIds { get; set; } = new List<string>(); |
|||
} |
@ -0,0 +1,20 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 获取角色权限查询命令
|
|||
/// </summary>
|
|||
public class GetRolePermissionsQuery : IRequest<OperationResult<GetRolePermissionsResponse>> |
|||
{ |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
public string RoleId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 是否包含权限详情
|
|||
/// </summary>
|
|||
public bool IncludeDetails { get; set; } = true; |
|||
} |
@ -0,0 +1,71 @@ |
|||
using MediatR; |
|||
using CellularManagement.Domain.Repositories; |
|||
using Microsoft.Extensions.Logging; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Identity; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 获取角色权限查询处理器
|
|||
/// </summary>
|
|||
public class GetRolePermissionsQueryHandler : IRequestHandler<GetRolePermissionsQuery, OperationResult<GetRolePermissionsResponse>> |
|||
{ |
|||
private readonly IRolePermissionRepository _rolePermissionRepository; |
|||
private readonly ILogger<GetRolePermissionsQueryHandler> _logger; |
|||
|
|||
public GetRolePermissionsQueryHandler( |
|||
IRolePermissionRepository rolePermissionRepository, |
|||
ILogger<GetRolePermissionsQueryHandler> logger) |
|||
{ |
|||
_rolePermissionRepository = rolePermissionRepository; |
|||
_logger = logger; |
|||
} |
|||
|
|||
public async Task<OperationResult<GetRolePermissionsResponse>> Handle( |
|||
GetRolePermissionsQuery request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 获取角色权限
|
|||
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync( |
|||
request.RoleId, |
|||
cancellationToken); |
|||
|
|||
if (!rolePermissions.Any()) |
|||
{ |
|||
return OperationResult<GetRolePermissionsResponse>.CreateFailure("未找到角色权限信息"); |
|||
} |
|||
|
|||
// 构建响应
|
|||
var response = new GetRolePermissionsResponse |
|||
{ |
|||
RoleId = request.RoleId, |
|||
RoleName = rolePermissions.First().Role.Name, |
|||
Permissions = rolePermissions.Select(rp => PermissionDto.FromEntity(rp.Permission)) |
|||
}; |
|||
|
|||
_logger.LogInformation( |
|||
"成功获取角色 {RoleId} 的权限信息,共 {Count} 个权限", |
|||
request.RoleId, |
|||
response.Permissions.Count()); |
|||
|
|||
return OperationResult<GetRolePermissionsResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError( |
|||
ex, |
|||
"获取角色 {RoleId} 的权限信息时发生错误", |
|||
request.RoleId); |
|||
|
|||
return OperationResult<GetRolePermissionsResponse>.CreateFailure( |
|||
"获取角色权限信息失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,65 @@ |
|||
using System.Collections.Generic; |
|||
using CellularManagement.Domain.Entities; |
|||
|
|||
namespace CellularManagement.Application.Features.RolePermissions.Queries.GetRolePermissions; |
|||
|
|||
/// <summary>
|
|||
/// 获取角色权限响应
|
|||
/// </summary>
|
|||
public class GetRolePermissionsResponse |
|||
{ |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
public string RoleId { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 角色名称
|
|||
/// </summary>
|
|||
public string RoleName { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 权限列表
|
|||
/// </summary>
|
|||
public IEnumerable<PermissionDto> Permissions { get; set; } = new List<PermissionDto>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 权限数据传输对象
|
|||
/// </summary>
|
|||
public class PermissionDto |
|||
{ |
|||
/// <summary>
|
|||
/// 权限ID
|
|||
/// </summary>
|
|||
public string Id { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 权限名称
|
|||
/// </summary>
|
|||
public string Name { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 权限编码
|
|||
/// </summary>
|
|||
public string Code { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 权限描述
|
|||
/// </summary>
|
|||
public string Description { get; set; } = string.Empty; |
|||
|
|||
/// <summary>
|
|||
/// 从权限实体创建DTO
|
|||
/// </summary>
|
|||
public static PermissionDto FromEntity(Permission permission) |
|||
{ |
|||
return new PermissionDto |
|||
{ |
|||
Id = permission.Id, |
|||
Name = permission.Name, |
|||
Code = permission.Code, |
|||
Description = permission.Description |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole; |
|||
|
|||
/// <summary>
|
|||
/// 创建角色命令
|
|||
/// </summary>
|
|||
/// <example>
|
|||
/// {
|
|||
/// "name": "Manager",
|
|||
/// "description": "部门经理角色,拥有管理权限"
|
|||
/// }
|
|||
/// </example>
|
|||
public sealed record CreateRoleCommand( |
|||
/// <summary>
|
|||
/// 角色名称
|
|||
/// </summary>
|
|||
/// <example>Manager</example>
|
|||
string Name, |
|||
|
|||
/// <summary>
|
|||
/// 角色描述
|
|||
/// </summary>
|
|||
/// <example>部门经理角色,拥有管理权限</example>
|
|||
string? Description) : IRequest<OperationResult<CreateRoleResponse>>; |
@ -0,0 +1,10 @@ |
|||
namespace CellularManagement.Application.Features.Roles.Commands.CreateRole; |
|||
|
|||
/// <summary>
|
|||
/// 创建角色响应
|
|||
/// </summary>
|
|||
public sealed record CreateRoleResponse( |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
string RoleId); |
@ -0,0 +1,13 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色命令
|
|||
/// </summary>
|
|||
public sealed record DeleteRoleCommand( |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
string RoleId) : IRequest<OperationResult<DeleteRoleResponse>>; |
@ -0,0 +1,10 @@ |
|||
namespace CellularManagement.Application.Features.Roles.Commands.DeleteRole; |
|||
|
|||
/// <summary>
|
|||
/// 删除角色响应
|
|||
/// </summary>
|
|||
public sealed record DeleteRoleResponse( |
|||
/// <summary>
|
|||
/// 是否删除成功
|
|||
/// </summary>
|
|||
bool Success); |
@ -0,0 +1,117 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Features.Roles.Commands.CreateRole; |
|||
using CellularManagement.Application.Features.Roles.Commands.DeleteRole; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Linq; |
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Commands; |
|||
|
|||
/// <summary>
|
|||
/// 角色命令处理器
|
|||
/// </summary>
|
|||
public sealed class RoleCommandHandler : |
|||
IRequestHandler<CreateRoleCommand, OperationResult<CreateRoleResponse>>, |
|||
IRequestHandler<DeleteRoleCommand, OperationResult<DeleteRoleResponse>> |
|||
{ |
|||
private readonly RoleManager<AppRole> _roleManager; |
|||
private readonly ILogger<RoleCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public RoleCommandHandler( |
|||
RoleManager<AppRole> roleManager, |
|||
ILogger<RoleCommandHandler> logger) |
|||
{ |
|||
_roleManager = roleManager; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理创建角色请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<CreateRoleResponse>> Handle( |
|||
CreateRoleCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 检查角色是否已存在
|
|||
var existingRole = await _roleManager.FindByNameAsync(request.Name); |
|||
if (existingRole != null) |
|||
{ |
|||
_logger.LogWarning("角色 {RoleName} 已存在", request.Name); |
|||
return OperationResult<CreateRoleResponse>.CreateFailure("角色已存在"); |
|||
} |
|||
|
|||
// 创建角色
|
|||
var role = new AppRole |
|||
{ |
|||
Name = request.Name, |
|||
Description = request.Description |
|||
}; |
|||
|
|||
var result = await _roleManager.CreateAsync(role); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("创建角色失败: {Errors}", string.Join(", ", errors)); |
|||
return OperationResult<CreateRoleResponse>.CreateFailure(errors); |
|||
} |
|||
|
|||
_logger.LogInformation("角色 {RoleName} 创建成功", request.Name); |
|||
|
|||
return OperationResult<CreateRoleResponse>.CreateSuccess( |
|||
new CreateRoleResponse(role.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建角色 {RoleName} 失败", request.Name); |
|||
return OperationResult<CreateRoleResponse>.CreateFailure("创建角色失败,请稍后重试"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理删除角色请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<DeleteRoleResponse>> Handle( |
|||
DeleteRoleCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 查找角色
|
|||
var role = await _roleManager.FindByIdAsync(request.RoleId); |
|||
if (role == null) |
|||
{ |
|||
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId); |
|||
return OperationResult<DeleteRoleResponse>.CreateFailure("角色不存在"); |
|||
} |
|||
|
|||
// 删除角色
|
|||
var result = await _roleManager.DeleteAsync(role); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("删除角色失败: {Errors}", string.Join(", ", errors)); |
|||
return OperationResult<DeleteRoleResponse>.CreateFailure(errors); |
|||
} |
|||
|
|||
_logger.LogInformation("角色 {RoleId} 删除成功", request.RoleId); |
|||
|
|||
return OperationResult<DeleteRoleResponse>.CreateSuccess( |
|||
new DeleteRoleResponse(true)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "删除角色 {RoleId} 失败", request.RoleId); |
|||
return OperationResult<DeleteRoleResponse>.CreateFailure("删除角色失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
using MediatR; |
|||
using CellularManagement.Application.Common; |
|||
using System; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Queries; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有角色的查询,支持分页和按名称模糊查询
|
|||
/// </summary>
|
|||
public sealed record GetAllRolesQuery( |
|||
int PageNumber = 1, |
|||
int PageSize = 10, |
|||
string? RoleName = null |
|||
) : IRequest<OperationResult<GetAllRolesResponse>>; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有角色响应,包含角色列表和分页信息
|
|||
/// </summary>
|
|||
public sealed record GetAllRolesResponse( |
|||
List<RoleDto> Roles, |
|||
int TotalCount, |
|||
int PageNumber, |
|||
int PageSize |
|||
) |
|||
{ |
|||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); |
|||
public bool HasPreviousPage => PageNumber > 1; |
|||
public bool HasNextPage => PageNumber < TotalPages; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 角色数据传输对象
|
|||
/// </summary>
|
|||
public record RoleDto( |
|||
string Id, |
|||
string Name, |
|||
string Description, |
|||
DateTime CreatedAt, |
|||
DateTime UpdatedAt); |
@ -0,0 +1,13 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Queries.GetRole; |
|||
|
|||
/// <summary>
|
|||
/// 获取角色查询
|
|||
/// </summary>
|
|||
public sealed record GetRoleQuery( |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
string RoleId) : IRequest<OperationResult<GetRoleResponse>>; |
@ -0,0 +1,32 @@ |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Queries.GetRole; |
|||
|
|||
/// <summary>
|
|||
/// 获取角色响应
|
|||
/// </summary>
|
|||
public sealed record GetRoleResponse( |
|||
/// <summary>
|
|||
/// 角色ID
|
|||
/// </summary>
|
|||
string Id, |
|||
|
|||
/// <summary>
|
|||
/// 角色名称
|
|||
/// </summary>
|
|||
string Name, |
|||
|
|||
/// <summary>
|
|||
/// 角色描述
|
|||
/// </summary>
|
|||
string? Description, |
|||
|
|||
/// <summary>
|
|||
/// 创建时间
|
|||
/// </summary>
|
|||
DateTime CreatedAt, |
|||
|
|||
/// <summary>
|
|||
/// 更新时间
|
|||
/// </summary>
|
|||
DateTime UpdatedAt); |
@ -0,0 +1,119 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Features.Roles.Queries.GetRole; |
|||
using CellularManagement.Application.Features.Roles.Queries; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System; |
|||
using System.Linq; |
|||
|
|||
namespace CellularManagement.Application.Features.Roles.Queries; |
|||
|
|||
/// <summary>
|
|||
/// 角色查询处理器
|
|||
/// </summary>
|
|||
public sealed class RoleQueryHandler : |
|||
IRequestHandler<GetRoleQuery, OperationResult<GetRoleResponse>>, |
|||
IRequestHandler<GetAllRolesQuery, OperationResult<GetAllRolesResponse>> |
|||
{ |
|||
private readonly RoleManager<AppRole> _roleManager; |
|||
private readonly ILogger<RoleQueryHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public RoleQueryHandler( |
|||
RoleManager<AppRole> roleManager, |
|||
ILogger<RoleQueryHandler> logger) |
|||
{ |
|||
_roleManager = roleManager; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理获取角色请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<GetRoleResponse>> Handle( |
|||
GetRoleQuery request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 查找角色
|
|||
var role = await _roleManager.FindByIdAsync(request.RoleId); |
|||
if (role == null) |
|||
{ |
|||
_logger.LogWarning("角色 {RoleId} 不存在", request.RoleId); |
|||
return OperationResult<GetRoleResponse>.CreateFailure("角色不存在"); |
|||
} |
|||
|
|||
_logger.LogInformation("获取角色 {RoleId} 成功", request.RoleId); |
|||
|
|||
return OperationResult<GetRoleResponse>.CreateSuccess( |
|||
new GetRoleResponse( |
|||
role.Id, |
|||
role.Name, |
|||
role.Description, |
|||
role.CreatedAt, |
|||
role.UpdatedAt)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "获取角色 {RoleId} 失败", request.RoleId); |
|||
return OperationResult<GetRoleResponse>.CreateFailure("获取角色失败,请稍后重试"); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理获取所有角色请求(分页+模糊查询)
|
|||
/// </summary>
|
|||
public async Task<OperationResult<GetAllRolesResponse>> Handle( |
|||
GetAllRolesQuery request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
var query = _roleManager.Roles.AsQueryable(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(request.RoleName)) |
|||
{ |
|||
query = query.Where(r => r.Name.Contains(request.RoleName)); |
|||
} |
|||
|
|||
var totalCount = await query.CountAsync(cancellationToken); |
|||
|
|||
var roles = await query |
|||
.Skip((request.PageNumber - 1) * request.PageSize) |
|||
.Take(request.PageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
var roleDtos = roles.Select(role => new RoleDto( |
|||
role.Id, |
|||
role.Name, |
|||
role.Description, |
|||
role.CreatedAt, |
|||
role.UpdatedAt |
|||
)).ToList(); |
|||
|
|||
var response = new GetAllRolesResponse( |
|||
roleDtos, |
|||
totalCount, |
|||
request.PageNumber, |
|||
request.PageSize |
|||
); |
|||
|
|||
_logger.LogInformation("成功获取所有角色,共 {Count} 个", totalCount); |
|||
|
|||
return OperationResult<GetAllRolesResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "获取所有角色失败"); |
|||
return OperationResult<GetAllRolesResponse>.CreateFailure("获取角色列表失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.CreateUser; |
|||
|
|||
/// <summary>
|
|||
/// 创建用户命令
|
|||
/// 用于创建新用户的命令对象
|
|||
/// </summary>
|
|||
/// <param name="UserName">账号</param>
|
|||
/// <param name="Email">电子邮箱</param>
|
|||
/// <param name="PhoneNumber">电话号码</param>
|
|||
/// <param name="Password">密码</param>
|
|||
public sealed record CreateUserCommand( |
|||
string UserName, |
|||
string Email, |
|||
string PhoneNumber, |
|||
string Password) : IRequest<OperationResult<CreateUserResponse>>; |
@ -0,0 +1,93 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System; |
|||
using System.Linq; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.CreateUser; |
|||
|
|||
/// <summary>
|
|||
/// 创建用户命令处理器
|
|||
/// 处理创建新用户的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, OperationResult<CreateUserResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<CreateUserCommandHandler> _logger; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="userManager">用户管理器,用于管理用户身份</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
/// <param name="unitOfWork">工作单元,用于事务管理</param>
|
|||
public CreateUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<CreateUserCommandHandler> logger, |
|||
IUnitOfWork unitOfWork) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
_unitOfWork = unitOfWork; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理创建用户命令
|
|||
/// </summary>
|
|||
/// <param name="request">创建用户命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>操作结果,包含创建的用户ID</returns>
|
|||
public async Task<OperationResult<CreateUserResponse>> Handle( |
|||
CreateUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 检查用户是否已存在
|
|||
var existingUser = await _userManager.FindByEmailAsync(request.Email); |
|||
if (existingUser != null) |
|||
{ |
|||
_logger.LogWarning("用户邮箱 {Email} 已存在", request.Email); |
|||
return OperationResult<CreateUserResponse>.CreateFailure("用户已存在"); |
|||
} |
|||
|
|||
// 创建新用户
|
|||
var user = new AppUser |
|||
{ |
|||
UserName = request.UserName, |
|||
Email = request.Email, |
|||
PhoneNumber = request.PhoneNumber |
|||
}; |
|||
|
|||
// 在事务中创建用户
|
|||
await _unitOfWork.ExecuteTransactionAsync(async () => |
|||
{ |
|||
var result = await _userManager.CreateAsync(user, request.Password); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("创建用户失败: {Errors}", string.Join(", ", errors)); |
|||
throw new InvalidOperationException(string.Join(", ", errors)); |
|||
} |
|||
}, cancellationToken: cancellationToken); |
|||
|
|||
_logger.LogInformation("用户 {UserId} 创建成功", user.Id); |
|||
|
|||
return OperationResult<CreateUserResponse>.CreateSuccess( |
|||
new CreateUserResponse(user.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "创建用户时发生错误"); |
|||
_logger.LogError(ex, "Error creating user"); |
|||
return OperationResult<CreateUserResponse>.CreateFailure("Failed to create user"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common.Extensions; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.CreateUser; |
|||
|
|||
public sealed class CreateUserCommandValidator : AbstractValidator<CreateUserCommand> |
|||
{ |
|||
public CreateUserCommandValidator() |
|||
{ |
|||
// 验证账号
|
|||
RuleFor(x => x.UserName) |
|||
.NotEmpty().WithMessage("账号不能为空") |
|||
.MinimumLength(3).WithMessage("账号长度不能少于3个字符") |
|||
.MaximumLength(50).WithMessage("账号长度不能超过50个字符") |
|||
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线"); |
|||
|
|||
// 验证邮箱
|
|||
RuleFor(x => x.Email) |
|||
.NotEmpty().WithMessage("邮箱不能为空") |
|||
.MaximumLength(256).WithMessage("邮箱长度不能超过256个字符") |
|||
.Must(x => x.IsValidEmail()).WithMessage("邮箱格式不正确"); |
|||
|
|||
// 验证密码
|
|||
RuleFor(x => x.Password) |
|||
.NotEmpty().WithMessage("密码不能为空") |
|||
.MinimumLength(6).WithMessage("密码长度不能少于6个字符") |
|||
.MaximumLength(50).WithMessage("密码长度不能超过50个字符") |
|||
.Matches("[A-Z]").WithMessage("密码必须包含至少一个大写字母") |
|||
.Matches("[a-z]").WithMessage("密码必须包含至少一个小写字母") |
|||
.Matches("[0-9]").WithMessage("密码必须包含至少一个数字") |
|||
.Matches("[^a-zA-Z0-9]").WithMessage("密码必须包含至少一个特殊字符"); |
|||
|
|||
|
|||
// 验证电话号码
|
|||
RuleFor(x => x.PhoneNumber) |
|||
.Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber)) |
|||
.WithMessage("电话号码格式不正确"); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
namespace CellularManagement.Application.Features.Users.Commands.CreateUser; |
|||
|
|||
/// <summary>
|
|||
/// 创建用户响应
|
|||
/// 包含创建成功后的用户ID
|
|||
/// </summary>
|
|||
/// <param name="UserId">用户ID</param>
|
|||
public sealed record CreateUserResponse(string UserId); |
@ -0,0 +1,11 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser; |
|||
|
|||
/// <summary>
|
|||
/// 删除用户命令
|
|||
/// 用于删除指定用户的命令对象
|
|||
/// </summary>
|
|||
/// <param name="UserId">要删除的用户ID</param>
|
|||
public sealed record DeleteUserCommand(string UserId) : IRequest<OperationResult<DeleteUserResponse>>; |
@ -0,0 +1,84 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser; |
|||
|
|||
/// <summary>
|
|||
/// 删除用户命令处理器
|
|||
/// 处理删除用户的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class DeleteUserCommandHandler : IRequestHandler<DeleteUserCommand, OperationResult<DeleteUserResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<DeleteUserCommandHandler> _logger; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="userManager">用户管理器,用于管理用户身份</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
/// <param name="unitOfWork">工作单元,用于事务管理</param>
|
|||
public DeleteUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<DeleteUserCommandHandler> logger, |
|||
IUnitOfWork unitOfWork) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
_unitOfWork = unitOfWork; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理删除用户命令
|
|||
/// </summary>
|
|||
/// <param name="request">删除用户命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>操作结果,包含删除的用户ID</returns>
|
|||
public async Task<OperationResult<DeleteUserResponse>> Handle( |
|||
DeleteUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 查找要删除的用户
|
|||
var user = await _userManager.FindByIdAsync(request.UserId); |
|||
if (user == null) |
|||
{ |
|||
_logger.LogWarning("用户 {UserId} 不存在", request.UserId); |
|||
return OperationResult<DeleteUserResponse>.CreateFailure("用户不存在"); |
|||
} |
|||
|
|||
// 在事务中删除用户
|
|||
await _unitOfWork.ExecuteTransactionAsync(async () => |
|||
{ |
|||
var result = await _userManager.DeleteAsync(user); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("删除用户失败: {Errors}", string.Join(", ", errors)); |
|||
throw new InvalidOperationException(string.Join(", ", errors)); |
|||
} |
|||
}, cancellationToken: cancellationToken); |
|||
|
|||
_logger.LogInformation("用户 {UserId} 删除成功", user.Id); |
|||
|
|||
return OperationResult<DeleteUserResponse>.CreateSuccess( |
|||
new DeleteUserResponse(user.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "删除用户时发生错误"); |
|||
return OperationResult<DeleteUserResponse>.CreateFailure("删除用户失败"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
namespace CellularManagement.Application.Features.Users.Commands.DeleteUser; |
|||
|
|||
/// <summary>
|
|||
/// 删除用户响应
|
|||
/// 包含删除成功后的用户ID
|
|||
/// </summary>
|
|||
/// <param name="UserId">用户ID</param>
|
|||
public sealed record DeleteUserResponse(string UserId); |
@ -0,0 +1,20 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser; |
|||
|
|||
/// <summary>
|
|||
/// 更新用户命令
|
|||
/// 用于更新指定用户信息的命令对象
|
|||
/// </summary>
|
|||
/// <param name="UserId">要更新的用户ID</param>
|
|||
/// <param name="UserName">新的账号</param>
|
|||
/// <param name="RealName">新的用户名</param>
|
|||
/// <param name="Email">新的电子邮箱</param>
|
|||
/// <param name="PhoneNumber">新的电话号码</param>
|
|||
public sealed record UpdateUserCommand( |
|||
string UserId, |
|||
string UserName, |
|||
string RealName, |
|||
string Email, |
|||
string PhoneNumber) : IRequest<OperationResult<UpdateUserResponse>>; |
@ -0,0 +1,73 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Domain.Repositories; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
using CellularManagement.Domain.Repositories.Base; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser; |
|||
|
|||
public sealed class UpdateUserCommandHandler : IRequestHandler<UpdateUserCommand, OperationResult<UpdateUserResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<UpdateUserCommandHandler> _logger; |
|||
private readonly IUnitOfWork _unitOfWork; |
|||
|
|||
public UpdateUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<UpdateUserCommandHandler> logger, |
|||
IUnitOfWork unitOfWork) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
_unitOfWork = unitOfWork; |
|||
} |
|||
|
|||
public async Task<OperationResult<UpdateUserResponse>> Handle( |
|||
UpdateUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
var user = await _userManager.FindByIdAsync(request.UserId); |
|||
if (user == null) |
|||
{ |
|||
_logger.LogWarning("User {UserId} not found", request.UserId); |
|||
return OperationResult<UpdateUserResponse>.CreateFailure("用户不存在"); |
|||
} |
|||
|
|||
// Update user properties if provided
|
|||
if (request.UserName != null) user.UserName = request.UserName; |
|||
if (request.RealName != null) user.RealName = request.RealName; |
|||
if (request.Email != null) user.Email = request.Email; |
|||
if (request.PhoneNumber != null) user.PhoneNumber = request.PhoneNumber; |
|||
|
|||
// Update user in transaction
|
|||
await _unitOfWork.ExecuteTransactionAsync(async () => |
|||
{ |
|||
var result = await _userManager.UpdateAsync(user); |
|||
if (!result.Succeeded) |
|||
{ |
|||
var errors = result.Errors.Select(e => e.Description).ToList(); |
|||
_logger.LogWarning("Failed to update user: {Errors}", string.Join(", ", errors)); |
|||
throw new InvalidOperationException(string.Join(", ", errors)); |
|||
} |
|||
}, cancellationToken: cancellationToken); |
|||
|
|||
_logger.LogInformation("User {UserId} updated successfully", user.Id); |
|||
|
|||
return OperationResult<UpdateUserResponse>.CreateSuccess( |
|||
new UpdateUserResponse(user.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "Error updating user"); |
|||
return OperationResult<UpdateUserResponse>.CreateFailure("更新用户失败"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
using FluentValidation; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser; |
|||
|
|||
public sealed class UpdateUserCommandValidator : AbstractValidator<UpdateUserCommand> |
|||
{ |
|||
public UpdateUserCommandValidator() |
|||
{ |
|||
RuleFor(x => x.UserId) |
|||
.NotEmpty().WithMessage("用户ID不能为空"); |
|||
|
|||
When(x => x.UserName != null, () => |
|||
{ |
|||
RuleFor(x => x.UserName) |
|||
.Length(3, 50).WithMessage("账号长度必须在3-50个字符之间") |
|||
.Matches("^[a-zA-Z0-9_]+$").WithMessage("账号只能包含字母、数字和下划线"); |
|||
}); |
|||
|
|||
When(x => x.RealName != null, () => |
|||
{ |
|||
RuleFor(x => x.RealName) |
|||
.MaximumLength(50).WithMessage("用户名长度不能超过50个字符"); |
|||
}); |
|||
|
|||
When(x => x.Email != null, () => |
|||
{ |
|||
RuleFor(x => x.Email) |
|||
.EmailAddress().WithMessage("邮箱格式不正确"); |
|||
}); |
|||
|
|||
When(x => x.PhoneNumber != null, () => |
|||
{ |
|||
RuleFor(x => x.PhoneNumber) |
|||
.Matches(@"^1[3-9]\d{9}$").WithMessage("手机号格式不正确"); |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
namespace CellularManagement.Application.Features.Users.Commands.UpdateUser; |
|||
|
|||
/// <summary>
|
|||
/// 更新用户响应
|
|||
/// 包含更新成功后的用户ID
|
|||
/// </summary>
|
|||
/// <param name="UserId">用户ID</param>
|
|||
public sealed record UpdateUserResponse(string UserId); |
@ -0,0 +1,16 @@ |
|||
namespace CellularManagement.Application.Features.Users.Queries.Dtos; |
|||
|
|||
/// <summary>
|
|||
/// 用户数据传输对象
|
|||
/// 用于在API层和领域层之间传输用户数据
|
|||
/// </summary>
|
|||
/// <param name="UserId">用户ID</param>
|
|||
/// <param name="UserName">用户名</param>
|
|||
/// <param name="Email">电子邮箱</param>
|
|||
/// <param name="PhoneNumber">电话号码</param>
|
|||
public sealed record UserDto( |
|||
string UserId, |
|||
string UserName, |
|||
string RealName, |
|||
string Email, |
|||
string PhoneNumber, DateTime CreatedAt, bool IsActive); |
@ -0,0 +1,20 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有用户查询
|
|||
/// 支持按用户名、邮箱、手机号进行条件查询
|
|||
/// </summary>
|
|||
/// <param name="PageNumber">页码,从1开始</param>
|
|||
/// <param name="PageSize">每页记录数</param>
|
|||
/// <param name="UserName">用户名(可选,支持模糊查询)</param>
|
|||
/// <param name="Email">邮箱(可选,支持模糊查询)</param>
|
|||
/// <param name="PhoneNumber">手机号(可选,支持模糊查询)</param>
|
|||
public sealed record GetAllUsersQuery( |
|||
int PageNumber = 1, |
|||
int PageSize = 10, |
|||
string? UserName = null, |
|||
string? Email = null, |
|||
string? PhoneNumber = null) : IRequest<OperationResult<GetAllUsersResponse>>; |
@ -0,0 +1,100 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Features.Users.Queries.Dtos; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Linq; |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有用户查询处理器
|
|||
/// 处理获取系统中所有用户信息的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery, OperationResult<GetAllUsersResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<GetAllUsersQueryHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="userManager">用户管理器,用于管理用户身份</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public GetAllUsersQueryHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<GetAllUsersQueryHandler> logger) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理获取所有用户命令
|
|||
/// </summary>
|
|||
/// <param name="request">查询命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>操作结果,包含所有用户信息列表</returns>
|
|||
public async Task<OperationResult<GetAllUsersResponse>> Handle( |
|||
GetAllUsersQuery request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 构建基础查询
|
|||
var query = _userManager.Users.AsQueryable(); |
|||
|
|||
// 应用条件过滤
|
|||
if (!string.IsNullOrWhiteSpace(request.UserName)) |
|||
{ |
|||
query = query.Where(u => u.UserName.Contains(request.UserName)); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(request.Email)) |
|||
{ |
|||
query = query.Where(u => u.Email.Contains(request.Email)); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(request.PhoneNumber)) |
|||
{ |
|||
query = query.Where(u => u.PhoneNumber.Contains(request.PhoneNumber)); |
|||
} |
|||
|
|||
// 获取总记录数
|
|||
var totalCount = await query.CountAsync(cancellationToken); |
|||
|
|||
// 应用分页
|
|||
var users = await query |
|||
.Skip((request.PageNumber - 1) * request.PageSize) |
|||
.Take(request.PageSize) |
|||
.ToListAsync(cancellationToken); |
|||
|
|||
// 转换为DTO对象
|
|||
var userDtos = users.Select(user => new UserDto( |
|||
user.Id, |
|||
user.UserName, |
|||
user.RealName, |
|||
user.Email, |
|||
user.PhoneNumber,user.CreatedTime,user.IsActive)).ToList(); |
|||
|
|||
// 构建响应对象
|
|||
var response = new GetAllUsersResponse( |
|||
userDtos, |
|||
totalCount, |
|||
request.PageNumber, |
|||
request.PageSize); |
|||
|
|||
return OperationResult<GetAllUsersResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "获取所有用户时发生错误"); |
|||
return OperationResult<GetAllUsersResponse>.CreateFailure("获取用户列表失败"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using CellularManagement.Application.Features.Users.Queries.Dtos; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetAllUsers; |
|||
|
|||
/// <summary>
|
|||
/// 获取所有用户响应
|
|||
/// 包含用户列表和分页信息
|
|||
/// </summary>
|
|||
/// <param name="Users">用户列表</param>
|
|||
/// <param name="TotalCount">总记录数</param>
|
|||
/// <param name="PageNumber">当前页码</param>
|
|||
/// <param name="PageSize">每页记录数</param>
|
|||
public sealed record GetAllUsersResponse( |
|||
List<UserDto> Users, |
|||
int TotalCount, |
|||
int PageNumber, |
|||
int PageSize) |
|||
{ |
|||
/// <summary>
|
|||
/// 总页数
|
|||
/// </summary>
|
|||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize); |
|||
|
|||
/// <summary>
|
|||
/// 是否有上一页
|
|||
/// </summary>
|
|||
public bool HasPreviousPage => PageNumber > 1; |
|||
|
|||
/// <summary>
|
|||
/// 是否有下一页
|
|||
/// </summary>
|
|||
public bool HasNextPage => PageNumber < TotalPages; |
|||
} |
@ -0,0 +1,11 @@ |
|||
using CellularManagement.Domain.Common; |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetUserById; |
|||
|
|||
/// <summary>
|
|||
/// 获取用户详情查询
|
|||
/// 用于获取指定用户详细信息的查询对象
|
|||
/// </summary>
|
|||
/// <param name="UserId">要查询的用户ID</param>
|
|||
public sealed record GetUserByIdQuery(string UserId) : IRequest<OperationResult<GetUserByIdResponse>>; |
@ -0,0 +1,71 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Features.Users.Queries.Dtos; |
|||
using CellularManagement.Domain.Common; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using System; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetUserById; |
|||
|
|||
/// <summary>
|
|||
/// 根据ID查询用户处理器
|
|||
/// 处理根据ID查询用户信息的业务逻辑
|
|||
/// </summary>
|
|||
public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, OperationResult<GetUserByIdResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<GetUserByIdQueryHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 构造函数
|
|||
/// </summary>
|
|||
/// <param name="userManager">用户管理器,用于管理用户身份</param>
|
|||
/// <param name="logger">日志记录器</param>
|
|||
public GetUserByIdQueryHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<GetUserByIdQueryHandler> logger) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理根据ID查询用户命令
|
|||
/// </summary>
|
|||
/// <param name="request">查询用户命令</param>
|
|||
/// <param name="cancellationToken">取消令牌</param>
|
|||
/// <returns>操作结果,包含查询到的用户信息</returns>
|
|||
public async Task<OperationResult<GetUserByIdResponse>> Handle( |
|||
GetUserByIdQuery request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 根据ID查询用户
|
|||
var user = await _userManager.FindByIdAsync(request.UserId); |
|||
if (user == null) |
|||
{ |
|||
_logger.LogWarning("用户 {UserId} 不存在", request.UserId); |
|||
return OperationResult<GetUserByIdResponse>.CreateFailure("用户不存在"); |
|||
} |
|||
var dto = new UserDto(user.Id, |
|||
user.UserName, |
|||
user.RealName, |
|||
user.Email, |
|||
user.PhoneNumber,user.CreatedTime, user.IsActive); |
|||
|
|||
// 构建响应对象
|
|||
var response = new GetUserByIdResponse(dto); |
|||
|
|||
return OperationResult<GetUserByIdResponse>.CreateSuccess(response); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "查询用户时发生错误"); |
|||
return OperationResult<GetUserByIdResponse>.CreateFailure("查询用户失败"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
using CellularManagement.Application.Features.Users.Queries.Dtos; |
|||
|
|||
namespace CellularManagement.Application.Features.Users.Queries.GetUserById; |
|||
|
|||
/// <summary>
|
|||
/// 用户查询响应
|
|||
/// 包含查询到的用户详细信息
|
|||
/// </summary>
|
|||
/// <param name="User">用户信息</param>
|
|||
public sealed record GetUserByIdResponse(UserDto User); |
@ -1,362 +0,0 @@ |
|||
{ |
|||
"runtimeTarget": { |
|||
"name": ".NETCoreApp,Version=v8.0", |
|||
"signature": "" |
|||
}, |
|||
"compilationOptions": {}, |
|||
"targets": { |
|||
".NETCoreApp,Version=v8.0": { |
|||
"CellularManagement.Application/1.0.0": { |
|||
"dependencies": { |
|||
"CellularManagement.Domain": "1.0.0", |
|||
"Microsoft.Extensions.DependencyInjection": "8.0.0" |
|||
}, |
|||
"runtime": { |
|||
"CellularManagement.Application.dll": {} |
|||
} |
|||
}, |
|||
"Microsoft.AspNetCore.Cryptography.Internal/7.0.0": { |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.AspNetCore.Cryptography.Internal.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51819" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.AspNetCore.Cryptography.KeyDerivation/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.AspNetCore.Cryptography.Internal": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.AspNetCore.Cryptography.KeyDerivation.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51819" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.EntityFrameworkCore.Relational": "7.0.0", |
|||
"Microsoft.Extensions.Identity.Stores": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.AspNetCore.Identity.EntityFrameworkCore.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51819" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.EntityFrameworkCore/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.EntityFrameworkCore.Abstractions": "7.0.0", |
|||
"Microsoft.EntityFrameworkCore.Analyzers": "7.0.0", |
|||
"Microsoft.Extensions.Caching.Memory": "7.0.0", |
|||
"Microsoft.Extensions.DependencyInjection": "8.0.0", |
|||
"Microsoft.Extensions.Logging": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net6.0/Microsoft.EntityFrameworkCore.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51807" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.EntityFrameworkCore.Abstractions/7.0.0": { |
|||
"runtime": { |
|||
"lib/net6.0/Microsoft.EntityFrameworkCore.Abstractions.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51807" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.EntityFrameworkCore.Analyzers/7.0.0": {}, |
|||
"Microsoft.EntityFrameworkCore.Relational/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.EntityFrameworkCore": "7.0.0", |
|||
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net6.0/Microsoft.EntityFrameworkCore.Relational.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51807" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Caching.Abstractions/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.Primitives": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Caching.Abstractions.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Caching.Memory/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.Caching.Abstractions": "7.0.0", |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", |
|||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0", |
|||
"Microsoft.Extensions.Options": "7.0.0", |
|||
"Microsoft.Extensions.Primitives": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Caching.Memory.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Configuration.Abstractions/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.Primitives": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Configuration.Abstractions.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.DependencyInjection/8.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.dll": { |
|||
"assemblyVersion": "8.0.0.0", |
|||
"fileVersion": "8.0.23.53103" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": { |
|||
"runtime": { |
|||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { |
|||
"assemblyVersion": "8.0.0.0", |
|||
"fileVersion": "8.0.23.53103" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Identity.Core/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.AspNetCore.Cryptography.KeyDerivation": "7.0.0", |
|||
"Microsoft.Extensions.Logging": "7.0.0", |
|||
"Microsoft.Extensions.Options": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Identity.Core.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51819" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Identity.Stores/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.Caching.Abstractions": "7.0.0", |
|||
"Microsoft.Extensions.Identity.Core": "7.0.0", |
|||
"Microsoft.Extensions.Logging": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Identity.Stores.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51819" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Logging/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.DependencyInjection": "8.0.0", |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", |
|||
"Microsoft.Extensions.Logging.Abstractions": "7.0.0", |
|||
"Microsoft.Extensions.Options": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Logging.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Logging.Abstractions/7.0.0": { |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Logging.Abstractions.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Options/7.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", |
|||
"Microsoft.Extensions.Primitives": "7.0.0" |
|||
}, |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Options.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"Microsoft.Extensions.Primitives/7.0.0": { |
|||
"runtime": { |
|||
"lib/net7.0/Microsoft.Extensions.Primitives.dll": { |
|||
"assemblyVersion": "7.0.0.0", |
|||
"fileVersion": "7.0.22.51805" |
|||
} |
|||
} |
|||
}, |
|||
"CellularManagement.Domain/1.0.0": { |
|||
"dependencies": { |
|||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "7.0.0", |
|||
"Microsoft.EntityFrameworkCore": "7.0.0", |
|||
"Microsoft.Extensions.DependencyInjection": "8.0.0" |
|||
}, |
|||
"runtime": { |
|||
"CellularManagement.Domain.dll": { |
|||
"assemblyVersion": "1.0.0", |
|||
"fileVersion": "1.0.0.0" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"libraries": { |
|||
"CellularManagement.Application/1.0.0": { |
|||
"type": "project", |
|||
"serviceable": false, |
|||
"sha512": "" |
|||
}, |
|||
"Microsoft.AspNetCore.Cryptography.Internal/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-hFF+HOqtiNrGtO5ZxLVAFo1ksDLQWf8IHEmGRmcF9azlUWvDLZp8+W8gDyLBcGcY5m3ugEvKy/ncElxO4d0NtQ==", |
|||
"path": "microsoft.aspnetcore.cryptography.internal/7.0.0", |
|||
"hashPath": "microsoft.aspnetcore.cryptography.internal.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.AspNetCore.Cryptography.KeyDerivation/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-rCQddWkUxGmObeftM0YVyFOPcXkXDEWKGCc4F1viRLEL4ojIbdKwbOYBSf5hfWDR+NO0aGq8r3a8COvNYN/bZA==", |
|||
"path": "microsoft.aspnetcore.cryptography.keyderivation/7.0.0", |
|||
"hashPath": "microsoft.aspnetcore.cryptography.keyderivation.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-mtomuG24wGpvdblVQUj/JHIZ1i8oNhRNHr0V0re8fTkv15hz+AQLdtwbdd6FdINNeXiKi3kGmzZ7PE1KOyzoSg==", |
|||
"path": "microsoft.aspnetcore.identity.entityframeworkcore/7.0.0", |
|||
"hashPath": "microsoft.aspnetcore.identity.entityframeworkcore.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.EntityFrameworkCore/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-9W+IfmAzMrp2ZpKZLhgTlWljSBM9Erldis1us61DAGi+L7Q6vilTbe1G2zDxtYO8F2H0I0Qnupdx5Cp4s2xoZw==", |
|||
"path": "microsoft.entityframeworkcore/7.0.0", |
|||
"hashPath": "microsoft.entityframeworkcore.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.EntityFrameworkCore.Abstractions/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-Pfu3Zjj5+d2Gt27oE9dpGiF/VobBB+s5ogrfI9sBsXQE1SG49RqVz5+IyeNnzhyejFrPIQsPDRMchhcojy4Hbw==", |
|||
"path": "microsoft.entityframeworkcore.abstractions/7.0.0", |
|||
"hashPath": "microsoft.entityframeworkcore.abstractions.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.EntityFrameworkCore.Analyzers/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-Qkd2H+jLe37o5ku+LjT6qf7kAHY75Yfn2bBDQgqr13DTOLYpEy1Mt93KPFjaZvIu/srEcbfGGMRL7urKm5zN8Q==", |
|||
"path": "microsoft.entityframeworkcore.analyzers/7.0.0", |
|||
"hashPath": "microsoft.entityframeworkcore.analyzers.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.EntityFrameworkCore.Relational/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-eQiYygtR2xZ0Uy7KtiFRHpoEx/U8xNwbNRgu1pEJgSxbJLtg6tDL1y2YcIbSuIRSNEljXIIHq/apEhGm1QL70g==", |
|||
"path": "microsoft.entityframeworkcore.relational/7.0.0", |
|||
"hashPath": "microsoft.entityframeworkcore.relational.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Caching.Abstractions/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", |
|||
"path": "microsoft.extensions.caching.abstractions/7.0.0", |
|||
"hashPath": "microsoft.extensions.caching.abstractions.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Caching.Memory/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", |
|||
"path": "microsoft.extensions.caching.memory/7.0.0", |
|||
"hashPath": "microsoft.extensions.caching.memory.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Configuration.Abstractions/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", |
|||
"path": "microsoft.extensions.configuration.abstractions/7.0.0", |
|||
"hashPath": "microsoft.extensions.configuration.abstractions.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.DependencyInjection/8.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", |
|||
"path": "microsoft.extensions.dependencyinjection/8.0.0", |
|||
"hashPath": "microsoft.extensions.dependencyinjection.8.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==", |
|||
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.0", |
|||
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Identity.Core/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-cq11jroq2szFcXLJ0IW5BlI7oqq3ZGCu1mXCnpJ8VIvhvpIzf30AOoWR/w3YRVdAgkYzxbUQpKGZd+oxAKQhLA==", |
|||
"path": "microsoft.extensions.identity.core/7.0.0", |
|||
"hashPath": "microsoft.extensions.identity.core.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Identity.Stores/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-feaaluQbzJAMMluwSc7Rebm7IEVAD8/5GWt0dMYLE0tcc6gAsHYjBIBrPzmTstORd7k405Qo18FPF/jTfRsM0A==", |
|||
"path": "microsoft.extensions.identity.stores/7.0.0", |
|||
"hashPath": "microsoft.extensions.identity.stores.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Logging/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", |
|||
"path": "microsoft.extensions.logging/7.0.0", |
|||
"hashPath": "microsoft.extensions.logging.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Logging.Abstractions/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==", |
|||
"path": "microsoft.extensions.logging.abstractions/7.0.0", |
|||
"hashPath": "microsoft.extensions.logging.abstractions.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Options/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", |
|||
"path": "microsoft.extensions.options/7.0.0", |
|||
"hashPath": "microsoft.extensions.options.7.0.0.nupkg.sha512" |
|||
}, |
|||
"Microsoft.Extensions.Primitives/7.0.0": { |
|||
"type": "package", |
|||
"serviceable": true, |
|||
"sha512": "sha512-um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==", |
|||
"path": "microsoft.extensions.primitives/7.0.0", |
|||
"hashPath": "microsoft.extensions.primitives.7.0.0.nupkg.sha512" |
|||
}, |
|||
"CellularManagement.Domain/1.0.0": { |
|||
"type": "project", |
|||
"serviceable": false, |
|||
"sha512": "" |
|||
} |
|||
} |
|||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,164 +0,0 @@ |
|||
{ |
|||
"format": 1, |
|||
"restore": { |
|||
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj": {} |
|||
}, |
|||
"projects": { |
|||
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj": { |
|||
"version": "1.0.0", |
|||
"restore": { |
|||
"projectUniqueName": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj", |
|||
"projectName": "CellularManagement.Application", |
|||
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\CellularManagement.Application.csproj", |
|||
"packagesPath": "C:\\Users\\changeself\\.nuget\\packages\\", |
|||
"outputPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Application\\obj\\", |
|||
"projectStyle": "PackageReference", |
|||
"fallbackFolders": [ |
|||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" |
|||
], |
|||
"configFilePaths": [ |
|||
"C:\\Users\\changeself\\AppData\\Roaming\\NuGet\\NuGet.Config", |
|||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", |
|||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" |
|||
], |
|||
"originalTargetFrameworks": [ |
|||
"net8.0" |
|||
], |
|||
"sources": { |
|||
"D:\\NuGetPackages": {}, |
|||
"https://api.nuget.org/v3/index.json": {}, |
|||
"https://www.nuget.org/api/v2": {} |
|||
}, |
|||
"frameworks": { |
|||
"net8.0": { |
|||
"targetAlias": "net8.0", |
|||
"projectReferences": { |
|||
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj": { |
|||
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"warningProperties": { |
|||
"warnAsError": [ |
|||
"NU1605" |
|||
] |
|||
}, |
|||
"restoreAuditProperties": { |
|||
"enableAudit": "true", |
|||
"auditLevel": "low", |
|||
"auditMode": "direct" |
|||
}, |
|||
"SdkAnalysisLevel": "9.0.100" |
|||
}, |
|||
"frameworks": { |
|||
"net8.0": { |
|||
"targetAlias": "net8.0", |
|||
"dependencies": { |
|||
"Microsoft.Extensions.DependencyInjection": { |
|||
"target": "Package", |
|||
"version": "[8.0.0, )" |
|||
} |
|||
}, |
|||
"imports": [ |
|||
"net461", |
|||
"net462", |
|||
"net47", |
|||
"net471", |
|||
"net472", |
|||
"net48", |
|||
"net481" |
|||
], |
|||
"assetTargetFallback": true, |
|||
"warn": true, |
|||
"frameworkReferences": { |
|||
"Microsoft.NETCore.App": { |
|||
"privateAssets": "all" |
|||
} |
|||
}, |
|||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.105/PortableRuntimeIdentifierGraph.json" |
|||
} |
|||
} |
|||
}, |
|||
"D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj": { |
|||
"version": "1.0.0", |
|||
"restore": { |
|||
"projectUniqueName": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj", |
|||
"projectName": "CellularManagement.Domain", |
|||
"projectPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\CellularManagement.Domain.csproj", |
|||
"packagesPath": "C:\\Users\\changeself\\.nuget\\packages\\", |
|||
"outputPath": "D:\\dev\\clean-architecture-starter-main\\CellularManagement\\src\\CellularManagement.Domain\\obj\\", |
|||
"projectStyle": "PackageReference", |
|||
"fallbackFolders": [ |
|||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" |
|||
], |
|||
"configFilePaths": [ |
|||
"C:\\Users\\changeself\\AppData\\Roaming\\NuGet\\NuGet.Config", |
|||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", |
|||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" |
|||
], |
|||
"originalTargetFrameworks": [ |
|||
"net8.0" |
|||
], |
|||
"sources": { |
|||
"D:\\NuGetPackages": {}, |
|||
"https://api.nuget.org/v3/index.json": {}, |
|||
"https://www.nuget.org/api/v2": {} |
|||
}, |
|||
"frameworks": { |
|||
"net8.0": { |
|||
"targetAlias": "net8.0", |
|||
"projectReferences": {} |
|||
} |
|||
}, |
|||
"warningProperties": { |
|||
"warnAsError": [ |
|||
"NU1605" |
|||
] |
|||
}, |
|||
"restoreAuditProperties": { |
|||
"enableAudit": "true", |
|||
"auditLevel": "low", |
|||
"auditMode": "direct" |
|||
}, |
|||
"SdkAnalysisLevel": "9.0.100" |
|||
}, |
|||
"frameworks": { |
|||
"net8.0": { |
|||
"targetAlias": "net8.0", |
|||
"dependencies": { |
|||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": { |
|||
"target": "Package", |
|||
"version": "[7.0.0, )" |
|||
}, |
|||
"Microsoft.EntityFrameworkCore": { |
|||
"target": "Package", |
|||
"version": "[7.0.0, )" |
|||
}, |
|||
"Microsoft.Extensions.DependencyInjection": { |
|||
"target": "Package", |
|||
"version": "[7.0.0, )" |
|||
} |
|||
}, |
|||
"imports": [ |
|||
"net461", |
|||
"net462", |
|||
"net47", |
|||
"net471", |
|||
"net472", |
|||
"net48", |
|||
"net481" |
|||
], |
|||
"assetTargetFallback": true, |
|||
"warn": true, |
|||
"frameworkReferences": { |
|||
"Microsoft.NETCore.App": { |
|||
"privateAssets": "all" |
|||
} |
|||
}, |
|||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.105/PortableRuntimeIdentifierGraph.json" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue