diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/SiteManagementSystem(SoftwareEngineering).sln b/SiteManagementSystem(SoftwareEngineering).sln new file mode 100644 index 0000000..3b0481b --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering).sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SiteManagementSystem(SoftwareEngineering)", "SiteManagementSystem(SoftwareEngineering)\SiteManagementSystem(SoftwareEngineering).csproj", "{CB0750D4-7BC3-4D7D-B9E1-AADA28283DFB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CB0750D4-7BC3-4D7D-B9E1-AADA28283DFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB0750D4-7BC3-4D7D-B9E1-AADA28283DFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB0750D4-7BC3-4D7D-B9E1-AADA28283DFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB0750D4-7BC3-4D7D-B9E1-AADA28283DFB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CB03CE44-F3D7-4391-AF5C-FB103A12A980} + EndGlobalSection +EndGlobal diff --git a/SiteManagementSystem(SoftwareEngineering)/Controllers/FieldController.cs b/SiteManagementSystem(SoftwareEngineering)/Controllers/FieldController.cs new file mode 100644 index 0000000..0f87bc0 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Controllers/FieldController.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SiteManagementSystem_SoftwareEngineering_.Controllers +{ + public class FieldController : ControllerBase + { + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Controllers/UserController.cs b/SiteManagementSystem(SoftwareEngineering)/Controllers/UserController.cs new file mode 100644 index 0000000..9819f0d --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Controllers/UserController.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc; +using SiteManagementSystem_SoftwareEngineering_.Entity; +using SiteManagementSystem_SoftwareEngineering_.Extension; +using SiteManagementSystem_SoftwareEngineering_.Factory; +using SiteManagementSystem_SoftwareEngineering_.Interface; +using SiteManagementSystem_SoftwareEngineering_.Model; + +namespace SiteManagementSystem_SoftwareEngineering_.Controllers +{ + public class UserController : ControllerBase + { + private readonly IUserManageService _userManageService; + private readonly ITokenFactory _tokenFactory; + public UserController(IUserManageService userManageService,ITokenFactory tokenFactory) + { + _userManageService = userManageService; + _tokenFactory = tokenFactory; + } + + [HttpGet("AddInitAdministratorUser")] + public IActionResult AddInitAdministratorUser() + { + _userManageService.AddUser( + new User + { + Name = "admin", + RoleName = "Administrator", + Secret = "admin" + } + ); + return this.Success(); + } + + [HttpPost("AddUser")] + public IActionResult AddUser(UserModel user) + { + _userManageService.AddUser(new User(user)); + return this.Success(); + } + + [HttpPost("Login")] + public IActionResult Login(UserModel model) + { + var user = new User(model); + var (result, info) = _userManageService.IsUserExist(ref user); + if (!result) + return this.Forbidden(info); + + return this.Success("", new + { + AccessToken = _tokenFactory.CreateAccessToken(user), + RefreshToken = _tokenFactory.CreateRefreshToken(user) + }); + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Dockerfile b/SiteManagementSystem(SoftwareEngineering)/Dockerfile new file mode 100644 index 0000000..728690e --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Dockerfile @@ -0,0 +1,30 @@ +# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。 + +# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + + +# 此阶段用于生成服务项目 +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).csproj", "SiteManagementSystem(SoftwareEngineering)/"] +RUN dotnet restore "./SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).csproj" +COPY . . +WORKDIR "/src/SiteManagementSystem(SoftwareEngineering)" +RUN dotnet build "./SiteManagementSystem(SoftwareEngineering).csproj" -c $BUILD_CONFIGURATION -o /app/build + +# 此阶段用于发布要复制到最终阶段的服务项目 +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./SiteManagementSystem(SoftwareEngineering).csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "SiteManagementSystem(SoftwareEngineering).dll"] \ No newline at end of file diff --git a/SiteManagementSystem(SoftwareEngineering)/Entity/User.cs b/SiteManagementSystem(SoftwareEngineering)/Entity/User.cs new file mode 100644 index 0000000..92a9397 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Entity/User.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Data; +using System.Security.Claims; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection; +using SiteManagementSystem_SoftwareEngineering_.Model; +using SiteManagementSystem_SoftwareEngineering_.Service; + +namespace SiteManagementSystem_SoftwareEngineering_.Entity +{ + public class User : ICloneable + { + public User() { } + + public User(UserModel model) + { + Name = model.Name; + RoleName = model.RoleName; + Secret = model.Secret; + } + + public User(User user) + { + Id = user.Id; + RoleName = user.RoleName; + Role = user.Role; + Name = user.Name; + Secret = user.Secret; + HashSecret = user.HashSecret; + } + + [Key] + public Guid Id { get; set; } = Guid.NewGuid(); + + [NotMapped] + public string RoleName { get; set; } = null!; + public IdentityPolicyNames Role + { + get + { + if (Enum.TryParse(RoleName, out var result)) + return result; + else + return IdentityPolicyNames.NotSupport; + } + set => RoleName = Enum.GetName(value)!; + } + + [StringLength(50)] + public string Name { get; set; } = null!; + + [NotMapped] + public string Secret { get; set; } = null!; + + public string HashSecret { get; set; } = null!; + + public object Clone() => new User(this); + + public IEnumerable GetUserClaims() => + [new Claim(nameof(Name), Name), new Claim(ClaimTypes.Role, RoleName)]; + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Extension/ControllerBaseExtension.cs b/SiteManagementSystem(SoftwareEngineering)/Extension/ControllerBaseExtension.cs new file mode 100644 index 0000000..cf2730c --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Extension/ControllerBaseExtension.cs @@ -0,0 +1,24 @@ +using SiteManagementSystem_SoftwareEngineering_.Model; +using Microsoft.AspNetCore.Mvc; + +namespace SiteManagementSystem_SoftwareEngineering_.Extension +{ + public static class ControllerBaseExtension + { + public static IActionResult Success( + this ControllerBase controller, + string msg = "", + object? data = null + ) => ApiResponse.Success(msg, data); + public static IActionResult Forbidden( + this ControllerBase controller, + string msg = "", + object? data = null + )=> ApiResponse.Forbidden(msg, data); + public static IActionResult Fail( + this ControllerBase controller, + string msg = "", + object? data = null + ) => ApiResponse.Fail(msg, data); + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Extension/IServiceCollectionExtension.cs b/SiteManagementSystem(SoftwareEngineering)/Extension/IServiceCollectionExtension.cs new file mode 100644 index 0000000..0cc37ba --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Extension/IServiceCollectionExtension.cs @@ -0,0 +1,42 @@ +using IwutMail.Model; +using SiteManagementSystem_SoftwareEngineering_.Factory; +using SiteManagementSystem_SoftwareEngineering_.Interface; +using SiteManagementSystem_SoftwareEngineering_.Model; +using SiteManagementSystem_SoftwareEngineering_.Service; +using static SiteManagementSystem_SoftwareEngineering_.Service.UserManagerService; + +namespace SiteManagementSystem_SoftwareEngineering_.Extension +{ + public static class IServiceCollectionExtension + { + public static IServiceCollection AddTokenFactory( + this IServiceCollection services, + Action options + ) + { + var config = new TokenFactoryConfiguration(); + options(config); + if (config.Audience is null) + throw new ArgumentNullException(nameof(config.Audience) + "can not be null."); + if (config.Issuer is null) + throw new ArgumentNullException(nameof(config.Issuer) + "can not be null."); + if (config.SigningKey is null) + throw new ArgumentNullException(nameof(config.SigningKey) + "can not be null."); + services.AddScoped(_ => new TokenFactory(config)); + return services; + } + public static IServiceCollection AddUserManager(this IServiceCollection services, Action options) + { + var config = new SecretConfig(); + options(config); + if (config.HashSalt is null) throw new ArgumentNullException(nameof(config.HashSalt) + "can not be null"); + services.AddScoped(services => + { + var logger = services.GetRequiredService>(); + var storageService= services.GetRequiredService(); + return new UserManageService(storageService, logger, config.HashSalt); + }); + return services; + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Factory/TokenFactory.cs b/SiteManagementSystem(SoftwareEngineering)/Factory/TokenFactory.cs new file mode 100644 index 0000000..3e81744 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Factory/TokenFactory.cs @@ -0,0 +1,54 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using SiteManagementSystem_SoftwareEngineering_.Entity; +using SiteManagementSystem_SoftwareEngineering_.Interface; +using SiteManagementSystem_SoftwareEngineering_.Model; + +namespace SiteManagementSystem_SoftwareEngineering_.Factory +{ + public class TokenFactory( + TokenFactoryConfiguration configuration + ) : ITokenFactory + { + private readonly TokenFactoryConfiguration _configuration = configuration; + + private string CreateToken(IEnumerable claims, User user, DateTime expires) + { + // 为什么把 NameIdentifier 单独写在这儿? + claims = claims.Append(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())); + var credentials = new SigningCredentials( + new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.SigningKey)), + SecurityAlgorithms.HmacSha256 + ); + var token = new JwtSecurityToken( + issuer: _configuration.Issuer, + audience: _configuration.Audience, + notBefore: DateTime.Now, + expires: expires, + claims: claims, + signingCredentials: credentials + ); + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public string CreateAccessToken(User user) + { + var expires = DateTime.Now.AddMinutes(_configuration.AccessTokenExpire); + return CreateToken( + user.GetUserClaims().Append(new Claim("TokenType", "AccessToken")), + user, + expires + ); + } + + public string CreateRefreshToken(User user) + { + var expires = DateTime.Now.AddMinutes(_configuration.RefreshTokenExpire); + return CreateToken([new Claim("TokenType", "RefreshToken")], user, expires); + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Interface/ITokenFactory.cs b/SiteManagementSystem(SoftwareEngineering)/Interface/ITokenFactory.cs new file mode 100644 index 0000000..7bb3cfe --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Interface/ITokenFactory.cs @@ -0,0 +1,10 @@ +using SiteManagementSystem_SoftwareEngineering_.Entity; + +namespace SiteManagementSystem_SoftwareEngineering_.Interface +{ + public interface ITokenFactory + { + public string CreateAccessToken(User user); + public string CreateRefreshToken(User user); + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Interface/IUserManageService.cs b/SiteManagementSystem(SoftwareEngineering)/Interface/IUserManageService.cs new file mode 100644 index 0000000..f1820c5 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Interface/IUserManageService.cs @@ -0,0 +1,10 @@ +using SiteManagementSystem_SoftwareEngineering_.Entity; + +namespace SiteManagementSystem_SoftwareEngineering_.Interface +{ + public interface IUserManageService + { + public void AddUser(User user); + public (bool, string) IsUserExist(ref User user); + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Model/ApiResponse.cs b/SiteManagementSystem(SoftwareEngineering)/Model/ApiResponse.cs new file mode 100644 index 0000000..43b34b9 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Model/ApiResponse.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; + +namespace SiteManagementSystem_SoftwareEngineering_.Model +{ + public class ApiResponse : OkObjectResult + { + private ApiResponse(int code, string message, object? data) + : base(new ResponseModel(code, message, data)) { } + + public static ApiResponse Success(string message = "", object? data = null) => + new ApiResponse(200, message, data); + public static ApiResponse Forbidden(string message = "", object? data = null) => + new ApiResponse(403, message, data); + public static ApiResponse Fail(string message = "", object? data = null) => + new ApiResponse(500, message, data); + } + + file class ResponseModel + { + public int Code { get; set; } + public string Message { get; set; } + public object? Data { get; set; } + + public ResponseModel(int code, string message, object? data) + { + Code = code; + Message = message; + Data = data; + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Model/SecretConfig.cs b/SiteManagementSystem(SoftwareEngineering)/Model/SecretConfig.cs new file mode 100644 index 0000000..6c19824 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Model/SecretConfig.cs @@ -0,0 +1,7 @@ +namespace IwutMail.Model +{ + public class SecretConfig + { + public string HashSalt { get; set; } = null!; + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Model/TokenFactoryConfiguration.cs b/SiteManagementSystem(SoftwareEngineering)/Model/TokenFactoryConfiguration.cs new file mode 100644 index 0000000..f60c17c --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Model/TokenFactoryConfiguration.cs @@ -0,0 +1,26 @@ +namespace SiteManagementSystem_SoftwareEngineering_.Model +{ + public class TokenFactoryConfiguration + { + /// + /// AccessToken有效期(分钟) + /// + public int AccessTokenExpire { get; set; } = 60; + + /// + /// RefreshToken有效期(分钟) + /// + public int RefreshTokenExpire { get; set; } = 10080; + + /// + /// 在RefreshToken过期前多久自动刷新RefreshToken + /// + public int RefreshTokenBefore { get; set; } = 1440; + + public string Issuer { get; set; } = null!; + + public string Audience { get; set; } = null!; + + public string SigningKey { get; set; } = null!; + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Model/UserModel.cs b/SiteManagementSystem(SoftwareEngineering)/Model/UserModel.cs new file mode 100644 index 0000000..476b486 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Model/UserModel.cs @@ -0,0 +1,16 @@ +using SiteManagementSystem_SoftwareEngineering_.Entity; +using System.ComponentModel.DataAnnotations; + +namespace SiteManagementSystem_SoftwareEngineering_.Model +{ + public class UserModel + { + public string RoleName { get; set; } = null!; + [Required] + [StringLength(50)] + public string Name { get; set; } = null!; + [Required] + [StringLength(50)] + public string Secret { get; set; } = null!; + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Program.cs b/SiteManagementSystem(SoftwareEngineering)/Program.cs new file mode 100644 index 0000000..568832d --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Program.cs @@ -0,0 +1,69 @@ +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using SiteManagementSystem_SoftwareEngineering_.Extension; +using SiteManagementSystem_SoftwareEngineering_.Service; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddDbContext(options => + options.UseMySql( + builder.Configuration.GetConnectionString("SQL"), + MariaDbServerVersion.AutoDetect(builder.Configuration.GetConnectionString("SQL")) + ) +); +builder + .Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + var jwtConfig = builder.Configuration.GetSection("Jwt"); + var key = Encoding.UTF8.GetBytes(jwtConfig.GetValue("SecurityKey")!); + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtConfig.GetValue("Issuer"), + ValidAudience = jwtConfig.GetValue("Audience"), + IssuerSigningKey = new SymmetricSecurityKey(key) + }; + }); +builder + .Services.AddTokenFactory(options => + { + var config = builder.Configuration.GetSection("Jwt"); + options.Issuer = config.GetValue("Issuer")!; + options.Audience = config.GetValue("Audience")!; + options.SigningKey = config.GetValue("SecurityKey")!; + options.AccessTokenExpire = config.GetValue("AccessTokenExpire"); + options.RefreshTokenExpire = config.GetValue("RefreshTokenExpire"); + options.RefreshTokenBefore = config.GetValue("RefreshTokenBefore"); + }) + .AddUserManager(options => + options.HashSalt = builder.Configuration.GetValue("SecretSalt")! + ); +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/SiteManagementSystem(SoftwareEngineering)/Properties/launchSettings.json b/SiteManagementSystem(SoftwareEngineering)/Properties/launchSettings.json new file mode 100644 index 0000000..1ef4c31 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5244" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7087;http://localhost:5244" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:4477", + "sslPort": 44379 + } + } +} \ No newline at end of file diff --git a/SiteManagementSystem(SoftwareEngineering)/Service/SQLService.cs b/SiteManagementSystem(SoftwareEngineering)/Service/SQLService.cs new file mode 100644 index 0000000..f27b7c5 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Service/SQLService.cs @@ -0,0 +1,16 @@ + +using Microsoft.EntityFrameworkCore; +using SiteManagementSystem_SoftwareEngineering_.Entity; + +namespace SiteManagementSystem_SoftwareEngineering_.Service +{ + public class SQLService : DbContext + { + public SQLService(DbContextOptions options) + : base(options) + { + Database.EnsureCreated(); + } + public DbSet Users { get; set; } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/Service/UserManagerService.cs b/SiteManagementSystem(SoftwareEngineering)/Service/UserManagerService.cs new file mode 100644 index 0000000..006fda5 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/Service/UserManagerService.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using SiteManagementSystem_SoftwareEngineering_.Entity; +using SiteManagementSystem_SoftwareEngineering_.Interface; +using System.Security.Cryptography; +using System.Text; + +namespace SiteManagementSystem_SoftwareEngineering_.Service +{ + public enum IdentityPolicyNames + { + NotSupport, + CommonUser, + Administrator + } + public class UserManagerService + { + public class UserManageService( + SQLService storageService, + ILogger logger, + string secretSalt + ) : IUserManageService + { + private readonly SQLService _storageService = storageService; + private readonly ILogger _logger = logger; + private readonly string _salt = secretSalt; + private static readonly object _lock = new(); + public void AddUser(User user) + { + if (user.Name is null) + throw new ArgumentException("Miss Name!"); + if (user.RoleName is null) + throw new ArgumentException("Miss PolicyName!"); + if (user.Role is IdentityPolicyNames.NotSupport) + throw new ArgumentException("PolicyName not support"); + if (user.Secret is null) + throw new ArgumentException("Miss Secret!"); + user.HashSecret = ComputeHash(user.Secret); + if (_storageService.Users.Where(x => x.Name==user.Name).Any()) + throw new InvalidOperationException($"已存在名字为{user.Name}的用户"); + lock (_lock) + { + _storageService.Users.Add(user); + _storageService.SaveChanges(); + } + } + + public (bool, string) IsUserExist(ref User user) + { + string name = user.Name; + var result = _storageService.Users.Where(x => x.Name == name); + if (!result.Any()) + return (false, "账号不存在"); + if (result.First().HashSecret != ComputeHash(user.Secret)) + return (false, "密码不匹配"); + user = (User)result.First().Clone(); + return (true, ""); + } + + private string ComputeHash(string key) + { + if (key.IsNullOrEmpty()) + throw new ArgumentException("ComputeHash: Key is null or empty."); + return Convert.ToBase64String( + SHA256.HashData(Encoding.UTF8.GetBytes(_salt + key + _salt)) + ); + } + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).csproj b/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).csproj new file mode 100644 index 0000000..9f1b7cc --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + SiteManagementSystem_SoftwareEngineering_ + 57011486-3ffe-4195-8276-49f577f48fd4 + Linux + + + + + + + + + + + diff --git a/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).http b/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).http new file mode 100644 index 0000000..bdb9094 --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/SiteManagementSystem(SoftwareEngineering).http @@ -0,0 +1,6 @@ +@SiteManagementSystem_SoftwareEngineering__HostAddress = http://localhost:5244 + +GET {{SiteManagementSystem_SoftwareEngineering__HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/SiteManagementSystem(SoftwareEngineering)/appsettings.Development.json b/SiteManagementSystem(SoftwareEngineering)/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SiteManagementSystem(SoftwareEngineering)/appsettings.json b/SiteManagementSystem(SoftwareEngineering)/appsettings.json new file mode 100644 index 0000000..a41b3be --- /dev/null +++ b/SiteManagementSystem(SoftwareEngineering)/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SQL": "Server=host.docker.internal;Port=3306;Database=SiteManagementSystem;Uid=SiteManagementSystem;Pwd=SiteManagementSystem;" + }, + "Jwt": { + "SecurityKey": "TheSecretKeyForIwutMail,RandomInfo:z#$WX%ec56rv^b8n", + "Issuer": "iwut-Mail", + "Audience": "iwut-app", + "AccessTokenExpire": 60, + "RefreshTokenExpire": 432000, + "RefreshTokenBefore": 10080 + }, + "SecretSalt": "@#$5$D55fR^YTF#S$D%F^4e5RT^e%^R67tED$5rf67tgS4ed5rf6g4d5f", + "AllowedHosts": "*" +}