【ABP】项目示例(4)——领域服务

领域服务

在上一章节中,已经完成了仓储的设计,在这一章节中,实现领域服务,即业务的核心逻辑
领域服务主要处理特定领域的业务逻辑,对内协调和整合聚合根与各个实体的业务关系,对外作为业务的边界,供应用服务组合来提供完整复杂的功能

规约

在名称为General.Backend.Domain的类库中的Specifications文件夹下,新建名称为SameUserSpecification和SameRoleSpecification的规约类,用于定义和复用相同用户和相同角色的逻辑判断

  • SameUserSpecification
public class SameUserSpecification : Specification<User>
{
    /// <summary>
    /// 账号
    /// </summary>
    public string Account { get; } = string.Empty;

    /// <summary>
    /// 用户名称
    /// </summary>
    public string UserName { get; } = string.Empty;

    public SameUserSpecification(string userName, string account = "")
    {
        Account = account;
        UserName = userName;
    }

    public override Expression<Func<User, bool>> ToExpression()
    {
        return user => user.Account == Account || user.Name == UserName;
    }
}
  • SameRoleSpecification
public class SameRoleSpecification : Specification<Role>
{
    /// <summary>
    /// 角色编码
    /// </summary>
    public string Code { get; } = string.Empty;

    /// <summary>
    /// 角色名称
    /// </summary>
    public string Name { get; } = string.Empty;

    public SameRoleSpecification(string name, string code = "")
    {
        Code = code;
        Name = name;
    }

    public override Expression<Func<Role, bool>> ToExpression()
    {
        return user => user.Code == Code || user.Name == Name;
    }
}

服务

在Services文件夹下分别新建名称为UserService、RoleService和MenuService的领域服务类

  • UserService
public class UserService : DomainService
{
    private readonly IUserRepository _userRepository;

    public UserService(
        IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public async Task<User> LoginCheckAsync(
        string account,
        string password)
    {
        var user = await _userRepository.FindUserByAccountAsync(account);
        if (user == null)
        {
            throw new UserFriendlyException("用户不存在");
        }
        if (user.IsFrozen == FrozenStatus.Frozen)
        {
            throw new UserFriendlyException("用户已被冻结");
        }
        var checkResult = user.CheckPassword(password);

        await _userRepository.UpdateAsync(user);

        if (!checkResult)
        {
            throw new UserFriendlyException("用户密码错误");
        }
        return user;
    }

    public async Task<User> AddAsync(
        string account,
        string password,
        string name,
        string? contact = null,
        string? address = null)
    {
        var sameUsers = await _userRepository.GetSameUserAsync(new SameUserSpecification(name, account));
        if (sameUsers.Count > 0)
        {
            if (sameUsers.Any(user => user.Account == account))
            {
                throw new UserFriendlyException("已存在相同账号的用户");
            }
            if (sameUsers.Any(user => user.Name == name))
            {
                throw new UserFriendlyException("已存在相同名称的用户");
            }
        }
        var user = new User(
            GuidGenerator.Create(),
            account,
            password,
            name,
            contact,
            address);
        return user;
    }

    public async Task<User> ModifyAsync(
        Guid id,
        string password,
        string name,
        string? contact = null,
        string? address = null)
    {
        var user = await _userRepository.FindAsync(id);
        if (user == null)
        {
            throw new UserFriendlyException("用户不存在");
        }
        if (user.Account == UserConsts.AdminAccount)
        {
            throw new UserFriendlyException("不可操作管理员用户");
        }
        var sameUsers = await _userRepository.GetSameUserAsync(new SameUserSpecification(name));
        if (sameUsers.Any(item => item.Name == name && item.Id != id))
        {
            throw new UserFriendlyException("已存在相同名称的用户");
        }
        user.SetName(name);
        user.SetPassword(password);
        user.Contact = contact;
        user.Address = address;
        return user;
    }

    public async Task<User> UnfreezeAsync(Guid id)
    {
        var user = await _userRepository.FindAsync(id);
        if (user == null)
        {
            throw new UserFriendlyException("用户不存在");
        }
        user.UnFrozen();
        return user;
    }

    public void BindRoles(User user, List<Guid> roleIds)
    {
        var userRoles = new List<UserRole>();
        roleIds.ForEach(roleId =>
        {
            var userRole = new UserRole(
                GuidGenerator.Create(),
                user.Id,
                roleId);
            userRoles.Add(userRole);
        });
        user.SetRoles(userRoles);
    }
}
  • RoleService
public class RoleService : DomainService
{
    private readonly IRoleRepository _roleRepository;

    public RoleService(IRoleRepository roleRepository)
    {
        _roleRepository = roleRepository;
    }

