Skip to content

Commit

Permalink
用DDD重构认证服务
Browse files Browse the repository at this point in the history
  • Loading branch information
yangzhongke committed Nov 3, 2021
1 parent 2a1e6e7 commit 1ba7c6f
Show file tree
Hide file tree
Showing 15 changed files with 442 additions and 433 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,46 @@
using System.Threading.Tasks;
using Zack.Commons;

namespace FileService.Domain
namespace FileService.Domain;
public class FSDomainService
{
public class FSDomainService
private readonly IFSRepository repository;
private readonly IStorageClient backupStorage;//备份服务器
private readonly IStorageClient remoteStorage;//文件存储服务器

public FSDomainService(IFSRepository repository,
IEnumerable<IStorageClient> storageClients)
{
private readonly IFSRepository repository;
private readonly IStorageClient backupStorage;//备份服务器
private readonly IStorageClient remoteStorage;//文件存储服务器
this.repository = repository;
//用这种方式可以解决内置DI不能使用名字注入不同实例的问题,而且从原则上来讲更加优美
this.backupStorage = storageClients.First(c => c.StorageType == StorageType.Backup);
this.remoteStorage = storageClients.First(c => c.StorageType == StorageType.Public);
}

public FSDomainService(IFSRepository repository,
IEnumerable<IStorageClient> storageClients)
{
this.repository = repository;
//用这种方式可以解决内置DI不能使用名字注入不同实例的问题,而且从原则上来讲更加优美
this.backupStorage = storageClients.First(c => c.StorageType == StorageType.Backup);
this.remoteStorage = storageClients.First(c => c.StorageType == StorageType.Public);
}
public async Task<UploadedItem> UploadAsync(Stream stream, string fileName,
CancellationToken cancellationToken)
{
string hash = HashHelper.ComputeSha256Hash(stream);
long fileSize = stream.Length;
DateTime today = DateTime.Today;
//用日期把文件分散在不同文件夹存储,同时由于加上了文件hash值作为目录,又用用户上传的文件夹做文件名,
//所以几乎不会发生不同文件冲突的可能
//用用户上传的文件名保存文件名,这样用户查看、下载文件的时候,文件名更灵活
string key = $"{today.Year}/{today.Month}/{today.Day}/{hash}/{fileName}";

public async Task<UploadedItem> UploadAsync(Stream stream,string fileName,
CancellationToken cancellationToken)
//查询是否有和上传文件的大小和SHA256一样的文件,如果有的话,就认为是同一个文件
//虽然说前端可能已经调用FileExists接口检查过了,但是前端可能跳过了,或者有并发上传等问题,所以这里再检查一遍。
var oldUploadItem = await repository.FindFileAsync(fileSize, hash);
if (oldUploadItem != null)
{
string hash = HashHelper.ComputeSha256Hash(stream);
long fileSize = stream.Length;
DateTime today = DateTime.Today;
//用日期把文件分散在不同文件夹存储,同时由于加上了文件hash值作为目录,又用用户上传的文件夹做文件名,
//所以几乎不会发生不同文件冲突的可能
//用用户上传的文件名保存文件名,这样用户查看、下载文件的时候,文件名更灵活
string key = $"{today.Year}/{today.Month}/{today.Day}/{hash}/{fileName}";

//查询是否有和上传文件的大小和SHA256一样的文件,如果有的话,就认为是同一个文件
//虽然说前端可能已经调用FileExists接口检查过了,但是前端可能跳过了,或者有并发上传等问题,所以这里再检查一遍。
var oldUploadItem = await repository.FindFileAsync(fileSize,hash);
if (oldUploadItem != null)
{
return oldUploadItem;
}
//backupStorage实现很稳定、速度很快,一般都使用本地存储(文件共享或者NAS)
Uri backupUrl = await backupStorage.SaveAsync(key, stream, cancellationToken);//保存到本地备份
stream.Position = 0;
Uri remoteUrl = await remoteStorage.SaveAsync(key, stream, cancellationToken);//保存到生产的存储系统
stream.Position = 0;
Guid id = Guid.NewGuid();
return UploadedItem.Create(id, fileSize, fileName, hash, backupUrl, remoteUrl);
return oldUploadItem;
}
//backupStorage实现很稳定、速度很快,一般都使用本地存储(文件共享或者NAS)
Uri backupUrl = await backupStorage.SaveAsync(key, stream, cancellationToken);//保存到本地备份
stream.Position = 0;
Uri remoteUrl = await remoteStorage.SaveAsync(key, stream, cancellationToken);//保存到生产的存储系统
stream.Position = 0;
Guid id = Guid.NewGuid();
return UploadedItem.Create(id, fileSize, fileName, hash, backupUrl, remoteUrl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ namespace FileService.Domain
public interface IFSRepository
{
Task<UploadedItem?> FindFileAsync(long fileSize, string sha256Hash);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public void Configure(EntityTypeBuilder<UploadedItem> builder)
builder.HasKey(e => e.Id).IsClustered(false);
builder.Property(e => e.FileName).IsUnicode().HasMaxLength(1024);
builder.Property(e => e.FileSHA256Hash).IsUnicode(false).HasMaxLength(64);
//builder.Property(e => e.BackupUrl);
builder.HasIndex(e => new { e.FileSHA256Hash, e.FileSizeInBytes });//经常要按照
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

namespace FileService.Infrastructure.Services
{
/// <summary>
/// 把文件服务器当成一个云存储服务器。文件保存在wwwroot文件夹下。
/// 这仅供开发、演示阶段使用,在生产环境中,一定要用专门的云存储服务器来代替。
/// </summary>
class MockCloudStorageClient : IStorageClient
{
public StorageType StorageType => StorageType.Public;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

using FluentValidation;
using Microsoft.AspNetCore.Http;

namespace FileService.WebAPI.Uploader;
Expand All @@ -7,3 +7,12 @@ public class UploadRequest
//不要声明为Action的参数,否则不会正常工作
public IFormFile File { get; set; }
}
public class UploadRequestValidator : AbstractValidator<UploadRequest>
{
public UploadRequestValidator()
{
//不用校验文件名的后缀,因为文件服务器会做好安全设置,所以即使用户上传exe、php等文件都是可以的
long maxFileSize = 50 * 1024 * 1024;//最大文件大小
RuleFor(e => e.File).NotNull().Must(f => f.Length > 0 && f.Length < maxFileSize);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

namespace IdentityService.Domain
{
public interface IIdDomainService
public interface IIdRepository
{
Task<User> FindByIdAsync(Guid userId);
Task<User> FindByNameAsync(string userName);
Task<User?> FindByIdAsync(Guid userId);
Task<User?> FindByNameAsync(string userName);
Task<IdentityResult> CreateAsync(User user, string password);
Task<IdentityResult> AccessFailedAsync(User user);

Task<User> FindByPhoneNumberAsync(string phoneNum);
Task<SignInResult> CheckPhoneNumAndPwdAsync(string phoneNum, string password);
Task<SignInResult> CheckUserNameAndPwdAsync(string userName, string password);
Task<User?> FindByPhoneNumberAsync(string phoneNum);

Task<string> GenerateChangePhoneNumberTokenAsync(User user, string phoneNumber);
/// <summary>
Expand All @@ -26,8 +24,11 @@ public interface IIdDomainService

Task<IList<string>> GetRolesAsync(User user);
Task<IdentityResult> AddToRoleAsync(User user, string role);

Task<(SignInResult Result, string? Token)> LoginByPhoneAndPwdAsync(string phoneNum, string password);
Task<(SignInResult Result, string? Token)> LoginByUserNameAndPwdAsync(string phoneNum, string password);
public Task<SignInResult> CheckForSignInAsync(User user, string password, bool lockoutOnFailure);
public Task ConfirmPhoneNumberAsync(Guid id);
public Task UpdatePhoneNumberAsync(Guid id, string phoneNum);
public Task<IdentityResult> RemoveUserAsync(Guid id);
public Task<(IdentityResult, User?, string? password)> AddAdminUserAsync(string userName, string phoneNum);
public Task<(IdentityResult, User?, string? password)> ResetPasswordAsync(Guid id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using IdentityService.Domain;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using Zack.JWT;

namespace IdentityService.Infrastructure
{
public class IdDomainService
{
private readonly IIdRepository repository;
private readonly ITokenService tokenService;
private readonly IOptions<JWTOptions> optJWT;

public IdDomainService(IIdRepository repository,
ITokenService tokenService,IOptions<JWTOptions> optJWT)
{
this.repository = repository;
this.tokenService = tokenService;
this.optJWT = optJWT;
}

public async Task<SignInResult> CheckUserNameAndPwdAsync(string userName, string password)
{
var user = await repository.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
//CheckPasswordSignInAsync会对于多次重复失败进行账号禁用
var result = await repository.CheckForSignInAsync(user, password, true);
return result;
}
public async Task<SignInResult> CheckPhoneNumAndPwdAsync(string phoneNum, string password)
{
var user = await repository.FindByPhoneNumberAsync(phoneNum);
if (user == null)
{
return SignInResult.Failed;
}
var result = await repository.CheckForSignInAsync(user, password, true);
return result;
}

public async Task<(SignInResult Result, string? Token)> LoginByPhoneAndPwdAsync(string phoneNum, string password)
{
var checkResult = await CheckPhoneNumAndPwdAsync(phoneNum, password);
if (checkResult.Succeeded)
{
var user = await repository.FindByPhoneNumberAsync(phoneNum);
string token = await BuildTokenAsync(user);
return (SignInResult.Success,token);
}
else
{
return (checkResult, null);
}
}

public async Task<(SignInResult Result, string? Token)> LoginByUserNameAndPwdAsync(string userName, string password)
{
var checkResult = await CheckUserNameAndPwdAsync(userName, password);
if (checkResult.Succeeded)
{
var user = await repository.FindByNameAsync(userName);
string token = await BuildTokenAsync(user);
return (SignInResult.Success, token);
}
else
{
return (checkResult, null);
}
}

private async Task<string> BuildTokenAsync(User user)
{
var roles = await repository.GetRolesAsync(user);
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
foreach (string role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
return tokenService.BuildToken(claims, optJWT.Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Zack.DomainCommons\Zack.DomainCommons.csproj" />
<ProjectReference Include="..\Zack.JWT\Zack.JWT.csproj" />
</ItemGroup>
</Project>
Loading

0 comments on commit 1ba7c6f

Please sign in to comment.