Merge branch 'master_add_auth_1027' into 'master'

Master add auth 1027

See merge request ql/apismnagaer_backend!6
This commit is contained in:
西街长安 2024-10-27 15:22:40 +00:00
commit 4f904441ac
15 changed files with 373 additions and 5 deletions

View File

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -16,10 +17,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Services\" />
<Folder Include="Tools\" /> <Folder Include="Tools\" />
<Folder Include="Config\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,14 @@
using Apimanager_backend.Dtos;
using Apimanager_backend.Models;
using AutoMapper;
namespace Apimanager_backend.Config
{
public class MyAutomapper:Profile
{
public MyAutomapper()
{
CreateMap<User,UserInfoDto>();
}
}
}

View File

@ -0,0 +1,15 @@
using Apimanager_backend.Services;
using System.Runtime.CompilerServices;
namespace Apimanager_backend.Config
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddAllService(this IServiceCollection services,IConfiguration configuration)
{
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddScoped<IUserService, UserService>();
return services;
}
}
}

View File

@ -0,0 +1,53 @@
using Apimanager_backend.Dtos;
using Apimanager_backend.Exceptions;
using Apimanager_backend.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Apimanager_backend.Filters;
namespace Apimanager_backend.Controllers
{
[ModelValidationFilter()]
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly IUserService userService;
public UserController(IUserService userService)
{
this.userService = userService;
}
/// <summary>
/// 用户登录控制器
/// </summary>
/// <param name="dto">登录信息</param>
/// <returns>通用返回信息格式</returns>
[HttpPost("Login")]
public async Task<ActionResult<ResponseBase<UserInfoDto>>> Login([FromBody]UserLoginDto dto)
{
try
{
UserInfoDto user = await userService.LoginAsync(dto.UserName, dto.Password);
var responseInfo = new ResponseBase<UserInfoDto>(
code: 2000,
message: "Login successful",
data: user
);
return Ok(responseInfo);
}
catch (BaseException e)
{
//错误时,构建错误信息对象
var responseInfo = new ResponseBase<object?>(
code:e.code,
message: e.message,
data: null
);
return Unauthorized(responseInfo);
}
}
}
}

View File

@ -24,6 +24,9 @@ namespace Apimanager_backend.Data
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApiContext).Assembly); modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApiContext).Assembly);
// 配置全局查询筛选器
modelBuilder.Entity<User>().HasQueryFilter(u => !u.IsDelete);
modelBuilder.Entity<Api>().HasQueryFilter(a => !a.IsDelete);
} }
} }

View File

@ -0,0 +1,6 @@
namespace Apimanager_backend.Dtos
{
public class CreateUserDto
{
}
}

View File

@ -0,0 +1,20 @@
namespace Apimanager_backend.Dtos
{
/// <summary>
/// 响应基类,构造最后的返回结果
/// </summary>
/// <typeparam name="T">返回的具体数据类型</typeparam>
public class ResponseBase<T>
{
public int Code { get; set; }
public string Message { get; set; }
public T? Data { get; set; }
public ResponseBase(int code,string message,T data)
{
this.Code = code;
this.Message = message;
this.Data = data;
}
public ResponseBase() { }
}
}

View File

@ -0,0 +1,6 @@
namespace Apimanager_backend.Dtos
{
public class UpdateUserDto
{
}
}

View File

