feat: 新增找回密码功能

This commit is contained in:
橙子
2024-10-04 00:00:44 +08:00
parent d7629763ef
commit 10e1fad7f3
21 changed files with 602 additions and 297 deletions

View File

@@ -9,11 +9,11 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public class DiscussGetListOutputDto : EntityDto<Guid> public class DiscussGetListOutputDto : EntityDto<Guid>
{ {
/// <summary> /// <summary>
/// <EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD><EFBFBD>۴<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> /// 是否禁止评论创建功能
/// </summary> /// </summary>
public bool IsDisableCreateComment { get; set; } public bool IsDisableCreateComment { get; set; }
/// <summary> /// <summary>
/// <EFBFBD>Ƿ<EFBFBD><EFBFBD>ѵ<EFBFBD><EFBFBD>ޣ<EFBFBD>Ĭ<EFBFBD><EFBFBD>δ<EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> /// 是否已点赞,默认未登录不点赞
/// </summary> /// </summary>
public bool IsAgree { get; set; } = false; public bool IsAgree { get; set; } = false;
public string Title { get; set; } public string Title { get; set; }
@@ -23,26 +23,26 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
public int AgreeNum { get; set; } public int AgreeNum { get; set; }
public int SeeNum { get; set; } public int SeeNum { get; set; }
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѯ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܿ<EFBFBD><EFBFBD><EFBFBD> //批量查询,不给内容,性能考虑
//public string Content { get; set; } //public string Content { get; set; }
public string? Color { get; set; } public string? Color { get; set; }
public Guid PlateId { get; set; } public Guid PlateId { get; set; }
//<EFBFBD>Ƿ<EFBFBD><EFBFBD>ö<EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD>false //是否置顶,默认false
public bool IsTop { get; set; } public bool IsTop { get; set; }
public DiscussPermissionTypeEnum PermissionType { get; set; } public DiscussPermissionTypeEnum PermissionType { get; set; }
//<EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD>false //是否禁止,默认false
public bool IsBan { get; set; } public bool IsBan { get; set; }
/// <summary> /// <summary>
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> /// 封面
/// </summary> /// </summary>
public string? Cover { get; set; } public string? Cover { get; set; }
//˽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ<EFBFBD>ж<EFBFBD>codeȨ<EFBFBD><EFBFBD> //私有需要判断code权限
public string? PrivateCode { get; set; } public string? PrivateCode { get; set; }
public DateTime CreationTime { get; set; } public DateTime CreationTime { get; set; }
@@ -55,7 +55,7 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
Title = DiscussConst.Privacy; Title = DiscussConst.Privacy;
Introduction = ""; Introduction = "";
Cover = null; Cover = null;
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ //被禁止
IsBan = true; IsBan = true;
} }
} }
@@ -73,14 +73,14 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
case DiscussPermissionTypeEnum.Public: case DiscussPermissionTypeEnum.Public:
break; break;
case DiscussPermissionTypeEnum.Oneself: case DiscussPermissionTypeEnum.Oneself:
//<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǽ<EFBFBD><EFBFBD>Լ<EFBFBD><EFBFBD>ɼ<EFBFBD><EFBFBD><EFBFBD>ͬʱ<EFBFBD><EFBFBD><EFBFBD>ǵ<EFBFBD>ǰ<EFBFBD><EFBFBD>¼<EFBFBD>û<EFBFBD> //当前主题是仅自己可见,同时不是当前登录用户
if (dto.User.Id != userId) if (dto.User.Id != userId)
{ {
dto.SetBan(); dto.SetBan();
} }
break; break;
case DiscussPermissionTypeEnum.User: case DiscussPermissionTypeEnum.User:
//<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD>ֿɼ<EFBFBD><EFBFBD><EFBFBD>ͬʱ<EFBFBD><EFBFBD><EFBFBD>ǵ<EFBFBD>ǰ<EFBFBD><EFBFBD>¼<EFBFBD>û<EFBFBD> Ҳ <20><><EFBFBD>ڿɼ<DABF><C9BC>û<EFBFBD><C3BB>б<EFBFBD><D0B1><EFBFBD> //当前主题为部分可见,同时不是当前登录用户 也 不在可见用户列表中
if (dto.User.Id != userId && !dto.PermissionUserIds.Contains(userId)) if (dto.User.Id != userId && !dto.PermissionUserIds.Contains(userId))
{ {
dto.SetBan(); dto.SetBan();

View File

@@ -17,9 +17,11 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
public class BbsForumAnalyseService : ApplicationService, IApplicationService public class BbsForumAnalyseService : ApplicationService, IApplicationService
{ {
private ForumManager _forumManager; private ForumManager _forumManager;
public BbsForumAnalyseService(ForumManager forumManager) private ISqlSugarRepository<AgreeEntity> _agreeRepository;
public BbsForumAnalyseService(ForumManager forumManager, ISqlSugarRepository<AgreeEntity> agreeRepository)
{ {
_forumManager = forumManager; _forumManager = forumManager;
_agreeRepository = agreeRepository;
} }
/// <summary> /// <summary>
@@ -38,7 +40,7 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
.Select((discuss, user, info) => new DiscussGetListOutputDto .Select((discuss, user, info) => new DiscussGetListOutputDto
{ {
Id = discuss.Id, Id = discuss.Id,
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(), // IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
User = new BbsUserGetListOutputDto() User = new BbsUserGetListOutputDto()
{ {
@@ -52,6 +54,26 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
}, true) }, true)
.ToPageListAsync(input.SkipCount, input.MaxResultCount); .ToPageListAsync(input.SkipCount, input.MaxResultCount);
var discussId = output.Select(x => x.Id);
//点赞字典key为主题idy为用户ids
var agreeDic =
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
//等级、是否点赞赋值
output?.ForEach(x =>
{
if (CurrentUser.Id is not null)
{
//默认fasle
if (agreeDic.TryGetValue(x.Id,out var userIds))
{
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
});
return output; return output;
} }

View File

@@ -29,19 +29,27 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <summary> /// <summary>
/// Discuss应用服务实现,用于参数校验、领域服务业务组合、日志记录、事务处理、账户信息 /// Discuss应用服务实现,用于参数校验、领域服务业务组合、日志记录、事务处理、账户信息
/// </summary> /// </summary>
public class DiscussService : YiCrudAppService<DiscussAggregateRoot, DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>, public class DiscussService : YiCrudAppService<DiscussAggregateRoot, DiscussGetOutputDto, DiscussGetListOutputDto,
IDiscussService Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>,
IDiscussService
{ {
private ISqlSugarRepository<DiscussTopEntity> _discussTopEntityRepository; private ISqlSugarRepository<DiscussTopEntity> _discussTopRepository;
private ISqlSugarRepository<AgreeEntity> _agreeRepository;
private BbsUserManager _bbsUserManager; private BbsUserManager _bbsUserManager;
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager, ISqlSugarRepository<DiscussTopEntity> discussTopEntityRepository, ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus) : base(forumManager._discussRepository)
public DiscussService(BbsUserManager bbsUserManager, ForumManager forumManager,
ISqlSugarRepository<DiscussTopEntity> discussTopRepository,
ISqlSugarRepository<PlateAggregateRoot> plateEntityRepository, ILocalEventBus localEventBus,
ISqlSugarRepository<AgreeEntity> agreeRepository) : base(forumManager._discussRepository)
{ {
_forumManager = forumManager; _forumManager = forumManager;
_plateEntityRepository = plateEntityRepository; _plateEntityRepository = plateEntityRepository;
_localEventBus = localEventBus; _localEventBus = localEventBus;
_discussTopEntityRepository = discussTopEntityRepository; _agreeRepository = agreeRepository;
_bbsUserManager=bbsUserManager; _discussTopRepository = discussTopRepository;
_bbsUserManager = bbsUserManager;
} }
private readonly ILocalEventBus _localEventBus; private readonly ILocalEventBus _localEventBus;
private ForumManager _forumManager { get; set; } private ForumManager _forumManager { get; set; }
@@ -49,8 +57,6 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; } private ISqlSugarRepository<PlateAggregateRoot> _plateEntityRepository { get; set; }
/// <summary> /// <summary>
/// 单查 /// 单查
/// </summary> /// </summary>
@@ -58,42 +64,43 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <returns></returns> /// <returns></returns>
public async override Task<DiscussGetOutputDto> GetAsync(Guid id) public async override Task<DiscussGetOutputDto> GetAsync(Guid id)
{ {
//查询主题发布 浏览主题 事件,浏览数+1 //查询主题发布 浏览主题 事件,浏览数+1
var item = await _forumManager._discussRepository._DbQueryable.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id) var item = await _forumManager._discussRepository._DbQueryable
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId) .LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
.LeftJoin<PlateAggregateRoot>((discuss, user, info, plate) => plate.Id == discuss.PlateId) .LeftJoin<PlateAggregateRoot>((discuss, user, info, plate) => plate.Id == discuss.PlateId)
.Select((discuss, user, info, plate) => new DiscussGetOutputDto .Select((discuss, user, info, plate) => new DiscussGetOutputDto
{ {
Id = discuss.Id, Id = discuss.Id,
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(), IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null,
User = new BbsUserGetListOutputDto() x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
{ User = new BbsUserGetListOutputDto()
UserName = user.UserName, {
Nick = user.Nick, UserName = user.UserName,
Icon = user.Icon, Nick = user.Nick,
Id = user.Id, Icon = user.Icon,
Level = info.Level, Id = user.Id,
UserLimit = info.UserLimit, Level = info.Level,
Money=info.Money, UserLimit = info.UserLimit,
Experience=info.Experience Money = info.Money,
}, Experience = info.Experience
Plate = new Contracts.Dtos.Plate.PlateGetOutputDto() },
{ Plate = new Contracts.Dtos.Plate.PlateGetOutputDto()
Name = plate.Name, {
Id = plate.Id, Name = plate.Name,
Code = plate.Code, Id = plate.Id,
Introduction = plate.Introduction, Code = plate.Code,
Logo = plate.Logo Introduction = plate.Introduction,
Logo = plate.Logo
} }
}, true) }, true)
.SingleAsync(discuss => discuss.Id == id); .SingleAsync(discuss => discuss.Id == id);
if (item is not null) if (item is not null)
{ {
await VerifyDiscussPermissionAsync(item.Id); await VerifyDiscussPermissionAsync(item.Id);
await _localEventBus.PublishAsync(new SeeDiscussEventArgs { DiscussId = item.Id, OldSeeNum = item.SeeNum }); await _localEventBus.PublishAsync(new SeeDiscussEventArgs
{ DiscussId = item.Id, OldSeeNum = item.SeeNum });
} }
return item; return item;
@@ -105,49 +112,65 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
public override async Task<PagedResultDto<DiscussGetListOutputDto>> GetListAsync([FromQuery] DiscussGetListInputVo input) public override async Task<PagedResultDto<DiscussGetListOutputDto>> GetListAsync(
[FromQuery] DiscussGetListInputVo input)
{ {
//需要关联创建者用户 //需要关联创建者用户
RefAsync<int> total = 0; RefAsync<int> total = 0;
var items = await _forumManager._discussRepository._DbQueryable var items = await _forumManager._discussRepository._DbQueryable
.WhereIF(!string.IsNullOrEmpty(input.Title), x => x.Title.Contains(input.Title)) .WhereIF(!string.IsNullOrEmpty(input.Title), x => x.Title.Contains(input.Title))
.WhereIF(input.PlateId is not null, x => x.PlateId == input.PlateId) .WhereIF(input.PlateId is not null, x => x.PlateId == input.PlateId)
.WhereIF(input.IsTop is not null, x => x.IsTop == input.IsTop) .WhereIF(input.IsTop is not null, x => x.IsTop == input.IsTop)
.WhereIF(input.UserId is not null,x=>x.CreatorId==input.UserId) .WhereIF(input.UserId is not null, x => x.CreatorId == input.UserId)
.LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id) .LeftJoin<UserAggregateRoot>((discuss, user) => discuss.CreatorId == user.Id)
.WhereIF(input.UserName is not null, (discuss, user)=>user.UserName==input.UserName!) .WhereIF(input.UserName is not null, (discuss, user) => user.UserName == input.UserName!)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId)
.LeftJoin<BbsUserExtraInfoEntity>((discuss, user, info) => user.Id == info.UserId) .OrderByDescending(discuss => discuss.OrderNum)
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc)
.OrderByDescending(discuss => discuss.OrderNum) .OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc) .OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc) .Select((discuss, user, info) => new DiscussGetListOutputDto
.OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc) {
Id = discuss.Id,
.Select((discuss, user, info) => new DiscussGetListOutputDto // 优化查询,不使用子查询
{ // IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
Id = discuss.Id, User = new BbsUserGetListOutputDto()
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(), {
Id = user.Id,
User = new BbsUserGetListOutputDto() UserName = user.UserName,
{ Nick = user.Nick,
Id = user.Id, Icon = user.Icon,
UserName = user.UserName, Level = info.Level,
Nick = user.Nick, UserLimit = info.UserLimit,
Icon = user.Icon, Money = info.Money,
Level = info.Level, Experience = info.Experience
UserLimit = info.UserLimit, }
Money = info.Money, }, true)
Experience = info.Experience
}
}, true)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total); .ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var discussId = items.Select(x => x.Id);
//点赞字典key为主题idy为用户ids
var agreeDic =
(await _agreeRepository._DbQueryable.Where(x => discussId.Contains(x.DiscussId)).ToListAsync())
.GroupBy(x => x.DiscussId)
.ToDictionary(x => x.Key, y => y.Select(y => y.CreatorId).ToList());
//查询完主题之后,要过滤一下私有的主题信息 //查询完主题之后,要过滤一下私有的主题信息
items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty); items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty);
items?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name); //等级、是否点赞赋值
items?.ForEach(x =>
{
x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name;
if (CurrentUser.Id is not null)
{
//默认fasle
if (agreeDic.TryGetValue(x.Id,out var userIds))
{
x.IsAgree = userIds.Contains(CurrentUser.Id);
}
}
});
return new PagedResultDto<DiscussGetListOutputDto>(total, items); return new PagedResultDto<DiscussGetListOutputDto>(total, items);
} }
@@ -157,14 +180,16 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
/// <returns></returns> /// <returns></returns>
public async Task<List<DiscussGetListOutputDto>> GetListTopAsync() public async Task<List<DiscussGetListOutputDto>> GetListTopAsync()
{ {
var output = await _discussTopEntityRepository._DbQueryable.LeftJoin<DiscussAggregateRoot>((top, discuss) => top.DiscussId == discuss.Id) var output = await _discussTopRepository._DbQueryable
.LeftJoin<DiscussAggregateRoot>((top, discuss) => top.DiscussId == discuss.Id)
.LeftJoin<UserAggregateRoot>((top, discuss, user) => discuss.CreatorId == user.Id) .LeftJoin<UserAggregateRoot>((top, discuss, user) => discuss.CreatorId == user.Id)
.LeftJoin<BbsUserExtraInfoEntity>((top, discuss, user, info) => user.Id == info.UserId) .LeftJoin<BbsUserExtraInfoEntity>((top, discuss, user, info) => user.Id == info.UserId)
.OrderByDescending(top => top.OrderNum) .OrderByDescending(top => top.OrderNum)
.Select((top, discuss, user, info) => new DiscussGetListOutputDto .Select((top, discuss, user, info) => new DiscussGetListOutputDto
{ {
Id = discuss.Id, Id = discuss.Id,
IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null, x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(), IsAgree = SqlFunc.Subqueryable<AgreeEntity>().WhereIF(CurrentUser.Id != null,
x => x.CreatorId == CurrentUser.Id && x.DiscussId == discuss.Id).Any(),
User = new BbsUserGetListOutputDto User = new BbsUserGetListOutputDto
{ {
Id = user.Id, Id = user.Id,
@@ -206,6 +231,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
throw new UserFriendlyException(PlateConst.No_Exist); throw new UserFriendlyException(PlateConst.No_Exist);
} }
if (await _forumManager._discussRepository.IsAnyAsync(x => x.Title == input.Title))
{
throw new UserFriendlyException(DiscussConst.Repeat);
}
//如果开启了禁用创建主题 //如果开启了禁用创建主题
if (plate.IsDisableCreateDiscuss == true) if (plate.IsDisableCreateDiscuss == true)
{ {
@@ -233,6 +263,7 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
{ {
throw new UserFriendlyException(DiscussConst.No_Exist); throw new UserFriendlyException(DiscussConst.No_Exist);
} }
if (discuss.PermissionType == DiscussPermissionTypeEnum.Oneself) if (discuss.PermissionType == DiscussPermissionTypeEnum.Oneself)
{ {
if (discuss.CreatorId != CurrentUser.Id) if (discuss.CreatorId != CurrentUser.Id)
@@ -240,9 +271,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
throw new UserFriendlyException(DiscussConst.Privacy); throw new UserFriendlyException(DiscussConst.Privacy);
} }
} }
if (discuss.PermissionType == DiscussPermissionTypeEnum.User) if (discuss.PermissionType == DiscussPermissionTypeEnum.User)
{ {
if (discuss.CreatorId != CurrentUser.Id && !discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty)) if (discuss.CreatorId != CurrentUser.Id &&
!discuss.PermissionUserIds.Contains(CurrentUser.Id ?? Guid.Empty))
{ {
throw new UserFriendlyException(DiscussConst.Privacy); throw new UserFriendlyException(DiscussConst.Privacy);
} }

View File

@@ -11,6 +11,7 @@ namespace Yi.Framework.Bbs.Domain.Shared.Consts
/// </summary> /// </summary>
public class DiscussConst public class DiscussConst
{ {
public const string Repeat = "创建主题重复";
public const string No_Exist = "传入的主题id不存在"; public const string No_Exist = "传入的主题id不存在";
public const string Privacy = "【私密】您无该主题权限,可联系作者申请开放"; public const string Privacy = "【私密】您无该主题权限,可联系作者申请开放";

View File

@@ -184,22 +184,24 @@ namespace Yi.Framework.Rbac.Application.Services
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("captcha-phone")] [HttpPost("account/captcha-phone")]
[AllowAnonymous] [AllowAnonymous]
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input) public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
{ {
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input); return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
} }
/// <summary> /// <summary>
/// 手机验证码-找回密码 /// 手机验证码-找回密码
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("captcha-phone/repassword")] [HttpPost("account/captcha-phone/repassword")]
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input) public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
{ {
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input); return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
} }
/// <summary> /// <summary>
/// 手机验证码 /// 手机验证码
/// </summary> /// </summary>
@@ -223,7 +225,7 @@ namespace Yi.Framework.Rbac.Application.Services
var uuid = Guid.NewGuid(); var uuid = Guid.NewGuid();
await _aliyunManger.SendSmsAsync(input.Phone, code); await _aliyunManger.SendSmsAsync(input.Phone, code);
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(ValidationPhoneTypeEnum.Register, input.Phone), await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(ValidationPhoneTypeEnum.RetrievePassword, input.Phone),
new CaptchaPhoneCacheItem(code), new CaptchaPhoneCacheItem(code),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) }); new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
return new return new
@@ -255,13 +257,10 @@ namespace Yi.Framework.Rbac.Application.Services
/// <param name="input"></param> /// <param name="input"></param>
[AllowAnonymous] [AllowAnonymous]
[UnitOfWork] [UnitOfWork]
public async Task PostRetrievePasswordAsync(RetrievePasswordDto input) public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input)
{ {
if (_rbacOptions.EnableCaptcha) //校验验证码,根据电话号码获取 value比对验证码已经uuid
{ await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
//校验验证码,根据电话号码获取 value比对验证码已经uuid
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
}
var entity = await _userRepository.GetFirstAsync(x => x.Phone == input.Phone); var entity = await _userRepository.GetFirstAsync(x => x.Phone == input.Phone);
if (entity is null) if (entity is null)
@@ -270,6 +269,8 @@ namespace Yi.Framework.Rbac.Application.Services
} }
await _accountManager.RestPasswordAsync(entity.Id, input.Password); await _accountManager.RestPasswordAsync(entity.Id, input.Password);
return entity.UserName;
} }

