1、项目简介 本项目为学习项目,由哔哩哔哩黑马程序员Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战 为蓝本,将所有代码更改为ASP.NET Core 6.0,构建单体应用。
2、数据准备 依据资料,将所需内容进行基准准备。
在搭建数据库时,为进行模拟,使用Docker Hub模拟Linux中的构建与使用
1 docker run --name mysql -d -p 33061 :3306 --restart unless-stopped -e MYSQL_ROOT_PASSWORD=mysql mysql
并且使用工具导入sql脚本,从而构建数据库。
3、内容初步重构 依据给的初始资料,将内容进行初步重构。首先创建各类相关文件夹,随后将SkyPojo中的内容全部重建。
1、数据库重构 由于ASP.NET Core中并不像Java一样,因此构建SkyDbContext.cs,负责与MySQL数据库的交互,管理实体到数据库表的映射关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 using Microsoft.EntityFrameworkCore;using ProgramBackEnd.SkyPojo.entity;namespace ProgramBackEnd.SkyServer.config { public class SkyDbContext : DbContext { public SkyDbContext (DbContextOptions<SkyDbContext> options ) : base (options ) { } #region 实体集合定义 public DbSet<AddressBook> AddressBooks { get ; set ; } public DbSet<Category> Categories { get ; set ; } public DbSet<Dish> Dishes { get ; set ; } public DbSet<DishFlavor> DishFlavors { get ; set ; } public DbSet<Employee> Employees { get ; set ; } public DbSet<OrderDetail> OrderDetails { get ; set ; } public DbSet<Orders> Orders { get ; set ; } public DbSet<Setmeal> Setmeals { get ; set ; } public DbSet<SetmealDish> SetmealDishes { get ; set ; } public DbSet<ShoppingCart> ShoppingCarts { get ; set ; } public DbSet<User> Users { get ; set ; } #endregion protected override void OnModelCreating (ModelBuilder modelBuilder ) { base .OnModelCreating(modelBuilder); modelBuilder.Entity<AddressBook>().ToTable("address_book" ); modelBuilder.Entity<Category>().ToTable("category" ); modelBuilder.Entity<Dish>().ToTable("dish" ); modelBuilder.Entity<DishFlavor>().ToTable("dish_flavor" ); modelBuilder.Entity<Employee>().ToTable("employee" ); modelBuilder.Entity<OrderDetail>().ToTable("order_detail" ); modelBuilder.Entity<Orders>().ToTable("orders" ); modelBuilder.Entity<Setmeal>().ToTable("setmeal" ); modelBuilder.Entity<SetmealDish>().ToTable("setmeal_dish" ); modelBuilder.Entity<ShoppingCart>().ToTable("shopping_cart" ); modelBuilder.Entity<User>().ToTable("user" ); } } }
2、依赖项注入 将服务注册逻辑从Program.cs中分离,提高代码的组织性和可维护性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 using Microsoft.Extensions.DependencyInjection;using System.Reflection;using ProgramBackEnd.SkyServer.service.Impl;using ProgramBackEnd.SkyServer.service;using ProgramBackEnd.SkyServer.mapper;using ProgramBackEnd.SkyServer.mapper.Impl;namespace ProgramBackEnd.SkyServer.config { public static class DependencyInjectionExtensions { #region 应用服务注册 public static IServiceCollection RegisterApplicationServices (this IServiceCollection services ) { services.AddScoped<IEmployeeService, EmployeeServiceImpl>(); services.AddScoped<IEmployeeMapper, EmployeeMapperImpl>(); return services; } #endregion #region 公共入口点 public static IServiceCollection RegisterAllServices (this IServiceCollection services ) { services.RegisterApplicationServices(); return services; } #endregion } }
3、全局异常处理器 由于在测试时经常出现问题,因此添加全局异常处理器,从而确保能够给用户合理的反馈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 using Microsoft.AspNetCore.Mvc;using Microsoft.AspNetCore.Mvc.Filters;using ProgramBackEnd.SkyCommon.exception;using ProgramBackEnd.SkyCommon.result;namespace ProgramBackEnd.SkyServer.Handler { public class GlobalExceptionHandler : ExceptionFilterAttribute { private readonly ILogger<GlobalExceptionHandler> _logger; public GlobalExceptionHandler (ILogger<GlobalExceptionHandler> logger ) { _logger = logger ?? throw new ArgumentNullException(nameof (logger)); } public override void OnException (ExceptionContext context ) { var request = context.HttpContext.Request; var requestPath = request.Path; var requestMethod = request.Method; string requestBody = "[无请求体]" ; if (request.HasFormContentType) { requestBody = "[表单数据]" ; } else if (request.ContentLength.HasValue && request.ContentLength > 0 ) { try { requestBody = "[请求体数据]" ; } catch { requestBody = "[无法读取请求体]" ; } } if (context.Exception is BaseException baseException) { _logger.LogInformation( "业务异常 - 请求:{Method} {Path} - 错误:{Message} - 请求数据:{RequestBody}" , requestMethod, requestPath, baseException.Message, requestBody); var result = Result<object >.Error(baseException.Message); context.Result = new ObjectResult(result) { StatusCode = StatusCodes.Status200OK }; context.ExceptionHandled = true ; } else { _logger.LogError(context.Exception, "系统异常 - 请求:{Method} {Path} - 错误:{Message} - 堆栈:{StackTrace} - 请求数据:{RequestBody}" , requestMethod, requestPath, context.Exception.Message, context.Exception.StackTrace, requestBody); var result = Result<object >.Error("系统繁忙,请稍后再试" ); context.Result = new ObjectResult(result) { StatusCode = StatusCodes.Status500InternalServerError }; context.ExceptionHandled = true ; } base .OnException(context); } } public static class GlobalExceptionHandlerExtensions { public static IMvcBuilder AddGlobalExceptionHandler (this IMvcBuilder builder ) { builder.AddMvcOptions(options => { options.Filters.Add<GlobalExceptionHandler>(); }); return builder; } } }
4、修改Program.cs 将上述修改后的内容注入到Program.cs中,以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using ProgramBackEnd.SkyCommon.properties; using ProgramBackEnd.SkyServer.config; using ProgramBackEnd.SkyServer.Handler; var builder = WebApplication.CreateBuilder(args );builder.Services.AddDbContext<SkyDbContext>(options => options.UseMySql( builder.Configuration.GetConnectionString("DefaultConnection" ), new MySqlServerVersion(new Version(9 , 3 , 0 )) ) ); builder.Services.Configure<JwtProperties>(builder.Configuration.GetSection("JwtConfig" )); builder.Services.AddControllers().AddGlobalExceptionHandler(); builder.Services.RegisterAllServices(); var app = builder.Build();app.MapControllers(); app.Run();
5、修改appsettings.json 因为引入了数据库与JWT,因此需要修改以适应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 { "Logging" : { "LogLevel" : { "Default" : "Information" , "Microsoft.AspNetCore" : "Warning" } } , "ConnectionStrings" : { "DefaultConnection" : "server=localhost;port=33061;database=sky_take_out;uid=root;pwd=mysql;" } , "Kestrel" : { "Endpoints" : { "Http" : { "Url" : "http://localhost:8080" } } } , "JwtConfig" : { "AdminSecretKey" : "AdminSecretKey_sky1234567890123456789012" , "AdminTtl" : 7200000 , "AdminTokenName" : "token" , } , "AllowedHosts" : "*" }
4、Employee相关内容 1、EmployeeController.cs重建 文件位置:ProgramBackEnd\SkyServer\controller\admin\EmployeeController.cs
依据初始代码进行重建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.Options;using ProgramBackEnd.SkyCommon.constant;using ProgramBackEnd.SkyCommon.properties;using ProgramBackEnd.SkyCommon.result;using ProgramBackEnd.SkyCommon.utils;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyPojo.vo;using ProgramBackEnd.SkyServer.service;namespace ProgramBackEnd.SkyServer.Controllers.Admin { [ApiController ] [Route("/admin/employee" ) ] public class EmployeeController : ControllerBase { private readonly IEmployeeService _employeeService; private readonly JwtProperties _jwtProperties; private readonly ILogger<EmployeeController> _logger; public EmployeeController (IEmployeeService employeeService, IOptions<JwtProperties> jwtOptions, ILogger<EmployeeController> logger ) { _employeeService = employeeService ?? throw new ArgumentNullException(nameof (employeeService)); _jwtProperties = jwtOptions?.Value ?? throw new ArgumentNullException(nameof (jwtOptions)); _logger = logger ?? throw new ArgumentNullException(nameof (logger)); } [HttpPost("login" ) ] public async Task<Result<EmployeeLoginVO>> Login([FromBody] EmployeeLoginDTO employeeLoginDTO) { _logger.LogInformation("员工登录尝试:{EmployeeInfo}" , employeeLoginDTO); Employee employee = await _employeeService.LoginAsync(employeeLoginDTO); _logger.LogInformation("员工 {Username}({Id}) 登录成功" , employee.Username, employee.Id); var claims = new Dictionary<string , object > { { JwtClaimsConstant.EMP_ID, employee.Id } }; string token = JwtUtil.CreateJWT( _jwtProperties.AdminSecretKey, _jwtProperties.AdminTtl, claims); var employeeLoginVO = new EmployeeLoginVO { Id = employee.Id, UserName = employee.Username, Name = employee.Name, Token = token }; return Result<EmployeeLoginVO>.Success(employeeLoginVO); } [HttpPost("logout" ) ] public Result<string > Logout () { _logger.LogInformation("员工退出登录" ); return Result<string >.Success("退出成功" ); } } }
2、EmployeeService 接口:ProgramBackEnd\SkyServer\service\IEmployeeService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 using System.Threading.Tasks;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;namespace ProgramBackEnd.SkyServer.service { public interface IEmployeeService { Task<Employee> LoginAsync (EmployeeLoginDTO employeeLoginDTO ) ; } }
具体实现: ProgramBackEnd\SkyServer\service\Impl\EmployeeServiceImpl.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 using ProgramBackEnd.SkyCommon.constant;using ProgramBackEnd.SkyPojo.dto;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyCommon.exception;using ProgramBackEnd.SkyServer.mapper;namespace ProgramBackEnd.SkyServer.service.Impl { public class EmployeeServiceImpl : IEmployeeService { private readonly IEmployeeMapper _employeeMapper; private readonly ILogger<EmployeeServiceImpl> _logger; public EmployeeServiceImpl (IEmployeeMapper employeeMapper, ILogger<EmployeeServiceImpl> logger ) { _employeeMapper = employeeMapper ?? throw new ArgumentNullException(nameof (employeeMapper)); _logger = logger ?? throw new ArgumentNullException(nameof (logger)); } public async Task<Employee> LoginAsync (EmployeeLoginDTO employeeLoginDTO ) { if (employeeLoginDTO == null ) { throw new ArgumentNullException(nameof (employeeLoginDTO), "登录信息不能为空" ); } string username = employeeLoginDTO.Username?.Trim(); string password = employeeLoginDTO.Password; if (string .IsNullOrEmpty(username)) { throw new ArgumentException("用户名不能为空" , nameof (employeeLoginDTO)); } if (string .IsNullOrEmpty(password)) { throw new ArgumentException("密码不能为空" , nameof (employeeLoginDTO)); } _logger.LogInformation("员工登录尝试:用户名 {Username}" , username); Employee employee = await _employeeMapper.GetByUsernameAsync(username); if (employee == null ) { _logger.LogWarning("登录失败:用户名 {Username} 不存在" , username); throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND); } if (password != employee.Password) { _logger.LogWarning("登录失败:用户名 {Username} 密码错误" , username); throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); } if (employee.Status == StatusConstant.DISABLE) { _logger.LogWarning("登录失败:用户名 {Username} 账号已被禁用" , username); throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED); } _logger.LogInformation("登录成功:用户 {Username}({Name}) ID:{Id}" ,username, employee.Name, employee.Id); return employee; } } }
3、EmployeeMapper 接口:ProgramBackEnd\SkyServer\mapper\IEmployeeMapper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using ProgramBackEnd.SkyPojo.entity;namespace ProgramBackEnd.SkyServer.mapper { public interface IEmployeeMapper { Task<Employee> GetByUsernameAsync (string username ) ; } }
具体实现: ProgramBackEnd\SkyServer\mapper\Impl\EmployeeMapperImpl.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 using Microsoft.EntityFrameworkCore;using ProgramBackEnd.SkyPojo.entity;using ProgramBackEnd.SkyServer.config;namespace ProgramBackEnd.SkyServer.mapper.Impl { public class EmployeeMapperImpl : IEmployeeMapper { private readonly SkyDbContext _context; public EmployeeMapperImpl (SkyDbContext skyDbContext ) { _context = skyDbContext ?? throw new ArgumentNullException(nameof (skyDbContext)); } public async Task<Employee> GetByUsernameAsync (string username ) { if (string .IsNullOrEmpty(username)) { throw new ArgumentNullException(nameof (username), "用户名不能为空" ); } return await _context.Employees.FirstOrDefaultAsync(e => e.Username == username); } } }
4、JWT使用 在“EmployeeController.cs”中使用了JWT令牌相关,因此也要重写。
1、JwtClaimsConstant 首先是JWT声明相关常量
ProgramBackEnd\SkyCommon\constant\JwtClaimsConstant.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 namespace ProgramBackEnd.SkyCommon.constant { public static class JwtClaimsConstant { public const string EMP_ID = "empId" ; public const string USER_ID = "userId" ; public const string PHONE = "phone" ; public const string USERNAME = "username" ; public const string NAME = "name" ; } }
2、Jwt工具 Jwt工具用以辅助构建、解析JWT
ProgramBackEnd\SkyCommon\utils\JwtUtil.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 using Microsoft.IdentityModel.Tokens;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;using System.Text;namespace ProgramBackEnd.SkyCommon.utils { public static class JwtUtil { public static string CreateJWT (string secretKey, long ttlMillis, Dictionary<string , object > claims ) { if (string .IsNullOrEmpty(secretKey)) throw new ArgumentNullException(nameof (secretKey), "JWT密钥不能为空" ); if (ttlMillis <= 0 ) throw new ArgumentException("令牌有效期必须大于0" , nameof (ttlMillis)); var issuedAt = DateTime.UtcNow; var expiration = issuedAt.AddMilliseconds(ttlMillis); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var tokenDescriptor = new SecurityTokenDescriptor { IssuedAt = issuedAt, NotBefore = issuedAt, Expires = expiration, SigningCredentials = credentials }; if (claims != null && claims.Count > 0 ) { var claimsList = new List<Claim>(); foreach (var claim in claims) { string value = claim.Value?.ToString() ?? "" ; claimsList.Add(new Claim(claim.Key, value )); } tokenDescriptor.Subject = new ClaimsIdentity(claimsList); } var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } } }
5、exception 由于需要很多的特定报错,因此构建ProgramBackEnd\SkyCommon\exception中的文件
1、BaseException.cs 构建为基础异常,作为基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 using System;using System.Runtime.Serialization;namespace ProgramBackEnd.SkyCommon.exception { [Serializable ] public class BaseException : Exception { public BaseException () : base () { } public BaseException (string message ) : base (message ) { } } }
2、AccountNotFoundException 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 using System;using System.Runtime.Serialization;namespace ProgramBackEnd.SkyCommon.exception { [Serializable ] public class AccountNotFoundException : BaseException { public AccountNotFoundException () : base ("用户不存在,请重新输入" ) { } public AccountNotFoundException (string message ) : base (message ) { } } }
3、PasswordErrorException 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System; using System.Runtime.Serialization; using System.Security.Authentication; namespace ProgramBackEnd.SkyCommon.exception { /// <summary> /// 密码错误异常 /// <para> /// 当用户提供的密码与系统中存储的密码不匹配时抛出此异常。此异常通常发生在用户登录、 /// 修改密码或其他需要密码验证的操作中,表示身份验证失败。 /// </para> /// <para> /// 作为业务异常的一种,此异常应当由全局异常处理器捕获并转换为对用户友好的错误消息, /// 而不暴露技术细节或安全敏感信息。 /// </para> /// </summary> /// <remarks> /// 使用场景: /// - 用户登录时提供了错误的密码 /// - 更改密码时,原密码验证失败 /// - API认证过程中提供了错误的凭证 /// /// 安全注意事项: /// - 不应在错误消息中指明密码的具体错误(如长度不足、缺少特殊字符等) /// - 应考虑实现登录失败尝试次数限制,防止暴力破解 /// </remarks> [Serializable] public class PasswordErrorException : BaseException { /// <summary> /// 默认无参构造函数 /// <para> /// 创建一个带有标准错误消息的密码错误异常。 /// </para> /// </summary> /// <example> /// <code> /// throw new PasswordErrorException(); /// // 将使用默认消息:"密码错误,请重新输入" /// </code> /// </example> public PasswordErrorException() : base("密码错误,请重新输入") { } /// <summary> /// 带错误消息的构造函数 /// <para> /// 创建一个包含自定义错误消息的密码错误异常。 /// 这是最常用的构造方式,可提供更具体或更友好的错误描述。 /// </para> /// </summary> /// <param name="message"> /// 错误消息,描述密码错误的具体情况。 /// 出于安全考虑,消息不应包含有关密码规则或密码错误原因的详细信息。 /// </param> /// <example> /// <code> /// throw new PasswordErrorException("用户名或密码不正确,请检查后重试"); /// </code> /// </example> public PasswordErrorException(string message) : base(message) { } } }
4、AccountLockedException 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System;using System.Runtime.Serialization;namespace ProgramBackEnd.SkyCommon.exception { [Serializable ] public class AccountLockedException : BaseException { public AccountLockedException () : base ("账号已被锁定,请联系管理员" ) { } public AccountLockedException (string message ) : base (message ) { } } }
5、完整登录内容 当完全正确时,可以正常登录
4、完善登录功能 由于现在时明文加密,现在将其改编为MD5加密
1、MD5Util 将MD5工具类进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using System.Security.Cryptography;using System.Text;namespace ProgramBackEnd.SkyCommon.utils { public static class MD5Util { public static string ComputeHash (string input ) { using (var md5 = MD5.Create()) { byte [] inputBytes = Encoding.UTF8.GetBytes(input); byte [] hashBytes = md5.ComputeHash(inputBytes); StringBuilder sb = new StringBuilder(); foreach (byte b in hashBytes) { sb.Append(b.ToString("x2" )); } return sb.ToString(); } } } }
2、EmployeeService 修改EmployeeService中的实现,将代码替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public async Task<Employee> LoginAsync (EmployeeLoginDTO employeeLoginDTO ){ if (employeeLoginDTO == null ) { throw new ArgumentNullException(nameof (employeeLoginDTO), "登录信息不能为空" ); } string username = employeeLoginDTO.Username?.Trim(); string password = employeeLoginDTO.Password; if (string .IsNullOrEmpty(username)) { throw new ArgumentException("用户名不能为空" , nameof (employeeLoginDTO)); } if (string .IsNullOrEmpty(password)) { throw new ArgumentException("密码不能为空" , nameof (employeeLoginDTO)); } _logger.LogInformation("员工登录尝试:用户名 {Username}" , username); Employee employee = await _employeeMapper.GetByUsernameAsync(username); if (employee == null ) { _logger.LogWarning("登录失败:用户名 {Username} 不存在" , username); throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND); } if (MD5Util.ComputeHash(password) != employee.Password) { _logger.LogWarning("登录失败:用户名 {Username} 密码错误" , username); throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); } if (employee.Status == StatusConstant.DISABLE) { _logger.LogWarning("登录失败:用户名 {Username} 账号已被禁用" , username); throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED); } _logger.LogInformation("登录成功:用户 {Username}({Name}) ID:{Id}" ,username, employee.Name, employee.Id); return employee; }