@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities ;
using TakeoutSaaS.Domain.Identity.Enums ;
using TakeoutSaaS.Domain.Identity.Repositories ;
using TakeoutSaaS.Domain.Tenants.Repositories ;
using TakeoutSaaS.Shared.Abstractions.Constants ;
using TakeoutSaaS.Shared.Abstractions.Exceptions ;
using TakeoutSaaS.Shared.Abstractions.Results ;
@@ -25,19 +24,8 @@ public sealed class AdminAuthService(
IPasswordHasher < IdentityUser > passwordHasher ,
IJwtTokenService jwtTokenService ,
IRefreshTokenStore refreshTokenStore ,
ITenantProvider tenantProvider ,
ITenantContextAccessor tenantContextAccessor ,
ITenantRepository tenantRepository ) : IAdminAuthService
ITenantProvider tenantProvider ) : IAdminAuthService
{
private readonly ITenantProvider _tenantProvider = tenantProvider ;
private readonly ITenantContextAccessor _tenantContextAccessor = tenantContextAccessor ;
private readonly ITenantRepository _tenantRepository = tenantRepository ;
private readonly IUserRoleRepository _userRoleRepository = userRoleRepository ;
private readonly IRoleRepository _roleRepository = roleRepository ;
private readonly IPermissionRepository _permissionRepository = permissionRepository ;
private readonly IRolePermissionRepository _rolePermissionRepository = rolePermissionRepository ;
private readonly IMenuRepository _menuRepository = menuRepository ;
/// <summary>
/// 管理后台登录:验证账号密码并生成令牌。
/// </summary>
@@ -48,7 +36,7 @@ public sealed class AdminAuthService(
public async Task < TokenResponse > LoginAsync ( AdminLoginRequest request , CancellationToken cancellationToken = default )
{
// 1. 根据账号查找用户
var user = await userRepository . FindByAccountAsync ( request . Account , cancellationToken )
var user = await userRepository . FindByAccountAsync ( PortalType . Admin , null , request. Account , cancellationToken )
? ? throw new BusinessException ( ErrorCodes . Unauthorized , "账号或密码错误" ) ;
// 2. 校验账号状态
@@ -90,57 +78,24 @@ public sealed class AdminAuthService(
}
/// <summary>
/// 简化登录:支持使用“账号@手机号”解析租户后登录 。
/// 简化登录:与标准登录一致( Admin Portal) 。
/// </summary>
/// <param name="request">登录请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>令牌响应</returns>
public async Task < TokenResponse > LoginSimpleAsync ( AdminLoginRequest request , CancellationToken cancellationToken = default )
{
// 1. 标准化输入
var rawAccount = request . Account ? . Trim ( ) ? ? string . Empty ;
// 2. 尝试解析 “账号@手机号”
var atIndex = rawAccount . LastIndexOf ( '@' ) ;
if ( atIndex > 0 & & atIndex < rawAccount . Length - 1 )
// 1. 参数校验
if ( string . IsNullOrWhiteSpace ( request . Account ) )
{
var accountPart = rawAccount [ . . atIndex ] . Trim ( ) ;
var phonePart = rawAccount [ ( atIndex + 1 ) . . ] . Trim ( ) ;
if ( IsLikelyPhone ( phonePart ) )
{
if ( string . IsNullOrWhiteSpace ( accountPart ) )
{
throw new BusinessException ( ErrorCodes . BadRequest , "账号格式错误,应为 账号@手机号" ) ;
}
var tenantId = await _tenantRepository . FindTenantIdByContactPhoneAsync ( phonePart , cancellationToken ) ;
if ( ! tenantId . HasValue | | tenantId . Value = = 0 )
{
throw new BusinessException ( ErrorCodes . Unauthorized , "账号或密码错误" ) ;
}
var originalTenant = _tenantContextAccessor . Current ;
_tenantContextAccessor . Current = new TenantContext ( tenantId . Value , null , "login:simple:contact_phone" ) ;
try
{
return await LoginAsync ( new AdminLoginRequest { Account = accountPart , Password = request . Password } , cancellationToken ) ;
}
finally
{
_tenantContextAccessor . Current = originalTenant ;
}
}
throw new BusinessException ( ErrorCodes . BadRequest , "账号不能为空" ) ;
}
// 3 . 未携带手机号时, 要求外部已解析租户( Header/Host 等)
if ( _tenantProvider . GetCurrentTenantId ( ) = = 0 )
{
throw new BusinessException ( ErrorCodes . BadRequest , "缺少租户标识,请使用 账号@手机号 登录" ) ;
}
// 2 . (空行后) 标准化账号
request . Account = request . Account . Trim ( ) ;
// 4 . 走原有登录逻辑
return await LoginAsync ( new AdminLoginRequest { Account = rawAccount , Password = request . Password } , cancellationToken ) ;
// 3 . (空行后) 走标准登录逻辑( Admin Portal)
return await LoginAsync ( request , cancellationToken ) ;
}
/// <summary>
@@ -197,7 +152,7 @@ public sealed class AdminAuthService(
// 1. 读取档案以获取权限
var profile = await GetProfileAsync ( userId , cancellationToken ) ;
// 2. 读取菜单定义
var definitions = await _ menuRepository. GetByPortalAsync ( profile . Portal , cancellationToken ) ;
var definitions = await menuRepository . GetByPortalAsync ( profile . Portal , cancellationToken ) ;
// 3. 生成菜单树
var menu = BuildMenuTree ( definitions , profile . Permissions ) ;
@@ -209,16 +164,19 @@ public sealed class AdminAuthService(
/// </summary>
public async Task < UserPermissionDto ? > GetUserPermissionsAsync ( long userId , CancellationToken cancellationToken = default )
{
var tenantId = _ tenantProvider. GetCurrentTenantId ( ) ;
var tenantId = tenantProvider . GetCurrentTenantId ( ) ;
var user = await userRepository . FindByIdAsync ( userId , cancellationToken ) ;
if ( user = = null | | user . TenantId ! = tenantId )
{
return null ;
}
// 1. 解析角色集合
var roleCodes = await ResolveUserRolesAsync ( user . Portal , user . TenantId , user . Id , cancellationToken ) ;
// 2. (空行后) 解析权限集合
var permissionCodes = await ResolveUserPermissionsAsync ( user . Portal , user . TenantId , user . Id , cancellationToken ) ;
// 3. (空行后) 返回概览
return new UserPermissionDto
{
UserId = user . Id ,
@@ -243,9 +201,12 @@ public sealed class AdminAuthService(
bool sortDescending ,
CancellationToken cancellationToken = default )
{
var tenantId = _tenantProvider . GetCurrentTenantId ( ) ;
// 1. 获取当前租户
var tenantId = tenantProvider . GetCurrentTenantId ( ) ;
// 2. (空行后) 查询用户列表
var users = await userRepository . SearchAsync ( tenantId , keyword , cancellationToken ) ;
// 3. (空行后) 排序
var sorted = sortBy ? . ToLowerInvariant ( ) switch
{
"account" = > sortDescending
@@ -259,12 +220,15 @@ public sealed class AdminAuthService(
: users . OrderBy ( x = > x . CreatedAt )
} ;
// 4. (空行后) 分页
var paged = sorted
. Skip ( ( page - 1 ) * pageSize )
. Take ( pageSize )
. ToList ( ) ;
// 5. (空行后) 解析角色与权限
var resolved = await ResolveRolesAndPermissionsAsync ( PortalType . Tenant , tenantId , paged , cancellationToken ) ;
// 6. (空行后) 映射为 DTO
var items = paged . Select ( user = > new UserPermissionDto
{
UserId = user . Id ,
@@ -277,14 +241,18 @@ public sealed class AdminAuthService(
CreatedAt = user . CreatedAt
} ) . ToList ( ) ;
// 7. (空行后) 返回分页结果
return new PagedResult < UserPermissionDto > ( items , page , pageSize , users . Count ) ;
}
private async Task < CurrentUserProfile > BuildProfileAsync ( IdentityUser user , CancellationToken cancellationToken )
{
// 1. 解析角色集合
var roles = await ResolveUserRolesAsync ( user . Portal , user . TenantId , user . Id , cancellationToken ) ;
// 2. (空行后) 解析权限集合
var permissions = await ResolveUserPermissionsAsync ( user . Portal , user . TenantId , user . Id , cancellationToken ) ;
// 3. (空行后) 组装档案
return new CurrentUserProfile
{
Portal = user . Portal ,
@@ -362,22 +330,26 @@ public sealed class AdminAuthService(
private static bool IsLikelyPhone ( string value )
{
// 1. 空值快速返回
if ( string . IsNullOrWhiteSpace ( value ) )
{
return false ;
}
// 2. 标准化前缀
var span = value . AsSpan ( ) ;
if ( span [ 0 ] = = '+' )
{
span = span [ 1. . ] ;
}
// 3. 长度校验
if ( span . Length < 6 | | span . Length > 32 )
{
return false ;
}
// 4. 逐字符校验
foreach ( var ch in span )
{
if ( ! char . IsDigit ( ch ) )
@@ -386,6 +358,7 @@ public sealed class AdminAuthService(
}
}
// 5. 返回校验结果
return true ;
}
@@ -394,7 +367,7 @@ public sealed class AdminAuthService(
IReadOnlyList < string > permissions )
{
// 1. 权限集合
var permissionSet = new HashSet < string > ( permissions ? ? [ ] , StringComparer . OrdinalIgnoreCase ) ;
var permissionSet = new HashSet < string > ( permissions , StringComparer . OrdinalIgnoreCase ) ;
// 2. 父子分组
var parentLookup = definitions
@@ -419,18 +392,13 @@ public sealed class AdminAuthService(
// 1.2 递归子节点
var sub = BuildChildren ( def . Id ) ;
// 1.3 可见性
var required = node . RequiredPermissions ? ? [ ] ;
if ( required . Length = = 0 & & node . Meta . Permissions . Length > 0 )
{
// Fall back to meta permissions when explicit required permissions are missing.
required = node . Meta . Permissions ;
}
// 1.3 可见性(严格使用 RequiredPermissions, 不做兜底)
var requiredPermissions = node . RequiredPermissions ;
// 1.4 (空行后) 判断可见性
var isVisible = requiredPermissions . Length = = 0 | | requiredPermissions . Any ( permissionSet . Contains ) ;
var visible = required . Length = = 0 | | required . Any ( permissionSet . Contains ) ;
// 1.4 收集
if ( visible | | sub . Count > 0 )
// 1.5 (空行后) 收集
if ( isVisible | | sub . Count > 0 )
{
result . Add ( node with { Children = sub } ) ;
}
@@ -494,34 +462,37 @@ public sealed class AdminAuthService(
private async Task < string [ ] > ResolveUserRolesAsync ( PortalType portal , long? tenantId , long userId , CancellationToken cancellationToken )
{
var relations = await _ userRoleRepository. GetByUserIdAsync ( portal , tenantId , userId , cancellationToken ) ;
var relations = await userRoleRepository . GetByUserIdAsync ( portal , tenantId , userId , cancellationToken ) ;
var roleIds = relations . Select ( x = > x . RoleId ) . Distinct ( ) . ToArray ( ) ;
if ( roleIds . Length = = 0 )
{
return Array . Empty < string > ( ) ;
}
var roles = await _roleRepository . GetByIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
// 2. (空行后) 查询角色定义并返回角色码
var roles = await roleRepository . GetByIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
return roles . Select ( x = > x . Code ) . Distinct ( StringComparer . OrdinalIgnoreCase ) . ToArray ( ) ;
}
private async Task < string [ ] > ResolveUserPermissionsAsync ( PortalType portal , long? tenantId , long userId , CancellationToken cancellationToken )
{
var relations = await _ userRoleRepository. GetByUserIdAsync ( portal , tenantId , userId , cancellationToken ) ;
var relations = await userRoleRepository . GetByUserIdAsync ( portal , tenantId , userId , cancellationToken ) ;
var roleIds = relations . Select ( x = > x . RoleId ) . Distinct ( ) . ToArray ( ) ;
if ( roleIds . Length = = 0 )
{
return Array . Empty < string > ( ) ;
}
var rolePermissions = await _rolePermissionRepository . GetByRoleIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
// 2. (空行后) 查询角色权限关联
var rolePermissions = await rolePermissionRepository . GetByRoleIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
var permissionIds = rolePermissions . Select ( x = > x . PermissionId ) . Distinct ( ) . ToArray ( ) ;
if ( permissionIds . Length = = 0 )
{
return Array . Empty < string > ( ) ;
}
var permissions = await _permissionRepository . GetByIdsAsync ( permissionIds , cancellationToken ) ;
// 3. (空行后) 查询权限定义并返回权限码
var permissions = await permissionRepository . GetByIdsAsync ( permissionIds , cancellationToken ) ;
return permissions . Select ( x = > x . Code ) . Distinct ( StringComparer . OrdinalIgnoreCase ) . ToArray ( ) ;
}
@@ -531,32 +502,39 @@ public sealed class AdminAuthService(
IReadOnlyCollection < IdentityUser > users ,
CancellationToken cancellationToken )
{
// 1. 读取用户-角色关系
var userIds = users . Select ( x = > x . Id ) . ToArray ( ) ;
var userRoleRelations = await _ userRoleRepository. GetByUserIdsAsync ( portal , tenantId , userIds , cancellationToken ) ;
var userRoleRelations = await userRoleRepository . GetByUserIdsAsync ( portal , tenantId , userIds , cancellationToken ) ;
var roleIds = userRoleRelations . Select ( x = > x . RoleId ) . Distinct ( ) . ToArray ( ) ;
// 2. (空行后) 读取角色定义
var roles = roleIds . Length = = 0
? Array . Empty < Role > ( )
: await _ roleRepository. GetByIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
: await roleRepository . GetByIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
var roleCodeMap = roles . ToDictionary ( r = > r . Id , r = > r . Code , comparer : EqualityComparer < long > . Default ) ;
// 3. (空行后) 读取角色-权限关系
var rolePermissions = roleIds . Length = = 0
? Array . Empty < RolePermission > ( )
: await _ rolePermissionRepository. GetByRoleIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
: await rolePermissionRepository . GetByRoleIdsAsync ( portal , tenantId , roleIds , cancellationToken ) ;
// 4. (空行后) 读取权限定义
var permissionIds = rolePermissions . Select ( x = > x . PermissionId ) . Distinct ( ) . ToArray ( ) ;
var permissions = permissionIds . Length = = 0
? Array . Empty < Permission > ( )
: await _ permissionRepository. GetByIdsAsync ( permissionIds , cancellationToken ) ;
: await permissionRepository . GetByIdsAsync ( permissionIds , cancellationToken ) ;
var permissionCodeMap = permissions . ToDictionary ( p = > p . Id , p = > p . Code , comparer : EqualityComparer < long > . Default ) ;
// 5. (空行后) 构建 Role -> PermissionId[] 映射
var rolePermissionsLookup = rolePermissions
. GroupBy ( rp = > rp . RoleId )
. ToDictionary ( g = > g . Key , g = > g . Select ( rp = > rp . PermissionId ) . ToArray ( ) , comparer : EqualityComparer < long > . Default ) ;
// 6. (空行后) 按用户聚合角色码与权限码
var result = new Dictionary < long , ( string [ ] roles , string [ ] permissions ) > ( ) ;
foreach ( var userId in userIds )
{
// 6.1 解析用户角色码
var rolesForUser = userRoleRelations . Where ( ur = > ur . UserId = = userId ) . Select ( ur = > ur . RoleId ) . Distinct ( ) . ToArray ( ) ;
var roleCodes = rolesForUser
. Select ( rid = > roleCodeMap . GetValueOrDefault ( rid ) )
@@ -565,6 +543,7 @@ public sealed class AdminAuthService(
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToArray ( ) ;
// 6.2 (空行后) 解析用户权限码
var permissionCodes = rolesForUser
. SelectMany ( rid = > rolePermissionsLookup . GetValueOrDefault ( rid ) ? ? Array . Empty < long > ( ) )
. Select ( pid = > permissionCodeMap . GetValueOrDefault ( pid ) )
@@ -573,9 +552,11 @@ public sealed class AdminAuthService(
. Distinct ( StringComparer . OrdinalIgnoreCase )
. ToArray ( ) ;
// 6.3 (空行后) 写入结果
result [ userId ] = ( roleCodes , permissionCodes ) ;
}
// 7. (空行后) 返回聚合结果
return result ;
}
}