View File

@@ -39,7 +39,8 @@ namespace Yi.Framework.Rbac.Application.Services
if (!File.Exists(path)) if (!File.Exists(path))
{ {
throw new UserFriendlyException("文件不存在",code:"404"); return new NotFoundResult();
// throw new UserFriendlyException("文件不存在",code:"404");
} }
@@ -66,12 +67,6 @@ namespace Yi.Framework.Rbac.Application.Services
// path = $"wwwroot/{FileTypeEnum.Thumbnail}/{file.Id}{Path.GetExtension(file.FileName)}"; // path = $"wwwroot/{FileTypeEnum.Thumbnail}/{file.Id}{Path.GetExtension(file.FileName)}";
//} //}
//路径为: 文件路径/文件id+文件扩展名 //路径为: 文件路径/文件id+文件扩展名
if (!File.Exists(path))
{
throw new UserFriendlyException("本地文件不存在", "404");
}
return path; return path;
} }

View File

@@ -8,3 +8,6 @@ VITE_APP_URL="http://localhost:19001/api/app"
# ws/开发环境 # ws/开发环境
VITE_APP_BASE_WS = '/dev-ws' VITE_APP_BASE_WS = '/dev-ws'
VITE_APP_BASE_URL_WS="http://localhost:19001/hub" VITE_APP_BASE_URL_WS="http://localhost:19001/hub"
# 是否开启ICP备案模式
VITE_APP_ICP = true

