Merge remote-tracking branch 'origin/ai-hub' into ai-hub
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
{
|
{
|
||||||
public class PhoneCaptchaImageDto
|
public class PhoneCaptchaImageDto
|
||||||
{
|
{
|
||||||
public string Phone { get; set; }
|
public string? Phone { get; set; }
|
||||||
|
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
public string Uuid { get; set; }
|
public string Uuid { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long? Phone { get; set; }
|
public long? Phone { get; set; }
|
||||||
|
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证码
|
/// 验证码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.User
|
|||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public int? Age { get; set; }
|
public int? Age { get; set; }
|
||||||
public string? Nick { get; set; }
|
public string? Nick { get; set; }
|
||||||
public string? Email { get; set; }
|
// public string? Email { get; set; }
|
||||||
public string? Address { get; set; }
|
public string? Address { get; set; }
|
||||||
public long? Phone { get; set; }
|
// public long? Phone { get; set; }
|
||||||
public string? Introduction { get; set; }
|
public string? Introduction { get; set; }
|
||||||
public string? Remark { get; set; }
|
public string? Remark { get; set; }
|
||||||
public SexEnum? Sex { get; set; }
|
public SexEnum? Sex { get; set; }
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using System.Net.Mail;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Lazy.Captcha.Core;
|
using Lazy.Captcha.Core;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
@@ -9,6 +12,7 @@ using Microsoft.Extensions.Options;
|
|||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Authorization;
|
using Volo.Abp.Authorization;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
|
using Volo.Abp.Emailing;
|
||||||
using Volo.Abp.EventBus.Local;
|
using Volo.Abp.EventBus.Local;
|
||||||
using Volo.Abp.Guids;
|
using Volo.Abp.Guids;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
@@ -34,13 +38,15 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
{
|
{
|
||||||
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
|
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
|
||||||
private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache;
|
private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache;
|
||||||
|
private IDistributedCache<CaptchaEmailCacheItem, CaptchaEmailCacheKey> _emailCache;
|
||||||
private readonly ICaptcha _captcha;
|
private readonly ICaptcha _captcha;
|
||||||
private readonly IGuidGenerator _guidGenerator;
|
private readonly IGuidGenerator _guidGenerator;
|
||||||
private readonly RbacOptions _rbacOptions;
|
private readonly RbacOptions _rbacOptions;
|
||||||
private readonly IAliyunManger _aliyunManger;
|
private readonly IAliyunManger _aliyunManger;
|
||||||
private IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
|
private readonly IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
|
||||||
private UserManager _userManager;
|
private readonly UserManager _userManager;
|
||||||
private IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
private readonly IEmailSender _emailSender;
|
||||||
|
|
||||||
public AccountService(IUserRepository userRepository,
|
public AccountService(IUserRepository userRepository,
|
||||||
ICurrentUser currentUser,
|
ICurrentUser currentUser,
|
||||||
@@ -52,7 +58,8 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
IGuidGenerator guidGenerator,
|
IGuidGenerator guidGenerator,
|
||||||
IOptions<RbacOptions> options,
|
IOptions<RbacOptions> options,
|
||||||
IAliyunManger aliyunManger,
|
IAliyunManger aliyunManger,
|
||||||
UserManager userManager, IHttpContextAccessor httpContextAccessor)
|
UserManager userManager, IHttpContextAccessor httpContextAccessor,
|
||||||
|
IDistributedCache<CaptchaEmailCacheItem, CaptchaEmailCacheKey> emailCache, IEmailSender emailSender)
|
||||||
{
|
{
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_currentUser = currentUser;
|
_currentUser = currentUser;
|
||||||
@@ -66,6 +73,8 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
_userCache = userCache;
|
_userCache = userCache;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_emailCache = emailCache;
|
||||||
|
_emailSender = emailSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -167,115 +176,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid, IsEnableCaptcha = enableCaptcha };
|
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid, IsEnableCaptcha = enableCaptcha };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证电话号码
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="phone"></param>
|
|
||||||
private async Task ValidationPhone(string phone)
|
|
||||||
{
|
|
||||||
var res = Regex.IsMatch(phone, @"^\d{11}$");
|
|
||||||
if (res == false)
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException("手机号码格式错误!请检查");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机验证码-注册
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("account/captcha-phone")]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
|
|
||||||
{
|
|
||||||
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机验证码-找回密码
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("account/captcha-phone/repassword")]
|
|
||||||
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
|
|
||||||
{
|
|
||||||
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机验证码-绑定
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost("account/captcha-phone/bind")]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<object> PostCaptchaPhoneForBindAsync(PhoneCaptchaImageDto input)
|
|
||||||
{
|
|
||||||
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Bind, input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 手机验证码-需通过图形验证码
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
[RemoteService(isEnabled: false)]
|
|
||||||
private async Task<object> PostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
|
|
||||||
PhoneCaptchaImageDto input)
|
|
||||||
{
|
|
||||||
//验证uuid 和 验证码
|
|
||||||
ValidationImageCaptcha(input.Uuid, input.Code);
|
|
||||||
|
|
||||||
await ValidationPhone(input.Phone);
|
|
||||||
|
|
||||||
if (validationPhoneType == ValidationPhoneTypeEnum.Register &&
|
|
||||||
await _userRepository.IsAnyAsync(x => x.Phone.ToString() == input.Phone))
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException("该手机号已被注册!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone));
|
|
||||||
|
|
||||||
//防止暴刷
|
|
||||||
if (value is not null)
|
|
||||||
{
|
|
||||||
throw new UserFriendlyException($"{input.Phone}已发送过验证码,10分钟后可重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
//生成一个4位数的验证码
|
|
||||||
//发送短信,同时生成uuid
|
|
||||||
////key: 电话号码 value:验证码+uuid
|
|
||||||
var code = Guid.NewGuid().ToString().Substring(0, 4);
|
|
||||||
var uuid = Guid.NewGuid();
|
|
||||||
await _aliyunManger.SendSmsAsync(input.Phone, code);
|
|
||||||
|
|
||||||
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
|
|
||||||
new CaptchaPhoneCacheItem(code),
|
|
||||||
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
|
||||||
return new
|
|
||||||
{
|
|
||||||
Uuid = uuid
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 校验电话验证码,需要与电话号码绑定
|
|
||||||
/// </summary>
|
|
||||||
public async Task ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum validationPhoneType, long phone,
|
|
||||||
string code)
|
|
||||||
{
|
|
||||||
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
|
|
||||||
if (item is not null && item.Code.Equals($"{code}"))
|
|
||||||
{
|
|
||||||
//成功,需要清空
|
|
||||||
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(validationPhoneType, code.ToString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UserFriendlyException("验证码错误");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 找回密码
|
/// 找回密码
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -284,6 +184,11 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
[UnitOfWork]
|
[UnitOfWork]
|
||||||
public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input)
|
public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input)
|
||||||
{
|
{
|
||||||
|
if (_rbacOptions.CaptchaType == CaptchaTypeEnum.Email)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("当前模式,不允许手机号找回密码,请联系管理员");
|
||||||
|
}
|
||||||
|
|
||||||
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
||||||
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
|
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
|
||||||
|
|
||||||
@@ -298,7 +203,6 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
return entity.UserName;
|
return entity.UserName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册,需要验证码通过
|
/// 注册,需要验证码通过
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -313,9 +217,9 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
throw new UserFriendlyException("该系统暂未开放注册功能");
|
throw new UserFriendlyException("该系统暂未开放注册功能");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.Phone is null)
|
if (input.Phone is null && input.Email is null)
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException("手机号不能为空");
|
throw new UserFriendlyException("手机号和邮箱不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
//临时账号
|
//临时账号
|
||||||
@@ -326,12 +230,23 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
|
|
||||||
if (_rbacOptions.EnableCaptcha)
|
if (_rbacOptions.EnableCaptcha)
|
||||||
{
|
{
|
||||||
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
switch (_rbacOptions.CaptchaType)
|
||||||
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.Register, input.Phone.Value, input.Code);
|
{
|
||||||
|
case CaptchaTypeEnum.Phone:
|
||||||
|
//校验验证码,根据电话号码获取 value,比对验证码已经uuid
|
||||||
|
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.Register, input.Phone.Value,
|
||||||
|
input.Code);
|
||||||
|
break;
|
||||||
|
case CaptchaTypeEnum.Email:
|
||||||
|
//校验验证码,根据电子邮箱获取 value,比对验证码已经uuid
|
||||||
|
await ValidationEmailCaptchaAsync(ValidationEmailTypeEnum.Register, input.Email, input.Code);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//注册之后,免再次登录,直接给前端token
|
//注册之后,免再次登录,直接给前端token
|
||||||
var userId = await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Nick);
|
var userId = await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Email,
|
||||||
|
input.Nick);
|
||||||
return await this.PostLoginAsync(userId);
|
return await this.PostLoginAsync(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +259,7 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
public async Task PostTempRegisterAsync(RegisterDto input)
|
public async Task PostTempRegisterAsync(RegisterDto input)
|
||||||
{
|
{
|
||||||
//注册领域逻辑
|
//注册领域逻辑
|
||||||
await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Nick);
|
await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Email, input.Nick);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -520,5 +435,241 @@ namespace Yi.Framework.Rbac.Application.Services
|
|||||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false);
|
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region 手机短信相关
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证电话号码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="phone"></param>
|
||||||
|
private async Task ValidationPhone(string phone)
|
||||||
|
{
|
||||||
|
var res = Regex.IsMatch(phone, @"^\d{11}$");
|
||||||
|
if (res == false)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("手机号码格式错误!请检查");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手机验证码-注册
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-phone/register")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手机验证码-找回密码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-phone/repassword")]
|
||||||
|
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手机验证码-绑定
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-phone/bind")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<object> PostCaptchaPhoneForBindAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Bind, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 手机验证码-内部使用-需通过图形验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[RemoteService(isEnabled: false)]
|
||||||
|
private async Task<object> InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
|
||||||
|
PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
//验证uuid 和 验证码
|
||||||
|
ValidationImageCaptcha(input.Uuid, input.Code);
|
||||||
|
|
||||||
|
await ValidationPhone(input.Phone);
|
||||||
|
|
||||||
|
if (validationPhoneType == ValidationPhoneTypeEnum.Register &&
|
||||||
|
await _userRepository.IsAnyAsync(x => x.Phone.ToString() == input.Phone))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("该手机号已被注册!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone));
|
||||||
|
|
||||||
|
//防止暴刷
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException($"{input.Phone}已发送过验证码,10分钟后可重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成一个4位数的验证码
|
||||||
|
//发送短信,同时生成uuid
|
||||||
|
////key: 电话号码 value:验证码+uuid
|
||||||
|
var code = Guid.NewGuid().ToString().Substring(0, 4);
|
||||||
|
var uuid = Guid.NewGuid();
|
||||||
|
await _aliyunManger.SendSmsAsync(input.Phone, code);
|
||||||
|
|
||||||
|
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
|
||||||
|
new CaptchaPhoneCacheItem(code),
|
||||||
|
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Uuid = uuid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验电话验证码,需要与电话号码绑定
|
||||||
|
/// </summary>
|
||||||
|
public async Task ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum validationPhoneType, long phone,
|
||||||
|
string code)
|
||||||
|
{
|
||||||
|
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
|
||||||
|
if (item is not null && item.Code.Equals($"{code}"))
|
||||||
|
{
|
||||||
|
//成功,需要清空
|
||||||
|
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 电子邮箱相关
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证电子邮箱
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="email"></param>
|
||||||
|
private async Task ValidationEmail(string email)
|
||||||
|
{
|
||||||
|
// 简单邮箱正则表达式
|
||||||
|
var res = Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
|
||||||
|
if (res == false)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("邮箱格式错误!请检查");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮箱验证码-注册
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-email/register")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<object> PostCaptchaEmailForRegisterAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.Register, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮箱验证码-找回密码
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-email/repassword")]
|
||||||
|
public async Task<object> PostCaptchaEmailForRetrievePasswordAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.RetrievePassword, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮箱验证码-绑定
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("account/captcha-email/bind")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<object> PostCaptchaEmailForBindAsync(PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.Bind, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮箱验证码-内部使用-需通过图形验证码
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[RemoteService(isEnabled: false)]
|
||||||
|
private async Task<object> InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum validationEmailType,
|
||||||
|
PhoneCaptchaImageDto input)
|
||||||
|
{
|
||||||
|
//验证uuid 和 验证码
|
||||||
|
ValidationImageCaptcha(input.Uuid, input.Code);
|
||||||
|
|
||||||
|
await ValidationEmail(input.Email);
|
||||||
|
|
||||||
|
if (validationEmailType == ValidationEmailTypeEnum.Register)
|
||||||
|
{
|
||||||
|
//处理大小写问题
|
||||||
|
var emailOrNull = await _userRepository._DbQueryable.Where(x => x.Email == input.Email)
|
||||||
|
.Select(x => x.Email)
|
||||||
|
.FirstAsync();
|
||||||
|
if (emailOrNull is not null && emailOrNull.Equals(input.Email))
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException("该邮箱已被注册!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = await _emailCache.GetAsync(new CaptchaEmailCacheKey(validationEmailType, input.Email));
|
||||||
|
|
||||||
|
//防止暴刷
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
throw new UserFriendlyException($"{input.Email}已发送过验证码,10分钟后可重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成一个4位数的验证码
|
||||||
|
//发送邮箱,同时生成uuid
|
||||||
|
////key: 邮箱 value:验证码+uuid
|
||||||
|
var code = Guid.NewGuid().ToString().Substring(0, 4);
|
||||||
|
var uuid = Guid.NewGuid();
|
||||||
|
//await _aliyunManger.SendSmsAsync(input.Phone, code);
|
||||||
|
//发送邮件
|
||||||
|
await _emailSender.SendAsync(input.Email,
|
||||||
|
"意社区官方邮件",
|
||||||
|
$"欢迎加入我们,您的验证码为 {code} ,该验证码10分钟内有效,请勿泄露于他人。");
|
||||||
|
|
||||||
|
await _emailCache.SetAsync(new CaptchaEmailCacheKey(validationEmailType, input.Email),
|
||||||
|
new CaptchaEmailCacheItem(code),
|
||||||
|
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Uuid = uuid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验电子邮箱验证码,需要与电子邮箱绑定
|
||||||
|
/// </summary>
|
||||||
|
public async Task ValidationEmailCaptchaAsync(ValidationEmailTypeEnum validationEmailType, string email,
|
||||||
|
string code)
|
||||||
|
{
|
||||||
|
var item = await _emailCache.GetAsync(new CaptchaEmailCacheKey(validationEmailType, email.ToString()));
|
||||||
|
if (item is not null && item.Code.Equals($"{code}"))
|
||||||
|
{
|
||||||
|
//成功,需要清空
|
||||||
|
await _emailCache.RemoveAsync(new CaptchaEmailCacheKey(validationEmailType, email.ToString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UserFriendlyException("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
|
|||||||
await _repository.UpdateAsync(entity);
|
await _repository.UpdateAsync(entity);
|
||||||
var dto = await MapToGetOutputDtoAsync(entity);
|
var dto = await MapToGetOutputDtoAsync(entity);
|
||||||
//发布更新昵称任务事件
|
//发布更新昵称任务事件
|
||||||
if (input.Nick != entity.Icon)
|
if (input.Nick != entity.Nick)
|
||||||
{
|
{
|
||||||
await this.LocalEventBus.PublishAsync(
|
await this.LocalEventBus.PublishAsync(
|
||||||
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateNick, _currentUser.GetId(), input.Nick),
|
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateNick, _currentUser.GetId(), input.Nick),
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
namespace Yi.Framework.Rbac.Domain.Shared.Caches
|
||||||
|
{
|
||||||
|
public class CaptchaEmailCacheItem
|
||||||
|
{
|
||||||
|
public CaptchaEmailCacheItem(string code)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Code { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CaptchaEmailCacheKey
|
||||||
|
{
|
||||||
|
public CaptchaEmailCacheKey(ValidationEmailTypeEnum validationPhoneType, string email)
|
||||||
|
{
|
||||||
|
Email = email;
|
||||||
|
ValidationEmailType = validationPhoneType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationEmailTypeEnum ValidationEmailType { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Email:{ValidationEmailType.ToString()}:{Email}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Yi.Framework.Rbac.Domain.Shared.Enums;
|
||||||
|
|
||||||
|
public enum ValidationEmailTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 注册
|
||||||
|
/// </summary>
|
||||||
|
Register,
|
||||||
|
/// <summary>
|
||||||
|
/// 忘记密码
|
||||||
|
/// </summary>
|
||||||
|
RetrievePassword,
|
||||||
|
/// <summary>
|
||||||
|
/// 绑定
|
||||||
|
/// </summary>
|
||||||
|
Bind
|
||||||
|
}
|
||||||
@@ -23,6 +23,11 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableCaptcha { get; set; } = false;
|
public bool EnableCaptcha { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证类型
|
||||||
|
/// </summary>
|
||||||
|
public CaptchaTypeEnum CaptchaType { get; set; } = CaptchaTypeEnum.Phone;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否开启用户注册功能
|
/// 是否开启用户注册功能
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -33,4 +38,20 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableDataBaseBackup { get; set; } = false;
|
public bool EnableDataBaseBackup { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证类型
|
||||||
|
/// </summary>
|
||||||
|
public enum CaptchaTypeEnum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 手机号
|
||||||
|
/// </summary>
|
||||||
|
Phone = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 邮箱
|
||||||
|
/// </summary>
|
||||||
|
Email = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,15 @@ namespace Yi.Framework.Rbac.Domain.Entities
|
|||||||
{
|
{
|
||||||
public UserAggregateRoot()
|
public UserAggregateRoot()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public UserAggregateRoot(string userName, string password, long? phone, string? nick = null)
|
|
||||||
|
public UserAggregateRoot(string userName, string password, long? phone, string? email, string? nick = null)
|
||||||
{
|
{
|
||||||
UserName = userName;
|
UserName = userName;
|
||||||
EncryPassword.Password = password;
|
EncryPassword.Password = password;
|
||||||
Phone = phone;
|
Phone = phone;
|
||||||
Nick =string.IsNullOrWhiteSpace(nick)?"萌新-"+userName:nick.Trim();
|
Email = email;
|
||||||
|
Nick = string.IsNullOrWhiteSpace(nick) ? "萌新-" + userName : nick.Trim();
|
||||||
BuildPassword();
|
BuildPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +186,10 @@ namespace Yi.Framework.Rbac.Domain.Entities
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(EncryPassword.Password));
|
throw new ArgumentNullException(nameof(EncryPassword.Password));
|
||||||
}
|
}
|
||||||
|
|
||||||
password = EncryPassword.Password;
|
password = EncryPassword.Password;
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryPassword.Salt = MD5Helper.GenerateSalt();
|
EncryPassword.Salt = MD5Helper.GenerateSalt();
|
||||||
EncryPassword.Password = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
|
EncryPassword.Password = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
|
||||||
return this;
|
return this;
|
||||||
@@ -203,14 +206,14 @@ namespace Yi.Framework.Rbac.Domain.Entities
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(EncryPassword.Salt);
|
throw new ArgumentNullException(EncryPassword.Salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
|
var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
|
||||||
if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt))
|
if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ using Yi.Framework.SqlSugarCore.Abstractions;
|
|||||||
|
|
||||||
namespace Yi.Framework.Rbac.Domain.Managers
|
namespace Yi.Framework.Rbac.Domain.Managers
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户领域服务
|
/// 用户领域服务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -62,7 +61,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
/// <param name="getUserInfo"></param>
|
/// <param name="getUserInfo"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="UserFriendlyException"></exception>
|
/// <exception cref="UserFriendlyException"></exception>
|
||||||
public async Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null)
|
public async Task<string> GetTokenByUserIdAsync(Guid userId, Action<UserRoleMenuDto>? getUserInfo = null)
|
||||||
{
|
{
|
||||||
//获取用户信息
|
//获取用户信息
|
||||||
var userInfo = await _userManager.GetInfoAsync(userId);
|
var userInfo = await _userManager.GetInfoAsync(userId);
|
||||||
@@ -77,6 +76,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
throw new UserFriendlyException(UserConst.No_Role);
|
throw new UserFriendlyException(UserConst.No_Role);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userInfo.PermissionCodes.Any())
|
if (!userInfo.PermissionCodes.Any())
|
||||||
{
|
{
|
||||||
throw new UserFriendlyException(UserConst.No_Permission);
|
throw new UserFriendlyException(UserConst.No_Permission);
|
||||||
@@ -86,7 +86,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
getUserInfo(userInfo);
|
getUserInfo(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessToken = CreateToken(this.UserInfoToClaim(userInfo));
|
var accessToken = CreateToken(this.UserInfoToClaim(userInfo));
|
||||||
//将用户信息添加到缓存中,需要考虑的是更改了用户、角色、菜单等整个体系都需要将缓存进行刷新,看具体业务进行选择
|
//将用户信息添加到缓存中,需要考虑的是更改了用户、角色、菜单等整个体系都需要将缓存进行刷新,看具体业务进行选择
|
||||||
return accessToken;
|
return accessToken;
|
||||||
@@ -103,12 +103,12 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
var claims = kvs.Select(x => new Claim(x.Key, x.Value.ToString())).ToList();
|
var claims = kvs.Select(x => new Claim(x.Key, x.Value.ToString())).ToList();
|
||||||
var token = new JwtSecurityToken(
|
var token = new JwtSecurityToken(
|
||||||
issuer: _jwtOptions.Issuer,
|
issuer: _jwtOptions.Issuer,
|
||||||
audience: _jwtOptions.Audience,
|
audience: _jwtOptions.Audience,
|
||||||
claims: claims,
|
claims: claims,
|
||||||
expires: DateTime.Now.AddSeconds(_jwtOptions.ExpiresSecondTime),
|
expires: DateTime.Now.AddSeconds(_jwtOptions.ExpiresSecondTime),
|
||||||
notBefore: DateTime.Now,
|
notBefore: DateTime.Now,
|
||||||
signingCredentials: creds);
|
signingCredentials: creds);
|
||||||
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
|
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
|
||||||
return returnToken;
|
return returnToken;
|
||||||
@@ -119,22 +119,23 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_refreshJwtOptions.SecurityKey));
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_refreshJwtOptions.SecurityKey));
|
||||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
//添加用户id,及刷新token的标识
|
//添加用户id,及刷新token的标识
|
||||||
var claims = new List<Claim> {
|
var claims = new List<Claim>
|
||||||
new Claim(AbpClaimTypes.UserId,userId.ToString()),
|
{
|
||||||
|
new Claim(AbpClaimTypes.UserId, userId.ToString()),
|
||||||
new Claim(TokenTypeConst.Refresh, "true")
|
new Claim(TokenTypeConst.Refresh, "true")
|
||||||
};
|
};
|
||||||
var token = new JwtSecurityToken(
|
var token = new JwtSecurityToken(
|
||||||
issuer: _refreshJwtOptions.Issuer,
|
issuer: _refreshJwtOptions.Issuer,
|
||||||
audience: _refreshJwtOptions.Audience,
|
audience: _refreshJwtOptions.Audience,
|
||||||
claims: claims,
|
claims: claims,
|
||||||
expires: DateTime.Now.AddSeconds(_refreshJwtOptions.ExpiresSecondTime),
|
expires: DateTime.Now.AddSeconds(_refreshJwtOptions.ExpiresSecondTime),
|
||||||
notBefore: DateTime.Now,
|
notBefore: DateTime.Now,
|
||||||
signingCredentials: creds);
|
signingCredentials: creds);
|
||||||
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
|
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
|
||||||
return returnToken;
|
return returnToken;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 登录校验
|
/// 登录校验
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -142,7 +143,8 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
/// <param name="password"></param>
|
/// <param name="password"></param>
|
||||||
/// <param name="userAction"></param>
|
/// <param name="userAction"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot> userAction = null)
|
public async Task LoginValidationAsync(string userName, string password,
|
||||||
|
Action<UserAggregateRoot> userAction = null)
|
||||||
{
|
{
|
||||||
var user = new UserAggregateRoot();
|
var user = new UserAggregateRoot();
|
||||||
if (await ExistAsync(userName, o => user = o))
|
if (await ExistAsync(userName, o => user = o))
|
||||||
@@ -151,12 +153,15 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
userAction.Invoke(user);
|
userAction.Invoke(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.EncryPassword.Password == MD5Helper.SHA2Encode(password, user.EncryPassword.Salt))
|
if (user.EncryPassword.Password == MD5Helper.SHA2Encode(password, user.EncryPassword.Salt))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UserFriendlyException(UserConst.Login_Error);
|
throw new UserFriendlyException(UserConst.Login_Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UserFriendlyException(UserConst.Login_User_No_Exist);
|
throw new UserFriendlyException(UserConst.Login_User_No_Exist);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,22 +178,22 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
userAction.Invoke(user);
|
userAction.Invoke(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
//这里为了兼容解决数据库开启了大小写不敏感问题,还要将用户名进行二次校验
|
//这里为了兼容解决数据库开启了大小写不敏感问题,还要将用户名进行二次校验
|
||||||
if (user != null && user.UserName == userName)
|
if (user != null && user.UserName == userName)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 令牌转换
|
/// 令牌转换
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto"></param>
|
/// <param name="dto"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|
||||||
public List<KeyValuePair<string, string>> UserInfoToClaim(UserRoleMenuDto dto)
|
public List<KeyValuePair<string, string>> UserInfoToClaim(UserRoleMenuDto dto)
|
||||||
{
|
{
|
||||||
var claims = new List<KeyValuePair<string, string>>();
|
var claims = new List<KeyValuePair<string, string>>();
|
||||||
@@ -198,18 +203,24 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
AddToClaim(claims, TokenTypeConst.DeptId, dto.User.DeptId.ToString());
|
AddToClaim(claims, TokenTypeConst.DeptId, dto.User.DeptId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.User.Email is not null)
|
if (dto.User.Email is not null)
|
||||||
{
|
{
|
||||||
AddToClaim(claims, AbpClaimTypes.Email, dto.User.Email);
|
AddToClaim(claims, AbpClaimTypes.Email, dto.User.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.User.Phone is not null)
|
if (dto.User.Phone is not null)
|
||||||
{
|
{
|
||||||
AddToClaim(claims, AbpClaimTypes.PhoneNumber, dto.User.Phone.ToString());
|
AddToClaim(claims, AbpClaimTypes.PhoneNumber, dto.User.Phone.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.Roles.Count > 0)
|
if (dto.Roles.Count > 0)
|
||||||
{
|
{
|
||||||
AddToClaim(claims, TokenTypeConst.RoleInfo, JsonConvert.SerializeObject(dto.Roles.Select(x => new RoleTokenInfoModel { Id = x.Id, DataScope = x.DataScope })));
|
AddToClaim(claims, TokenTypeConst.RoleInfo,
|
||||||
|
JsonConvert.SerializeObject(dto.Roles.Select(x => new RoleTokenInfoModel
|
||||||
|
{ Id = x.Id, DataScope = x.DataScope })));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UserConst.Admin.Equals(dto.User.UserName))
|
if (UserConst.Admin.Equals(dto.User.UserName))
|
||||||
{
|
{
|
||||||
AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode);
|
AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode);
|
||||||
@@ -246,6 +257,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
{
|
{
|
||||||
throw new UserFriendlyException("无效更新!原密码错误!");
|
throw new UserFriendlyException("无效更新!原密码错误!");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.EncryPassword.Password = newPassword;
|
user.EncryPassword.Password = newPassword;
|
||||||
user.BuildPassword();
|
user.BuildPassword();
|
||||||
await _repository.UpdateAsync(user);
|
await _repository.UpdateAsync(user);
|
||||||
@@ -271,14 +283,21 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
/// <param name="userName"></param>
|
/// <param name="userName"></param>
|
||||||
/// <param name="password"></param>
|
/// <param name="password"></param>
|
||||||
/// <param name="phone"></param>
|
/// <param name="phone"></param>
|
||||||
|
/// <param name="email"></param>
|
||||||
|
/// <param name="nick"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<Guid> RegisterAsync(string userName, string password, long? phone,string? nick)
|
public async Task<Guid> RegisterAsync(string userName, string password, long? phone, string? email,
|
||||||
|
string? nick)
|
||||||
{
|
{
|
||||||
var user = new UserAggregateRoot(userName, password, phone,nick);
|
if (phone is null && string.IsNullOrWhiteSpace(email))
|
||||||
var userId=await _userManager.CreateAsync(user);
|
{
|
||||||
|
throw new UserFriendlyException("注册时,电话与邮箱不能同时为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = new UserAggregateRoot(userName, password, phone, email, nick);
|
||||||
|
var userId = await _userManager.CreateAsync(user);
|
||||||
await _userManager.SetDefautRoleAsync(user.Id);
|
await _userManager.SetDefautRoleAsync(user.Id);
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
|
|||||||
string CreateRefreshToken(Guid userId);
|
string CreateRefreshToken(Guid userId);
|
||||||
Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null);
|
Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null);
|
||||||
Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot>? userAction = null);
|
Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot>? userAction = null);
|
||||||
Task<Guid> RegisterAsync(string userName, string password, long? phone,string? nick);
|
Task<Guid> RegisterAsync(string userName, string password, long? phone, string? email, string? nick);
|
||||||
Task<bool> RestPasswordAsync(Guid userId, string password);
|
Task<bool> RestPasswordAsync(Guid userId, string password);
|
||||||
Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword);
|
Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
|
||||||
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" />
|
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" />
|
||||||
|
<PackageReference Include="Volo.Abp.MailKit" Version="$(AbpVersion)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj" />
|
<ProjectReference Include="..\..\..\framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj" />
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ using Volo.Abp.AspNetCore.SignalR;
|
|||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.DistributedLocking;
|
using Volo.Abp.DistributedLocking;
|
||||||
using Volo.Abp.Domain;
|
using Volo.Abp.Domain;
|
||||||
|
using Volo.Abp.Emailing;
|
||||||
using Volo.Abp.Imaging;
|
using Volo.Abp.Imaging;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.MailKit;
|
||||||
using Yi.Framework.Caching.FreeRedis;
|
using Yi.Framework.Caching.FreeRedis;
|
||||||
using Yi.Framework.Mapster;
|
|
||||||
using Yi.Framework.Rbac.Domain.Authorization;
|
using Yi.Framework.Rbac.Domain.Authorization;
|
||||||
using Yi.Framework.Rbac.Domain.Operlog;
|
using Yi.Framework.Rbac.Domain.Operlog;
|
||||||
using Yi.Framework.Rbac.Domain.Shared;
|
using Yi.Framework.Rbac.Domain.Shared;
|
||||||
@@ -26,7 +26,8 @@ namespace Yi.Framework.Rbac.Domain
|
|||||||
typeof(AbpDddDomainModule),
|
typeof(AbpDddDomainModule),
|
||||||
typeof(AbpCachingModule),
|
typeof(AbpCachingModule),
|
||||||
typeof(AbpImagingImageSharpModule),
|
typeof(AbpImagingImageSharpModule),
|
||||||
typeof(AbpDistributedLockingModule)
|
typeof(AbpDistributedLockingModule),
|
||||||
|
typeof(AbpMailKitModule)
|
||||||
)]
|
)]
|
||||||
public class YiFrameworkRbacDomainModule : AbpModule
|
public class YiFrameworkRbacDomainModule : AbpModule
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,17 @@
|
|||||||
},
|
},
|
||||||
//配置
|
//配置
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"Test": "hello"
|
"Test": "hello",
|
||||||
|
//邮箱
|
||||||
|
"Abp.Mailing.Smtp.Host": "127.0.0.1",
|
||||||
|
"Abp.Mailing.Smtp.Port": "25",
|
||||||
|
"Abp.Mailing.Smtp.UserName": "",
|
||||||
|
"Abp.Mailing.Smtp.Password": "",
|
||||||
|
"Abp.Mailing.Smtp.Domain": "",
|
||||||
|
"Abp.Mailing.Smtp.EnableSsl": "false",
|
||||||
|
"Abp.Mailing.Smtp.UseDefaultCredentials": "true",
|
||||||
|
"Abp.Mailing.DefaultFromAddress": "noreply@abp.io",
|
||||||
|
"Abp.Mailing.DefaultFromDisplayName": "ABP application"
|
||||||
},
|
},
|
||||||
//数据库类型列表
|
//数据库类型列表
|
||||||
"DbList": [
|
"DbList": [
|
||||||
@@ -95,6 +105,8 @@
|
|||||||
"AdminPassword": "123456",
|
"AdminPassword": "123456",
|
||||||
//是否开启验证码验证
|
//是否开启验证码验证
|
||||||
"EnableCaptcha": true,
|
"EnableCaptcha": true,
|
||||||
|
//验证类型:email/Phone
|
||||||
|
"CaptchaType": "Email",
|
||||||
//是否开启注册功能
|
//是否开启注册功能
|
||||||
"EnableRegister": false,
|
"EnableRegister": false,
|
||||||
//开启定时数据库备份
|
//开启定时数据库备份
|
||||||
@@ -107,15 +119,5 @@
|
|||||||
],
|
],
|
||||||
"Endpoint": "https://xxx.com/v1",
|
"Endpoint": "https://xxx.com/v1",
|
||||||
"ApiKey": "sk-xxxxxx"
|
"ApiKey": "sk-xxxxxx"
|
||||||
},
|
|
||||||
//AI网关
|
|
||||||
"AiGateWay": {
|
|
||||||
"Chats": {
|
|
||||||
"AzureChatService": {
|
|
||||||
"ModelIds": ["gpt-4o"],
|
|
||||||
"Endpoint": "https://xxx.com/v1",
|
|
||||||
"ApiKey": "sk-xxxxxx"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,19 @@ export function getCodeImg() {
|
|||||||
// 获取短信验证码
|
// 获取短信验证码
|
||||||
export function getCodePhone(phoneForm) {
|
export function getCodePhone(phoneForm) {
|
||||||
return request({
|
return request({
|
||||||
url: "/account/captcha-phone",
|
url: "/account/captcha-phone/register",
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
},
|
||||||
|
method: "post",
|
||||||
|
timeout: 20000,
|
||||||
|
data: phoneForm,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 获取邮箱验证码
|
||||||
|
export function getCodeEmail(phoneForm) {
|
||||||
|
return request({
|
||||||
|
url: "/account/captcha-email/register",
|
||||||
headers: {
|
headers: {
|
||||||
isToken: false,
|
isToken: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function userLogout() {
|
|||||||
*/
|
*/
|
||||||
export function getCodePhone(data) {
|
export function getCodePhone(data) {
|
||||||
return request({
|
return request({
|
||||||
url: `/account/captcha-phone`,
|
url: `/account/captcha-phone/register`,
|
||||||
method: "post",
|
method: "post",
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -234,7 +234,8 @@ const enterStart = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enterTemp=()=>{
|
const enterTemp=()=>{
|
||||||
alert("即将上线!意社区-高质量ai平台,一心一意只为打造更良心的ai平台")
|
alert("默认访问海外意心ai节点,受网络影响,首次加载静态资源需要一定时间")
|
||||||
|
window.location.href = 'https://yxai.chat';
|
||||||
//router.push("/discuss/24cc0526-86e7-aabf-e091-3a0f83c3e604/false");
|
//router.push("/discuss/24cc0526-86e7-aabf-e091-3a0f83c3e604/false");
|
||||||
}
|
}
|
||||||
const enterShop=()=>{
|
const enterShop=()=>{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
// 注册逻辑
|
// 注册逻辑
|
||||||
import {computed, reactive, ref} from "vue";
|
import {computed, reactive, ref} from "vue";
|
||||||
import {getCodePhone} from "@/apis/accountApi";
|
import {getCodeEmail} from "@/apis/accountApi";
|
||||||
import useAuths from "@/hooks/useAuths";
|
import useAuths from "@/hooks/useAuths";
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import useUserStore from "@/stores/user";
|
import useUserStore from "@/stores/user";
|
||||||
@@ -20,15 +20,16 @@ const codeUUid = computed(() => useUserStore().codeUUid);
|
|||||||
const passwordConfirm = ref("");
|
const passwordConfirm = ref("");
|
||||||
const registerForm = reactive({
|
const registerForm = reactive({
|
||||||
userName: "",
|
userName: "",
|
||||||
phone: "",
|
//phone: "",
|
||||||
password: "",
|
password: "",
|
||||||
uuid: "",
|
uuid: "",
|
||||||
code: "",
|
code: "",
|
||||||
nick:""
|
nick:"",
|
||||||
|
email: "",
|
||||||
});
|
});
|
||||||
const phoneForm=reactive({
|
const phoneForm=reactive({
|
||||||
code:"",
|
code:"",
|
||||||
phone:"",
|
email:"",
|
||||||
uuid:codeUUid
|
uuid:codeUUid
|
||||||
});
|
});
|
||||||
const registerRules = reactive({
|
const registerRules = reactive({
|
||||||
@@ -39,7 +40,7 @@ const registerRules = reactive({
|
|||||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||||
{ min: 2, message: "用户名需大于两位", trigger: "blur" },
|
{ min: 2, message: "用户名需大于两位", trigger: "blur" },
|
||||||
],
|
],
|
||||||
phone: [{ required: true, message: "请输入手机号", trigger: "blur" }],
|
email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
|
||||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: "请输入新的密码", trigger: "blur" },
|
{ required: true, message: "请输入新的密码", trigger: "blur" },
|
||||||
@@ -52,7 +53,7 @@ const register = async (formEl) => {
|
|||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
try {
|
||||||
if (registerForm.password != passwordConfirm.value) {
|
if (registerForm.password !== passwordConfirm.value) {
|
||||||
ElMessage.error("两次密码输入不一致");
|
ElMessage.error("两次密码输入不一致");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ const register = async (formEl) => {
|
|||||||
|
|
||||||
|
|
||||||
//验证码
|
//验证码
|
||||||
const codeInfo = ref("发送短信");
|
const codeInfo = ref("发送邮箱");
|
||||||
const isDisabledCode = ref(false);
|
const isDisabledCode = ref(false);
|
||||||
|
|
||||||
//点击验证码
|
//点击验证码
|
||||||
@@ -80,9 +81,9 @@ const handleGetCodeImage=()=>{
|
|||||||
useUserStore().updateCodeImage();
|
useUserStore().updateCodeImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
//点击手机发送短信
|
//点击手机发送邮箱
|
||||||
const clickPhoneCaptcha=()=>{
|
const clickPhoneCaptcha=()=>{
|
||||||
if (registerForm.phone !== "")
|
if (registerForm.email !== "")
|
||||||
{
|
{
|
||||||
isDisabledCode.value=true;
|
isDisabledCode.value=true;
|
||||||
handleGetCodeImage();
|
handleGetCodeImage();
|
||||||
@@ -90,7 +91,7 @@ const clickPhoneCaptcha=()=>{
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: `请先输入手机号`,
|
message: `请先输入邮箱`,
|
||||||
type: "warning",
|
type: "warning",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -101,14 +102,14 @@ const handleSignInNow=()=>{
|
|||||||
router.push("/login");
|
router.push("/login");
|
||||||
}
|
}
|
||||||
const captcha = async () => {
|
const captcha = async () => {
|
||||||
if (registerForm.phone!==""&&phoneForm.code!=="")
|
if (registerForm.email!==""&&phoneForm.code!=="")
|
||||||
{
|
{
|
||||||
phoneForm.phone=registerForm.phone;
|
phoneForm.email=registerForm.email;
|
||||||
const { data } = await getCodePhone(phoneForm);
|
const { data } = await getCodeEmail(phoneForm);
|
||||||
registerForm.uuid = data.uuid;
|
registerForm.uuid = data.uuid;
|
||||||
codeDialogVisible.value=false;
|
codeDialogVisible.value=false;
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: `已向${registerForm.phone}发送验证码,请注意查收`,
|
message: `已向${registerForm.email}发送验证码,请注意查收`,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
isDisabledCode.value = true;
|
isDisabledCode.value = true;
|
||||||
@@ -171,16 +172,16 @@ const captcha = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="input" style="margin-top: 0">
|
<div class="input" style="margin-top: 0">
|
||||||
<p>*电话</p>
|
<p>*邮箱</p>
|
||||||
<el-form-item prop="phone">
|
<el-form-item prop="email">
|
||||||
<div class="phone-code">
|
<div class="phone-code">
|
||||||
<input class="phone-code-input" type="text" v-model.trim="registerForm.phone">
|
<input class="phone-code-input" type="text" v-model.trim="registerForm.email">
|
||||||
<button type="button" class="phone-code-btn" @click="clickPhoneCaptcha()">{{codeInfo}}</button>
|
<button type="button" class="phone-code-btn" @click="clickPhoneCaptcha()">{{codeInfo}}</button>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<p>*短信验证码</p>
|
<p>*邮箱验证码</p>
|
||||||
<el-form-item prop="code" >
|
<el-form-item prop="code" >
|
||||||
<input :disabled="!isDisabledCode" type="text" v-model.trim="registerForm.code">
|
<input :disabled="!isDisabledCode" type="text" v-model.trim="registerForm.code">
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -211,7 +212,7 @@ const captcha = async () => {
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="codeDialogVisible"
|
v-model="codeDialogVisible"
|
||||||
title="发送短信"
|
title="发送邮箱"
|
||||||
width="500"
|
width="500"
|
||||||
center
|
center
|
||||||
>
|
>
|
||||||
@@ -223,7 +224,8 @@ const captcha = async () => {
|
|||||||
<el-form-item prop="code" >
|
<el-form-item prop="code" >
|
||||||
<input type="text" v-model.trim="phoneForm.code">
|
<input type="text" v-model.trim="phoneForm.code">
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<p style="color: red">由于国内短信严格程度在2025年5月连续加强3次,你的验证码有一定概率被运营商拦截</p>
|
<!-- <p style="color: red">由于国内短信严格程度在2025年5月连续加强3次,你的验证码有一定概率被运营商拦截</p>-->
|
||||||
|
<p style="color: red">请检查你的邮箱垃圾箱,可能在那</p>
|
||||||
<p style="color: red">如果未收到验证码,请联系微信chengzilaoge520 站长进行手动创建</p>
|
<p style="color: red">如果未收到验证码,请联系微信chengzilaoge520 站长进行手动创建</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
<el-input v-model="user.nick" maxlength="30" :disabled="isDisable"/>
|
<el-input v-model="user.nick" maxlength="30" :disabled="isDisable"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="手机号码" prop="phone">
|
<el-form-item label="手机号码" prop="phone">
|
||||||
<el-input v-model="user.phone" maxlength="11" :disabled="isDisable" />
|
<el-input v-model="user.phone" maxlength="11" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邮箱" prop="email">
|
<el-form-item label="邮箱" prop="email">
|
||||||
<el-input v-model="user.email" maxlength="50" :disabled="isDisable" />
|
<el-input v-model="user.email" maxlength="50" disabled />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="性别">
|
<el-form-item label="性别">
|
||||||
<el-radio-group v-model="user.sex" :disabled="isDisable">
|
<el-radio-group v-model="user.sex" :disabled="isDisable">
|
||||||
@@ -46,22 +46,22 @@ const userRef = ref(null);
|
|||||||
|
|
||||||
const rules = ref({
|
const rules = ref({
|
||||||
nick: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
|
nick: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
|
||||||
email: [
|
// email: [
|
||||||
{ required: true, message: "邮箱地址不能为空", trigger: "blur" },
|
// { required: true, message: "邮箱地址不能为空", trigger: "blur" },
|
||||||
{
|
// {
|
||||||
type: "email",
|
// type: "email",
|
||||||
message: "请输入正确的邮箱地址",
|
// message: "请输入正确的邮箱地址",
|
||||||
trigger: ["blur", "change"],
|
// trigger: ["blur", "change"],
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
phone: [
|
// phone: [
|
||||||
{ required: true, message: "手机号码不能为空", trigger: "blur" },
|
// { required: true, message: "手机号码不能为空", trigger: "blur" },
|
||||||
{
|
// {
|
||||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
// pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||||
message: "请输入正确的手机号码",
|
// message: "请输入正确的手机号码",
|
||||||
trigger: "blur",
|
// trigger: "blur",
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 提交按钮 */
|
/** 提交按钮 */
|
||||||
|
|||||||
Reference in New Issue
Block a user