Files
OrpaonVision/OrpaonVision.ConfigApp/Infrastructure/Services/UserService.cs
2026-04-06 22:04:05 +08:00

634 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Logging;
using OrpaonVision.Core.Results;
using OrpaonVision.Model.Security;
using System.Security.Cryptography;
using System.Text;
namespace OrpaonVision.ConfigApp.Infrastructure.Services;
/// <summary>
/// 用户管理服务实现。
/// </summary>
public sealed class UserService : IUserService
{
private readonly ILogger<UserService> _logger;
private readonly List<UserModel> _users;
private readonly Dictionary<string, int> _loginFailCounts;
/// <summary>
/// 构造函数。
/// </summary>
public UserService(ILogger<UserService> logger)
{
_logger = logger;
_users = new List<UserModel>();
_loginFailCounts = new Dictionary<string, int>();
// 初始化示例数据
InitializeSampleData();
}
/// <inheritdoc />
public Result<UserModel> CreateUser(UserModel user)
{
try
{
if (user == null)
{
return Result<UserModel>.Fail("USER_NULL", "用户不能为空。");
}
if (string.IsNullOrWhiteSpace(user.Username))
{
return Result<UserModel>.Fail("USERNAME_REQUIRED", "用户名不能为空。");
}
// 检查用户名是否已存在
if (_users.Any(u => u.Username.Equals(user.Username, StringComparison.OrdinalIgnoreCase)))
{
return Result<UserModel>.Fail("USERNAME_EXISTS", "用户名已存在。");
}
_logger.LogInformation("正在创建用户: {Username}", user.Username);
// 生成密码哈希
var (hash, salt) = GeneratePasswordHash(user.PasswordHash);
user.Id = Guid.NewGuid();
user.PasswordHash = hash;
user.PasswordSalt = salt;
user.Status = UserStatus.Enabled;
user.IsFirstLogin = true;
user.LoginFailedCount = 0;
user.CreatedAtUtc = DateTime.UtcNow;
user.UpdatedAtUtc = DateTime.UtcNow;
_users.Add(user);
_logger.LogInformation("用户创建成功: {UserId} - {Username}", user.Id, user.Username);
return Result<UserModel>.Success(user, message: "用户创建成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "创建用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CREATE_USER_FAILED", "创建用户失败。", traceId);
return Result<UserModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<UserModel> UpdateUser(UserModel user)
{
try
{
if (user == null)
{
return Result<UserModel>.Fail("USER_NULL", "用户不能为空。");
}
var existingUser = _users.FirstOrDefault(u => u.Id == user.Id);
if (existingUser == null)
{
return Result<UserModel>.Fail("USER_NOT_FOUND", $"未找到ID为 {user.Id} 的用户。");
}
_logger.LogInformation("正在更新用户: {UserId} - {Username}", user.Id, user.Username);
// 更新属性(不包含密码相关字段)
existingUser.DisplayName = user.DisplayName;
existingUser.Email = user.Email;
existingUser.PhoneNumber = user.PhoneNumber;
existingUser.Remark = user.Remark;
existingUser.UpdatedAtUtc = DateTime.UtcNow;
existingUser.UpdatedBy = user.UpdatedBy;
_logger.LogInformation("用户更新成功: {UserId} - {Username}", user.Id, user.Username);
return Result<UserModel>.Success(existingUser, message: "用户更新成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "更新用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "UPDATE_USER_FAILED", "更新用户失败。", traceId);
return Result<UserModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result DeleteUser(Guid userId)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
_logger.LogInformation("正在删除用户: {UserId} - {Username}", user.Id, user.Username);
// 软删除
user.Status = UserStatus.Deleted;
user.UpdatedAtUtc = DateTime.UtcNow;
user.UpdatedBy = "System";
_logger.LogInformation("用户删除成功: {UserId} - {Username}", user.Id, user.Username);
return Result.Success(message: "用户删除成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "删除用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "DELETE_USER_FAILED", "删除用户失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<UserModel> GetUserById(Guid userId)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result<UserModel>.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
// 清除敏感信息
var safeUser = CloneUserWithoutSensitiveData(user);
return Result<UserModel>.Success(safeUser, message: "获取用户成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_USER_FAILED", "获取用户失败。", traceId);
return Result<UserModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<UserModel> GetUserByUsername(string username)
{
try
{
if (string.IsNullOrWhiteSpace(username))
{
return Result<UserModel>.Fail("USERNAME_REQUIRED", "用户名不能为空。");
}
var user = _users.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
if (user == null)
{
return Result<UserModel>.Fail("USER_NOT_FOUND", $"未找到用户名为 {username} 的用户。");
}
// 清除敏感信息
var safeUser = CloneUserWithoutSensitiveData(user);
return Result<UserModel>.Success(safeUser, message: "获取用户成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_USER_FAILED", "获取用户失败。", traceId);
return Result<UserModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<bool> VerifyPassword(string username, string password)
{
try
{
if (string.IsNullOrWhiteSpace(username))
{
return Result<bool>.Fail("USERNAME_REQUIRED", "用户名不能为空。");
}
if (string.IsNullOrWhiteSpace(password))
{
return Result<bool>.Fail("PASSWORD_REQUIRED", "密码不能为空。");
}
var user = _users.FirstOrDefault(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase));
if (user == null)
{
return Result<bool>.Fail("USER_NOT_FOUND", $"未找到用户名为 {username} 的用户。");
}
if (user.Status != UserStatus.Enabled)
{
return Result<bool>.Fail("USER_INACTIVE", "用户已被禁用。");
}
// 检查登录失败次数
if (_loginFailCounts.TryGetValue(username, out var failCount) && failCount >= 5)
{
return Result<bool>.Fail("ACCOUNT_LOCKED", "账户已被锁定,请联系管理员。");
}
// 验证密码(简化版本,实际应该使用哈希比较)
var isPasswordValid = VerifyPasswordHash(password, user.PasswordHash);
if (isPasswordValid)
{
// 登录成功,清除失败计数
_loginFailCounts.Remove(username);
_logger.LogInformation("用户 {Username} 登录成功", username);
return Result<bool>.Success(true, "登录成功。");
}
else
{
// 登录失败,增加失败计数
_loginFailCounts[username] = _loginFailCounts.GetValueOrDefault(username, 0) + 1;
_logger.LogWarning("用户 {Username} 登录失败,当前失败次数: {FailCount}", username, _loginFailCounts[username]);
var remainingAttempts = 5 - _loginFailCounts[username];
var message = remainingAttempts > 0
? $"密码错误,还有 {remainingAttempts} 次尝试机会。"
: "账户已被锁定,请联系管理员。";
return Result<bool>.Fail("PASSWORD_INCORRECT", message);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "验证密码失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "VERIFY_PASSWORD_FAILED", "验证密码失败。", traceId);
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<PagedResult<UserModel>> GetUserPagedList(int pageIndex = 1, int pageSize = 20, UserStatus? status = null, string? keyword = null)
{
try
{
var query = _users.Where(u => u.Status != UserStatus.Deleted).AsQueryable();
// 状态过滤
if (status.HasValue)
{
query = query.Where(u => u.Status == status.Value);
}
// 关键词搜索
if (!string.IsNullOrWhiteSpace(keyword))
{
query = query.Where(u =>
u.Username.Contains(keyword, StringComparison.OrdinalIgnoreCase) ||
u.DisplayName.Contains(keyword, StringComparison.OrdinalIgnoreCase) ||
(u.Email != null && u.Email.Contains(keyword, StringComparison.OrdinalIgnoreCase)));
}
// 排序
query = query.OrderByDescending(u => u.CreatedAtUtc);
var totalCount = query.Count();
var items = query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
// 清除敏感信息
var safeItems = items.Select(CloneUserWithoutSensitiveData).ToList();
var pagedResult = PagedResult<UserModel>.Success(safeItems, totalCount, pageIndex, pageSize);
return Result<PagedResult<UserModel>>.Success(pagedResult, message: "获取用户列表成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取用户列表失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_USER_LIST_FAILED", "获取用户列表失败。", traceId);
return Result<PagedResult<UserModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result EnableUser(Guid userId, string enabledBy)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
_logger.LogInformation("正在启用用户: {UserId} - {Username}", user.Id, user.Username);
user.Status = UserStatus.Enabled;
user.UpdatedAtUtc = DateTime.UtcNow;
user.UpdatedBy = enabledBy;
_logger.LogInformation("用户启用成功: {UserId} - {Username}", user.Id, user.Username);
return Result.Success(message: "用户启用成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "启用用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "ENABLE_USER_FAILED", "启用用户失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result DisableUser(Guid userId, string disabledBy)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
_logger.LogInformation("正在禁用用户: {UserId} - {Username}", user.Id, user.Username);
user.Status = UserStatus.Disabled;
user.UpdatedAtUtc = DateTime.UtcNow;
user.UpdatedBy = disabledBy;
_logger.LogInformation("用户禁用成功: {UserId} - {Username}", user.Id, user.Username);
return Result.Success(message: "用户禁用成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "禁用用户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "DISABLE_USER_FAILED", "禁用用户失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result ResetPassword(Guid userId, string newPassword, string resetBy)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
if (string.IsNullOrWhiteSpace(newPassword))
{
return Result.Fail("PASSWORD_REQUIRED", "新密码不能为空。");
}
_logger.LogInformation("正在重置用户密码: {UserId} - {Username}", user.Id, user.Username);
// 生成新密码哈希
var (hash, salt) = GeneratePasswordHash(newPassword);
user.PasswordHash = hash;
user.PasswordSalt = salt;
user.IsFirstLogin = true;
user.LoginFailedCount = 0;
user.LockedUntilUtc = null;
user.UpdatedAtUtc = DateTime.UtcNow;
user.UpdatedBy = resetBy;
_logger.LogInformation("用户密码重置成功: {UserId} - {Username}", user.Id, user.Username);
return Result.Success(message: "用户密码重置成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "重置用户密码失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "RESET_PASSWORD_FAILED", "重置用户密码失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result UnlockUser(Guid userId, string unlockedBy)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
_logger.LogInformation("正在解锁用户账户: {UserId} - {Username}", user.Id, user.Username);
user.Status = UserStatus.Enabled;
user.LoginFailedCount = 0;
user.LockedUntilUtc = null;
user.UpdatedAtUtc = DateTime.UtcNow;
user.UpdatedBy = unlockedBy;
// 清除登录失败计数
_loginFailCounts.Remove(user.Username);
_logger.LogInformation("用户账户解锁成功: {UserId} - {Username}", user.Id, user.Username);
return Result.Success(message: "用户账户解锁成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "解锁用户账户失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "UNLOCK_USER_FAILED", "解锁用户账户失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result UpdateLastLogin(Guid userId, string ipAddress, string userAgent)
{
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
{
return Result.Fail("USER_NOT_FOUND", $"未找到ID为 {userId} 的用户。");
}
user.LastLoginAtUtc = DateTime.UtcNow;
user.LastLoginIp = ipAddress;
user.LastLoginIp = userAgent; // 这里应该是UserAgent但为了简化使用同一个字段
_logger.LogInformation("更新用户最后登录信息成功: {UserId}", userId);
return Result.Success(message: "更新用户最后登录信息成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "更新用户最后登录信息失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "UPDATE_LAST_LOGIN_FAILED", "更新用户最后登录信息失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public Result<UserStatistics> GetUserStatistics()
{
try
{
var now = DateTime.UtcNow;
var today = now.Date;
var thisWeekStart = today.AddDays(-(int)today.DayOfWeek);
var thisMonthStart = new DateTime(now.Year, now.Month, 1);
var stats = new UserStatistics
{
TotalUsers = _users.Count(u => u.Status != UserStatus.Deleted),
EnabledUsers = _users.Count(u => u.Status == UserStatus.Enabled),
DisabledUsers = _users.Count(u => u.Status == UserStatus.Disabled),
LockedUsers = _users.Count(u => u.Status == UserStatus.Locked),
TodayLoginUsers = _users.Count(u => u.LastLoginAtUtc.HasValue && u.LastLoginAtUtc.Value.Date == today),
ThisWeekLoginUsers = _users.Count(u => u.LastLoginAtUtc.HasValue && u.LastLoginAtUtc.Value >= thisWeekStart),
ThisMonthLoginUsers = _users.Count(u => u.LastLoginAtUtc.HasValue && u.LastLoginAtUtc.Value >= thisMonthStart)
};
return Result<UserStatistics>.Success(stats, message: "获取用户统计信息成功。");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取用户统计信息失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_USER_STATISTICS_FAILED", "获取用户统计信息失败。", traceId);
return Result<UserStatistics>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <summary>
/// 生成密码哈希。
/// </summary>
private static (string Hash, string Salt) GeneratePasswordHash(string password)
{
using var hmac = new HMACSHA256();
var salt = Convert.ToBase64String(hmac.Key);
var hash = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(password)));
return (hash, salt);
}
/// <summary>
/// 验证密码哈希。
/// </summary>
private static bool VerifyPasswordHash(string password, string storedHash)
{
try
{
// 简化版本:直接比较明文(实际项目中应该使用安全的哈希算法)
// 这里为了演示,使用简单的哈希方式
var computedHash = ComputeSimpleHash(password);
return storedHash.Equals(computedHash, StringComparison.OrdinalIgnoreCase);
}
catch
{
return false;
}
}
/// <summary>
/// 计算简单哈希(仅用于演示,生产环境应使用 BCrypt 等安全算法)。
/// </summary>
private static string ComputeSimpleHash(string input)
{
using var sha256 = SHA256.Create();
var bytes = Encoding.UTF8.GetBytes(input + "OrpaonVision2024"); // 加盐
var hash = sha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
/// <summary>
/// 克隆用户信息,排除敏感数据。
/// </summary>
private static UserModel CloneUserWithoutSensitiveData(UserModel user)
{
return new UserModel
{
Id = user.Id,
Username = user.Username,
DisplayName = user.DisplayName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
Status = user.Status,
LastLoginAtUtc = user.LastLoginAtUtc,
LastLoginIp = user.LastLoginIp,
LoginFailedCount = user.LoginFailedCount,
LockedUntilUtc = user.LockedUntilUtc,
IsFirstLogin = user.IsFirstLogin,
CreatedAtUtc = user.CreatedAtUtc,
UpdatedAtUtc = user.UpdatedAtUtc,
CreatedBy = user.CreatedBy,
UpdatedBy = user.UpdatedBy,
Remark = user.Remark,
// 不包含密码相关字段
PasswordHash = string.Empty,
PasswordSalt = string.Empty
};
}
/// <summary>
/// 初始化示例数据。
/// </summary>
private void InitializeSampleData()
{
var sampleUsers = new List<UserModel>
{
new UserModel
{
Id = Guid.NewGuid(),
Username = "admin",
DisplayName = "系统管理员",
Email = "admin@orpaon.com",
Status = UserStatus.Enabled,
PasswordHash = ComputeSimpleHash("admin"),
PasswordSalt = "OrpaonVision2024",
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
UpdatedAtUtc = DateTime.UtcNow.AddDays(-1),
CreatedBy = "System",
UpdatedBy = "System"
},
new UserModel
{
Id = Guid.NewGuid(),
Username = "operator1",
DisplayName = "操作员1",
Email = "operator1@orpaon.com",
Status = UserStatus.Enabled,
PasswordHash = ComputeSimpleHash("operator1"),
PasswordSalt = "OrpaonVision2024",
CreatedAtUtc = DateTime.UtcNow.AddDays(-20),
UpdatedAtUtc = DateTime.UtcNow.AddDays(-2),
CreatedBy = "admin",
UpdatedBy = "admin"
},
new UserModel
{
Id = Guid.NewGuid(),
Username = "engineer1",
DisplayName = "工艺工程师1",
Email = "engineer1@orpaon.com",
Status = UserStatus.Enabled,
PasswordHash = ComputeSimpleHash("engineer1"),
PasswordSalt = "OrpaonVision2024",
CreatedAtUtc = DateTime.UtcNow.AddDays(-15),
UpdatedAtUtc = DateTime.UtcNow.AddDays(-3),
CreatedBy = "admin",
UpdatedBy = "admin"
}
};
_users.AddRange(sampleUsers);
foreach (var user in sampleUsers)
{
var (hash, salt) = GeneratePasswordHash("123456"); // 默认密码
user.PasswordHash = hash;
user.PasswordSalt = salt;
}
_users.AddRange(sampleUsers);
_logger.LogInformation("已初始化 {Count} 个示例用户", sampleUsers.Count);
}
}