    public async Task<Role> AddAsync(
        string code,
        string name,
        string? remark)
    {
        var sameRoles = await _roleRepository.GetSameRoleAsync(new SameRoleSpecification(name, code));
        if (sameRoles.Count > 0)
        {
            if (sameRoles.Any(role => role.Code == code))
            {
                throw new UserFriendlyException("已存在相同编码的角色");
            }
            if (sameRoles.Any(role => role.Name == name))
            {
                throw new UserFriendlyException("已存在相同名称的角色");
            }
        }
        var role = new Role(
            GuidGenerator.Create(),
            code,
            name,
            remark);
        return role;
    }

    public async Task<Role> ModifyAsync(
        Guid id,
        string name,
        string? remark)
    {
        var role = await _roleRepository.FindAsync(id);
        if (role == null)
        {
            throw new UserFriendlyException("角色不存在");
        }
        var sameRoles = await _roleRepository.GetSameRoleAsync(new SameRoleSpecification(name));
        if (sameRoles.Any(item => item.Name == name && item.Id != id))
        {
            throw new UserFriendlyException("已存在相同名称的角色");
        }
        role.SetName(name);
        role.Remark = remark;
        return role;
    }

    public void BindMenus(Role role, List<string> menuCodes)
    {
        var roleMenus = new List<RoleMenu>();
        menuCodes.ForEach(menuCode =>
        {
            var roleMenu = new RoleMenu(
                GuidGenerator.Create(),
                role.Id,
                menuCode);
            roleMenus.Add(roleMenu);
        });
        role.SetMenus(roleMenus);
    }
}
  • MenuService
public class MenuService : DomainService
{
    private readonly IMenuRepository _menuRepository;
    private readonly IRoleRepository _roleRepository;
    private readonly IRepository<RoleMenu> _roleMenuRepository;

    public MenuService(
        IMenuRepository menuRepository,
        IRoleRepository roleRepository,
        IRepository<RoleMenu> roleMenuRepository)
    {
        _menuRepository = menuRepository;
        _roleRepository = roleRepository;
        _roleMenuRepository = roleMenuRepository;
    }

    /// <summary>
    /// 获取用户权限缓存
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    public async Task<UserPermissionCacheItem> GetPermissionAsync(User user)
    {
        var userPermissionCacheItem = new UserPermissionCacheItem
        {
            UserId = user.Id,
            UserAccount = user.Account,
            UserName = user.Name
        };
        var menuModules = new List<Menu>();
        var account = user.Account);
        if (account == UserConsts.AdminAccount)
        {
            menuModules = await _menuRepository.GetListAsync();
            userPermissionCacheItem.Role = RoleConsts.AdminRoleCode;
            userPermissionCacheItem.RoleName = RoleConsts.AdminRoleName;
        }
        else
        {
            var roleIds = user.UserRoles.Select(rel => rel.RoleId);
            var roles = await _roleRepository.GetListAsync(role => roleIds.Contains(role.Id));
            var roleMenus = await _roleMenuRepository.GetListAsync(rel => roleIds.Contains(rel.RoleId));
            var menuCodes = roleMenus.Select(rel => rel.MenuCode).Distinct();
            menuModules = await _menuRepository.GetListAsync(menu => menuCodes.Contains(menu.Code));
            userPermissionCacheItem.Role = string.Join(",", roles.Select(x => x.Code).ToList());
            userPermissionCacheItem.RoleName = string.Join(",", roles.Select(x => x.Name).ToList());
        }
        userPermissionCacheItem.MenuRouters = await GetMenuRouter(menuModules);
        foreach (var menuModule in menuModules)
        {
            userPermissionCacheItem.Modules.Add(menuModule.Code);
        }
        return userPermissionCacheItem;
    }

    /// <summary>
    /// 获取路由菜单
    /// </summary>
    /// <param name="menus"></param>
    /// <returns></returns>
    public async Task<List<MenuRouterCacheItem>> GetMenuRouter(List<Menu> menus)
    {
        var menuRouterCacheItems = new List<MenuRouterCacheItem>();
        menus = menus
          .Where(x => x.Type == MenuConsts.CatalogTypeName || x.Type == MenuConsts.MenuTypeName)
          .OrderBy(y => y.Sort)
          .ToList();
        //获取部分勾选的菜单的目录列表
        var parentCodes = menus.Where(x => x.Type == MenuConsts.MenuTypeName).Select(y => y.ParentCode);
        var exceptCodes = parentCodes.Except(menus.Select(x => x.Code));
        if (exceptCodes.Any())
        {
            var partMenus = await _menuRepository.GetListAsync(x => exceptCodes.Contains(x.Code));
            menus.AddRange(partMenus);
            menus = menus.OrderBy(x => x.Sort).ToList();
        }
        var menuTree = BuildTreeMenus(menus);
        menuRouterCacheItems = BuildRouter(menuTree);
        return menuRouterCacheItems;
    }

