25.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户服务接口

用户管理是任何系统的基础功能之一,本篇介绍了如何实现一个完整的用户管理模块,包括用户信息的增删改查、用户状态管理、分页查询、数据验证和权限控制。核心代码实现部分涵盖了控制器(UserController)、服务接口(IUserService)、请求/响应模型以及服务实现(UserServiceImpl),并详细说明了各接口的功能,如获取用户信息、分页获取用户列表、删除用户、禁用/启用用户和更新用户信息。最佳实践建议包括数据验证、性能优化、安全性和代码组织等方面,强调了使用数据注解、异步方法、依赖注入、权限控制和统一响应格式等措施。使用示例展示了如何通过HTTP请求获取用户列表、更新用户信息和禁用用户。注意事项提醒用户删除操作建议采用软删除、敏感操作需记录日志、用户状态变更应通知相关系统、响应中不返回敏感信息以及并发操作的处理。该用户管理模块为基本的CRUD操作提供了实现基础,并可根据实际需求扩展更多功能,如增加查询条件、导入导出、操作日志等。

Tip:由于大部分内容跟单体应用中的知识点一样,因此我们只讲解核心设计要点和高级特性实现。

一、核心设计要点

1.1 用户状态管理
csharp 复制代码
/// <summary>
/// 禁用/启用用户
/// </summary>
/// <param name="id"></param>
/// <param name="isDisabled"></param>
/// <returns></returns>
public async Task DisableUser(long id, bool isDisabled)
{
    var user = await _userManager.FindByIdAsync(id.ToString());
    if (user == null)
    {
        throw new NotFoundException($"用户不存在");
    }

    if (isDisabled)
    {
        user.LockoutEnd = DateTimeOffset.UtcNow.AddYears(100); // 设置一个很远的未来时间
    }
    else
    {
        user.LockoutEnd = null; // 解除锁定
    }

    user.LockoutEnabled = isDisabled;
    var result = await _userManager.UpdateAsync(user);
    if (!result.Succeeded)
    {
        throw new BadRequestException($"禁用/启用用户失败");
    }
}

这段代码是一个用于"禁用/启用用户"的服务方法,常见于基于ASP.NET Core Identity的用户管理系统。下面我们详细讲解代码和设计思路。

代码接收接收用户ID和一个布尔值,表示是否禁用用户。如果禁用,则将用户的锁定时间设置为100年后;如果启用,则将锁定时间设置为null,表示解除锁定。其中LockoutEnd属性用于控制用户锁定时间,LockoutEnabled属性用于启用或禁用锁定功能。代码中使用了_userManager来管理用户对象,FindByIdAsync方法用于查找用户,UpdateAsync方法用于更新用户信息。在查找用户时,如果找不到用户,则抛出"用户不存在"异常;在更新用户信息时,如果更新失败,则抛出"禁用/启用用户失败"异常。

代码的设计思路很简单,我们采用ASP.NET Core Identity的锁定机制,通过设置LockoutEndLockoutEnabled属性来控制用户的登录权限,而不是直接删除或修改用户的敏感信息,从而提升系统的安全性。其次,方法中通过抛出异常的方式处理各种错误情况,便于上层统一捕获和处理,提高了代码的可维护性。此外,该实现方式具有良好的扩展性,只需调整锁定时间或相关条件,即可满足临时禁用、永久禁用等不同业务场景的需求。

在实际开发过程中,需要注意以下几点。第一,只有当 LockoutEnabled 属性设置为 true 时,LockoutEnd 的锁定机制才会生效。其次,将锁定时间设置为100年后虽然不能算作真正的"永久禁用",但对于大多数业务场景来说已经足够使用。此外,如果用户已经处于禁用状态,再次执行禁用操作不会引发错误,但建议在业务逻辑层面实现幂等性处理,以避免重复操作带来的潜在问题。

Tip:通过Identity的锁定机制实现了用户的禁用和启用,既安全又易于维护,是.NET项目中常见的用户状态管理方式。

1.2 分页查询优化
csharp 复制代码
/// <summary>
/// 获取用户列表
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
public async Task<PagedResponse<UserResponse>> GetUserList(UserPageRequest page)
{
    var query = _userManager.Users.AsQueryable();
    if (!string.IsNullOrEmpty(page.UserName))
    {
        query = query.Where(x => x.UserName.Contains(page.UserName));
    }

    if (!string.IsNullOrEmpty(page.Email))
    {
        query = query.Where(x => x.Email.Contains(page.Email));
    }

    // 使用单次查询获取总数和分页数据
    var totalQuery = query;
    var pagedQuery = query.OrderByDescending(o => o.Id)
        .Skip((page.Page - 1) * page.PageSize)
        .Take(page.PageSize);

    // 并行执行两个查询
    var countTask = totalQuery.CountAsync();
    var usersTask = pagedQuery.ToListAsync();

    await Task.WhenAll(countTask, usersTask);

    int total = countTask.Result;
    List<SpUser> users = usersTask.Result;

    var result = new PagedResponse<UserResponse>
    {
        TotalRow = total,
        TotalPage = (int)Math.Ceiling((double)total / page.PageSize),
        Data = users.Select(x => new UserResponse
        {
            Id = x.Id,
            UserName = x.UserName,
            Email = x.Email,
            IsLocked = x.LockoutEnabled
        }).ToList()
    };

    return result;
}