View File

@@ -7,3 +7,6 @@ VITE_APP_URL="http://ccnetcore.com:19001/api/app"
# ws # ws
VITE_APP_BASE_WS = '/prod-ws' VITE_APP_BASE_WS = '/prod-ws'
VITE_APP_BASE_URL_WS="http://ccnetcore.com:19001/hub" VITE_APP_BASE_URL_WS="http://ccnetcore.com:19001/hub"
# 是否开启ICP备案模式
VITE_APP_ICP = true

View File

@@ -4,20 +4,22 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>.Net意社区</title> <!--<title>.Net意社区</title>-->
<link rel="stylesheet" href="/src/assets/loading.css" /> <!-- 为了icp备案-->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5453339688995325" <title>个人成果展示</title>
crossorigin="anonymous"></script> <link rel="stylesheet" href="/src/assets/loading.css" />
</head> <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-5453339688995325"
<body> crossorigin="anonymous"></script>
<div id="Loading"> </head>
<div class="loader JS_on"> <body>
<span class="binary"></span> <div id="Loading">
<span class="binary"></span> <div class="loader JS_on">
<span class="getting-there">意社区很大,你要等一下...</span> <span class="binary"></span>
</div> <span class="binary"></span>
</div> <span class="getting-there">意社区很大,你要等一下...</span>
<div id="app"></div> </div>
<script type="module" src="/src/main.js"></script> </div>
</body> <div id="app"></div>
</html> <script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -18,6 +18,24 @@ export function login(username, password, code, uuid) {
}); });
} }
//找回密码
export function retrievePassword(password, phone, code, uuid) {
const data = {
password,
phone,
code,
uuid,
};
return request({
url: "/account/retrieve-password",
headers: {
isToken: false,
},
method: "post",
data: data,
});
}
// 注册方法 // 注册方法
export function register(userName, password, phone, code, uuid) { export function register(userName, password, phone, code, uuid) {
const data = { const data = {
@@ -76,3 +94,15 @@ export function getCodePhone(phone) {
data: { phone }, data: { phone },
}); });
} }
// 获取短信验证码-为了重置密码
export function getCodePhoneForRetrievePassword(phone) {
return request({
url: "/account/captcha-phone/repassword",
headers: {
isToken: false,
},
method: "post",
timeout: 20000,
data: { phone },
});
}

