From b6043126fae039c58512f60a576b10925b06df4c Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 29 Jan 2020 00:17:45 +0800 Subject: ... --- Timeline/Services/UserService.cs | 271 ++++++++++++++++++++------------------- 1 file changed, 139 insertions(+), 132 deletions(-) (limited to 'Timeline/Services/UserService.cs') diff --git a/Timeline/Services/UserService.cs b/Timeline/Services/UserService.cs index 104db1b0..c5595c99 100644 --- a/Timeline/Services/UserService.cs +++ b/Timeline/Services/UserService.cs @@ -1,13 +1,14 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Timeline.Entities; using Timeline.Helpers; using Timeline.Models; using Timeline.Models.Validation; +using static Timeline.Resources.Services.UserService; namespace Timeline.Services { @@ -18,12 +19,12 @@ namespace Timeline.Services /// /// The username of the user to verify. /// The password of the user to verify. - /// The user info. + /// The user info and auth info. /// Thrown when or is null. - /// Thrown when username is of bad format. + /// Thrown when username is of bad format. /// Thrown when the user with given username does not exist. /// Thrown when password is wrong. - Task VerifyCredential(string username, string password); + Task VerifyCredential(string username, string password); /// /// Try to get a user by id. @@ -31,7 +32,7 @@ namespace Timeline.Services /// The id of the user. /// The user info. /// Thrown when the user with given id does not exist. - Task GetUserById(long id); + Task GetUserById(long id); /// /// Get the user info of given username. @@ -39,30 +40,51 @@ namespace Timeline.Services /// Username of the user. /// The info of the user. /// Thrown when is null. - /// Thrown when is of bad format. + /// Thrown when is of bad format. /// Thrown when the user with given username does not exist. - Task GetUserByUsername(string username); + Task GetUserByUsername(string username); /// /// List all users. /// /// The user info of users. - Task ListUsers(); + Task ListUsers(); /// - /// Create or modify a user with given username. - /// Username must be match with [a-zA-z0-9-_]. + /// Create a user with given info. /// - /// Username of user. - /// Password of user. - /// Whether the user is administrator. - /// - /// Return if a new user is created. - /// Return if a existing user is modified. - /// - /// Thrown when or is null. - /// Thrown when is of bad format. - Task PutUser(string username, string password, bool administrator); + /// The info of new user. + /// The password, can't be null or empty. + /// The id of the new user. + /// Thrown when is null. + /// Thrown when some fields in is bad. + /// Thrown when a user with given username already exists. + /// + /// must not be null and must be a valid username. + /// must not be null or empty. + /// is false by default (null). + /// Other fields are ignored. + /// + Task CreateUser(User info); + + /// + /// Modify a user's info. + /// + /// The id of the user. + /// The new info. May be null. + /// Thrown when some fields in is bad. + /// Thrown when user with given id does not exist. + /// + /// Only , and will be used. + /// If null, then not change. + /// Other fields are ignored. + /// After modified, even if nothing is changed, version will increase. + /// + /// can't be empty. + /// + /// Note: Whether is set or not, version will increase and not set to the specified value if there is one. + /// + Task ModifyUser(long id, User? info); /// /// Partially modify a user of given username. @@ -116,181 +138,164 @@ namespace Timeline.Services private readonly DatabaseContext _databaseContext; - private readonly IMemoryCache _memoryCache; private readonly IPasswordService _passwordService; private readonly UsernameValidator _usernameValidator = new UsernameValidator(); - public UserService(ILogger logger, IMemoryCache memoryCache, DatabaseContext databaseContext, IPasswordService passwordService) + public UserService(ILogger logger, DatabaseContext databaseContext, IPasswordService passwordService) { _logger = logger; - _memoryCache = memoryCache; _databaseContext = databaseContext; _passwordService = passwordService; } - private static string GenerateCacheKeyByUserId(long id) => $"user:{id}"; - - private void RemoveCache(long id) + private void CheckUsernameFormat(string username, string? paramName, Func? messageBuilder = null) { - var key = GenerateCacheKeyByUserId(id); - _memoryCache.Remove(key); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheRemove, ("Key", key))); - } - - private void CheckUsernameFormat(string username, string? message = null) - { - var (result, validationMessage) = _usernameValidator.Validate(username); - if (!result) + if (!_usernameValidator.Validate(username, out var message)) { - if (message == null) - throw new UsernameBadFormatException(username, validationMessage); + if (messageBuilder == null) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionUsernameBadFormat, message), paramName); else - throw new UsernameBadFormatException(username, validationMessage, message); + throw new ArgumentException(messageBuilder(message), paramName); } } - private static UserInfo CreateUserInfoFromEntity(UserEntity user) + private static User CreateUserFromEntity(UserEntity entity) { - return new UserInfo + return new User { - Id = user.Id, - Username = user.Name, - Administrator = UserRoleConvert.ToBool(user.RoleString), - Version = user.Version + Username = entity.Username, + Administrator = UserRoleConvert.ToBool(entity.Roles), + Nickname = string.IsNullOrEmpty(entity.Nickname) ? entity.Username : entity.Nickname, + Version = entity.Version }; } - public async Task VerifyCredential(string username, string password) + public async Task VerifyCredential(string username, string password) { if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); - CheckUsernameFormat(username); + CheckUsernameFormat(username, nameof(username)); - // We need password info, so always check the database. - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + var entity = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); - if (user == null) + if (entity == null) throw new UserNotExistException(username); - if (!_passwordService.VerifyPassword(user.EncryptedPassword, password)) + if (!_passwordService.VerifyPassword(entity.Password, password)) throw new BadPasswordException(password); - return CreateUserInfoFromEntity(user); + return CreateUserFromEntity(entity); } - public async Task GetUserById(long id) + public async Task GetUserById(long id) { - var key = GenerateCacheKeyByUserId(id); - if (!_memoryCache.TryGetValue(key, out var cache)) - { - // no cache, check the database - var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - - if (user == null) - throw new UserNotExistException(id); + var user = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); - // create cache - cache = CreateUserInfoFromEntity(user); - _memoryCache.CreateEntry(key).SetValue(cache); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogCacheCreate, ("Key", key))); - } + if (user == null) + throw new UserNotExistException(id); - return cache; + return CreateUserFromEntity(user); } - public async Task GetUserByUsername(string username) + public async Task GetUserByUsername(string username) { if (username == null) throw new ArgumentNullException(nameof(username)); - CheckUsernameFormat(username); + CheckUsernameFormat(username, nameof(username)); - var entity = await _databaseContext.Users - .Where(user => user.Name == username) - .SingleOrDefaultAsync(); + var entity = await _databaseContext.Users.Where(user => user.Username == username).SingleOrDefaultAsync(); if (entity == null) throw new UserNotExistException(username); - return CreateUserInfoFromEntity(entity); + return CreateUserFromEntity(entity); } - public async Task ListUsers() + public async Task ListUsers() { var entities = await _databaseContext.Users.ToArrayAsync(); - return entities.Select(user => CreateUserInfoFromEntity(user)).ToArray(); + return entities.Select(user => CreateUserFromEntity(user)).ToArray(); } - public async Task PutUser(string username, string password, bool administrator) + public async Task CreateUser(User info) { - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (password == null) - throw new ArgumentNullException(nameof(password)); - CheckUsernameFormat(username); + if (info == null) + throw new ArgumentNullException(nameof(info)); - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + if (string.IsNullOrEmpty(info.Username)) + throw new ArgumentException(ExceptionUsernameNullOrEmpty, nameof(info)); - if (user == null) - { - var newUser = new UserEntity - { - Name = username, - EncryptedPassword = _passwordService.HashPassword(password), - RoleString = UserRoleConvert.ToString(administrator), - Avatar = null - }; - await _databaseContext.AddAsync(newUser); - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseCreate, - ("Id", newUser.Id), ("Username", username), ("Administrator", administrator))); - return PutResult.Create; - } + CheckUsernameFormat(info.Username, nameof(info)); - user.EncryptedPassword = _passwordService.HashPassword(password); - user.RoleString = UserRoleConvert.ToString(administrator); - user.Version += 1; + if (string.IsNullOrEmpty(info.Password)) + throw new ArgumentException(ExceptionPasswordNullOrEmpty); + + var username = info.Username; + + var conflict = await _databaseContext.Users.AnyAsync(u => u.Username == username); + + if (conflict) + throw new UsernameConfictException(username); + + var administrator = info.Administrator ?? false; + var password = info.Password; + + var newEntity = new UserEntity + { + Username = username, + Password = _passwordService.HashPassword(password), + Roles = UserRoleConvert.ToString(administrator), + Version = 1 + }; + _databaseContext.Users.Add(newEntity); await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, - ("Id", user.Id), ("Username", username), ("Administrator", administrator))); - //clear cache - RemoveCache(user.Id); + _logger.LogInformation(Log.Format(LogDatabaseCreate, + ("Id", newEntity.Id), ("Username", username), ("Administrator", administrator))); - return PutResult.Modify; + return newEntity.Id; } - public async Task PatchUser(string username, string? password, bool? administrator) + public async Task ModifyUser(long id, User? info) { - if (username == null) - throw new ArgumentNullException(nameof(username)); - CheckUsernameFormat(username); + if (info != null && info.Password != null && info.Password.Length == 0) + throw new ArgumentException(ExceptionPasswordEmpty, nameof(info)); - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); - if (user == null) - throw new UserNotExistException(username); + var entity = await _databaseContext.Users.Where(u => u.Id == id).SingleOrDefaultAsync(); + if (entity == null) + throw new UserNotExistException(id); - if (password != null) + if (info != null) { - user.EncryptedPassword = _passwordService.HashPassword(password); - } + var password = info.Password; + if (password != null) + { + entity.Password = _passwordService.HashPassword(password); + } - if (administrator != null) - { - user.RoleString = UserRoleConvert.ToString(administrator.Value); + var administrator = info.Administrator; + if (administrator.HasValue) + { + entity.Roles = UserRoleConvert.ToString(administrator.Value); + } + + var nickname = info.Nickname; + if (nickname != null) + { + entity.Nickname = nickname; + } } - user.Version += 1; - await _databaseContext.SaveChangesAsync(); - _logger.LogInformation(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id)); + entity.Version += 1; - //clear cache - RemoveCache(user.Id); + await _databaseContext.SaveChangesAsync(); + _logger.LogInformation(LogDatabaseUpdate, ("Id", id)); } public async Task DeleteUser(string username) @@ -299,7 +304,7 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(username)); CheckUsernameFormat(username); - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(username); @@ -309,7 +314,7 @@ namespace Timeline.Services ("Id", user.Id))); //clear cache - RemoveCache(user.Id); + await _cache.RemoveCache(user.Id); } public async Task ChangePassword(string username, string oldPassword, string newPassword) @@ -322,21 +327,21 @@ namespace Timeline.Services throw new ArgumentNullException(nameof(newPassword)); CheckUsernameFormat(username); - var user = await _databaseContext.Users.Where(u => u.Name == username).SingleOrDefaultAsync(); + var user = await _databaseContext.Users.Where(u => u.Username == username).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(username); - var verifyResult = _passwordService.VerifyPassword(user.EncryptedPassword, oldPassword); + var verifyResult = _passwordService.VerifyPassword(user.Password, oldPassword); if (!verifyResult) throw new BadPasswordException(oldPassword); - user.EncryptedPassword = _passwordService.HashPassword(newPassword); + user.Password = _passwordService.HashPassword(newPassword); user.Version += 1; await _databaseContext.SaveChangesAsync(); _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id), ("Operation", "Change password"))); //clear cache - RemoveCache(user.Id); + await _cache.RemoveCache(user.Id); } public async Task ChangeUsername(string oldUsername, string newUsername) @@ -348,20 +353,22 @@ namespace Timeline.Services CheckUsernameFormat(oldUsername, Resources.Services.UserService.ExceptionOldUsernameBadFormat); CheckUsernameFormat(newUsername, Resources.Services.UserService.ExceptionNewUsernameBadFormat); - var user = await _databaseContext.Users.Where(u => u.Name == oldUsername).SingleOrDefaultAsync(); + var user = await _databaseContext.Users.Where(u => u.Username == oldUsername).SingleOrDefaultAsync(); if (user == null) throw new UserNotExistException(oldUsername); - var conflictUser = await _databaseContext.Users.Where(u => u.Name == newUsername).SingleOrDefaultAsync(); + var conflictUser = await _databaseContext.Users.Where(u => u.Username == newUsername).SingleOrDefaultAsync(); if (conflictUser != null) throw new UsernameConfictException(newUsername); - user.Name = newUsername; + user.Username = newUsername; user.Version += 1; await _databaseContext.SaveChangesAsync(); _logger.LogInformation(Log.Format(Resources.Services.UserService.LogDatabaseUpdate, ("Id", user.Id), ("Old Username", oldUsername), ("New Username", newUsername))); - RemoveCache(user.Id); + await _cache.RemoveCache(user.Id); } + + } } -- cgit v1.2.3