【.NET 8 实战--孢子记账--从单体到微服务】--角色(增加/删除/修改/查询)

本节我们将开始编写角色相关的接口

一、需求

本节的要做的需求如下:

编号 需求标题 需求内容
1 增加角色 角色名称不能重复
2 删除角色 角色逻辑删除
3 修改角色 修改的名称不能和已有名称重复
4 查询角色 不分页查询,根据角色名模糊匹配

二、Role类和Role表

这一小节和创建User类和User表类似,这里就不多讲了,我把类代码和要在数据库连接上下文 SporeAccountingDBContext 类中加入的代码列出来,同学们根据前面学习的内容自行操作这一小节。

新建SysRole 类:

csharp 复制代码
using SporeAccounting.BaseModels;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace SporeAccounting.Models;

/// <summary>
/// 角色
/// </summary>
[Table(name: "SysRole")]
public class SysRole : BaseModel
{
    /// <summary>
    /// 角色名称
    /// </summary>
    [Column(TypeName = "nvarchar(20)")]
    [Required]
    public string RoleName { get; set; }
    /// <summary>
    /// 导航属性
    /// </summary>
    public SysUser User { get; set; }
}

修改SysUser类:

csharp 复制代码
using SporeAccounting.BaseModels;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace SporeAccounting.Models;

/// <summary>
/// 用户表自定义属性类
/// </summary>
[Table(name:"SysUser")]
public class SysUser:BaseModel
{
    //++++++++++++++++
    //
    // Other Code
    //
    //++++++++++++++++
   
    /// <summary>
    /// 导航属性
    /// </summary>
    public ICollection<SysRole> Roles { get; set; }
}

在上面代码中我们增加了两个导航属性,这两个导航属性标明了SysRole 类和SysUser它们之间的关系是一对多的关系(一个用户创建多个角色)。

TIP:导航属性的作用和用法,请同学们访问专栏《轻松学EntityFramework Core》 中的文章《关系映射》

修改数据库连接上下文SporeAccountingDBContext类:

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using SporeAccounting.BaseModels;
using SporeAccounting.Models;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using System.Text;

namespace SporeAccounting;

/// <summary>
/// 数据库连接上下文
/// </summary>
public class SporeAccountingDBContext : DbContext
{
    /// <summary>
    /// 用户表
    /// </summary>
    public DbSet<SysUser> SysUsers { get; set; }
    /// <summary>
    /// 角色表
    /// </summary>
    public DbSet<SysRole> SysRoles { get; set; }

    IConfiguration _dbConfig;
    public SporeAccountingDBContext(IConfiguration dbConfig)
    {
        _dbConfig = dbConfig;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        //++++++++++++++++
	    //
	    // Other Code
	    //
	    //++++++++++++++++
        modelBuilder.Entity<SysRole>().HasData(new List<SysRole>()
        {
            new SysRole()
            {
                RoleName = "Administrator",
                CreateUserId = adminUserId
            },
            new SysRole()
            {
                RoleName = "Consumer",
                CreateUserId =adminUserId
            }
        });
        base.OnModelCreating(modelBuilder);
    }

    //++++++++++++++++
    //
    // Other Code
    //
    //++++++++++++++++

    private static string HashPasswordWithSalt(string password, string salt)
    {
        using (var sha256 = SHA256.Create())
        {
            string saltedPassword = password + salt;
            byte[] saltedPasswordBytes = Encoding.UTF8.GetBytes(saltedPassword);
            byte[] hashBytes = sha256.ComputeHash(saltedPasswordBytes);
            return Convert.ToBase64String(hashBytes);
        }
    }
}

|2|删除角色|角色逻辑删除|

|3|修改角色|修改的名称不能和已有名称重复|

|4|查询角色|不分页查询,根据角色名模糊匹配|

三、增加角色

这一小节我们一起来看一下如何实现角色的新增功能。

首先,我们来看一下需求:角色名称不能重复。从中我们可以推断出代码中需要有一个根据角色名判断角色名是否重复的方法。下面我们就来一起看一下代码。

3.1 ViewModel 定义

第一步,我们来创建视图模型SysRoleViewModel,它接收来自客户端的数据,代码如下:

csharp 复制代码
namespace SporeAccounting.Models.ViewModels;

/// <summary>
/// 角色视图模型
/// </summary>
public class SysRoleViewModel
{
    /// <summary>
    /// 角色名称
    /// </summary>
    public string RoleName { get; set; }
}

类比较简单,因此我们不再讲解。

3.2 服务开发

我们创建 ISysRoleServer接口和SysRoleImp实现类,并增加IsExistByRoleNameAdd方法。

csharp 复制代码
//ISysRoleServer 接口
using SporeAccounting.Models;