View File

@@ -1,5 +1,4 @@
import request from "@/config/axios/service"; import request from "@/config/axios/service";
/** /**
* 用户登录 * 用户登录
* @param {*} data 账号密码 * @param {*} data 账号密码
@@ -23,6 +22,17 @@ export function userRegister(data) {
data, data,
}); });
} }
/**
* 用户找回密码
* @param {*} data 账号密码
*/
export function userRetrievePassword(data) {
return request({
url: `/account/retrieve-password`,
method: "post",
data,
});
}
/** /**
* 获取用户详细信息 * 获取用户详细信息
@@ -44,15 +54,6 @@ export function userLogout() {
}); });
} }
/**
* 获取验证码
*/
export function getCodeImg() {
return request({
url: `/account/captcha-image`,
method: "get",
});
}
/** /**
* 获取短信验证码 * 获取短信验证码
*/ */

View File

@@ -103,13 +103,18 @@ height: 25px;
.left-lable .left-lable
{ {
display: flex; display: flex;
align-items: center; justify-content: space-between;
font-size: 12px; font-size: 12px;
} }
.left-lable label{ .left-lable label{
margin-left: 5px; margin-left: 5px;
} }
.right-forgot{
cursor: pointer;
}
.right-forgot:hover{
color: #7f438c;
}
.bottom-div .bottom-div
{ {
font-size: 12px; font-size: 12px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -7,7 +7,7 @@ import {
userLogin, userLogin,
getUserDetailInfo, getUserDetailInfo,
userLogout, userLogout,
userRegister, userRegister, userRetrievePassword,
} from "@/apis/auth"; } from "@/apis/auth";
const TokenKey = "AccessToken"; const TokenKey = "AccessToken";
export const AUTH_MENUS = "AUTH_MENUS"; export const AUTH_MENUS = "AUTH_MENUS";
@@ -182,6 +182,20 @@ const currentUserInfo=computed(()=>{
// } // }
}; };
// 找回密码
const retrievePasswordFun = async (params) => {
// try {
const {data}=await userRetrievePassword(params);
ElMessage({
message: `恭喜!账号:${data},找回成功!密码已重置,请登录!`,
type: "success",
duration: 8000
});
// } catch (error) {
// console.log(error);
// }
};
return { return {
getToken, getToken,
setToken, setToken,
@@ -189,6 +203,7 @@ const currentUserInfo=computed(()=>{
loginFun, loginFun,
getUserInfo, getUserInfo,
logoutFun, logoutFun,
retrievePasswordFun,
clearStorage, clearStorage,
registerFun, registerFun,
loginSuccess, loginSuccess,

View File

@@ -4,7 +4,8 @@
<div class="image"> <div class="image">
<img class="img-icon" src="@/assets/common/icons/logo.ico" /> <img class="img-icon" src="@/assets/common/icons/logo.ico" />
</div> </div>
<div class="text">{{ configStore.name }}</div>
<div class="text">{{ isIcp===true?"个人成果展示":configStore.name }}</div>
</div> </div>
<div class="tab"> <div class="tab">
<el-menu :default-active="activeIndex" mode="horizontal" :ellipsis="false" @select="handleSelect"> <el-menu :default-active="activeIndex" mode="horizontal" :ellipsis="false" @select="handleSelect">
@@ -152,6 +153,8 @@ const searchText = ref("");
const noticeForNoReadCount = computed(() => { const noticeForNoReadCount = computed(() => {
return noticeList.value.filter(x => x.isRead == false).length; return noticeList.value.filter(x => x.isRead == false).length;
}) })
const isIcp=import.meta.env.VITE_APP_ICP==="true";
//加载初始化离线消息 //加载初始化离线消息
onMounted(async () => { onMounted(async () => {
//登录了才去判断消息通知 //登录了才去判断消息通知

View File

@@ -26,13 +26,18 @@ const router = createRouter({
name: "login", name: "login",
path: "/login", path: "/login",
// component: () => import("../views/Login.vue"), // component: () => import("../views/Login.vue"),
component: () => import("../views/login/index.vue"), component: () => import("../views/login/login.vue"),
}, },
{ {
name: "register", name: "register",
path: "/register", path: "/register",
component: () => import("../views/login/register.vue"), component: () => import("../views/login/register.vue"),
}, },
{
name: "forgotPassword",
path: "/forgotPassword",
component: () => import("../views/login/forgotPassword.vue"),
},
{ {
name: "auth", name: "auth",
path: "/auth/:type", path: "/auth/:type",

View File

@@ -1,4 +1,4 @@
import { login, logout, register } from "@/apis/accountApi"; import { login, logout, register,retrievePassword } from "@/apis/accountApi";
import { getUserDetailInfo, getLoginCode } from "@/apis/auth"; import { getUserDetailInfo, getLoginCode } from "@/apis/auth";
import useAuths from "@/hooks/useAuths"; import useAuths from "@/hooks/useAuths";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
@@ -122,6 +122,23 @@ const useUserStore = defineStore("user", {
}); });
}); });
}, },
//找回密码
retrievePassword(userInfo)
{
const password = userInfo.password.trim();
const phone = userInfo.phone;
const uuid = userInfo.uuid;
const code = userInfo.code;
return new Promise((resolve, reject) => {
retrievePassword(password, phone, code, uuid)
.then((response) => {
resolve(response);
})
.catch((error) => {
reject(error);
});
});
},
// 重置用户信息 // 重置用户信息
resetInfo() { resetInfo() {
this.roles = []; this.roles = [];

View File

@@ -4,7 +4,26 @@
<el-col :span="17"> <el-col :span="17">
<div class="chat-hub"> <div class="chat-hub">
<!-- <p @click="onClickToChatHub">点击前往-最新上线<span>聊天室 </span>现已支持<span>Ai助手</span>希望能帮助大家</p>--> <!-- <p @click="onClickToChatHub">点击前往-最新上线<span>聊天室 </span>现已支持<span>Ai助手</span>希望能帮助大家</p>-->
<p @click="onClickToWeChat">点击关注-最新上线<span>.Net官方微信公众号 </span>分享有<span>深度</span>.Net知识希望能帮助大家</p>
<p v-if="isIcp"
style="font-size: 25px;
color: blue;
background: red;
/* height: 80px; */
font-weight: 900;
text-align: center;
margin: 10px auto;">
本站点为个人内容分享全部资料免费开源学习所有数据为假数据
<br/>
不涉及企业团体论坛和经营销售等内容只做简单的成果展示
<br/>
富强民主文明和谐自由平等
<br/>
公正法治爱国敬业诚信友善
</p>
<p v-else @click="onClickToWeChat">点击关注-最新上线<span>.Net官方微信公众号 </span>分享有<span>深度</span>.Net知识希望能帮助大家</p>
</div> </div>
<div class="scrollbar"> <div class="scrollbar">
<ScrollbarInfo/> <ScrollbarInfo/>
@@ -272,7 +291,7 @@ const activeList = [
{name: "开始", path: "/start", icon: "Position"}, {name: "开始", path: "/start", icon: "Position"},
{name: "聊天室", path: "/chat", icon: "ChatRound"}, {name: "聊天室", path: "/chat", icon: "ChatRound"},
]; ];
const isIcp=import.meta.env.VITE_APP_ICP==="true";
//主题查询参数 //主题查询参数
const query = reactive({ const query = reactive({
skipCount: 1, skipCount: 1,
@@ -622,7 +641,7 @@ const onClickToWeChat=()=>{
display: flex; display: flex;
align-content: center; align-content: center;
flex-wrap: wrap; flex-wrap: wrap;
height: 30px; min-height: 30px;
p { p {
margin: 0 auto; margin: 0 auto;

View File

@@ -1,11 +0,0 @@
<script setup>
</script>
<template>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,153 @@
<script setup>
// 注册逻辑
import {reactive, ref} from "vue";
import {getCodePhoneForRetrievePassword} from "@/apis/accountApi";
import useAuths from "@/hooks/useAuths";
import { useRouter} from "vue-router";
const { retrievePasswordFun } = useAuths();
const router = useRouter();
const retrievePasswordFormRef = ref();
// 确认密码
const passwordConfirm = ref("");
const registerForm = reactive({
phone: "",
password: "",
uuid: "",
code: ""
});
const registerRules = reactive({
phone: [{ required: true, message: "请输入手机号", trigger: "blur" }],
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
password: [
{ required: true, message: "请输入新的密码", trigger: "blur" },
{ min: 6, message: "密码需大于六位", trigger: "blur" },
],
});
const retrievePassword = async (formEl) => {
if (!formEl) return;
await formEl.validate(async (valid) => {
if (valid) {
try {
if (registerForm.password != passwordConfirm.value) {
ElMessage.error("两次密码输入不一致");
return;
}
await retrievePasswordFun(registerForm);
//注册成功返回登录
handleSignInNow();
} catch (error) {
ElMessage({
message: error.message,
type: "error",
duration: 2000,
});
}
}
});
};
//验证码
const codeInfo = ref("发送短信");
const isDisabledCode = ref(false);
//前往登录
const handleSignInNow=()=>{
router.push("/login");
}
const captcha = async () => {
if (registerForm.phone !== "") {
const { data } = await getCodePhoneForRetrievePassword(registerForm.phone);
registerForm.uuid = data.uuid;
ElMessage({
message: `已向${registerForm.phone}发送验证码,请注意查收`,
type: "success",
});
isDisabledCode.value = true;
let time = 60; //定义剩下的秒数
let timer = setInterval(function () {
if (time == 0) {
//清除定时器和复原按钮
clearInterval(timer);
codeInfo.value = "发送验证码";
time = 60; //这个10是重新开始
} else {
codeInfo.value = "剩余" + time + "秒";
time--;
}
}, 1000);
} else {
ElMessage({
message: `请先输入手机号`,
type: "warning",
});
}
};
</script>
<template>
<div class="container">
<!-- 找回密码 -->
<div class="div-content">
<div class="div-right-register">
<img class="div-img" src="@/assets/login.png"/>
</div>
<div class="div-left-register">
<div class="left-container">
<p class="title register-title">Find Password!</p>
<el-form
class="registerForm"
ref="retrievePasswordFormRef"
:model="registerForm"
:rules="registerRules"
>
<div class="input-content">
<div class="input" style="margin-top: 0">
<p>*电话</p>
<el-form-item prop="phone">
<div class="phone-code">
<input class="phone-code-input" type="text" v-model.trim="registerForm.phone">
<button type="button" class="phone-code-btn" @click="captcha()">{{codeInfo}}</button>
</div>
</el-form-item>
</div>
<div class="input">
<p>*短信验证码</p>
<el-form-item prop="code" >
<input :disabled="!isDisabledCode" type="text" v-model.trim="registerForm.code">
</el-form-item>
</div>
<div class="input">
<p>*新的密码</p>
<el-form-item prop="password">
<input :disabled="!isDisabledCode" type="password" v-model.trim="registerForm.password">
</el-form-item>
</div>
<div class="input">
<p>*确认密码</p>
<el-form-item>
<input :disabled="!isDisabledCode" type="password" v-model.trim="passwordConfirm">
</el-form-item>
</div>
</div>
</el-form>
<div class="left-btn">
<button type="button" class="btn-login" @click="retrievePassword(retrievePasswordFormRef)">确认重置密码</button>
<button type="button" class="btn-reg" @click="handleSignInNow">前往登录</button>
</div>
</div>
</div>
</div>
</div>
</template>
<style src="@/assets/styles/login.css" scoped>
</style>

View File

@@ -39,8 +39,12 @@
</div> </div>
</el-form> </el-form>
<div class="left-lable"> <div class="left-lable">
<div>
<input type="checkbox"> <input type="checkbox">
<label>记住我</label> <label>记住我</label>
</div>
<span class="right-forgot" @click="handleForgotPassword">忘记密码点击找回</span>
</div> </div>
<div class="left-btn"> <div class="left-btn">
@@ -82,6 +86,10 @@ const loginForm = reactive({
const handleRegister=()=>{ const handleRegister=()=>{
router.push("/register"); router.push("/register");
} }
//
const handleForgotPassword = () => {
router.push("/forgotPassword");
}
// //
const guestlogin = () => { const guestlogin = () => {
const redirect = route.query?.redirect ?? "/index"; const redirect = route.query?.redirect ?? "/index";