【ABP】项目示例(5)——应用服务
应用服务
在上一章节中,已经完成了领域服务的设计,在这一章节中,实现应用服务,主要内容为业务逻辑的设计和组合
创建名称为General.Backend.Application.Contracts的标准类库,以及在该类库中新建名称为Dtos的文件夹
在程序包管理控制台选中General.Backend.Application.Contracts,执行以下命令安装ABP应用服务依赖相关的Nuget包
Install-Package Volo.Abp.Ddd.Application.Contracts -v 8.3.0
General.Backend.Application.Contracts添加项目引用DPower.Store.Domain.Shared
新建名称为GeneralApplicationContractsModule的应用服务依赖模块类
public class GeneralApplicationContractsModule : AbpModule
{
}
数据传输类
在Dtos文件夹中新建名称为PageDto和PageAndSortDto的分页和排序类
- PageDto
/// <summary>
/// 分页
/// </summary>
public class PageDto
{
/// <summary>
/// 分页大小
/// </summary>
public int PageSize { get; set; } = 10;
/// <summary>
/// 分页下标
/// </summary>
public int PageIndex { get; set; } = 1;
}
- PageAndSortDto
/// <summary>
/// 排序
/// </summary>
public class PageAndSortDto : PageDto
{
/// <summary>
/// 排序类型:asc desc
/// </summary>
public string SortType { get; set; } = string.Empty;
/// <summary>
/// 排序字段
/// </summary>
public string SortField { get; set; } = string.Empty;
}
在Dtos文件夹中分别新建名称为Users、Roles、Menus和Auths的文件夹,用于存放用户、角色、菜单和认证授权相关的数据传输类
在Users文件夹中分别新建名称为UserDto、UserCreateDto、UserUpdateDto、UserQueryDto和UserRoleDto的数据传输类
- UserDto
/// <summary>
/// 用户
/// </summary>
public class UserDto : AuditedEntityDto<Guid>
{
/// <summary>
/// 账号
/// </summary>
public string Account { get; set; } = string.Empty;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 联系方式
/// </summary>
public string? Contact { get; set; }
/// <summary>
/// 地址
/// </summary>
public string? Address { get; set; }
/// <summary>
/// 是否冻结(0:未冻结,1:已冻结)
/// </summary>
public FrozenStatus IsFrozen { get; set; }
/// <summary>
/// 角色Id
/// </summary>
public List<Guid> RoleIds { get; set; } = [];
/// <summary>
/// 角色名称
/// </summary>
public List<string> RoleNames { get; set; } = [];
}
- UserCreateDto
/// <summary>
/// 用户创建
/// </summary>
public class UserCreateDto
{
/// <summary>
/// 账号
/// </summary>
public string Account { get; set; } = string.Empty;
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 联系方式
/// </summary>
public string? Contact { get; set; }
/// <summary>
/// 地址
/// </summary>
public string? Address { get; set; }
/// <summary>
/// 用户关联角色列表
/// </summary>
public List<Guid> RoleIds { get; set; } = [];
}
- UserUpdateDto
/// <summary>
/// 用户更新
/// </summary>
public class UserUpdateDto : EntityDto<Guid>
{
/// <summary>
/// 密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 联系方式
/// </summary>
public string? Contact { get; set; }
/// <summary>
/// 地址
/// </summary>
public string? Address { get; set; }
/// <summary>
/// 用户关联角色列表
/// </summary>
public List<Guid> RoleIds { get; set; } = [];
}
- UserQueryDto
/// <summary>
/// 用户查询
/// </summary>
public class UserQueryDto : PageAndSortDto
{
/// <summary>
/// 用户账号
/// </summary>
public string? Account { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string? Name { get; set; } = string.Empty;
}
- UserRoleDto
/// <summary>
/// 用户角色
/// </summary>
public class UserRoleDto : EntityDto<Guid>, IHasCreationTime
{
/// <summary>
/// 用户Id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 角色Id
/// </summary>
public Guid RoleId { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; set; }
}
在Roles文件夹中分别新建名称为RoleDto、RoleCreateDto、RoleUpdateDto、RoleQueryDto和RoleMenuDto的数据传输类
- RoleDto
/// <summary>
/// 角色
/// </summary>
public class RoleDto : AuditedEntityDto<Guid>
{
/// <summary>
/// 编码
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string? Remark { get; set; }
}
- RoleCreateDto
/// <summary>
/// 角色创建
/// </summary>
public class RoleCreateDto
{
/// <summary>
/// 编码
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string? Remark { get; set; }
}
- RoleUpdateDto
/// <summary>
/// 角色更新
/// </summary>
public class RoleUpdateDto : EntityDto<Guid>
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 描述
/// </summary>
public string? Remark { get; set; }
}
- RoleQueryDto
/// <summary>
/// 角色查询
/// </summary>
public class RoleQueryDto : PageAndSortDto
{
/// <summary>
/// 角色名称
/// </summary>
public string? Name { get; set; } = string.Empty;
}
- RoleMenuDto
/// <summary>
/// 角色菜单
/// </summary>
public class RoleMenuDto : EntityDto<Guid>, IHasCreationTime
{
/// <summary>
/// 角色编码
/// </summary>
public string RoleCode { get; set; } = string.Empty;
/// <summary>
/// 菜单编码
/// </summary>
public string MenuCode { get; set; } = string.Empty;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; }
}
在Menus文件夹中分别新建名称为MenuDto、MenuModuleDto、PermissionDto和PermissionUpdateDto的数据传输类
- MenuDto
/// <summary>
/// 菜单
/// </summary>
public class MenuDto : EntityDto<Guid>, IHasCreationTime
{
/// <summary>
/// 编码
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 父编码
/// </summary>
public string ParentCode { get; set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 类型
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 层级
/// </summary>
public int Level { get; set; }
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = string.Empty;
/// <summary>
/// 路由地址
/// </summary>
public string UrlAddress { get; set; } = string.Empty;
/// <summary>
/// 组件地址
/// </summary>
public string ComponentAddress { get; set; } = string.Empty;
/// <summary>
/// 排序
/// </summary>
public int Sort { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreationTime { get; }
}
- MenuModuleDto
/// <summary>
/// 菜单模块
/// </summary>
public class MenuModuleDto
{
/// <summary>
/// 父编码
/// </summary>
public string ParentCode { get; set; } = string.Empty;
/// <summary>
/// 编码
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 类型
/// </summary>
public string Type { get; set; } = string.Empty;
}
- PermissionDto
/// <summary>
/// 权限
/// </summary>
public class PermissionDto
{
/// <summary>
/// 菜单树
/// </summary>
public List<MenuModuleDto> MenuModule { get; set; } = [];
/// <summary>
/// 选中的菜单
/// </summary>
public List<string> CheckedMenu { get; set; } = [];
/// <summary>
/// 选中的模块
/// </summary>
public List<string> CheckedModule { get; set; } = [];
}
- PermissionUpdateDto
/// <summary>
/// 权限更新
/// </summary>
public class PermissionUpdateDto
{
/// <summary>
/// 角色Id
/// </summary>
public Guid RoleId { get; set; }
/// <summary>
/// 菜单模块列表
/// </summary>
public List<string> MenuModules { get; set; } = new List<string>();
}
在Auths文件夹中分别新建名称为CaptchaDto、UserLoginDto、LoginUserDto、UserPermissionDto、UserTokenDto、AccessTokenDto、MenuRouterDto、RefreshTokenCreateDto和RefreshTokenDto的数据传输类
- CaptchaDto
/// <summary>
/// 验证码
/// </summary>
public class CaptchaDto
{
/// <summary>
/// 键
/// </summary>
public string Key { get; set; } = string.Empty;
/// <summary>
/// 验证码文字
/// </summary>
public string Text { get; set; } = string.Empty;
/// <summary>
/// 验证码图片
/// </summary>
public string Img { get; set; } = string.Empty;
}
- UserLoginDto
/// <summary>
/// 用户登录
/// </summary>
public class UserLoginDto
{
/// <summary>
/// 用户账号
/// </summary>
public string UserAccount { get; set; } = string.Empty;
/// <summary>
/// 用户密码
/// </summary>
public string Password { get; set; } = string.Empty;
/// <summary>
/// 验证码
/// </summary>
public string Captcha { get; set; } = string.Empty;
/// <summary>
/// 验证码发放时间时间戳
/// </summary>
public string CaptchaTime { get; set; } = string.Empty;
}
- LoginUserDto
/// <summary>
/// 登录用户
/// </summary>
public class LoginUserDto
{
/// <summary>
/// 用户Id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 用户账号
/// </summary>
public string UserAccount { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; } = string.Empty;
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// 访问凭证值
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 访问凭证有效时间,单位:分钟
/// </summary>
public int ExpiresIn { get; set; }
/// <summary>
/// 访问过期时间
/// </summary>
public DateTime ExpiresTime { get; set; }
/// <summary>
/// 刷新凭证值
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
/// <summary>
/// 刷新凭证有效时间,单位:分钟
/// </summary>
public int RefreshExpiresIn { get; set; }
/// <summary>
/// 刷新过期时间
/// </summary>
public DateTime RefreshExpiresTime { get; set; }
}
- UserPermissionDto
/// <summary>
/// 用户权限
/// </summary>
public class UserPermissionDto
{
/// <summary>
/// 用户Id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 用户账号
/// </summary>
public string UserAccount { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; } = string.Empty;
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// 授权访问菜单
/// </summary>
public List<MenuRouterDto> MenuRouters { get; set; } = [];
/// <summary>
/// 授权使用功能
/// </summary>
public List<string> Modules { get; set; } = [];
}
- UserTokenDto
/// <summary>
/// 用户令牌
/// </summary>
public class UserTokenDto
{
/// <summary>
/// 用户Id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 用户账号
/// </summary>
public string UserAccount { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 角色编码
/// </summary>
public string RoleCode { get; set; } = string.Empty;
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = string.Empty;
}
- AccessTokenDto
/// <summary>
/// 令牌
/// </summary>
public class AccessTokenDto
{
/// <summary>
/// 访问令牌
/// </summary>
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 访问过期时间
/// </summary>
public DateTime ExpiresTime { get; set; }
/// <summary>
/// 访问凭证有效时间,单位:分钟
/// </summary>
public int ExpiresIn { get; set; }
}
- MenuRouterDto
/// <summary>
/// 菜单路由
/// </summary>
public class MenuRouterDto
{
/// <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 MetaDto Meta { get; set; }
/// <summary>
/// 子菜单路由
/// </summary>
public List<MenuRouterDto> Children { get; set; } = [];
}
/// <summary>
/// 菜单内容
/// </summary>
public class MetaDto
{
/// <summary>
/// 路由在侧边栏和面包屑中展示的名称
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// 路由的图标
/// </summary>
public string? Icon { get; set; }
}
- RefreshTokenCreateDto
/// <summary>
/// 刷新令牌创建
/// </summary>
public class RefreshTokenCreateDto
{
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
}
- RefreshTokenDto
/// <summary>
/// 令牌
/// </summary>
public class RefreshTokenDto
{
/// <summary>
/// 刷新令牌
/// </summary>
public string RefreshToken { get; set; } = string.Empty;
/// <summary>
/// 访问过期时间
/// </summary>
public DateTime RefreshExpiresTime { get; set; }
/// <summary>
/// 访问凭证有效时间,单位:分钟
/// </summary>
public int RefreshExpiresIn { get; set; }
}
应用服务接口
新建名称为IUserAppService的用户应用服务接口
/// <summary>
/// 用户应用服务
/// </summary>
public interface IUserAppService : IApplicationService
{
/// <summary>
/// 获取用户分页列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<PagedResultDto<UserDto>> GetListAsync(UserQueryDto input);
/// <summary>
/// 新增用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<UserDto> CreateAsync(UserCreateDto input);
/// <summary>
/// 更新用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<UserDto> UpdateAsync(UserUpdateDto input);
/// <summary>
/// 获取用户
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Task<UserDto> GetAsync(Guid id);
/// <summary>
/// 删除用户
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public Task DeleteAsync(List<Guid> ids);
/// <summary>
/// 解冻用户
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Task<UserDto> UnfreezeAsync(Guid id);
}
新建名称为IRoleAppService的角色应用服务接口
/// <summary>
/// 角色应用服务
/// </summary>
public interface IRoleAppService : IApplicationService
{
/// <summary>
/// 获取角色分页列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<PagedResultDto<RoleDto>> GetListAsync(RoleQueryDto input);
/// <summary>
/// 新增角色
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<RoleDto> CreateAsync(RoleCreateDto input);
/// <summary>
/// 更新角色
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<RoleDto> UpdateAsync(RoleUpdateDto input);
/// <summary>
/// 获取角色
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Task<RoleDto> GetAsync(Guid id);
/// <summary>
/// 删除角色
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public Task DeleteAsync(List<Guid> ids);
/// <summary>
/// 获取所有角色
/// </summary>
/// <returns></returns>
public Task<List<RoleMapDto>> GetAllRoleAsync();
}
新建名称为IMenuAppService的菜单应用服务接口
/// <summary>
/// 菜单应用服务
/// </summary>
public interface IMenuAppService : IApplicationService
{
/// <summary>
/// 根据角色编码获取权限
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Task<PermissionDto> GetPermissionAsync(Guid id);
/// <summary>
/// 更新权限
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task UpdateAsync(PermissionUpdateDto input);
}
新建名称为IAuthAppService 的认证应用服务接口
/// <summary>
/// 认证应用服务
/// </summary>
public interface IAuthAppService : IApplicationService
{
/// <summary>
/// 登录
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<LoginUserDto> LoginAsync(UserLoginDto input);
/// <summary>
/// 获取当前用户
/// </summary>
/// <returns></returns>
public Task<UserPermissionDto> GetUserAsync();
/// <summary>
/// 获取验证码
/// </summary>
/// <returns></returns>
public Task<CaptchaDto> GetCaptchaAsync();
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public Task<LoginUserDto> RefreshAsync(RefreshTokenCreateDto input);
}
应用服务实现
创建名称为General.Backend.Application的标准类库,新建名称为Options的文件夹,用于存放选项类
在程序包管理控制台选中General.Backend.Application,执行以下命令安装ABP应用服务和对象映射相关的Nuget包
Install-Package Volo.Abp.Ddd.Application -v 8.3.0
Install-Package Volo.Abp.AutoMapper -v 8.3.0
General.Backend.Application添加项目引用General.Backend.Application.Contracts和DPower.Store.Domain
新建名称为GeneralApplicationAutoMapperProfile的对象映射配置类
/// <summary>
/// Profile
/// </summary>
public class GeneralApplicationAutoMapperProfile : Profile
{
/// <summary>
///
/// </summary>
public GeneralApplicationAutoMapperProfile()
{
CreateMap<User, UserDto>();
CreateMap<Role, RoleDto>();
CreateMap<Role, RoleMapDto>();
CreateMap<Menu, MenuDto>();
CreateMap<UserPermissionCacheItem, UserPermissionDto>();
CreateMap<MenuRouterCacheItem, MenuRouterDto>();
CreateMap<Meta, MetaDto>();
}
}
新建名称为GeneralApplicationModule的应用服务模块类
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(GeneralDomainModule),
typeof(GeneralApplicationContractsModule)
)]
public class GeneralApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<GeneralApplicationModule>();
});
}
}
在程序包管理控制台选中General.Backend.Application,执行以下命令安装MVC和JWT相关的Nuget包
Install-Package Volo.Abp.AspNetCore.Mvc -v 8.3.0
Install-Package System.IdentityModel.Tokens.Jwt -v 7.1.2
在名称为Options文件夹下,新建名称为JwtOptions的JWT配置选项类,后续在启动项目中的默认加载文件appsetting.json中配置具体的参数
/// <summary>
/// JsonWebToken
/// </summary>
public class JwtOptions
{
/// <summary>
/// JWT
/// </summary>
public const string JwtOption = "JWT";
/// <summary>
/// 签发者
/// </summary>
public string Issuer { get; set; } = string.Empty;
/// <summary>
/// 收发者
/// </summary>
public string Audience { get; set; } = string.Empty;
/// <summary>
/// 密钥
/// </summary>
public string Secret { get; set; } = string.Empty;
/// <summary>
/// Token有效期(单位:分钟)
/// </summary>
public int ExpirationTime { get; set; }
/// <summary>
/// Token有效刷新时间(单位:分钟)
/// </summary>
public int RefreshTime { get; set; }
}
接下来实现具体的业务应用服务实现类
新建名称为UserAppService、RoleAppService、MenuAppService和AuthAppService的用户应用服务类、角色应用服务类、菜单应用服务类和认证授权应用服务类
- UserAppService
/// <summary>
/// 用户应用服务
/// </summary>
public class UserAppService : ApplicationService, IUserAppService
{
private readonly ILogger<UserAppService> _logger;
private readonly ICurrentUser _currentUser;
private readonly IDistributedCache<UserPermissionCacheItem, Guid> _permissionCache;
private readonly UserService _userService;
private readonly IUserRepository _userRepository;
private readonly IRoleRepository _roleRepository;
private readonly IRepository<UserRole> _userRoleRepository;
public UserAppService(ILogger<UserAppService> logger,
ICurrentUser currentUser,
IDistributedCache<UserPermissionCacheItem, Guid> permissionCache,
UserService userService,
IUserRepository userRepository,
IRoleRepository roleRepository,
IRepository<UserRole> userRoleRepository)
{
_logger = logger;
_currentUser = currentUser;
_permissionCache = permissionCache;
_userService = userService;
_userRepository = userRepository;
_roleRepository = roleRepository;
_userRoleRepository = userRoleRepository;
}
/// <summary>
/// 获取用户分页列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<PagedResultDto<UserDto>> GetListAsync(UserQueryDto input)
{
string fieldToSort = _userRepository.GenerateSortExpression(input.SortField, input.SortType, typeof(User));
var query = await _userRepository.GetQueryableAsync();
var list = await AsyncExecuter.ToListAsync(query);
list = list
.WhereIf(!string.IsNullOrEmpty(input.Account) && !string.IsNullOrWhiteSpace(input.Account),
user => user.Account.Contains(input.Account!))
.WhereIf(!string.IsNullOrEmpty(input.Name) && !string.IsNullOrWhiteSpace(input.Name),
user => user.Name.Contains(input.Name!))
.OrderBy(user => user.CreationTime)
.ToList();
var totalCount = list.Count;
list = list.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize).ToList();
var userIds = list.Select(user => user.Id);
var userRoles = await _userRoleRepository.GetListAsync(rel => userIds.Contains(rel.UserId));
var roleIds = userRoles.Select(rel => rel.RoleId).Distinct().ToList();
var roles = await _roleRepository.GetListAsync(role => roleIds.Contains(role.Id));
var listDto = list.Select(user =>
{
var userDto = ObjectMapper.Map<User, UserDto>(user);
userDto.RoleIds = userRoles.Where(rel => rel.UserId == user.Id).Select(rel => rel.RoleId).ToList();
userDto.RoleNames = roles.Where(x => userDto.RoleIds.Contains(x.Id)).Select(y => y.Name).ToList();
return userDto;
}).ToList();
return new PagedResultDto<UserDto>(totalCount, listDto);
}
/// <summary>
/// 新增用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<UserDto> CreateAsync(UserCreateDto input)
{
var user = await _userService.AddAsync(
input.Account,
input.Password,
input.Name,
input.Contact,
input.Address);
var roles = await _roleRepository.GetListAsync(role => input.RoleIds.Contains(role.Id));
var notExistRoles = input.RoleIds.Except(roles.Select(x => x.Id));
if (notExistRoles.Any())
{
throw new UserFriendlyException("角色不存在");
}
_userService.BindRoles(user, input.RoleIds);
user = await _userRepository.InsertAsync(user);
var userDto = ObjectMapper.Map<User, UserDto>(user);
return userDto;
}
/// <summary>
/// 更新用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<UserDto> UpdateAsync(UserUpdateDto input)
{
var user = await _userService.ModifyAsync(
input.Id,
input.Password,
input.Name,
input.Contact,
input.Address);
var roles = await _roleRepository.GetListAsync(role => input.RoleIds.Contains(role.Id));
var notExistRoles = input.RoleIds.Except(roles.Select(x => x.Id));
if (notExistRoles.Any())
{
throw new UserFriendlyException("角色不存在");
}
_userService.BindRoles(user, input.RoleIds);
user = await _userRepository.UpdateAsync(user);
var userDto = ObjectMapper.Map<User, UserDto>(user);
return userDto;
}
/// <summary>
/// 获取用户
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<UserDto> GetAsync(Guid id)
{
var user = await _userRepository.FindAsync(id);
if (user == null)
{
throw new UserFriendlyException("用户不存在");
}
var userDto = ObjectMapper.Map<User, UserDto>(user);
userDto.RoleIds = user.UserRoles.Select(x => x.RoleId).ToList();
var roles = await _roleRepository.GetListAsync(role => userDto.RoleIds.Contains(role.Id));
userDto.RoleNames = roles.Select(role => role.Name).ToList();
return userDto;
}
/// <summary>
/// 删除用户
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task DeleteAsync([FromBody] List<Guid> ids)
{
var users = await _userRepository.GetListAsync(user => ids.Contains(user.Id));
foreach (var user in users)
{
var account = user.Account;
if (account == UserConsts.AdminAccount)
{
throw new UserFriendlyException("不可操作管理员用户");
}
if (_currentUser.Id.HasValue)
{
if (ids.Contains(_currentUser.Id.Value))
{
throw new UserFriendlyException("不可删除当前用户");
}
}
}
await _userRepository.DeleteAsync(user => ids.Contains(user.Id));
await _permissionCache.RemoveManyAsync(ids);
}
/// <summary>
/// 解冻用户
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<UserDto> UnfreezeAsync(Guid id)
{
var user = await _userService.UnfreezeAsync(id);
user = await _userRepository.UpdateAsync(user);
var userDto = ObjectMapper.Map<User, UserDto>(user);
return userDto;
}
}
- RoleAppService
/// <summary>
/// 角色应用服务
/// </summary>
public class RoleAppService : ApplicationService, IRoleAppService
{
private readonly RoleService _roleService;
private readonly IRoleRepository _roleRepository;
private readonly IRepository<UserRole> _userRoleRepository;
public RoleAppService(
RoleService roleService,
IRoleRepository roleRepository,
IRepository<UserRole> userRoleRepository
)
{
_roleService = roleService;
_roleRepository = roleRepository;
_userRoleRepository = userRoleRepository;
}
/// <summary>
/// 获取角色分页列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<PagedResultDto<RoleDto>> GetListAsync(RoleQueryDto input)
{
string fieldToSort = _roleRepository.GenerateSortExpression(input.SortField, input.SortType, typeof(Role));
var query = await _roleRepository.GetQueryableAsync();
query = query
.WhereIf(!string.IsNullOrEmpty(input.Name) && !string.IsNullOrWhiteSpace(input.Name),
role => role.Name.Contains(input.Name!))
.OrderByIf<Role, IQueryable<Role>>(!string.IsNullOrEmpty(fieldToSort),
fieldToSort);
var totalCount = query.Count();
query = query.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize);
var list = await AsyncExecuter.ToListAsync(query);
var listDto = list.Select(role =>
{
var roleDto = ObjectMapper.Map<Role, RoleDto>(role);
return roleDto;
}).ToList();
return new PagedResultDto<RoleDto>(totalCount, listDto);
}
/// <summary>
/// 新增角色
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<RoleDto> CreateAsync(RoleCreateDto input)
{
var role = await _roleService.AddAsync(
input.Code,
input.Name,
input.Remark);
role = await _roleRepository.InsertAsync(role);
var roleDto = ObjectMapper.Map<Role, RoleDto>(role);
return roleDto;
}
/// <summary>
/// 更新角色
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<RoleDto> UpdateAsync(RoleUpdateDto input)
{
var role = await _roleService.ModifyAsync(
input.Id,
input.Name,
input.Remark);
role = await _roleRepository.UpdateAsync(role);
var roleDto = ObjectMapper.Map<Role, RoleDto>(role);
return roleDto;
}
/// <summary>
/// 获取角色
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<RoleDto> GetAsync(Guid id)
{
var role = await _roleRepository.FindAsync(id);
if (role == null)
{
throw new UserFriendlyException("角色不存在");
}
var roleDto = ObjectMapper.Map<Role, RoleDto>(role);
return roleDto;
}
/// <summary>
/// 删除角色
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task DeleteAsync([FromBody] List<Guid> ids)
{
var userRoles = await _userRoleRepository.GetListAsync(rel => ids.Contains(rel.RoleId));
if (userRoles.Count > 0)
{
throw new UserFriendlyException("角色已被使用,不可删除");
}
await _roleRepository.DeleteAsync(role => ids.Contains(role.Id));
}
/// <summary>
/// 获取所有角色
/// </summary>
/// <returns></returns>
public async Task<List<RoleMapDto>> GetAllRoleAsync()
{
var roles = await _roleRepository.GetListAsync();
return ObjectMapper.Map<List<Role>, List<RoleMapDto>>(roles);
}
}
- MenuAppService
/// <summary>
/// 菜单应用服务
/// </summary>
public class MenuAppService : ApplicationService, IMenuAppService
{
private readonly MenuService _menuService;
private readonly RoleService _roleService;
private readonly IRoleRepository _roleRepository;
private readonly IMenuRepository _menuRepository;
public MenuAppService(
MenuService menuService,
RoleService roleService,
IRoleRepository roleRepository,
IMenuRepository menuRepository)
{
_menuService = menuService;
_roleService = roleService;
_roleRepository = roleRepository;
_menuRepository = menuRepository;
}
/// <summary>
/// 根据角色编码获取权限
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<PermissionDto> GetPermissionAsync(Guid id)
{
var permissionDto = new PermissionDto();
var role = await _roleRepository.FindAsync(id);
if (role == null)
{
throw new UserFriendlyException("角色不存在");
}
var menuCodes = role.RoleMenus.Select(x => x.MenuCode).ToList();
var allMenus = await _menuRepository.GetListAsync();
var menus = allMenus.Where(x => menuCodes.Contains(x.Code));
permissionDto.CheckedMenu = menus.Where(x => x.Type == "C" || x.Type == "M").Select(y => y.Code).ToList();
permissionDto.CheckedModule = menus.Where(x => x.Type == "F").Select(y => y.Code).ToList();
var menuDtos = ObjectMapper.Map<List<Menu>, List<MenuDto>>(allMenus);
menuDtos = menuDtos.OrderBy(x => x.Sort).ToList();
permissionDto.MenuModule = [];
menuDtos.ForEach(item =>
{
permissionDto.MenuModule.Add(new MenuModuleDto
{
Code = item.Code,
ParentCode = item.ParentCode,
Name = item.Name,
Type = item.Type
});
});
return permissionDto;
}
/// <summary>
/// 更新权限
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task UpdateAsync(PermissionUpdateDto input)
{
var role = await _roleRepository.FindAsync(input.RoleId);
if (role == null)
{
throw new UserFriendlyException("角色不存在");
}
_roleService.BindMenus(role, input.MenuModules);
await _roleRepository.UpdateAsync(role);
}
}
- AuthAppService
public class AuthAppService : ApplicationService, IAuthAppService
{
private readonly ILogger<AuthAppService> _logger;
private readonly JwtOptions _jwtOptions;
private readonly ICurrentUser _currentUser;
private readonly IDistributedCache<string> _captchaCache;
private readonly IDistributedCache<UserPermissionCacheItem, Guid> _permissionCache;
private readonly UserService _userService;
private readonly MenuService _menuService;
private readonly IUserRepository _userRepository;
public AuthAppService(
ILogger<AuthAppService> logger,
IOptions<JwtOptions> jwtOptions,
ICurrentUser currentUser,
IDistributedCache<string> captchaCache,
IDistributedCache<UserPermissionCacheItem, Guid> permissionCache,
UserService userService,
MenuService menuService,
IUserRepository userRepository)
{
_logger = logger;
_jwtOptions = jwtOptions.Value;
_currentUser = currentUser;
_captchaCache = captchaCache;
_permissionCache = permissionCache;
_userService = userService;
_menuService = menuService;
_userRepository = userRepository;
}
/// <summary>
/// 登录
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[UnitOfWork(IsDisabled = true)]
public async Task<LoginUserDto> LoginAsync(UserLoginDto input)
{
string? captchaCode = await _captchaCache.GetAsync(input.CaptchaTime);
if (string.IsNullOrEmpty(captchaCode))
{
throw new UserFriendlyException("验证码已过期");
}
if (!captchaCode.ToUpper().Equals(input.Captcha.ToUpper()))
{
throw new UserFriendlyException("验证码错误");
}
var user = await _userService.LoginCheckAsync(input.UserAccount, input.Password);
var userPermissionCacheItem = await _permissionCache.GetOrAddAsync(
user.Id,
async () => await GetUserPermissionAsync(user)
) ?? throw new UserFriendlyException("获取登录用户权限异常");
var userToken = new UserTokenDto
{
UserId = user.Id,
UserAccount = user.Account,
UserName = user.Name,
RoleCode = userPermissionCacheItem.Role,
RoleName = userPermissionCacheItem.RoleName
};
var accessToken = GetAccessToken(userToken);
var refreshToken = GetRefreshToken(userToken);
await _captchaCache.RemoveAsync(input.CaptchaTime);
return new LoginUserDto
{
UserId = user.Id,
UserAccount = user.Account,
UserName = user.Name,
Role = userToken.RoleCode,
RoleName = userToken.RoleName,
AccessToken = accessToken.AccessToken,
ExpiresIn = accessToken.ExpiresIn,
ExpiresTime = accessToken.ExpiresTime,
RefreshToken = refreshToken.RefreshToken,
RefreshExpiresIn = refreshToken.RefreshExpiresIn,
RefreshExpiresTime = refreshToken.RefreshExpiresTime
};
}
/// <summary>
/// 获取当前用户
/// </summary>
/// <returns></returns>
public async Task<UserPermissionDto> GetUserAsync()
{
if (!_currentUser.IsAuthenticated)
{
throw new UserFriendlyException("获取当前用户权限异常");
}
var userPermissionCacheItem = await _permissionCache.GetOrAddAsync(
_currentUser.Id!.Value,
async () =>
{
var user = await _userRepository.GetAsync(user => user.Id == _currentUser.Id!.Value);
return await GetUserPermissionAsync(user);
}
) ?? throw new UserFriendlyException("获取缓存用户权限异常");
return ObjectMapper.Map<UserPermissionCacheItem, UserPermissionDto>(userPermissionCacheItem);
}
/// <summary>
/// 获取验证码
/// </summary>
/// <returns></returns>
[AllowAnonymous]
public async Task<CaptchaDto> GetCaptchaAsync()
{
var captchaCode = CaptchaHelper.GenerateRandomCaptcha();
var dateTimeNow = DateTime.UtcNow;
var key = dateTimeNow.ToTimestampMillis().ToString();
await _captchaCache.SetAsync(key, captchaCode, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
return new CaptchaDto
{
Key = key,
Text = captchaCode,
Img = Convert.ToBase64String(CaptchaHelper.CreateCaptchaImage(captchaCode))
};
}
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[AllowAnonymous]
public async Task<LoginUserDto> RefreshAsync(RefreshTokenCreateDto input)
{
var claim = ValidateToken(
input.RefreshToken,
_jwtOptions.Secret,
_jwtOptions.Audience,
_jwtOptions.Issuer);
if (claim.Count <= 2 || !Guid.TryParse(claim[2].Value, out Guid userId))
{
throw new UserFriendlyException("无效的令牌");
}
var userPermissionCacheItem = await _permissionCache.GetOrAddAsync(
userId,
async () =>
{
var user = await _userRepository.GetAsync(user => user.Id == userId);
return await GetUserPermissionAsync(user);
}
) ?? throw new UserFriendlyException("获取缓存用户权限异常");
var userToken = new UserTokenDto
{
UserId = userId,
UserAccount = userPermissionCacheItem.UserAccount,
UserName = userPermissionCacheItem.UserName,
RoleCode = userPermissionCacheItem.Role,
RoleName = userPermissionCacheItem.RoleName
};
var accessToken = GetAccessToken(userToken);
var refreshToken = GetRefreshToken(userToken);
return new LoginUserDto
{
UserId = userId,
UserAccount = userPermissionCacheItem.UserAccount,
UserName = userPermissionCacheItem.UserName,
Role = userToken.RoleCode,
RoleName = userToken.RoleName,
AccessToken = accessToken.AccessToken,
ExpiresIn = accessToken.ExpiresIn,
ExpiresTime = accessToken.ExpiresTime,
RefreshToken = refreshToken.RefreshToken,
RefreshExpiresIn = refreshToken.RefreshExpiresIn,
RefreshExpiresTime = refreshToken.RefreshExpiresTime
};
}
/// <summary>
/// 获取用户权限
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
private async Task<UserPermissionCacheItem> GetUserPermissionAsync(User user)
{
var userPermissionCacheItem = await _menuService.GetPermissionAsync(user);
return userPermissionCacheItem;
}
/// <summary>
/// 获取访问令牌
/// </summary>
/// <param name="userToken"></param>
/// <returns></returns>
private AccessTokenDto GetAccessToken(UserTokenDto userToken)
{
string secret = _jwtOptions.Secret;
var key = Encoding.UTF8.GetBytes(secret);
var authTime = DateTime.UtcNow; // 授权时间
var expiresIn = TimeSpan.FromMinutes(_jwtOptions.ExpirationTime);
DateTime expires = authTime.Add(expiresIn); // 过期时间
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescripor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity([
new Claim(AuthConsts.Audience, _jwtOptions.Audience),
new Claim(AuthConsts.Issuer, _jwtOptions.Issuer),
new Claim(AbpClaimTypes.UserId, userToken.UserId.ToString()),
new Claim(AuthConsts.UserAccount, userToken.UserAccount),
new Claim(AbpClaimTypes.UserName, userToken.UserName),
new Claim(AbpClaimTypes.Role, userToken.RoleName),
new Claim(AuthConsts.Subject, AuthConsts.ValidateType)
]),
Expires = expires,
// 对称秘钥SymmetricSecurityKey
// 签名证书(秘钥,加密算法)SecurityAlgorithms
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
};
var token = tokenHandler.CreateToken(tokenDescripor);
var tokenString = tokenHandler.WriteToken(token);
var accessToken = new AccessTokenDto
{
AccessToken = tokenString,
ExpiresIn = (int)expiresIn.TotalMinutes,
ExpiresTime = expires,
};
return accessToken;
}
/// <summary>
/// 获取刷新令牌
/// </summary>
/// <param name="userToken"></param>
/// <returns></returns>
private RefreshTokenDto GetRefreshToken(UserTokenDto userToken)
{
string secret = _jwtOptions.Secret;
var key = Encoding.UTF8.GetBytes(secret);
var authTime = DateTime.UtcNow; // 授权时间
var expiresIn = TimeSpan.FromMinutes(_jwtOptions.RefreshTime);
DateTime expires = authTime.Add(expiresIn); // 过期时间
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescripor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity([
new Claim(AuthConsts.Audience, _jwtOptions.Audience),
new Claim(AuthConsts.Issuer, _jwtOptions.Issuer),
new Claim(AbpClaimTypes.UserId, userToken.UserId.ToString()),
new Claim(AuthConsts.UserAccount, userToken.UserAccount),
new Claim(AbpClaimTypes.UserName, userToken.UserName),
new Claim(AbpClaimTypes.Role, userToken.RoleName),
new Claim(AuthConsts.Subject, AuthConsts.ValidateType)
]),
Expires = expires,
// 对称秘钥SymmetricSecurityKey
// 签名证书(秘钥,加密算法)SecurityAlgorithms
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
};
var token = tokenHandler.CreateToken(tokenDescripor);
var tokenString = tokenHandler.WriteToken(token);
var refreshToken = new RefreshTokenDto
{
RefreshToken = tokenString,
RefreshExpiresIn = (int)expiresIn.TotalMinutes,
RefreshExpiresTime = expires,
};
return refreshToken;
}
/// <summary>
/// 校验令牌
/// </summary>
/// <param name="token"></param>
/// <param name="secret"></param>
/// <param name="audience"></param>
/// <param name="issuer"></param>
/// <returns></returns>
private List<Claim> ValidateToken(string token, string secret, string audience, string issuer)
{
var claims = new List<Claim>();
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(secret))
{
throw new UserFriendlyException("无效的令牌");
}
try
{
JwtSecurityToken jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);
if (jwtToken == null)
{
throw new UserFriendlyException("无效的令牌");
}
//string audience = jwtToken.Claims.ToList()[0].Value;
//string issuer = jwtToken.Claims.ToList()[1].Value;
var keyByteArray = Encoding.UTF8.GetBytes(secret);
new JwtSecurityTokenHandler().ValidateToken(token, new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(keyByteArray),
ValidateAudience = true,
ValidAudience = audience,
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
if (jwtToken.Payload.Claims is List<Claim> claimlist)
{
claims = claimlist;
}
}
catch (SecurityTokenExpiredException ex)
{
_logger.LogError(ex, "令牌超时,登录失效");
throw new UserFriendlyException("令牌超时,登录失效");
}
catch (SecurityTokenInvalidLifetimeException ex)
{
_logger.LogError(ex, "令牌超时,登录失效");
throw new UserFriendlyException("令牌超时,登录失效");
}
catch (Exception ex)
{
_logger.LogError(ex, "无效的令牌");
throw new UserFriendlyException("无效的令牌");
}
return claims;
}
}
在程序包管理控制台选中General.Backend.Domain.Shared,执行以下命令安装图形相关的Nuget包,用于生成验证码图形
Install-Package SkiaSharp.NativeAssets.Linux.NoDependencies -v 2.88.8
Install-Package System.Drawing.Common -v 8.0.0
在General.Backend.Domain.Shared标准类库中,新建名称为Extensions和Helper的文件夹,用户存放扩展类和帮助类
在Extensions文件夹下,新建名称为DataExtension的数据扩展类
/// <summary>
/// 数据扩展
/// </summary>
public static class DataExtension
{
/// <summary>
/// 时间戳转换为时间
/// </summary>
/// <param name="timestampMillis"></param>
/// <param name="dateTimeKind"></param>
/// <returns></returns>
public static DateTime ToDateTime(this long timestampMillis, DateTimeKind dateTimeKind = DateTimeKind.Utc)
{
var dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, dateTimeKind).AddMilliseconds(timestampMillis);
return dateTime;
}
/// <summary>
/// 时间转换为时间戳
/// </summary>
/// <param name="dateTime"></param>
/// <param name="dateTimeKind"></param>
/// <returns></returns>
public static long ToTimestampMillis(this DateTime dateTime, DateTimeKind dateTimeKind = DateTimeKind.Utc)
{
var utcDateTime = new DateTime(1970, 1, 1, 0, 0, 0, dateTimeKind);
TimeSpan timeSpan = dateTime - utcDateTime;
long timestampMillis = Convert.ToInt64(timeSpan.TotalMilliseconds);
return timestampMillis;
}
}
在Helper文件夹下,新建名称为CaptchaHelper的验证码生成类
public partial class CaptchaHelper
{
private const string Letters = "1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z";
/// <summary>
/// 生成随机验证码字符
/// </summary>
/// <param name="codeLength">验证码位数</param>
/// <returns></returns>
public static string GenerateRandomCaptcha(int codeLength = 4)
{
var array = Letters.Split(new[] { ',' });
var random = new Random();
var temp = -1;
var captcheCode = string.Empty;
for (int i = 0; i < codeLength; i++)
{
if (temp != -1)
{
random = new Random(i * temp * unchecked((int)DateTime.UtcNow.Ticks));
}
var index = random.Next(array.Length);
if (temp != -1 && temp == index)
{
return GenerateRandomCaptcha(codeLength);
}
temp = index;
captcheCode += array[index];
}
return captcheCode;
}
/// <summary>
/// 生成验证码图片
/// </summary>
/// <param name="randomCode"></param>
/// <returns></returns>
public static byte[] CreateCaptchaImage(string randomCode = "")
{
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows))
{
return CreateCaptchaImageForWindows(randomCode);
}
else
{
try
{
var verifyCode = new Captcha
{
SetIsBackgroundLine = true,
SetVerifyCodeText = randomCode,
SetIsRandomColor = true,
SetRandomAngle = 30
};
verifyCode.SetWith = verifyCode.SetVerifyCodeText.Length * 16;
verifyCode.SetHeight = 28;
verifyCode.SetFontSize = 14;
verifyCode.SetForeNoisePointCount = 0;
verifyCode.SetDisturbStyleLine = false;
var bytes = verifyCode.GetVerifyCodeImage();
return bytes;
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// 生成验证码图片
/// </summary>
/// <param name="randomCode"></param>
/// <returns></returns>
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
public static byte[] CreateCaptchaImageForWindows(string randomCode)
{
// 随机转动角度
const int randAngle = 45;
int mapwidth = randomCode.Length * 16;
// 创建图片背景
var map = new Bitmap(mapwidth, 28);
Graphics graph = Graphics.FromImage(map);
// 清除画面,填充背景
graph.Clear(Color.AliceBlue);
var random = new Random();
// 绘制干扰曲线
for (int i = 0; i < 2; i++)
{
var p1 = new Point(0, random.Next(map.Height));
var p2 = new Point(random.Next(map.Width), random.Next(map.Height));
var p3 = new Point(random.Next(map.Width), random.Next(map.Height));
var p4 = new Point(map.Width, random.Next(map.Height));
Point[] p = { p1, p2, p3, p4 };
using (var pen = new Pen(Color.Gray, 1))
{
graph.DrawBeziers(pen, p);
}
}
// 文字距中
using (StringFormat format = new StringFormat(StringFormatFlags.NoClip))
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// 定义颜色
Color[] c = { Color.Black, Color.Red, Color.DarkBlue,
Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
// 定义字体
string[] fonts = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "宋体" };
int cindex = random.Next(7);
// 验证码旋转,防止机器识别
// 拆散字符串成单字符数组
char[] chars = randomCode.ToCharArray();
foreach (char t in chars)
{
int findex = random.Next(5);
// 字体样式(参数2为字体大小)
using (Font font = new Font(fonts[findex], 14, FontStyle.Bold))
{
using (Brush brush = new SolidBrush(c[cindex]))
{
Point dot = new Point(14, 14);
// 转动的度数
float angle = random.Next(-randAngle, randAngle);
if (t == '+' || t == '-' || t == '*')
{
// 加减乘运算符不进行旋转
// 移动光标到指定位置
graph.TranslateTransform(dot.X, dot.Y);
graph.DrawString(t.ToString(), font, brush, 1, 1, format);
// 移动光标到指定位置,每个字符紧凑显示,避免被软件识别
graph.TranslateTransform(-2, -dot.Y);
}
else
{
// 移动光标到指定位置
graph.TranslateTransform(dot.X, dot.Y);
graph.RotateTransform(angle);
graph.DrawString(t.ToString(), font, brush, 1, 1, format);
// 转回去
graph.RotateTransform(-angle);
// 移动光标到指定位置,每个字符紧凑显示,避免被软件识别
graph.TranslateTransform(-2, -dot.Y);
}
}
}
}
}
// 生成图片
using (var ms = new MemoryStream())
{
map.Save(ms, ImageFormat.Gif);
graph.Dispose();
map.Dispose();
return ms.GetBuffer();
}
}
public class Captcha
{
private readonly Random objRandom = new();
/// <summary>
/// //验证码长度
/// </summary>
public int SetLength { get; set; } = 4;
/// <summary>
/// 验证码字符串
/// </summary>
public string SetVerifyCodeText { get; set; } = string.Empty;
/// <summary>
/// 是否加入小写字母
/// </summary>
public bool SetAddLowerLetter { get; set; } = true;
/// <summary>
/// 是否加入大写字母
/// </summary>
public bool SetAddUpperLetter { get; set; } = true;
/// <summary>
/// 字体大小
/// </summary>
public int SetFontSize { get; set; } = 36;
/// <summary>
/// //字体颜色
/// </summary>
public SKColor SetFontColor { get; set; } = SKColors.Blue;
/// <summary>
/// 字体类型
/// </summary>
public string SetFontFamily = "Verdana";
/// <summary>
/// 背景色
/// </summary>
public SKColor SetBackgroundColor { get; set; } = SKColors.AliceBlue;
/// <summary>
/// 是否加入背景线
/// </summary>
public bool SetIsBackgroundLine { get; set; }
/// <summary>
/// 设置干扰线
/// </summary>
public bool SetDisturbStyleLine { get; set; }
/// <summary>
/// 前景噪点数量
/// </summary>
public int SetForeNoisePointCount { get; set; } = 2;
/// <summary>
/// 随机码的旋转角度
/// </summary>
public int SetRandomAngle { get; set; } = 40;
/// <summary>
/// 是否随机字体颜色
/// </summary>
public bool SetIsRandomColor { get; set; } = true;
/// <summary>
/// 图片宽度
/// </summary>
public int SetWith { get; set; } = 200;
/// <summary>
/// 图片高度
/// </summary>
public int SetHeight { get; set; } = 40;
/// <summary>
/// 问题验证码答案,适用于运算符
/// </summary>
public string VerifyCodeResult { get; private set; } = string.Empty;
public Captcha(int length = 4, bool isOperation = false)
{
if (isOperation)
{
var dic = GetQuestion();
SetVerifyCodeText = dic.Key;
VerifyCodeResult = dic.Value;
SetRandomAngle = 0;
}
else
{
SetLength = length;
GetVerifyCodeText();
}
SetWith = SetVerifyCodeText.Length * SetFontSize;
SetHeight = Convert.ToInt32(60.0 / 100 * SetFontSize + SetFontSize);
InitColors();
}
/// <summary>
/// 得到验证码字符串
/// </summary>
private void GetVerifyCodeText()
{
// 没有外部输入验证码时随机生成
if (string.IsNullOrEmpty(SetVerifyCodeText))
{
StringBuilder objStringBuilder = new StringBuilder();
// 加入数字1-9
for (int i = 1; i <= 9; i++)
{
objStringBuilder.Append(i.ToString());
}
// 加入大写字母A-Z,不包括O
if (SetAddUpperLetter)
{
char temp = ' ';
for (int i = 0; i < 26; i++)
{
temp = Convert.ToChar(i + 65);
// 如果生成的字母不是'O'
if (!temp.Equals('O'))
{
objStringBuilder.Append(temp);
}
}
}
// 加入小写字母a-z,不包括o
if (SetAddLowerLetter)
{
char temp = ' ';
for (int i = 0; i < 26; i++)
{
temp = Convert.ToChar(i + 97);
//如果生成的字母不是'o'
if (!temp.Equals('o'))
{
objStringBuilder.Append(temp);
}
}
}
// 生成验证码字符串
{
int index = 0;
for (int i = 0; i < SetLength; i++)
{
index = objRandom.Next(0, objStringBuilder.Length);
SetVerifyCodeText += objStringBuilder[index];
objStringBuilder.Remove(index, 1);
}
}
}
}
/// <summary>
/// 获取随机颜色
/// </summary>
/// <returns></returns>
private SKColor GetRandomColor()
{
Random random = new Random();
return Colors2[random.Next(Colors2.Count)];
}
/// <summary>
/// 获取问题
/// </summary>
/// <param name="questionList">默认数字加减验证</param>
/// <returns></returns>
public KeyValuePair<string, string> GetQuestion(Dictionary<string, string> questionList = null)
{
if (questionList == null)
{
questionList = new Dictionary<string, string>();
var operArray = new string[] { "+", "*", "-", "/" };
var left = objRandom.Next(0, 10);
var right = objRandom.Next(0, 10);
var oper = operArray[objRandom.Next(0, operArray.Length)];
string key = string.Empty, val = string.Empty;
switch (oper)
{
case "+":
key = string.Format("{0}+{1}=?", left, right);
val = (left + right).ToString();
questionList.Add(key, val);
break;
case "*":
key = string.Format("{0}×{1}=?", left, right);
val = (left * right).ToString();
questionList.Add(key, val);
break;
case "-":
if (left < right)
{
var intTemp = left;
left = right;
right = intTemp;
}
questionList.Add(left + "-" + right + "= ?", (left - right).ToString());
break;
case "/":
right = objRandom.Next(1, 10);
left = right * objRandom.Next(1, 10);
questionList.Add(left + "÷" + right + "= ?", (left / right).ToString());
break;
}
}
return questionList.ToList()[objRandom.Next(0, questionList.Count)];
}
/// <summary>
/// 干扰线的颜色集合
/// </summary>
private List<SKColor> Colors { get; set; } = new List<SKColor>();
private List<SKColor> Colors2 { get; set; } = new List<SKColor>();
public void InitColors()
{
Colors2 = new List<SKColor>
{
SKColors.Orange,
SKColors.Purple,
SKColors.DarkBlue,
SKColors.Green,
SKColors.RoyalBlue,
SKColors.Black,
SKColors.Brown
};
Colors = new List<SKColor>
{
SKColors.AliceBlue,
SKColors.PaleGreen,
SKColors.PaleGoldenrod,
SKColors.Orchid,
SKColors.OrangeRed,
SKColors.Orange,
SKColors.OliveDrab,
SKColors.Olive,
SKColors.OldLace,
SKColors.Navy,
SKColors.NavajoWhite,
SKColors.Moccasin,
SKColors.MistyRose,
SKColors.MintCream,
SKColors.MidnightBlue,
SKColors.MediumVioletRed,
SKColors.MediumTurquoise,
SKColors.MediumSpringGreen,
SKColors.LightSlateGray,
SKColors.LightSteelBlue,
SKColors.LightYellow,
SKColors.Lime,
SKColors.LimeGreen,
SKColors.Linen,
SKColors.PaleTurquoise,
SKColors.Magenta,
SKColors.MediumAquamarine,
SKColors.MediumBlue,
SKColors.MediumOrchid,
SKColors.MediumPurple,
SKColors.MediumSeaGreen,
SKColors.MediumSlateBlue,
SKColors.Maroon,
SKColors.PaleVioletRed,
SKColors.PapayaWhip,
SKColors.PeachPuff,
SKColors.Snow,
SKColors.SpringGreen,
SKColors.SteelBlue,
SKColors.Tan,
SKColors.Teal,
SKColors.Thistle,
SKColors.SlateGray,
SKColors.Tomato,
SKColors.Violet,
SKColors.Wheat,
SKColors.White,
SKColors.WhiteSmoke,
SKColors.Yellow,
SKColors.YellowGreen,
SKColors.Turquoise,
SKColors.LightSkyBlue,
SKColors.SlateBlue,
SKColors.Silver,
SKColors.Peru,
SKColors.Pink,
SKColors.Plum,
SKColors.PowderBlue,
SKColors.Purple,
SKColors.Red,
SKColors.SkyBlue,
SKColors.RosyBrown,
SKColors.SaddleBrown,
SKColors.Salmon,
SKColors.SandyBrown,
SKColors.SeaGreen,
SKColors.SeaShell,
SKColors.Sienna,
SKColors.RoyalBlue,
SKColors.LightSeaGreen,
SKColors.LightSalmon,
SKColors.LightPink,
SKColors.Crimson,
SKColors.Cyan,
SKColors.DarkBlue,
SKColors.Green,
SKColors.RoyalBlue,
SKColors.DarkGoldenrod,
SKColors.DarkGray,
SKColors.Cornsilk,
SKColors.DarkGreen,
SKColors.DarkMagenta,
SKColors.DarkOliveGreen,
SKColors.DarkOrange,
SKColors.DarkOrchid,
SKColors.DarkRed,
SKColors.DarkSalmon,
SKColors.DarkKhaki,
SKColors.DarkSeaGreen,
SKColors.CornflowerBlue,
SKColors.Chocolate,
SKColors.AntiqueWhite,
SKColors.Aqua,
SKColors.Aquamarine,
SKColors.Azure,
SKColors.Beige,
SKColors.Bisque,
SKColors.Coral,
SKColors.Black,
SKColors.Blue,
SKColors.BlueViolet,
SKColors.Brown,
SKColors.BurlyWood,
SKColors.CadetBlue,
SKColors.Chartreuse,
SKColors.BlanchedAlmond,
SKColors.Transparent,
SKColors.DarkSlateBlue,
SKColors.DarkTurquoise,
SKColors.IndianRed,
SKColors.Indigo,
SKColors.Ivory,
SKColors.Khaki,
SKColors.Lavender,
SKColors.LavenderBlush,
SKColors.HotPink,
SKColors.LawnGreen,
SKColors.LightBlue,
SKColors.LightCoral,
SKColors.LightCyan,
SKColors.LightGoldenrodYellow,
SKColors.LightGray,
SKColors.LightGreen,
SKColors.LemonChiffon,
SKColors.DarkSlateGray,
SKColors.Honeydew,
SKColors.Green,
SKColors.DarkViolet,
SKColors.DeepPink,
SKColors.DeepSkyBlue,
SKColors.DimGray,
SKColors.DodgerBlue,
SKColors.Firebrick,
SKColors.GreenYellow,
SKColors.FloralWhite,
SKColors.Fuchsia,
SKColors.Gainsboro,
SKColors.GhostWhite,
SKColors.Gold,
SKColors.Goldenrod,
SKColors.Gray,
SKColors.ForestGreen
};
}
/// <summary>
/// 创建画笔
/// </summary>
/// <param name="color"></param>
/// <param name="fontSize"></param>
/// <returns></returns>
private static SKPaint CreatePaint(SKColor color, float fontSize)
{
SKTypeface font = SKTypeface.FromFamilyName(
null,
SKFontStyleWeight.SemiBold,
SKFontStyleWidth.ExtraCondensed,
SKFontStyleSlant.Upright);
var paint = new SKPaint
{
IsAntialias = true,
Color = color,
Typeface = font,
TextSize = fontSize
};
return paint;
}
/// <summary>
/// 获取验证码
/// </summary>
/// <param name="captchaText">验证码文字</param>
/// <param name="width">图片宽度</param>
/// <param name="height">图片高度</param>
/// <param name="lineNum">干扰线数量</param>
/// <param name="lineStrookeWidth">干扰线宽度</param>
/// <returns></returns>
public byte[] GetVerifyCodeImage(int lineNum = 1, int lineStrookeWidth = 1)
{
// 创建bitmap位图
using (var image2d = new SKBitmap(
SetWith,
SetHeight,
SKColorType.Bgra8888,
SKAlphaType.Premul))
{
// 创建画笔
using (SKCanvas canvas = new SKCanvas(image2d))
{
// 填充背景颜色为白色
if (SetIsRandomColor)
{
SetFontColor = GetRandomColor();
}
// 填充白色背景
canvas.Clear(SetBackgroundColor);
AddForeNoisePoint(image2d);
AddBackgroundNoisePoint(image2d, canvas);
var drawStyle = new SKPaint();
drawStyle.IsAntialias = true;
drawStyle.TextSize = SetFontSize;
char[] chars = SetVerifyCodeText.ToCharArray();
for (int i = 0; i < chars.Length; i++)
{
var font = SKTypeface.FromFamilyName(
SetFontFamily,
SKFontStyleWeight.SemiBold,
SKFontStyleWidth.ExtraCondensed,
SKFontStyleSlant.Upright);
// 转动的度数
float angle = objRandom.Next(-30, 30);
canvas.Translate(12, 12);
float px = i * SetFontSize;
float py = SetHeight / 3;
canvas.RotateDegrees(angle, px, py);
drawStyle.Typeface = font;
drawStyle.Color = SetFontColor;
canvas.DrawText(chars[i].ToString(), px, py, drawStyle);
canvas.RotateDegrees(-angle, px, py);
canvas.Translate(-12, -12);
}
if (SetDisturbStyleLine)
{
// 画随机干扰线
using (SKPaint disturbStyle = new SKPaint())
{
Random random = new Random();
for (int i = 0; i < lineNum; i++)
{
disturbStyle.Color = Colors[random.Next(Colors.Count)];
disturbStyle.StrokeWidth = lineStrookeWidth;
canvas.DrawLine(
random.Next(0, SetWith),
random.Next(0, SetHeight),
random.Next(0, SetWith),
random.Next(0, SetHeight),
disturbStyle);
}
}
}
// 返回图片byte
using (SKImage img = SKImage.FromBitmap(image2d))
{
using (SKData p = img.Encode(SKEncodedImageFormat.Png, 100))
{
return p.ToArray();
}
}
}
}
}
private void AddForeNoisePoint(SKBitmap objBitmap)
{
for (int i = 0; i < objBitmap.Width * SetForeNoisePointCount; i++)
{
objBitmap.SetPixel(objRandom.Next(
objBitmap.Width),
objRandom.Next(objBitmap.Height),
SetFontColor);
}
}
private void AddBackgroundNoisePoint(SKBitmap objBitmap, SKCanvas objGraphics)
{
using (SKPaint objPen = CreatePaint(SKColors.Azure, 0))
{
for (int i = 0; i < objBitmap.Width * 2; i++)
{
objGraphics.DrawRect(
objRandom.Next(objBitmap.Width),
objRandom.Next(objBitmap.Height),
1,
1,
objPen);
}
}
if (SetIsBackgroundLine)
{
// 画图片的背景噪音线
for (var i = 0; i < 12; i++)
{
var x1 = objRandom.Next(objBitmap.Width);
var x2 = objRandom.Next(objBitmap.Width);
var y1 = objRandom.Next(objBitmap.Height);
var y2 = objRandom.Next(objBitmap.Height);
objGraphics.DrawLine(x1, y1, x2, y2, CreatePaint(SKColors.Silver, 0));
}
}
}
}
}
在General.Backend.Domain.Shared中的Consts文件夹下,新建名称为AuthConsts的认证授权常量类
public class AuthConsts
{
public const string Issuer = "iss";
public const string Audience = "aud";
public const string ValidateType = "password";
public const string Subject = "subject";
public const string UserAccount = "account";
}
在General.Backend.Domain中的CacheItems文件夹下,新建名称为UserPermissionCacheItem的用户权限缓存类
/// <summary>
/// 用户权限
/// </summary>
public class UserPermissionCacheItem
{
/// <summary>
/// 用户Id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 用户账号
/// </summary>
public string UserAccount { get; set; } = string.Empty;
/// <summary>
/// 用户名称
/// </summary>
public string UserName { get; set; } = string.Empty;
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; } = string.Empty;
/// <summary>
/// 角色名称
/// </summary>
public string RoleName { get; set; } = string.Empty;
/// <summary>
/// 授权访问菜单
/// </summary>
public List<MenuRouterCacheItem> MenuRouters { get; set; } = new List<MenuRouterCacheItem>();
/// <summary>
/// 授权使用功能
/// </summary>
public List<string> Modules { get; set; } = [];
}
完善基础仓储的功能,实现一个生成排序方式的方法
- IBaseRepository
/// <summary>
/// 基础仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IBaseRepository<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
{
public string GenerateSortExpression(string sortField, string sortType, Type type);
}
- BaseRepository
/// <summary>
/// 基础仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TKey"></typeparam>
public class BaseRepository<TEntity, TKey> : EfCoreRepository<GeneralDbContext, TEntity, TKey> where TEntity : class, IEntity<TKey>
{
public BaseRepository(IDbContextProvider<GeneralDbContext> dbContextProvider) : base(dbContextProvider)
{
}
public string GenerateSortExpression(string sortField, string sortType, Type type)
{
string sortExpression = string.Empty;
if (IsValidField(sortField, type))
{
sortExpression = sortField;
if (sortType != "asc")
{
sortExpression += " desc";
}
else
{
sortExpression += " asc";
}
}
return sortExpression;
}
public bool IsValidField(string field, Type type)
{
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.Name.Equals(field))
{
return true;
}
}
return false;
}
}
解决方案的目录结构现如下

在下一章节中,针对仓储层和应用层进行单元测试

浙公网安备 33010602011771号