领域服务
在上一章节中,已经完成了仓储的设计,在这一章节中,实现领域服务,即业务的核心逻辑
领域服务主要处理特定领域的业务逻辑,对内协调和整合聚合根与各个实体的业务关系,对外作为业务的边界,供应用服务组合来提供完整复杂的功能
规约
在名称为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);
}
解决方案的目录结构现如下
在下一章节中,实现应用服务