@ -0,0 +1,15 @@
using Apimanager_backend.Models;
namespace Apimanager_backend.Dtos
{
public class UserInfoDto
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public UserRole Role { get; set; }
public bool IsBan { get; set; }
public decimal Balance { get; set; }
public DateTime Created { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Apimanager_backend.Dtos
{
public class UserLoginDto
{
[MaxLength(20,ErrorMessage = "The maximum username is 20 characters")]
public string UserName { get; set; }
[MaxLength(50,ErrorMessage = "The maximum password is 50 characters")]
[Required(ErrorMessage = "password is required")]
public string Password { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Apimanager_backend.Exceptions
{
public class BaseException:Exception
{
public int code;
public string message;
public BaseException(int code, string message):base(message)
{
this.code = code;
this.message = message;
}
}
}

View File

@ -0,0 +1,34 @@
using Apimanager_backend.Dtos;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Apimanager_backend.Filters
{
public class ModelValidationFilter : Attribute,IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context) { }
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// 将所有的错误信息收集到列表中
var errors = context.ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
// 构造统一的响应格式
var response = new ResponseBase<object?>
{
Code = 1001, // 统一的错误码
Message = errors[0],
Data = null
};
// 设置错误响应和 HTTP 状态码
context.Result = new BadRequestObjectResult(response);
}
}
}
}

View File

@ -1,4 +1,6 @@
using Apimanager_backend.Config;
using Apimanager_backend.Data; using Apimanager_backend.Data;
using Apimanager_backend.Filters;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -15,8 +17,11 @@ string? constr = configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApiContext>(option => builder.Services.AddDbContext<ApiContext>(option =>
option.UseMySql(constr, MySqlServerVersion.AutoDetect(constr)) option.UseMySql(constr, MySqlServerVersion.AutoDetect(constr))
); );
builder.Services.AddAllService(configuration);
builder.Services.AddControllers(); builder.Services.AddControllers(options =>
{
options.Filters.Add<ModelValidationFilter>();
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();

View File

@ -0,0 +1,84 @@
using Apimanager_backend.Dtos;
using Apimanager_backend.Models;
using System.Runtime.CompilerServices;
namespace Apimanager_backend.Services
{
public interface IUserService
{
/// <summary>
/// 登录用户,根据用户名和密码进行身份验证。
/// </summary>
/// <param name="username">用户名</param>
/// <param name="password">密码</param>
/// <returns>包含用户信息的 <see cref="UserInfoDto"/></returns>
Task<UserInfoDto> LoginAsync(string username, string password);
/// <summary>
/// 发送密码重置邮件到指定邮箱。
/// </summary>
/// <param name="email">用户注册的邮箱地址</param>
/// <returns>异步操作</returns>
Task SendResetPasswordEmailAsync(string email);
/// <summary>
/// 重置用户密码,验证重置令牌的有效性并更新密码。
/// </summary>
/// <param name="email">用户邮箱地址</param>
/// <param name="token">重置密码的令牌</param>
/// <param name="newPassword">新的密码</param>
/// <returns>异步操作</returns>
Task ResetPasswordAsync(string email, string token, string newPassword);
/// <summary>
/// 获取用户信息。
/// </summary>
/// <param name="username">用户名</param>
/// <returns>包含用户信息的 <see cref="UserInfoDto"/></returns>
Task<UserInfoDto> GetUserAsync(string username);
/// <summary>
/// 更新用户信息。
/// </summary>
/// <param name="user">包含更新信息的 <see cref="UpdateUserDto"/></param>
/// <returns>更新后的 <see cref="UserInfoDto"/></returns>
Task<UserInfoDto> UpdateUserAsync(UpdateUserDto user);
/// <summary>
/// 删除指定的用户。
/// </summary>
/// <param name="username">要删除的用户名</param>
/// <returns>异步操作</returns>
Task DeleteUserAsync(string username);
/// <summary>
/// 创建新用户。
/// </summary>
/// <param name="user">包含新用户信息的 <see cref="CreateUserDto"/></param>
/// <returns>创建成功的用户信息 <see cref="UserInfoDto"/></returns>
Task<UserInfoDto> CreateUserAsync(CreateUserDto user);
/// <summary>
/// 禁用用户,使其无法登录。
/// </summary>
/// <param name="username">要禁用的用户名</param>
/// <returns>异步操作</returns>
Task BanUserAsync(string username);
/// <summary>
/// 取消禁用用户,恢复登录权限。
/// </summary>
/// <param name="username">要取消禁用的用户名</param>
/// <returns>异步操作</returns>
Task UnbanUserAsync(string username);
/// <summary>
/// 获取分页的用户列表。
/// </summary>
/// <param name="page">要获取的页码从1开始</param>
/// <param name="pageSize">每页的用户数量</param>
/// <param name="desc">是否按降序排序</param>
/// <returns>包含用户信息的 <see cref="List{UserInfoDto}"/></returns>
Task<List<UserInfoDto>> GetUsersAsync(int page, int pageSize, bool desc);
}
}

View File

@ -0,0 +1,89 @@
using Apimanager_backend.Config;
using Apimanager_backend.Data;
using Apimanager_backend.Dtos;
using Apimanager_backend.Exceptions;
using Apimanager_backend.Models;
using AutoMapper;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
namespace Apimanager_backend.Services
{
public class UserService : IUserService
{
private readonly ApiContext apiContext;
private readonly IMapper mapper;
public UserService(ApiContext apiContext,IMapper automapper)
{
this.apiContext = apiContext;
this.mapper = automapper;
}
public Task BanUserAsync(string username)
{
throw new NotImplementedException();
}
public Task<UserInfoDto> CreateUserAsync(CreateUserDto user)
{
throw new NotImplementedException();
}
public Task DeleteUserAsync(string username)
{
throw new NotImplementedException();
}
public Task<UserInfoDto> GetUserAsync(string username)
{
throw new NotImplementedException();
}
public Task<List<UserInfoDto>> GetUsersAsync(int page, int pageSize, bool desc)
{
throw new NotImplementedException();
}
public async Task<UserInfoDto> LoginAsync(string username, string password)
{
//查找用户
User? user = await apiContext.Users.SingleOrDefaultAsync(x =>
x.Username == username && x.PassHash == password
);
//用户不存在或密码错误都为登录失败
if(user == null)
{
throw new BaseException(2001, "Invalid username or password");
}
//用户被禁用
if (user.IsBan)
{
throw new BaseException(2002, "User account is disabled");
}
return mapper.Map<UserInfoDto>(user);
}
public Task ResetPasswordAsync(string email, string token, string newPassword)
{
throw new NotImplementedException();
}
public Task SendResetPasswordEmailAsync(string email)
{
throw new NotImplementedException();
}
public Task UnbanUserAsync(string username)
{
throw new NotImplementedException();
}
public Task<UserInfoDto> UpdateUserAsync(UpdateUserDto user)
{
throw new NotImplementedException();
}
}
}