using Microsoft.Extensions.Logging; using Microsoft.Data.SqlClient; using OrpaonVision.Core.Results; using OrpaonVision.Core.Security; using OrpaonVision.Model.Security; using System.Data; using System.Text.Json; namespace OrpaonVision.ConfigApp.Infrastructure.Persistence; /// /// SQL Server 用户仓储实现。 /// public sealed class SqlUserStore : IUserStore { private readonly ILogger _logger; private readonly string _connectionString; /// /// 构造函数。 /// public SqlUserStore(ILogger logger, string connectionString) { _logger = logger; _connectionString = connectionString; } /// public async Task> GetByIdAsync(Guid id) { try { const string sql = @" SELECT Id, Username, DisplayName, Email, PhoneNumber, PasswordHash, PasswordSalt, Status, LastLoginAtUtc, LastLoginIp, LoginFailedCount, LockedUntilUtc, IsFirstLogin, CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy, Remark FROM sec_users WHERE Id = @Id AND Status != @DeletedStatus"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Id", id); command.Parameters.AddWithValue("@DeletedStatus", (int)UserStatus.Deleted); using var reader = await command.ExecuteReaderAsync(); if (await reader.ReadAsync()) { return Result.Success(MapReaderToUser(reader)); } return Result.Success(null); } catch (Exception ex) { _logger.LogError(ex, "根据ID获取用户失败: {UserId}", id); return Result.Fail("USER_GET_BY_ID_FAILED", "获取用户失败"); } } /// public async Task> GetByUsernameAsync(string username) { try { const string sql = @" SELECT Id, Username, DisplayName, Email, PhoneNumber, PasswordHash, PasswordSalt, Status, LastLoginAtUtc, LastLoginIp, LoginFailedCount, LockedUntilUtc, IsFirstLogin, CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy, Remark FROM sec_users WHERE Username = @Username AND Status != @DeletedStatus"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Username", username); command.Parameters.AddWithValue("@DeletedStatus", (int)UserStatus.Deleted); using var reader = await command.ExecuteReaderAsync(); if (await reader.ReadAsync()) { return Result.Success(MapReaderToUser(reader)); } return Result.Success(null); } catch (Exception ex) { _logger.LogError(ex, "根据用户名获取用户失败: {Username}", username); return Result.Fail("USER_GET_BY_USERNAME_FAILED", "获取用户失败"); } } /// public async Task> CreateAsync(UserModel user) { try { const string sql = @" INSERT INTO sec_users ( Id, Username, DisplayName, Email, PhoneNumber, PasswordHash, PasswordSalt, Status, CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy, Remark ) VALUES ( @Id, @Username, @DisplayName, @Email, @PhoneNumber, @PasswordHash, @PasswordSalt, @Status, @CreatedAtUtc, @UpdatedAtUtc, @CreatedBy, @UpdatedBy, @Remark )"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Id", user.Id); command.Parameters.AddWithValue("@Username", user.Username); command.Parameters.AddWithValue("@DisplayName", user.DisplayName); command.Parameters.AddWithValue("@Email", user.Email); command.Parameters.AddWithValue("@PhoneNumber", (object?)user.PhoneNumber ?? DBNull.Value); command.Parameters.AddWithValue("@PasswordHash", user.PasswordHash); command.Parameters.AddWithValue("@PasswordSalt", user.PasswordSalt); command.Parameters.AddWithValue("@Status", (int)user.Status); command.Parameters.AddWithValue("@CreatedAtUtc", user.CreatedAtUtc); command.Parameters.AddWithValue("@UpdatedAtUtc", user.UpdatedAtUtc); command.Parameters.AddWithValue("@CreatedBy", user.CreatedBy); command.Parameters.AddWithValue("@UpdatedBy", user.UpdatedBy); command.Parameters.AddWithValue("@Remark", (object?)user.Remark ?? DBNull.Value); await command.ExecuteNonQueryAsync(); _logger.LogInformation("用户创建成功: {UserId} - {Username}", user.Id, user.Username); return Result.Success(user); } catch (Exception ex) { _logger.LogError(ex, "创建用户失败: {Username}", user.Username); return Result.Fail("USER_CREATE_FAILED", "创建用户失败"); } } /// public async Task> UpdateAsync(UserModel user) { try { const string sql = @" UPDATE sec_users SET DisplayName = @DisplayName, Email = @Email, PhoneNumber = @PhoneNumber, Status = @Status, UpdatedAtUtc = @UpdatedAtUtc, UpdatedBy = @UpdatedBy, Remark = @Remark WHERE Id = @Id"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Id", user.Id); command.Parameters.AddWithValue("@DisplayName", user.DisplayName); command.Parameters.AddWithValue("@Email", user.Email); command.Parameters.AddWithValue("@PhoneNumber", (object?)user.PhoneNumber ?? DBNull.Value); command.Parameters.AddWithValue("@Status", (int)user.Status); command.Parameters.AddWithValue("@UpdatedAtUtc", user.UpdatedAtUtc); command.Parameters.AddWithValue("@UpdatedBy", user.UpdatedBy); command.Parameters.AddWithValue("@Remark", (object?)user.Remark ?? DBNull.Value); var rowsAffected = await command.ExecuteNonQueryAsync(); if (rowsAffected == 0) { return Result.Fail("USER_NOT_FOUND", "用户不存在"); } _logger.LogInformation("用户更新成功: {UserId} - {Username}", user.Id, user.Username); return Result.Success(user); } catch (Exception ex) { _logger.LogError(ex, "更新用户失败: {UserId}", user.Id); return Result.Fail("USER_UPDATE_FAILED", "更新用户失败"); } } /// public async Task DeleteAsync(Guid id) { try { const string sql = "UPDATE sec_users SET Status = @DeletedStatus, UpdatedAtUtc = @UpdatedAtUtc WHERE Id = @Id"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Id", id); command.Parameters.AddWithValue("@DeletedStatus", (int)UserStatus.Deleted); command.Parameters.AddWithValue("@UpdatedAtUtc", DateTime.UtcNow); await command.ExecuteNonQueryAsync(); _logger.LogInformation("用户删除成功: {UserId}", id); return Result.Success("用户删除成功"); } catch (Exception ex) { _logger.LogError(ex, "删除用户失败: {UserId}", id); return Result.Fail("USER_DELETE_FAILED", "删除用户失败"); } } /// public async Task> UsernameExistsAsync(string username) { try { const string sql = "SELECT COUNT(1) FROM sec_users WHERE Username = @Username AND Status != @DeletedStatus"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Username", username); command.Parameters.AddWithValue("@DeletedStatus", (int)UserStatus.Deleted); var exists = (int)await command.ExecuteScalarAsync() > 0; return Result.Success(exists); } catch (Exception ex) { _logger.LogError(ex, "检查用户名是否存在失败: {Username}", username); return Result.Fail("USER_CHECK_USERNAME_FAILED", "检查用户名失败"); } } /// public async Task users, int totalCount)>> GetPagedListAsync( int pageIndex, int pageSize, string? keyword = null, UserStatus? status = null) { try { var offset = pageIndex * pageSize; var whereConditions = new List { "Status != @DeletedStatus" }; var parameters = new List { new("@DeletedStatus", (int)UserStatus.Deleted), new("@Offset", offset), new("@PageSize", pageSize) }; if (!string.IsNullOrWhiteSpace(keyword)) { whereConditions.Add("(Username LIKE @Keyword OR DisplayName LIKE @Keyword OR Email LIKE @Keyword)"); parameters.Add(new SqlParameter("@Keyword", $"%{keyword}%")); } if (status.HasValue) { whereConditions.Add("Status = @Status"); parameters.Add(new SqlParameter("@Status", (int)status.Value)); } var whereClause = string.Join(" AND ", whereConditions); // 查询总数 var countSql = $"SELECT COUNT(1) FROM sec_users WHERE {whereClause}"; // 查询数据 var dataSql = $@" SELECT Id, Username, DisplayName, Email, PhoneNumber, PasswordHash, PasswordSalt, Status, LastLoginAtUtc, LastLoginIp, LoginFailedCount, LockedUntilUtc, IsFirstLogin, CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy, Remark FROM sec_users WHERE {whereClause} ORDER BY CreatedAtUtc DESC OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); // 获取总数 using var countCommand = new SqlCommand(countSql, connection); countCommand.Parameters.AddRange(parameters.ToArray()); var totalCount = (int)await countCommand.ExecuteScalarAsync(); // 获取数据 var users = new List(); using var dataCommand = new SqlCommand(dataSql, connection); dataCommand.Parameters.AddRange(parameters.ToArray()); using var reader = await dataCommand.ExecuteReaderAsync(); while (await reader.ReadAsync()) { users.Add(MapReaderToUser(reader)); } return Result<(IReadOnlyList, int)>.Success((users.AsReadOnly(), totalCount)); } catch (Exception ex) { _logger.LogError(ex, "获取用户分页列表失败"); return Result<(IReadOnlyList, int)>.Fail("USER_GET_PAGED_LIST_FAILED", "获取用户列表失败"); } } /// public async Task UpdateLastLoginAsync(Guid userId, string ipAddress) { try { const string sql = @" UPDATE sec_users SET LastLoginAtUtc = @LastLoginAtUtc, LastLoginIp = @IpAddress, LoginFailedCount = 0, IsFirstLogin = 0, UpdatedAtUtc = @UpdatedAtUtc WHERE Id = @Id"; using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); using var command = new SqlCommand(sql, connection); command.Parameters.AddWithValue("@Id", userId); command.Parameters.AddWithValue("@LastLoginAtUtc", DateTime.UtcNow); command.Parameters.AddWithValue("@IpAddress", ipAddress); command.Parameters.AddWithValue("@UpdatedAtUtc", DateTime.UtcNow); await command.ExecuteNonQueryAsync(); _logger.LogInformation("更新用户最后登录信息成功: {UserId}", userId); return Result.Success("更新登录信息成功"); } catch (Exception ex) { _logger.LogError(ex, "更新用户最后登录信息失败: {UserId}", userId); return Result.Fail("USER_UPDATE_LOGIN_FAILED", "更新登录信息失败"); } } /// /// 将DataReader映射到UserModel。 /// private static UserModel MapReaderToUser(SqlDataReader reader) { return new UserModel { Id = reader.GetGuid("Id"), Username = reader.GetString("Username"), DisplayName = reader.GetString("DisplayName"), Email = reader.GetString("Email"), PhoneNumber = reader.IsDBNull("PhoneNumber") ? null : reader.GetString("PhoneNumber"), PasswordHash = reader.GetString("PasswordHash"), PasswordSalt = reader.GetString("PasswordSalt"), Status = (UserStatus)reader.GetInt32("Status"), LastLoginAtUtc = reader.IsDBNull("LastLoginAtUtc") ? null : reader.GetDateTime("LastLoginAtUtc"), LastLoginIp = reader.IsDBNull("LastLoginIp") ? null : reader.GetString("LastLoginIp"), LoginFailedCount = reader.GetInt32("LoginFailedCount"), LockedUntilUtc = reader.IsDBNull("LockedUntilUtc") ? null : reader.GetDateTime("LockedUntilUtc"), IsFirstLogin = reader.GetBoolean("IsFirstLogin"), CreatedAtUtc = reader.GetDateTime("CreatedAtUtc"), UpdatedAtUtc = reader.GetDateTime("UpdatedAtUtc"), CreatedBy = reader.GetString("CreatedBy"), UpdatedBy = reader.GetString("UpdatedBy"), Remark = reader.IsDBNull("Remark") ? null : reader.GetString("Remark") }; } }