namespace SporeAccounting.Server.Interface;
/// <summary>
/// 角色数据库操作接口
/// </summary>
public interface ISysRoleServer
{
    /// <summary>
    /// 新增角色
    /// </summary>
    /// <param name="role"></param>
    void Add(SysRole role);
    /// <summary>
    /// 角色是否存在
    /// </summary>
    /// <param name="roleName"></param>
    /// <returns></returns>
    bool IsExistByRoleName(string roleName);
}

//SysRoleImp 实现类
using SporeAccounting.Models;
using SporeAccounting.Server.Interface;
using System.Data;

namespace SporeAccounting.Server;

public class SysRoleImp : ISysRoleServer
{

    private SporeAccountingDBContext _dbContext;

    public SysRoleImp(SporeAccountingDBContext dbContext)
    {
        _dbContext = dbContext;
    }
    /// <summary>
    /// 新增角色
    /// </summary>
    /// <param name="role"></param>
    public void Add(SysRole role)
    {
        try
        {
            _dbContext.SysRoles.Add(role);
            _dbContext.SaveChanges();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    /// <summary>
    /// 角色是否存在
    /// </summary>
    /// <param name="roleName"></param>
    /// <returns></returns>
    public bool IsExistByRoleName(string roleName)
    {
        try
        {
            return _dbContext.SysRoles.Any(p => p.RoleName == roleName && !p.IsDeleted);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

在上面的代码中,定义了一个角色管理接口 ISysRoleServer 及其实现类 SysRoleImp,用于在数据库中操作角色信息。ISysRoleServer 接口定义了两个方法:添加角色方法Add 检查是否存在指定名称的角色的方法IsExistByRoleName。而SysRoleImp 类则实现了 ISysRoleServer 接口。

3.3 Controller 开发

Controller 代码的编写和前面几篇文章讲解的类似,在这里就不多讲解了,代码如下:

csharp 复制代码
using System.Data;
using System.Net;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SporeAccounting.BaseModels;
using SporeAccounting.Models;
using SporeAccounting.Models.ViewModels;
using SporeAccounting.Server.Interface;

namespace SporeAccounting.Controllers
{
    /// <summary>
    /// 角色接口
    /// </summary>
    [Route("api/[controller]/")]
    [ApiController]
    public class SysRoleController : ControllerBase
    {
        private readonly ISysRoleServer _sysRoleServer;
        private readonly IMapper _mapper;

        public SysRoleController(ISysRoleServer sysRoleServer, IMapper mapper)
        {
            _sysRoleServer = sysRoleServer;
            _mapper = mapper;
        }

        /// <summary>
        /// 新增角色
        /// </summary>
        /// <param name="role"></param>
        /// <returns></returns>
        [HttpPost]
        [Route("Add")]
        public ActionResult<ResponseData<bool>> Add([FromBody] SysRoleViewModel role)
        {
            try
            {
                bool isExist = _sysRoleServer.IsExistByRoleName(role.RoleName);
                if (isExist)
                {
                    return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"角色{role.RoleName}已存在!", false));
                }

                SysRole dbRole = _mapper.Map<SysRole>(role);
                //TODO:这里暂时写死,等权限和授权完成后再改为动态获取
                dbRole.CreateUserId = "08f35c1e-117f-431d-979d-9e51e29b0b7d";
                _sysRoleServer.Add(dbRole);
                return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
            }
            catch (Exception e)
            {
                return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务器异常", false));
            }
        }
    }
}

这段代码中实现了新增角色接口 Add, 它是一个只接受POST请求的方法,路由地址为 api/SysRole/Add,参数是来自Body 的SysRoleViewModel类型的参数。Add接口首先使用我们在上一小节中定义并实现的 IsExistByRoleName 方法来检查数据库中是否已存在同名角色,如果角色存在,返回 409 状态码并提示角色名称已存在,否则利用 AutoMapperSysRoleViewModel 映射为数据库实体 SysRole。这里我们通过硬编码设置了 CreateUserId(未来会替换为动态获取用户信息),最后我们调用 Add 方法将角色添加到数据库中,并返回 200 状态码。

3.4 Profile

SporeAccountingProfile 中加入如下代码

csharp 复制代码
CreateMap<SysRoleViewModel, SysRole>().ForMember(d => d.RoleName, opt =>opt.MapFrom(s => s.RoleName));

四、删除角色

删除角色在某些情况下是必要的,下面我们就来实现这个功能。

4.1 服务开发

我们首先在ISysRoleServer接口和SysRoleImp类中增加Delete方法和IsExistById

csharp 复制代码
//ISysRoleServer 接口
/// <summary>
/// 删除角色(逻辑)
/// </summary>
/// <param name="roleId"></param>
/// <param name="userId"></param>
void Delete(string roleId, string userId);
/// <summary>
/// 角色是否存在
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
bool IsExistById(string roleId);



//SysRoleImp 类
/// <summary>
/// 删除角色(逻辑)
/// </summary>
/// <param name="roleId"></param>
/// <param name="userId"></param>
public void Delete(string roleId, string userId)
{
    try
    {
        SysRole role = _dbContext.SysRoles.FirstOrDefault(p => p.Id == roleId)!;
        role.IsDeleted = true;
        role.DeleteDateTime = DateTime.Now;
        role.DeleteUserId = userId;
        _dbContext.SysRoles.Update(role);
        _dbContext.SaveChanges();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
/// <summary>
/// 角色是否存在
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
public bool IsExistById(string roleId)
{
    try
    {
        return _dbContext.SysRoles.Any(p => p.Id == roleId && !p.IsDeleted);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

代码和User的删除代码类似这里我们就不讲解了,需要注意的是这里我们的删除操作是逻辑删除,而不是物理删除。在实现IsExistById方法时一定要注意加上!p.IsDeleted这个过滤条件

Tip:从这篇文章开始,我们项目中简单的代码以及和已有代码类似的代码将不再详细讲解,只列出代码。我们只针对详复杂的业务代码以及架构代码进行讲解。

4.2 Controller 开发

我们在 SysRoleController 中添加Remove方法并实现删除功能,代码如下:

csharp 复制代码
/// <summary>
/// 删除角色(逻辑删除)
/// </summary>
/// <param name="roleId"></param>
/// <returns></returns>
[HttpDelete]
[Route("Remove/{roleId}")]
public ActionResult<ResponseData<bool>> Remove([FromQuery] string roleId)
{
    try
    {
        bool isExist = _sysRoleServer.IsExistById(roleId);
        if (!isExist)
        {
            return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"角色不存在!", false));
        }

        //TODO:这里暂时写死,等权限和授权完成后再改为动态获取
        _sysRoleServer.Delete(roleId, "08f35c1e-117f-431d-979d-9e51e29b0b7d");
        return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
    }
    catch (Exception e)
    {
        return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务器异常", false));
    }
}

代码中需要注意的是,在删除前我们需要查询一次角色是否存在

Tip:这一小节我们暂时不去考虑删除角色后,绑定了该角色的用户该如何处理。这个问题我们将会在下一篇文章《补充单元测试》中处理。

五、修改角色

修改角色我们修改的时角色的名称,这一小节我们来看一下实现。

5.1 ViewModel

新建SysRoleEditViewModel视图类,类中的内容如下:

csharp 复制代码
using System.ComponentModel.DataAnnotations;

namespace SporeAccounting.Models.ViewModels;
/// <summary>
/// 修改角色视图类
/// </summary>
public class SysRoleEditViewModel
{
    /// <summary>
    /// 角色id
    /// </summary>
    [Required(ErrorMessage = "角色Id不能为空")]
    public string RoleId { get; set; }

    /// <summary>
    /// 角色名称
    /// </summary>
    [Required(ErrorMessage = "角色名称不能为空")]
    public string RoleName { get; set; }
}
5.2 服务开发

和前面类似,我们在ISysRoleServer接口和SysRoleImp类中增加Update方法和IsRepeat方法,其中IsRepeat方法用来验证新的角色名称是否重复。实现如下:

csharp 复制代码
//ISysRoleServer 接口
/// <summary>
/// 修改角色
/// </summary>
/// <param name="role"></param>
void Update(SysRole role);
/// <summary>
/// 角色是否重复
/// </summary>
/// <param name="roleId"></param>
/// <param name="roleName"></param>
/// <returns></returns>
bool IsRepeat(string roleId, string roleName);


//SysRoleImp 方法
/// <summary>
/// 修改角色
/// </summary>
/// <param name="role"></param>
public void Update(SysRole role)
{
    try
    {
        SysRole dbRole = _dbContext.SysRoles.FirstOrDefault(p => p.Id == role.Id)!;
        dbRole.RoleName = role.RoleName;
        dbRole.UpdateDateTime = DateTime.Now;
        _dbContext.SysRoles.Update(role);
        _dbContext.SaveChanges();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}
/// <summary>
/// 角色是否重复
/// </summary>
/// <param name="roleId"></param>
/// <param name="roleName"></param>
/// <returns></returns>
public bool IsRepeat(string roleId, string roleName)
{
    try
    {
        return _dbContext.SysRoles.Any(p => p.Id != roleId && p.RoleName == roleName && !p.IsDeleted);
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

这里需要注意,IsRepeat方法传入角色Id的目的是在判断角色名是否重复时屏蔽当前要修改的角色,这是因为如果不屏蔽当前角色的话,在不修改角色直接保存的情况下会出现提示角色重复的情况。

5.3 Controller 开发

SysRoleController 中添加Edit方法,代码如下:

csharp 复制代码
/// <summary>
/// 修改角色
/// </summary>
/// <param name="roleView"></param>
/// <returns></returns>
[HttpPut]
[Route("Edit")]
public ActionResult<ResponseData<bool>> Edit([FromBody] SysRoleEditViewModel roleView)
{
    try
    {
        //判断角色是否存在
        bool isExist = _sysRoleServer.IsExistById(roleView.RoleId);
        if (!isExist)
        {
            return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"角色不存在!", false));
        }

        //判断角色名字是否重复
        isExist = _sysRoleServer.IsRepeat(roleView.RoleId, roleView.RoleName);
        if (isExist)
        {
            return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"角色{roleView.RoleName}已存在!", false));
        }

        SysRole role = _mapper.Map<SysRole>(roleView);
        //TODO:这里暂时写死,等权限和授权完成后再改为动态获取
        role.UpdateUserId = "08f35c1e-117f-431d-979d-9e51e29b0b7d";
        _sysRoleServer.Update(role);
        return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
    }
    catch (Exception e)
    {
        return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务器异常", false));
    }
}
5.4 Profile

SporeAccountingProfile 中加入如下代码

csharp 复制代码
CreateMap<SysRoleEditViewModel, SysRole>().ForMember(d => d.RoleName, opt =>opt.MapFrom(s => s.RoleName));

六、查询角色

查询角色是最后一个功能,一起来看一下实现代码。

6.1 ViewModel

新建SysRoleQueryViewModel类,并写入如下代码:

csharp 复制代码
namespace SporeAccounting.Models.ViewModels;

public class SysRoleQueryViewModel
{
    /// <summary>
    /// 角色id
    /// </summary>
    public string RoleId { get; set; }
    /// <summary>
    /// 角色名
    /// </summary>
    public string RoleName { get; set; }
}
6.2 服务开发

ISysRoleServer接口和SysRoleImp类中增加Query方法

csharp 复制代码
//ISysRoleServer 接口
/// <summary>
/// 查询角色
/// </summary>
/// <param name="roleName"></param>
List<SysRole> Query(string roleName);


//SysRoleImp 方法
/// <summary>
/// 查询角色
/// </summary>
/// <param name="roleName"></param>
public List<SysRole> Query(string roleName)
{
    try
    {
        List<SysRole> sysRoles = _dbContext.SysRoles.Where(p => p.RoleName.Contains(roleName)).ToList();
        return sysRoles;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Query 方法中我们并没有使用 == 来查询角色,而是使用 Contains 方法。这是因为客户端传递过来的角色名称有很大的可能性不是完整的角色名。

6.3 Controller 开发

最后在SysRoleController 中实现Query 方法

csharp 复制代码
/// <summary>
/// 根据角色名查询
/// </summary>
/// <param name="roleName"></param>
/// <returns></returns>
[HttpGet]
[Route("Query/{roleName}")]
public ActionResult<ResponseData<List<SysRoleQueryViewModel>>> Query([FromQuery] string roleName)
{
    try
    {
        List<SysRole> roles = _sysRoleServer.Query(roleName);
        List<SysRoleQueryViewModel> rolesQuery = _mapper.Map<List<SysRoleQueryViewModel>>(roles);
        return Ok(new ResponseData<List<SysRoleQueryViewModel>>(HttpStatusCode.OK, data: rolesQuery));
    }
    catch (Exception e)
    {
        return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务器异常", false));
    }
}
6.4 Profile

SporeAccountingProfile 中加入如下代码

csharp 复制代码
CreateMap<SysRole, SysRoleQueryViewModel>().ForMember(d => d.RoleName, opt =>opt.MapFrom(s => s.RoleName));

七、总结

这篇文章我们一起实现了角色的增删改查功能,并对主要代码进行了讲解。

相关推荐
利瑞华2 小时前
基于静态编译构建微服务
微服务·云原生·架构
界面开发小八哥2 小时前
「实战应用」如何用图表控件LightningChart可视化天气数据?(一)
信息可视化·.net·数据可视化·图表控件·lightningchart
多多*3 小时前
OJ在线评测系统 微服务高级 网关跨域权限校验 集中解决跨域问题 拓展 JWT校验和实现接口限流降级
java·开发语言·微服务·云原生·架构·maven
Crazy Struggle4 小时前
.NET 工控网关 轻量级组态软件
.net·物联网平台·工控网关
液态不合群4 小时前
架构与思维:漫谈高并发业务的CAS及ABA
架构
W Y4 小时前
【软件系统架构设计师-案例-1】架构风格
架构·系统架构·软考
爱吃牛肉的大老虎4 小时前
Oracle架构之用户,权限,角色讲解
oracle·架构·ffmpeg
后端从入门到精通5 小时前
鸿蒙架构-系统架构师(七十八)
架构·系统架构·harmonyos
王彬泽6 小时前
【微服务】链路追踪 - Micrometer(day9)
微服务·分布式链路追踪·zipkin·micrometer