权限系统设计
This commit is contained in:
parent
67c567eb2c
commit
afd2c68fbf
7
notes/helloshop/model-binding.md
Normal file
7
notes/helloshop/model-binding.md
Normal file
@ -0,0 +1,7 @@
|
||||
## 模型绑定约定
|
||||
|
||||
实体对象是 EF 中的概念, 每个实体对象对应数据库中的一张表。模型对象是 MVC 中的概念,是 HTTP 请求和响应的数据结构。HTTP 请求中通过 URL 参数、表单、标头、 JSON 数据等方式传递数据,这些数据最终会被绑定为模型对象中,模型经过转换为实体对象后,被持久化到数据库中。
|
||||
|
||||
针对不同 HTTP 操作,应该使用不同的模型对象,比如创建模型、更新模型、查询模型、删除模型等,这样的好处是可以更好的区分模型对象的职责,比如创建模型只需要包含创建所需的字段,更新模型只需要包含更新所需的字段,查询模型只需要包含查询所需的字段,删除模型只需要包含删除所需的字段,职责单一,维护性好,还可以针对不同的模型提供不同的验证规则。
|
||||
|
||||

|
66
notes/helloshop/model-mapper.md
Normal file
66
notes/helloshop/model-mapper.md
Normal file
@ -0,0 +1,66 @@
|
||||
# 自动映射实体和模型
|
||||
|
||||
|
||||
## 使用 AutoMapper 自动映射实体和模型
|
||||
|
||||
AutoMapper 是一个对象映射工具,可以自动映射实体对象和模型对象,减少手动映射的工作量,提高开发效率,除此之外 Mapster 也是一个高性能的对象映射工具,支持源生成代码,性能更好。AutoMapper 比 Mapster 更加流行,更加成熟,更加稳定,更加易用,更加灵活,更加强大,更加全面,更加受欢迎。AutoMapper 是 .NET Foundation 的一部分,是一个开源项目,是一个非常优秀的对象映射工具。
|
||||
|
||||
## 安装 NuGet 包
|
||||
|
||||
```shell
|
||||
dotnet add package AutoMapper
|
||||
```
|
||||
|
||||
## 在依赖注入中注册 AutoMapper
|
||||
|
||||
```csharp
|
||||
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
||||
```
|
||||
|
||||
## 创建映射配置文件
|
||||
|
||||
```csharp
|
||||
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<User, UserDto>();
|
||||
CreateMap<UserDto, User>();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 使用 AutoMapper 映射实体和模型
|
||||
|
||||
```csharp
|
||||
|
||||
public class UserService
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UserService(IMapper mapper)
|
||||
{
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public UserDto GetUser(int id)
|
||||
{
|
||||
var user = _dbContext.Users.Find(id);
|
||||
return _mapper.Map<UserDto>(user);
|
||||
}
|
||||
|
||||
public void UpdateUser(UserDto userDto)
|
||||
{
|
||||
var user = _mapper.Map<User>(userDto);
|
||||
_dbContext.Users.Update(user);
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 分页模式
|
||||
|
||||
常见的分页模式有两种,一种是基于页码和页大小的分页模式,一种是基于游标和页大小的分页模式。
|
54
notes/helloshop/model-validations.md
Normal file
54
notes/helloshop/model-validations.md
Normal file
@ -0,0 +1,54 @@
|
||||
# 模型自动验证机制
|
||||
|
||||
## 使用 FluentValidation 开源库
|
||||
|
||||
FluentValidation 是一个.NET库,用于构建类型安全的验证规则。它的设计目标是提供一个简单、清晰的API,同时还能够支持复杂的验证规则。
|
||||
|
||||
```shell
|
||||
dotnet add package FluentValidation.DependencyInjectionExtensions
|
||||
```
|
||||
|
||||
## 实现验证器
|
||||
|
||||
```csharp
|
||||
public class UserValidator : AbstractValidator<User>
|
||||
{
|
||||
public UserValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(32);
|
||||
RuleFor(x => x.Email).NotEmpty().EmailAddress();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 自动依赖注入
|
||||
|
||||
```csharp
|
||||
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
```
|
||||
|
||||
## 自定义验证错误消息
|
||||
|
||||
```csharp
|
||||
public class UserValidator : AbstractValidator<User>
|
||||
{
|
||||
public UserValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("Name is required and must be less than 32 characters.");
|
||||
RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Email is required and must be a valid email address.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 通用错误消息
|
||||
|
||||
```csharp
|
||||
public class UserValidator : AbstractValidator<User>
|
||||
{
|
||||
public UserValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("{PropertyName} is required and must be less than {MaxLength} characters.");
|
||||
RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("{PropertyName} is required and must be a valid email address.");
|
||||
}
|
||||
}
|
||||
```
|
90
notes/helloshop/resource-based-authorization.md
Normal file
90
notes/helloshop/resource-based-authorization.md
Normal file
@ -0,0 +1,90 @@
|
||||
# 基于资源的授权的最佳实践
|
||||
|
||||
## 在 Identity Service 中提供权限检查接口
|
||||
|
||||
```csharp
|
||||
[HttpHead]
|
||||
public async Task<ActionResult<IEnumerable<PermissionGrantedResponse>>> CheckPermission(string permissionName, string? resourceType = null, string? resourceId = null
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(permissionName, resourceType, resourceId))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return Forbid();
|
||||
}
|
||||
```
|
||||
|
||||
## 远程权限检查器
|
||||
|
||||
```csharp
|
||||
Dictionary<string, string?> parameters = new()
|
||||
{
|
||||
[nameof(name)] = name,
|
||||
[nameof(resourceType)] = resourceType,
|
||||
[nameof(resourceId)] = resourceId
|
||||
};
|
||||
|
||||
string queryString = QueryHelpers.AddQueryString(string.Empty, parameters);
|
||||
|
||||
HttpRequestMessage request = new(HttpMethod.Head, queryString);
|
||||
|
||||
using HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
```
|
||||
|
||||
## 重新实现权限处理程序
|
||||
|
||||
```csharp
|
||||
public class PermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler<OperationAuthorizationRequirement>
|
||||
{
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
|
||||
{
|
||||
if (context.Resource is IAuthorizationResource resource)
|
||||
{
|
||||
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name, resource.ResourceType, resource.ResourceId))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Fail();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 取消缓存以便测试
|
||||
|
||||
```csharp
|
||||
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = DateTimeOffset.Now
|
||||
});
|
||||
```
|
||||
|
||||
## 可读性重构
|
||||
|
||||
重新生成演示数据,并将授权中的 Name 改为 PermissionName,具有更强的可读性。
|
||||
|
||||
## 资源描述重构
|
||||
|
||||
```csharp
|
||||
public record struct ResourceInfo(string ResourceType, string ResourceId) : IAuthorizationResource
|
||||
{
|
||||
public override readonly string ToString() => $"{ResourceType}:{ResourceId}";
|
||||
}
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user