    /// <summary>
    /// 构建前端菜单树
    /// </summary>
    /// <param name="menus"></param>
    /// <returns></returns>
    private static List<Menu> BuildTreeMenus(List<Menu> menus)
    {
        var result = new List<Menu>();
        var queue = new Queue<Menu>();
        var rootMenu = Menu.BuildRoot();
        queue.Enqueue(rootMenu);
        while (queue.Count > 0)
        {
            var parentMenu = queue.Dequeue();
            var childMenus = menus.Where(x => x.ParentCode == parentMenu.Code).ToList();
            parentMenu.SetSubMenu(childMenus);
            foreach (var childMenu in childMenus)
            {
                queue.Enqueue(childMenu);
            }
        }
        result = rootMenu.SubMenu;
        return result;
    }

    /// <summary>
    /// 构建路由
    /// </summary>
    /// <param name="menus"></param>
    /// <returns></returns>
    private static List<MenuRouterCacheItem> BuildRouter(List<Menu> menus)
    {
        var result = new List<MenuRouterCacheItem>();
        foreach (var menu in menus)
        {
            var menuRouterCacheItem = new MenuRouterCacheItem
            {
                Name = menu.Name,
                Hidden = false,
                Path = menu.UrlAddress,
                Component = !string.IsNullOrEmpty(menu.ComponentAddress) 
                  && menu.Type == MenuConsts.MenuTypeName ? menu.ComponentAddress : "Layout"
            };
            var meta = new Meta(menu.Name, menu.Icon);
            menuRouterCacheItem.Meta = meta;
            var subMenu = menu.SubMenu;
            if (subMenu != null && menu.Type == MenuConsts.CatalogTypeName)
            {
                menuRouterCacheItem.AlwaysShow = true;
                menuRouterCacheItem.Redirect = "noRedirect";
                menuRouterCacheItem.Children = BuildRouter(subMenu);
            }
            result.Add(menuRouterCacheItem);
        }
        return result;
    }
}

在General.Backend.Domain中新建名称为CacheItems的文件夹,存放缓存对象类

/// <summary>
/// 菜单路由
/// </summary>
public class MenuRouterCacheItem
{
    /// <summary>
    /// 路由名称
    /// </summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// 路由地址
    /// </summary>
    public string? Path { get; set; }

    /// <summary>
    /// 是否隐藏路由,当设置true的时候该路由不会在侧边栏出现
    /// </summary>
    public bool Hidden { get; set; }

    /// <summary>
    /// 组件地址
    /// </summary>
    public string Component { get; set; } = string.Empty;

    /// <summary>
    /// 重定向地址,当设置noRedirect的时候该路由在面包屑导航中不可被点击
    /// </summary>
    public string Redirect { get; set; } = string.Empty;

    /// <summary>
    /// 当你一个路由下面的children声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
    /// </summary>
    public bool AlwaysShow { get; set; }

    /// <summary>
    /// 菜单内容
    /// </summary>
    public Meta Meta { get; set; }

    /// <summary>
    /// 子菜单路由
    /// </summary>
    public List<MenuRouterCacheItem> Children { get; set; } = [];
}

/// <summary>
/// 菜单内容
/// </summary>
public class Meta
{
    public Meta(string title, string? icon)
    {
        Title = title;
        Icon = icon;
    }

    /// <summary>
    /// 路由在侧边栏和面包屑中展示的名称
    /// </summary>
    public string Title { get; set; } = string.Empty;

    /// <summary>
    /// 路由的图标
    /// </summary>
    public string? Icon { get; set; }
}

对IRepositories文件夹下的IUserRepository和IRoleRepository仓储接口增加业务方法定义

  • IUserRepository
/// <summary>
/// 用户仓储
/// </summary>
public interface IUserRepository : IBaseRepository<User, Guid>
{
    public Task<User?> FindUserByAccountAsync(string account, bool include = true, CancellationToken cancellationToken = default);

    public Task<List<User>> GetSameUserAsync(SameUserSpecification specification, CancellationToken cancellationToken = default);
}
  • IRoleRepository
/// <summary>
/// 角色仓储
/// </summary>
public interface IRoleRepository : IBaseRepository<Role, Guid>
{
    public Task<List<Role>> GetSameRoleAsync(SameRoleSpecification specification, CancellationToken cancellationToken = default);
}

解决方案的目录结构现如下

在下一章节中,实现应用服务

posted @ 2025-02-25 21:40  loveraindeme  阅读(105)  评论(0)    收藏  举报