这段代码是一个用于获取用户列表的异步方法,常见于.NET后端服务中,主要实现了分页、条件筛选和高效查询。GetUserList方法是一个异步操作,返回类型为 Task<PagedResponse<UserResponse>>,用于获取分页后的用户列表。参数 UserPageRequest page 封装了分页信息和筛选条件(如用户名、邮箱等),便于灵活查询用户数据。这段代码的设计思路是通过使用IQueryable接口来构建动态查询,结合分页和异步操作来提高性能和用户体验。其中_userManager.Users 获取了所有用户的可查询对象,AsQueryable() 方法使得可以对用户数据进行动态查询。并且我们使用了LINQ的 Where 方法来根据用户名和邮箱进行条件筛选,OrderByDescending 方法按用户ID倒序排列,SkipTake 方法实现了分页功能。这里要重点关注并行查询总数和数据的实现,使用 Task.WhenAll 方法可以同时执行两个异步查询任务,分别获取总记录数和当前页的用户列表,从而提高查询效率。

Tip:Task.WhenAll能提升性能,但要确保两个查询不会互相影响。

1.3 用户信息更新
csharp 复制代码
/// <summary>
/// 更新用户信息
/// </summary>
/// <param name="id"></param>
/// <param name="user"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task UpdateUser(long id, UserUpdateRequest user)
{
    var spUser = await _userManager.FindByIdAsync(id.ToString());
    if (spUser == null)
    {
        throw new NotFoundException($"用户不存在");
    }

    spUser.UserName = user.UserName;
    spUser.Email = user.Email;

    var result = await _userManager.UpdateAsync(spUser);
    if (!result.Succeeded)
    {
        throw new BadRequestException($"更新用户失败");
    }
}

这段代码是一个用于更新用户信息的服务方法,常见于基于ASP.NET Core Identity的用户管理系统。代码接收用户ID和一个包含更新信息的请求对象 UserUpdateRequest。首先通过 _userManager.FindByIdAsync 方法查找用户,如果找不到用户,则抛出"用户不存在"异常。接着,将请求中的新信息赋值给用户对象的相应属性,如用户名、邮箱和手机号。最后,使用 _userManager.UpdateAsync 方法更新用户信息,如果更新失败,则抛出"更新用户信息失败"异常,并将错误信息拼接成字符串返回。

代码的设计思路很简单,我们采用ASP.NET Core Identity的用户管理功能,通过 _userManager 来操作用户对象。首先通过ID查找用户,确保用户存在;然后更新用户信息,确保数据的完整性和一致性。最后通过抛出异常的方式处理各种错误情况,便于上层统一捕获和处理,提高了代码的可维护性。此外,该实现方式具有良好的扩展性,只需调整请求对象中的属性,即可满足不同业务场景下的用户信息更新需求。

二、高级特性实现

2.1 用户角色关联
csharp 复制代码
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<UserResponse?> GetUserInfo(long id)
{
    // 尝试从缓存获取
    string cacheKey = $"user:{id}";
    var cachedUser = await _redis.GetStringAsync(cacheKey);

    if (!string.IsNullOrEmpty(cachedUser))
    {
        return JsonSerializer.Deserialize<UserResponse>(cachedUser);
    }

    // 缓存未命中,从数据库查询
    var user = await _userManager.FindByIdAsync(id.ToString());
    if (user == null)
    {
        throw new NotFoundException($"用户不存在");
    }

    var response = new UserResponse
    {
        Id = user.Id,
        UserName = user.UserName,
        Email = user.Email,
        IsLocked = user.LockoutEnabled
    };
    // 缓存结果,设置适当的过期时间
    await _redis.SetStringAsync(cacheKey, JsonSerializer.Serialize(response), 60 * 10);
    return response;
}

这段代码是一个用于获取用户信息的服务方法。代码首先尝试从Redis缓存中获取用户信息,如果缓存命中,则直接返回缓存中的数据;如果缓存未命中,则从数据库中查询用户信息,并将查询结果存入缓存中,以便下次快速访问。代码使用了 _userManager.FindByIdAsync 方法来查找用户,如果找不到用户,则抛出"用户不存在"异常。查询到的用户信息被封装成 UserResponse 对象,并存入Redis缓存中,设置了10分钟的过期时间。

代码的设计思路是通过使用Redis缓存来提高用户信息查询的性能,减少数据库访问次数。首先尝试从缓存中获取数据,如果缓存命中,则直接返回;如果缓存未命中,则从数据库查询并更新缓存。这样可以显著提高查询速度,尤其是在高并发场景下。此外,使用Redis作为缓存存储,可以有效降低数据库负载,提高系统的整体性能。

Tip:使用Redis缓存用户信息可以显著提高查询性能,尤其是在高并发场景下。

三、总结

用户管理模块是任何系统的基础功能之一,本篇介绍了如何实现一个完整的用户管理模块,包括用户信息的增删改查、用户状态管理、分页查询、数据验证和权限控制。核心代码实现部分涵盖了控制器(UserController)、服务接口(IUserService)、请求/响应模型以及服务实现(UserServiceImpl),并详细说明了各接口的功能,如获取用户信息、分页获取用户列表、删除用户、禁用/启用用户和更新用户信息。

相关推荐
hello早上好1 分钟前
Spring不同类型的ApplicationContext的创建方式
java·后端·架构
Python智慧行囊1 小时前
Python 中 Django 中间件:原理、方法与实战应用
python·中间件·架构·django·开发
HyggeBest1 小时前
Mysql的数据存储结构
后端·架构
冰橙子id2 小时前
centos7编译安装LNMP架构
mysql·nginx·架构·centos·php
o0向阳而生0o2 小时前
65、.NET 中DllImport的用途
.net·非托管·dllimport
o0向阳而生0o5 小时前
63、.NET 异常处理
c#·.net·异常处理
吾日三省Java7 小时前
微服务体系下将环境流量路由到开发本机
微服务·系统架构·团队开发
WindrunnerMax7 小时前
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
前端·架构·github
hstar95277 小时前
三十五、面向对象底层逻辑-Spring MVC中AbstractXlsxStreamingView的设计
java·后端·spring·设计模式·架构·mvc