diff --git a/Apimanager_backend/Apimanager_backend.csproj b/Apimanager_backend/Apimanager_backend.csproj index ce65827..2d17395 100644 --- a/Apimanager_backend/Apimanager_backend.csproj +++ b/Apimanager_backend/Apimanager_backend.csproj @@ -7,6 +7,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -16,10 +17,7 @@ - - - diff --git a/Apimanager_backend/Config/MyAutomapper.cs b/Apimanager_backend/Config/MyAutomapper.cs new file mode 100644 index 0000000..159ff8c --- /dev/null +++ b/Apimanager_backend/Config/MyAutomapper.cs @@ -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(); + } + } +} diff --git a/Apimanager_backend/Config/ServiceCollectionExtensions.cs b/Apimanager_backend/Config/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..28a5528 --- /dev/null +++ b/Apimanager_backend/Config/ServiceCollectionExtensions.cs @@ -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(); + return services; + } + } +} diff --git a/Apimanager_backend/Controllers/UserController.cs b/Apimanager_backend/Controllers/UserController.cs new file mode 100644 index 0000000..279a954 --- /dev/null +++ b/Apimanager_backend/Controllers/UserController.cs @@ -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; + } + /// + /// 用户登录控制器 + /// + /// 登录信息 + /// 通用返回信息格式 + [HttpPost("Login")] + public async Task>> Login([FromBody]UserLoginDto dto) + { + try + { + UserInfoDto user = await userService.LoginAsync(dto.UserName, dto.Password); + + var responseInfo = new ResponseBase( + code: 2000, + message: "Login successful", + data: user + ); + return Ok(responseInfo); + } + catch (BaseException e) + { + + //错误时,构建错误信息对象 + var responseInfo = new ResponseBase( + code:e.code, + message: e.message, + data: null + ); + + return Unauthorized(responseInfo); + } + } + } +} diff --git a/Apimanager_backend/Data/ApiContext.cs b/Apimanager_backend/Data/ApiContext.cs index 1be7614..9038552 100644 --- a/Apimanager_backend/Data/ApiContext.cs +++ b/Apimanager_backend/Data/ApiContext.cs @@ -24,6 +24,9 @@ namespace Apimanager_backend.Data protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApiContext).Assembly); + // 配置全局查询筛选器 + modelBuilder.Entity().HasQueryFilter(u => !u.IsDelete); + modelBuilder.Entity().HasQueryFilter(a => !a.IsDelete); } } diff --git a/Apimanager_backend/Dtos/CreateUserDto.cs b/Apimanager_backend/Dtos/CreateUserDto.cs new file mode 100644 index 0000000..9c8b9d2 --- /dev/null +++ b/Apimanager_backend/Dtos/CreateUserDto.cs @@ -0,0 +1,6 @@ +namespace Apimanager_backend.Dtos +{ + public class CreateUserDto + { + } +} diff --git a/Apimanager_backend/Dtos/ResponseBase.cs b/Apimanager_backend/Dtos/ResponseBase.cs new file mode 100644 index 0000000..94d94f9 --- /dev/null +++ b/Apimanager_backend/Dtos/ResponseBase.cs @@ -0,0 +1,20 @@ +namespace Apimanager_backend.Dtos +{ + /// + /// 响应基类,构造最后的返回结果 + /// + /// 返回的具体数据类型 + public class ResponseBase + { + 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() { } + } +} diff --git a/Apimanager_backend/Dtos/UpdateUserDto.cs b/Apimanager_backend/Dtos/UpdateUserDto.cs new file mode 100644 index 0000000..3c53eed --- /dev/null +++ b/Apimanager_backend/Dtos/UpdateUserDto.cs @@ -0,0 +1,6 @@ +namespace Apimanager_backend.Dtos +{ + public class UpdateUserDto + { + } +} diff --git a/Apimanager_backend/Dtos/UserInfoDto.cs b/Apimanager_backend/Dtos/UserInfoDto.cs new file mode 100644 index 0000000..927ffed --- /dev/null +++ b/Apimanager_backend/Dtos/UserInfoDto.cs @@ -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; } + } +} diff --git a/Apimanager_backend/Dtos/UserLoginDto.cs b/Apimanager_backend/Dtos/UserLoginDto.cs new file mode 100644 index 0000000..676efce --- /dev/null +++ b/Apimanager_backend/Dtos/UserLoginDto.cs @@ -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; } + } +} diff --git a/Apimanager_backend/Exceptions/BaseException.cs b/Apimanager_backend/Exceptions/BaseException.cs new file mode 100644 index 0000000..a75a609 --- /dev/null +++ b/Apimanager_backend/Exceptions/BaseException.cs @@ -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; + } + } +} diff --git a/Apimanager_backend/Filters/ModelValidationFilter.cs b/Apimanager_backend/Filters/ModelValidationFilter.cs new file mode 100644 index 0000000..8349db8 --- /dev/null +++ b/Apimanager_backend/Filters/ModelValidationFilter.cs @@ -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 + { + Code = 1001, // 统一的错误码 + Message = errors[0], + Data = null + }; + + // 设置错误响应和 HTTP 状态码 + context.Result = new BadRequestObjectResult(response); + } + } + } +} diff --git a/Apimanager_backend/Program.cs b/Apimanager_backend/Program.cs index 8661d8a..78c9887 100644 --- a/Apimanager_backend/Program.cs +++ b/Apimanager_backend/Program.cs @@ -1,4 +1,6 @@ +using Apimanager_backend.Config; using Apimanager_backend.Data; +using Apimanager_backend.Filters; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -15,8 +17,11 @@ string? constr = configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(option => option.UseMySql(constr, MySqlServerVersion.AutoDetect(constr)) ); - -builder.Services.AddControllers(); +builder.Services.AddAllService(configuration); +builder.Services.AddControllers(options => +{ + options.Filters.Add(); +}); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); diff --git a/Apimanager_backend/Services/IUserService.cs b/Apimanager_backend/Services/IUserService.cs new file mode 100644 index 0000000..0d2212a --- /dev/null +++ b/Apimanager_backend/Services/IUserService.cs @@ -0,0 +1,84 @@ +using Apimanager_backend.Dtos; +using Apimanager_backend.Models; +using System.Runtime.CompilerServices; + +namespace Apimanager_backend.Services +{ + public interface IUserService + { + /// + /// 登录用户,根据用户名和密码进行身份验证。 + /// + /// 用户名 + /// 密码 + /// 包含用户信息的 + Task LoginAsync(string username, string password); + + /// + /// 发送密码重置邮件到指定邮箱。 + /// + /// 用户注册的邮箱地址 + /// 异步操作 + Task SendResetPasswordEmailAsync(string email); + + /// + /// 重置用户密码,验证重置令牌的有效性并更新密码。 + /// + /// 用户邮箱地址 + /// 重置密码的令牌 + /// 新的密码 + /// 异步操作 + Task ResetPasswordAsync(string email, string token, string newPassword); + + /// + /// 获取用户信息。 + /// + /// 用户名 + /// 包含用户信息的 + Task GetUserAsync(string username); + + /// + /// 更新用户信息。 + /// + /// 包含更新信息的 + /// 更新后的 + Task UpdateUserAsync(UpdateUserDto user); + + /// + /// 删除指定的用户。 + /// + /// 要删除的用户名 + /// 异步操作 + Task DeleteUserAsync(string username); + + /// + /// 创建新用户。 + /// + /// 包含新用户信息的 + /// 创建成功的用户信息 + Task CreateUserAsync(CreateUserDto user); + + /// + /// 禁用用户,使其无法登录。 + /// + /// 要禁用的用户名 + /// 异步操作 + Task BanUserAsync(string username); + + /// + /// 取消禁用用户,恢复登录权限。 + /// + /// 要取消禁用的用户名 + /// 异步操作 + Task UnbanUserAsync(string username); + + /// + /// 获取分页的用户列表。 + /// + /// 要获取的页码,从1开始 + /// 每页的用户数量 + /// 是否按降序排序 + /// 包含用户信息的 + Task> GetUsersAsync(int page, int pageSize, bool desc); + } +} diff --git a/Apimanager_backend/Services/UserService.cs b/Apimanager_backend/Services/UserService.cs new file mode 100644 index 0000000..c6859a7 --- /dev/null +++ b/Apimanager_backend/Services/UserService.cs @@ -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 CreateUserAsync(CreateUserDto user) + { + throw new NotImplementedException(); + } + + public Task DeleteUserAsync(string username) + { + throw new NotImplementedException(); + } + + public Task GetUserAsync(string username) + { + throw new NotImplementedException(); + } + + public Task> GetUsersAsync(int page, int pageSize, bool desc) + { + throw new NotImplementedException(); + } + + public async Task 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(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 UpdateUserAsync(UpdateUserDto user) + { + throw new NotImplementedException(); + } + } +}