using FreeSql;
using LiveForum.Code.ExceptionExtend;
using LiveForum.Code.JwtInfrastructure;
using LiveForum.Code.JwtInfrastructure.Interface;
using LiveForum.Code.Utility;
using LiveForum.IService.Auth;
using LiveForum.IService.Users;
using LiveForum.Model;
using LiveForum.Model.Dto.Others;
using LiveForum.Model.Enum;
using LiveForum.Model.Enum.Users;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using SystemConvert = System.Convert;
namespace LiveForum.Service.Auth
{
///
/// 登录服务
///
public class LoginService : ILoginService
{
private readonly IJwtAuthManager _jwtAuthManager;
private readonly IBaseRepository _userRepository;
private readonly IBaseRepository _userTokensRepository;
private readonly IBaseRepository _wechatMiniProgramLoginsRepository;
private readonly IBaseRepository _accountPasswordLoginsRepository;
private readonly JwtUserInfoModel _userInfoModel;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptionsSnapshot _appSettingsSnapshot;
private readonly IUserInfoService _userInfoService;
private readonly IBaseRepository _identityGroupsRepository;
private readonly IBaseRepository _userIdentityGroupsRepository;
///
/// 注意,要先创建私有对象,在构造函数中进行赋值
///
/// jwt管理对象
/// 用户仓储类
/// 用户token仓储类
/// 微信小程序登录仓储类
/// 账号密码登录仓储类
/// 在验证jwt的时候进行
/// HTTP上下文访问器
/// 应用配置快照(支持配置热更新)
/// 用户信息服务
/// 身份组仓储类
/// 用户身份组关联仓储类
public LoginService(IJwtAuthManager jwtAuthManager,
IBaseRepository userRepository,
IBaseRepository userTokensRepository,
IBaseRepository wechatMiniProgramLoginsRepository,
IBaseRepository accountPasswordLoginsRepository,
JwtUserInfoModel userInfoModel,
IHttpContextAccessor httpContextAccessor,
IOptionsSnapshot appSettingsSnapshot,
IUserInfoService userInfoService,
IBaseRepository identityGroupsRepository,
IBaseRepository userIdentityGroupsRepository)
{
_jwtAuthManager = jwtAuthManager;
_userRepository = userRepository;
_userTokensRepository = userTokensRepository;
_wechatMiniProgramLoginsRepository = wechatMiniProgramLoginsRepository;
_accountPasswordLoginsRepository = accountPasswordLoginsRepository;
_userInfoModel = userInfoModel;
_httpContextAccessor = httpContextAccessor;
_appSettingsSnapshot = appSettingsSnapshot;
_userInfoService = userInfoService;
_identityGroupsRepository = identityGroupsRepository;
_userIdentityGroupsRepository = userIdentityGroupsRepository;
}
///
/// 微信小程序登录
/// 实现步奏:
/// 1. 根据 openId 去查询T_WechatMiniProgramLogins用户是否存在
/// 2. 如果存在,获取对应的用户信息,生成JwtAccessToken返回(生成的jwt要看一下JwtUserInfoModel实体是否能满足)
/// 3. 如果不存在,创建用户,并保存T_WechatMiniProgramLogins记录,然后生成JwtAccessToken返回
/// 4. 将生成的token保存到T_UserTokens中
/// 5. 返回JwtAccessToken
///
///
///
///
///
///
public async Task WechatMpLogin(string openId, string sessionKey = "", string unionId = "")
{
if (string.IsNullOrEmpty(openId))
{
throw new ArgumentNullException(nameof(openId), "openId 不能为空");
}
var now = DateTime.Now;
var clientIp = _httpContextAccessor.GetClientIpAddress();
T_WechatMiniProgramLogins wechatLogin;
T_Users user;
// 1. 根据 openId 查询微信小程序登录记录
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (wechatLogin != null)
{
// 2. 如果存在,获取对应的用户信息
user = await _userRepository.Select
.Where(x => x.Id == wechatLogin.UserId)
.FirstAsync();
if (user == null)
{
// 用户已被删除(如管理后台删除),重新创建用户
var appSettings1 = _appSettingsSnapshot.Value;
var defaultNickName1 = string.IsNullOrEmpty(appSettings1.UserDefaultName)
? "用户"
: appSettings1.UserDefaultName;
var defaultAvatar1 = string.IsNullOrEmpty(appSettings1.UserDefaultIcon)
? ""
: appSettings1.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName1}_{openId.Substring(0, 8)}",
Avatar = defaultAvatar1,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.未认证,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 更新微信登录记录关联到新用户
wechatLogin.UserId = user.Id;
}
// 更新最后登录时间和IP
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
// 更新用户最后登录信息
user.LastLoginTime = now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
}
else
{
// 3. 如果不存在,创建新用户
// 使用配置中的默认头像和昵称(从 IOptionsSnapshot 获取最新配置)
var appSettings = _appSettingsSnapshot.Value;
var defaultNickName = string.IsNullOrEmpty(appSettings.UserDefaultName)
? "用户"
: appSettings.UserDefaultName;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon)
? ""
: appSettings.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName}_{openId.Substring(0, 8)}",
Avatar = defaultAvatar,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.未认证,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 创建微信登录记录
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
// 4. 生成JWT Token
var claims = new List
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType",((int)LoginTypeEnum.WechatLogin).ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
// 5. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
LoginType = LoginTypeEnum.WechatLogin,
LoginId = wechatLogin.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = jwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
///
/// 刷新token
/// 1.根据注入的userInfoModel对象,获取当前用户信息
/// 2.生成新的JwtAccessToken
/// 3.更新T_UserTokens中的token信息
/// 4.旧的token作废,去除redis缓存(RemoveTokenByUserNameAsync)
/// 5.返回新的JwtAccessToken
///
///
///
public async Task RefreshToken()
{
if (_userInfoModel == null || string.IsNullOrEmpty(_userInfoModel.UserName))
{
throw new Exception("用户未登录");
}
var now = DateTime.Now;
// 1. 根据注入的userInfoModel对象,获取当前用户信息
if (!long.TryParse(_userInfoModel.UserName, out long userId))
{
throw new Exception("无效的用户ID");
}
var user = await _userRepository.Select
.Where(x => x.Id == userId)
.FirstAsync();
if (user == null)
{
throw new Exception("用户不存在");
}
//if (_userInfoModel.ExpireAt < DateTime.Now.AddDays(3))
//{
//}
// 2. 生成新的JwtAccessToken
var claims = new List
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType",_userInfoModel.LoginType.ToString())
};
var newJwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == userId).ToDelete().ExecuteDeletedAsync();
// 3. 更新T_UserTokens中的token信息(将旧的token标记为无效,插入新token)
//var oldTokens = await _userTokensRepository.Select
// .Where(x => x.UserId == userId)
// .ToListAsync();
//foreach (var oldToken in oldTokens)
//{
// oldToken.UpdatedAt = now;
// await _userTokensRepository.UpdateAsync(oldToken);
//}
// 插入新的token记录
var clientIp = _httpContextAccessor.GetClientIpAddress();
var newUserToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = newJwtToken.Token,
LoginType = (LoginTypeEnum)_userInfoModel.LoginType, // 可以根据实际情况调整
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = newJwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now,
TokenMD5 = newJwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(newUserToken);
// 4. 旧的token作废,去除redis缓存 生成的时候好像会覆盖掉
//await _jwtAuthManager.RemoveTokenByUserNameAsync(_userInfoModel.UserName);
// 5. 返回新的JwtAccessToken
return newJwtToken;
}
///
/// 账号密码登录
///
/// 账号(用户名/手机号/邮箱)
/// 密码
/// 账号类型:1-用户名,2-手机号,3-邮箱
///
public async Task AccountLogin(string account, string password, int accountType = 1)
{
// 1. 根据账号查找登录记录
var loginRecord = await _accountPasswordLoginsRepository.Select
.Where(x => x.LoginAccount == account && x.AccountType == accountType && !x.IsDeleted)
.FirstAsync();
if (loginRecord == null)
{
throw new LoginErrorException("账号不存在或密码错误");
}
// 2. 验证密码
if (!VerifyPassword(password, loginRecord.PasswordHash, loginRecord.PasswordSalt))
{
throw new LoginErrorException("账号不存在或密码错误");
}
// 3. 获取用户信息
var user = await _userRepository.Select.Where(x => x.Id == loginRecord.UserId).FirstAsync();
if (user == null || user.Status != UserStatusEnum.Normal)
{
throw new LoginErrorException("用户状态异常");
}
// 4. 更新登录信息
var clientIp = _httpContextAccessor.GetClientIpAddress(); ;
loginRecord.LastLoginTime = DateTime.Now;
loginRecord.LastLoginIp = clientIp;
await _accountPasswordLoginsRepository.UpdateAsync(loginRecord);
// 5. 更新用户最后登录信息
user.LastLoginTime = DateTime.Now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
// 6. 生成JWT Token
var claims = new List
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.NickName),
new Claim("loginType", "2"), // 2-账号密码登录
new Claim("userId", loginRecord.Id.ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims.ToList(), DateTime.Now);
// 3. 删除T_UserTokens中的token信息
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
// 7. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
TokenType = "Bearer",
ExpiresAt = jwtToken.ExpireAt,
LoginType = LoginTypeEnum.AccountPasswordLogin, // 2-账号密码登录
LoginId = loginRecord.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
TokenMD5 = jwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
///
/// 账号密码注册
///
/// 登录账号(用户名/手机号/邮箱)
/// 账号类型:1-用户名,2-手机号,3-邮箱
/// 密码
/// 昵称
/// 验证码(手机号/邮箱注册时必填)
///
public async Task AccountRegister(string loginAccount, int accountType, string password, string nickName, string verifyCode = "")
{
// 1. 验证账号是否已存在
var existingLogin = await _accountPasswordLoginsRepository.Select
.Where(x => x.LoginAccount == loginAccount && x.AccountType == accountType && !x.IsDeleted)
.FirstAsync();
if (existingLogin != null)
{
throw new LoginErrorException("该账号已存在");
}
// 2. 验证验证码(如果需要)
if (accountType == 2 || accountType == 3) // 手机号或邮箱注册
{
if (string.IsNullOrEmpty(verifyCode))
{
throw new LoginErrorException("验证码不能为空");
}
// TODO: 实现验证码验证逻辑
}
// 3. 密码加密
var (passwordHash, passwordSalt) = HashPassword(password);
// 4. 创建用户记录
var clientIp = _httpContextAccessor.GetClientIpAddress(); ;
var now = DateTime.Now;
// 使用配置中的默认头像(从 IOptionsSnapshot 获取最新配置)
var appSettings = _appSettingsSnapshot.Value;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon)
? ""
: appSettings.UserDefaultIcon;
var user = new T_Users
{
NickName = nickName,
Avatar = defaultAvatar,
LevelId = 1, // 默认等级
Experience = 0,
Status = UserStatusEnum.Normal,
CertifiedType = 0, // 0表示未认证
CertifiedStatus = CertifiedStatusEnum.未认证,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
// 5. 创建登录记录
var loginRecord = new T_AccountPasswordLogins
{
UserId = user.Id,
LoginAccount = loginAccount,
AccountType = (byte)accountType,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
IsPrimary = true, // 第一个登录方式为主登录方式
CreatedAt = now,
UpdatedAt = now
};
await _accountPasswordLoginsRepository.InsertAsync(loginRecord);
// 6. 生成JWT Token
var claims = new List
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.NickName),
new Claim("loginType", "2"), // 2-账号密码登录
new Claim("userId", user.Id.ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims.ToList(), DateTime.Now);
// 7. 保存Token到数据库
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
TokenType = "Bearer",
ExpiresAt = jwtToken.ExpireAt,
LoginType = LoginTypeEnum.AccountPasswordLogin, // 2-账号密码登录
LoginId = loginRecord.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
CreatedAt = now,
UpdatedAt = now,
TokenMD5 = jwtToken.Token.ToMD5()
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
///
/// 微信手机号登录
/// 用户匹配逻辑:手机号 → openId → 新用户
///
public async Task WechatPhoneLogin(string openId, string phoneNumber, string sessionKey = "", string unionId = "")
{
if (string.IsNullOrEmpty(openId)) throw new ArgumentNullException(nameof(openId));
if (string.IsNullOrEmpty(phoneNumber)) throw new ArgumentNullException(nameof(phoneNumber));
var now = DateTime.Now;
var clientIp = _httpContextAccessor.GetClientIpAddress();
T_Users user = null;
T_WechatMiniProgramLogins wechatLogin = null;
// 1. 根据手机号查询用户
user = await _userRepository.Select
.Where(x => x.PhoneNumber == phoneNumber)
.FirstAsync();
if (user != null)
{
// 找到手机号用户,确保绑定/更新 OpenID
// 先按 UserId 查当前用户的微信记录
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.UserId == user.Id)
.FirstAsync();
if (wechatLogin == null)
{
// 当前用户没有微信记录,检查该 OpenId 是否已被其他用户占用
var existingByOpenId = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (existingByOpenId != null)
{
// OpenId 已存在,将其重新关联到当前手机号用户
existingByOpenId.UserId = user.Id;
existingByOpenId.SessionKey = sessionKey;
existingByOpenId.LastLoginTime = now;
existingByOpenId.LastLoginIp = clientIp;
existingByOpenId.UpdatedAt = now;
await _wechatMiniProgramLoginsRepository.UpdateAsync(existingByOpenId);
wechatLogin = existingByOpenId;
}
else
{
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
}
else
{
wechatLogin.OpenId = openId;
wechatLogin.SessionKey = sessionKey;
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
wechatLogin.UpdatedAt = now;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
}
}
else
{
// 2. 根据 OpenID 查询
wechatLogin = await _wechatMiniProgramLoginsRepository.Select
.Where(x => x.OpenId == openId)
.FirstAsync();
if (wechatLogin != null)
{
user = await _userRepository.Select
.Where(x => x.Id == wechatLogin.UserId)
.FirstAsync();
if (user != null)
{
// 绑定手机号
user.PhoneNumber = phoneNumber;
await _userRepository.UpdateAsync(user);
wechatLogin.SessionKey = sessionKey;
wechatLogin.LastLoginTime = now;
wechatLogin.LastLoginIp = clientIp;
await _wechatMiniProgramLoginsRepository.UpdateAsync(wechatLogin);
}
}
}
// 3. 新用户注册
if (user == null)
{
var appSettings = _appSettingsSnapshot.Value;
var defaultNickName = string.IsNullOrEmpty(appSettings.UserDefaultName) ? "用户" : appSettings.UserDefaultName;
var defaultAvatar = string.IsNullOrEmpty(appSettings.UserDefaultIcon) ? "" : appSettings.UserDefaultIcon;
user = new T_Users
{
NickName = $"{defaultNickName}_{openId.Substring(0, Math.Min(8, openId.Length))}",
Avatar = defaultAvatar,
PhoneNumber = phoneNumber,
Experience = 0,
LevelId = 1,
Status = UserStatusEnum.Normal,
CertifiedType = 0,
CertifiedStatus = CertifiedStatusEnum.未认证,
IsCertified = false,
IsVip = false,
UID = await _userInfoService.GenerateUniqueUIDAsync(),
CreatedAt = now,
UpdatedAt = now,
RegisterIp = clientIp,
LastLoginIp = clientIp,
LastLoginTime = now
};
await _userRepository.InsertAsync(user);
// 新用户自动关联默认身份组
await AssignDefaultIdentityGroupAsync(user.Id);
wechatLogin = new T_WechatMiniProgramLogins
{
OpenId = openId,
SessionKey = sessionKey,
UnionId = unionId ?? "",
UserId = user.Id,
LastLoginIp = clientIp,
LastLoginTime = now,
CreatedAt = now,
UpdatedAt = now,
};
await _wechatMiniProgramLoginsRepository.InsertAsync(wechatLogin);
}
// 更新用户最后登录信息
user.LastLoginTime = now;
user.LastLoginIp = clientIp;
await _userRepository.UpdateAsync(user);
// 生成JWT Token
var claims = new List
{
new Claim("userId", user.Id.ToString()),
new Claim("loginType", ((int)LoginTypeEnum.WechatLogin).ToString())
};
var jwtToken = await _jwtAuthManager.GenerateTokensAsync(user.Id.ToString(), claims, now);
await _userTokensRepository.Select
.Where(x => x.UserId == user.Id).ToDelete().ExecuteDeletedAsync();
var userToken = new T_UserTokens
{
UserId = user.Id,
AccessToken = jwtToken.Token,
LoginType = LoginTypeEnum.WechatLogin,
LoginId = wechatLogin.Id,
ClientIp = clientIp,
UserAgent = _httpContextAccessor.GetUserAgent(),
ExpiresAt = jwtToken.ExpireAt,
TokenType = "Bearer",
CreatedAt = now,
UpdatedAt = now
};
await _userTokensRepository.InsertAsync(userToken);
return jwtToken;
}
///
/// 为新用户自动关联默认身份组
///
/// 新用户ID
private async Task AssignDefaultIdentityGroupAsync(long userId)
{
var defaultGroup = await _identityGroupsRepository.Select
.Where(x => x.IsDefault == true)
.FirstAsync();
if (defaultGroup != null)
{
await _userIdentityGroupsRepository.InsertAsync(new T_UserIdentityGroups
{
UserId = userId,
IdentityGroupId = defaultGroup.Id,
CreatedAt = DateTime.Now
});
}
}
///
/// 密码加密
///
/// 原始密码
/// 加密后的密码哈希和盐值
private (string hash, string salt) HashPassword(string password)
{
// 生成随机盐值
var saltBytes = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(saltBytes);
}
var salt = SystemConvert.ToBase64String(saltBytes);
// 使用PBKDF2进行密码哈希
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
var hashBytes = pbkdf2.GetBytes(32);
var hash = SystemConvert.ToBase64String(hashBytes);
return (hash, salt);
}
}
///
/// 验证密码
///
/// 原始密码
/// 存储的密码哈希
/// 存储的盐值
/// 密码是否正确
private bool VerifyPassword(string password, string hash, string salt)
{
try
{
var saltBytes = SystemConvert.FromBase64String(salt);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
var hashBytes = pbkdf2.GetBytes(32);
var computedHash = SystemConvert.ToBase64String(hashBytes);
return computedHash == hash;
}
}
catch
{
return false;
}
}
}
}