92 changed files with 1047 additions and 46 deletions
@ -0,0 +1,72 @@ |
|||
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,76 @@ |
|||
/// <summary>
|
|||
/// 泛型结果类,用于封装操作结果
|
|||
/// </summary>
|
|||
/// <typeparam name="T">结果数据类型</typeparam>
|
|||
public sealed record OperationResult<T>( |
|||
/// <summary>
|
|||
/// 成功消息
|
|||
/// </summary>
|
|||
string? SuccessMessage, |
|||
|
|||
/// <summary>
|
|||
/// 错误消息列表
|
|||
/// </summary>
|
|||
List<string>? ErrorMessages, |
|||
|
|||
/// <summary>
|
|||
/// 结果数据
|
|||
/// </summary>
|
|||
T? Data) |
|||
{ |
|||
/// <summary>
|
|||
/// 判断操作是否成功
|
|||
/// </summary>
|
|||
public bool IsSuccess => ErrorMessages == null || !ErrorMessages.Any(); |
|||
|
|||
/// <summary>
|
|||
/// 创建成功结果(仅包含数据)
|
|||
/// </summary>
|
|||
/// <param name="data">结果数据</param>
|
|||
/// <returns>成功结果</returns>
|
|||
public static OperationResult<T> CreateSuccess(T data) |
|||
{ |
|||
return new OperationResult<T>(null, null, data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建成功结果(仅包含成功消息)
|
|||
/// </summary>
|
|||
/// <param name="successMessage">成功消息</param>
|
|||
/// <returns>成功结果</returns>
|
|||
public static OperationResult<T> CreateSuccess(string successMessage) |
|||
{ |
|||
return new OperationResult<T>(successMessage, null, default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建成功结果(包含成功消息和数据)
|
|||
/// </summary>
|
|||
/// <param name="successMessage">成功消息</param>
|
|||
/// <param name="data">结果数据</param>
|
|||
/// <returns>成功结果</returns>
|
|||
public static OperationResult<T> CreateSuccess(string successMessage, T data) |
|||
{ |
|||
return new OperationResult<T>(successMessage, null, data); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建失败结果(单个错误消息)
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">错误消息</param>
|
|||
/// <returns>失败结果</returns>
|
|||
public static OperationResult<T> CreateFailure(string errorMessage) |
|||
{ |
|||
return new OperationResult<T>(null, new List<string>() { errorMessage }, default); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 创建失败结果(多个错误消息)
|
|||
/// </summary>
|
|||
/// <param name="errorMessages">错误消息列表</param>
|
|||
/// <returns>失败结果</returns>
|
|||
public static OperationResult<T> CreateFailure(List<string> errorMessages) |
|||
{ |
|||
return new OperationResult<T>(null, errorMessages, default); |
|||
} |
|||
} |
@ -0,0 +1,28 @@ |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace CellularManagement.Application.Common; |
|||
|
|||
/// <summary>
|
|||
/// 字符串扩展方法
|
|||
/// </summary>
|
|||
public static class StringExtensions |
|||
{ |
|||
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); |
|||
|
|||
/// <summary>
|
|||
/// 验证邮箱格式
|
|||
/// </summary>
|
|||
/// <param name="email">邮箱地址</param>
|
|||
/// <returns>是否有效</returns>
|
|||
public static bool IsValidEmail(this string email) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(email)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return EmailRegex.IsMatch(email); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证命令
|
|||
/// </summary>
|
|||
public sealed record AuthenticateUserCommand( |
|||
/// <summary>
|
|||
/// 用户名或邮箱
|
|||
/// </summary>
|
|||
string UserNameOrEmail, |
|||
|
|||
/// <summary>
|
|||
/// 密码
|
|||
/// </summary>
|
|||
string Password) : IRequest<OperationResult<AuthenticateUserResponse>>; |
@ -0,0 +1,113 @@ |
|||
using System.Security.Claims; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Application.Services; |
|||
using CellularManagement.Domain.Entities; |
|||
using CellularManagement.Application.Features.Auth.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证命令处理器
|
|||
/// </summary>
|
|||
public sealed class AuthenticateUserCommandHandler : IRequestHandler<AuthenticateUserCommand, OperationResult<AuthenticateUserResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly IJwtProvider _jwtProvider; |
|||
private readonly ILogger<AuthenticateUserCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public AuthenticateUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
IJwtProvider jwtProvider, |
|||
ILogger<AuthenticateUserCommandHandler> logger) |
|||
{ |
|||
_userManager = userManager; |
|||
_jwtProvider = jwtProvider; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理认证请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<AuthenticateUserResponse>> Handle( |
|||
AuthenticateUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 先尝试通过邮箱查找用户
|
|||
AppUser? user = null; |
|||
if (request.UserNameOrEmail.IsValidEmail()) |
|||
{ |
|||
user = await _userManager.FindByEmailAsync(request.UserNameOrEmail); |
|||
} |
|||
|
|||
// 如果通过邮箱没找到用户,则尝试通过用户名查找
|
|||
if (user == null) |
|||
{ |
|||
user = await _userManager.FindByNameAsync(request.UserNameOrEmail); |
|||
} |
|||
|
|||
if (user == null) |
|||
{ |
|||
_logger.LogWarning("用户 {UserNameOrEmail} 不存在", request.UserNameOrEmail); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("用户名或密码错误"); |
|||
} |
|||
|
|||
// 验证密码
|
|||
var isValidPassword = await _userManager.CheckPasswordAsync(user, request.Password); |
|||
if (!isValidPassword) |
|||
{ |
|||
_logger.LogWarning("用户 {UserNameOrEmail} 密码错误", request.UserNameOrEmail); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("用户名或密码错误"); |
|||
} |
|||
|
|||
// 获取用户角色
|
|||
var roles = await _userManager.GetRolesAsync(user); |
|||
|
|||
// 创建用户声明
|
|||
var claims = new List<Claim> |
|||
{ |
|||
new(ClaimTypes.NameIdentifier, user.Id), |
|||
new(ClaimTypes.Name, user.UserName!), |
|||
new(ClaimTypes.Email, user.Email!) |
|||
}; |
|||
|
|||
// 添加角色声明
|
|||
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); |
|||
|
|||
// 生成访问令牌
|
|||
var accessToken = _jwtProvider.GenerateAccessToken(claims); |
|||
|
|||
// 生成刷新令牌
|
|||
var refreshToken = _jwtProvider.GenerateRefreshToken(claims); |
|||
|
|||
// 获取令牌过期时间
|
|||
var expiresAt = _jwtProvider.GetTokenExpiration(accessToken); |
|||
|
|||
// 创建用户信息
|
|||
var userInfo = new UserInfo( |
|||
user.Id, |
|||
user.UserName!, |
|||
user.Email!, |
|||
user.PhoneNumber, |
|||
roles); |
|||
|
|||
_logger.LogInformation("用户 {UserNameOrEmail} 认证成功", request.UserNameOrEmail); |
|||
|
|||
// 返回认证结果
|
|||
return OperationResult<AuthenticateUserResponse>.CreateSuccess( |
|||
new AuthenticateUserResponse(accessToken, refreshToken, expiresAt, userInfo)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "用户 {UserNameOrEmail} 认证失败", request.UserNameOrEmail); |
|||
return OperationResult<AuthenticateUserResponse>.CreateFailure("认证失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证命令验证器
|
|||
/// </summary>
|
|||
public sealed class AuthenticateUserCommandValidator : AbstractValidator<AuthenticateUserCommand> |
|||
{ |
|||
/// <summary>
|
|||
/// 初始化验证器
|
|||
/// </summary>
|
|||
public AuthenticateUserCommandValidator() |
|||
{ |
|||
// 验证用户名或邮箱
|
|||
RuleFor(x => x.UserNameOrEmail) |
|||
.NotEmpty().WithMessage("用户名或邮箱不能为空") |
|||
.MaximumLength(256).WithMessage("用户名或邮箱长度不能超过256个字符") |
|||
.Must(x => x.Contains('@') ? x.IsValidEmail() : true) |
|||
.WithMessage("邮箱格式不正确"); |
|||
|
|||
// 验证密码
|
|||
RuleFor(x => x.Password) |
|||
.NotEmpty().WithMessage("密码不能为空") |
|||
.MinimumLength(6).WithMessage("密码长度不能少于6个字符") |
|||
.MaximumLength(50).WithMessage("密码长度不能超过50个字符"); |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
using CellularManagement.Application.Features.Auth.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.AuthenticateUser; |
|||
|
|||
/// <summary>
|
|||
/// 用户认证响应
|
|||
/// </summary>
|
|||
public sealed record AuthenticateUserResponse( |
|||
/// <summary>
|
|||
/// 访问令牌
|
|||
/// </summary>
|
|||
string AccessToken, |
|||
|
|||
/// <summary>
|
|||
/// 刷新令牌
|
|||
/// </summary>
|
|||
string RefreshToken, |
|||
|
|||
/// <summary>
|
|||
/// 令牌过期时间
|
|||
/// </summary>
|
|||
DateTime ExpiresAt, |
|||
|
|||
/// <summary>
|
|||
/// 用户信息
|
|||
/// </summary>
|
|||
UserInfo User); |
@ -0,0 +1,32 @@ |
|||
using MediatR; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.SignUp; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册命令
|
|||
/// </summary>
|
|||
public sealed record RegisterUserCommand( |
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
string UserName, |
|||
|
|||
/// <summary>
|
|||
/// 邮箱
|
|||
/// </summary>
|
|||
string Email, |
|||
|
|||
/// <summary>
|
|||
/// 密码
|
|||
/// </summary>
|
|||
string Password, |
|||
|
|||
/// <summary>
|
|||
/// 确认密码
|
|||
/// </summary>
|
|||
string ConfirmPassword, |
|||
|
|||
/// <summary>
|
|||
/// 电话号码
|
|||
/// </summary>
|
|||
string? PhoneNumber) : IRequest<OperationResult<RegisterUserResponse>>; |
@ -0,0 +1,85 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Logging; |
|||
using MediatR; |
|||
using CellularManagement.Application.Common; |
|||
using CellularManagement.Domain.Entities; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.SignUp; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册命令处理器
|
|||
/// </summary>
|
|||
public sealed class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, OperationResult<RegisterUserResponse>> |
|||
{ |
|||
private readonly UserManager<AppUser> _userManager; |
|||
private readonly ILogger<RegisterUserCommandHandler> _logger; |
|||
|
|||
/// <summary>
|
|||
/// 初始化处理器
|
|||
/// </summary>
|
|||
public RegisterUserCommandHandler( |
|||
UserManager<AppUser> userManager, |
|||
ILogger<RegisterUserCommandHandler> logger) |
|||
{ |
|||
_userManager = userManager; |
|||
_logger = logger; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 处理注册请求
|
|||
/// </summary>
|
|||
public async Task<OperationResult<RegisterUserResponse>> Handle( |
|||
RegisterUserCommand request, |
|||
CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
// 检查用户名是否已存在
|
|||
var existingUser = await _userManager.FindByNameAsync(request.UserName); |
|||
if (existingUser != null) |
|||
{ |
|||
_logger.LogWarning("用户名 {UserName} 已存在", request.UserName); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure("用户名已存在"); |
|||
} |
|||
|
|||
// 检查邮箱是否已存在
|
|||
existingUser = await _userManager.FindByEmailAsync(request.Email); |
|||
if (existingUser != null) |
|||
{ |
|||
_logger.LogWarning("邮箱 {Email} 已存在", request.Email); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure("邮箱已存在"); |
|||
} |
|||
|
|||
// 创建用户
|
|||
var user = new AppUser |
|||
{ |
|||
UserName = request.UserName, |
|||
Email = request.Email, |
|||
PhoneNumber = request.PhoneNumber |
|||
}; |
|||
|
|||
// 创建用户
|
|||
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)); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure(errors); |
|||
} |
|||
|
|||
// 添加默认角色
|
|||
await _userManager.AddToRoleAsync(user, "User"); |
|||
|
|||
_logger.LogInformation("用户 {UserName} 注册成功", request.UserName); |
|||
|
|||
// 返回注册结果
|
|||
return OperationResult<RegisterUserResponse>.CreateSuccess( |
|||
new RegisterUserResponse(user.Id)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex, "用户 {UserName} 注册失败", request.UserName); |
|||
return OperationResult<RegisterUserResponse>.CreateFailure("注册失败,请稍后重试"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
using FluentValidation; |
|||
using CellularManagement.Application.Common; |
|||
|
|||
namespace CellularManagement.Application.Features.Auth.Commands.SignUp; |
|||
|
|||
/// <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.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("两次输入的密码不一致"); |
|||
|
|||
// 验证电话号码
|
|||
RuleFor(x => x.PhoneNumber) |
|||
.Matches(@"^1[3-9]\d{9}$").When(x => !string.IsNullOrEmpty(x.PhoneNumber)) |
|||
.WithMessage("电话号码格式不正确"); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
namespace CellularManagement.Application.Features.Auth.Commands.SignUp; |
|||
|
|||
/// <summary>
|
|||
/// 用户注册响应
|
|||
/// </summary>
|
|||
public sealed record RegisterUserResponse( |
|||
/// <summary>
|
|||
/// 用户ID
|
|||
/// </summary>
|
|||
string UserId); |
@ -0,0 +1,30 @@ |
|||
namespace CellularManagement.Application.Features.Auth.Common; |
|||
|
|||
/// <summary>
|
|||
/// 用户信息
|
|||
/// </summary>
|
|||
public sealed record UserInfo( |
|||
/// <summary>
|
|||
/// 用户ID
|
|||
/// </summary>
|
|||
string Id, |
|||
|
|||
/// <summary>
|
|||
/// 用户名
|
|||
/// </summary>
|
|||
string UserName, |
|||
|
|||
/// <summary>
|
|||
/// 邮箱
|
|||
/// </summary>
|
|||
string Email, |
|||
|
|||
/// <summary>
|
|||
/// 电话号码
|
|||
/// </summary>
|
|||
string? PhoneNumber, |
|||
|
|||
/// <summary>
|
|||
/// 用户角色列表
|
|||
/// </summary>
|
|||
IList<string> Roles); |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?> |
|||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> |
|||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\7.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\7.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" /> |
|||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" /> |
|||
</ImportGroup> |
|||
</Project> |
@ -1 +1 @@ |
|||
81cfc18adde389c3ed735e73d4a5a1df2880dce2491e526c9dfa723bea74f632 |
|||
f64dca1f5ddfed87103436ba0d18e90d8786f1f105d8dbb750a557489f67538f |
|||
|
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
8dbfacc6d4b66280cdce61e7719a6ebfaf1d5c1b90fa32c02ef4a1dfb60e4950 |
|||
24e50c8ecf618762aaae8f56114fa8e731158179ddfc58fa34cc62c5c5fc1dcd |
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
12e431942fef011f2b9b1a9adf8ef5e43c8f65aad53fe3677eb3730c08dcdf15 |
|||
0ecc48c296d0461cd1a5e952c2f6f289df683878ec748cacb3532116b1689419 |
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
4400330d23beb4f3571ec0e32e6f70021f06398ff98ae8ace59717d132777aff |
|||
b7a5030c29a32873ce7ea034aded49f9f2b3d3d14db6d4a0804a58141d52a6d7 |
|||
|
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
d9bda7c9895a366d26bd4ed2ba5c9826acbb03d47916eaac923e899d16ee5cac |
|||
20193b07a68300ee5d3c1474d0d724cfa64e140c9c3946f731fd113c1607af61 |
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
1637dd0f1f892e1caaa12a0723bde19c8a3100f22a8056535f2eb729253e56a9 |
|||
4b46dc01ba9eba595a73c10c781aec70ddf52cd18d9e1395728666b921bbc6af |
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
9e10b8095ccbd1d20bac88937388182d495d2b9f74e1b0b851868a018b03446c |
|||
dbfbe4e2c4d18d017eb8a73ba89158f89c182b81edb771ea092c5199b464a83b |
|||
|
Binary file not shown.
Binary file not shown.
@ -1 +1 @@ |
|||
2cd5d39f44b24a8b172f11cb1a7c55162a87d62528b276566a3d2fb042fdd903 |
|||
f4a006ce19ed3e318693f1d968e2c2e1d263909a7ba5255d4a725e7addc29930 |
|||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue