docs: add xml comments and update ignore rules
This commit is contained in:
@@ -9,17 +9,41 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfMiniUserRepository(IdentityDbContext dbContext) : IMiniUserRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据 OpenId 获取小程序用户。
|
||||
/// </summary>
|
||||
/// <param name="openId">微信 OpenId。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>匹配的小程序用户或 null。</returns>
|
||||
public Task<MiniUser?> FindByOpenIdAsync(string openId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户 ID 获取小程序用户。
|
||||
/// </summary>
|
||||
/// <param name="id">用户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>匹配的小程序用户或 null。</returns>
|
||||
public Task<MiniUser?> FindByIdAsync(long id, CancellationToken cancellationToken = default)
|
||||
=> dbContext.MiniUsers.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 创建或更新小程序用户信息。
|
||||
/// </summary>
|
||||
/// <param name="openId">微信 OpenId。</param>
|
||||
/// <param name="unionId">微信 UnionId。</param>
|
||||
/// <param name="nickname">昵称。</param>
|
||||
/// <param name="avatar">头像地址。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>创建或更新后的小程序用户。</returns>
|
||||
public async Task<MiniUser> CreateOrUpdateAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询现有用户
|
||||
var user = await dbContext.MiniUsers.FirstOrDefaultAsync(x => x.OpenId == openId, cancellationToken);
|
||||
if (user == null)
|
||||
{
|
||||
// 2. 未找到则创建
|
||||
user = new MiniUser
|
||||
{
|
||||
Id = 0,
|
||||
@@ -33,11 +57,13 @@ public sealed class EfMiniUserRepository(IdentityDbContext dbContext) : IMiniUse
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. 已存在则更新可变字段
|
||||
user.UnionId = unionId ?? user.UnionId;
|
||||
user.Nickname = nickname ?? user.Nickname;
|
||||
user.Avatar = avatar ?? user.Avatar;
|
||||
}
|
||||
|
||||
// 4. 保存更改
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -9,66 +9,136 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfPermissionRepository(IdentityDbContext dbContext) : IPermissionRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据权限 ID 获取权限。
|
||||
/// </summary>
|
||||
/// <param name="permissionId">权限 ID。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>权限实体或 null。</returns>
|
||||
public Task<Permission?> FindByIdAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Permissions.AsNoTracking().FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 根据权限编码获取权限。
|
||||
/// </summary>
|
||||
/// <param name="code">权限编码。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>权限实体或 null。</returns>
|
||||
public Task<Permission?> FindByCodeAsync(string code, long tenantId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Permissions.AsNoTracking().FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 根据权限编码集合批量获取权限。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="codes">权限编码集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>权限列表。</returns>
|
||||
public Task<IReadOnlyList<Permission>> GetByCodesAsync(long tenantId, IEnumerable<string> codes, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 规范化编码集合
|
||||
var normalizedCodes = codes
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Select(code => code.Trim())
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
// 2. 按租户筛选权限
|
||||
return dbContext.Permissions.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && normalizedCodes.Contains(x.Code))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据权限 ID 集合批量获取权限。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="permissionIds">权限 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>权限列表。</returns>
|
||||
public Task<IReadOnlyList<Permission>> GetByIdsAsync(long tenantId, IEnumerable<long> permissionIds, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Permissions.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && permissionIds.Contains(x.Id))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 按关键字搜索权限。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="keyword">搜索关键字。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>权限列表。</returns>
|
||||
public Task<IReadOnlyList<Permission>> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 构建基础查询
|
||||
var query = dbContext.Permissions.AsNoTracking().Where(x => x.TenantId == tenantId);
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
// 2. 追加关键字过滤
|
||||
var normalized = keyword.Trim();
|
||||
query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized));
|
||||
}
|
||||
|
||||
// 3. 返回列表
|
||||
return query.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<Permission>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增权限。
|
||||
/// </summary>
|
||||
/// <param name="permission">权限实体。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public Task AddAsync(Permission permission, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 添加实体
|
||||
dbContext.Permissions.Add(permission);
|
||||
// 2. 返回完成任务
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新权限。
|
||||
/// </summary>
|
||||
/// <param name="permission">权限实体。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public Task UpdateAsync(Permission permission, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 标记实体更新
|
||||
dbContext.Permissions.Update(permission);
|
||||
// 2. 返回完成任务
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定权限。
|
||||
/// </summary>
|
||||
/// <param name="permissionId">权限 ID。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task DeleteAsync(long permissionId, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询目标权限
|
||||
var entity = await dbContext.Permissions.FirstOrDefaultAsync(x => x.Id == permissionId && x.TenantId == tenantId, cancellationToken);
|
||||
if (entity != null)
|
||||
{
|
||||
// 2. 删除实体
|
||||
dbContext.Permissions.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存仓储变更。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>保存任务。</returns>
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
=> dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -9,25 +9,49 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfRolePermissionRepository(IdentityDbContext dbContext) : IRolePermissionRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据角色 ID 集合获取角色权限映射。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="roleIds">角色 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色权限映射列表。</returns>
|
||||
public Task<IReadOnlyList<RolePermission>> GetByRoleIdsAsync(long tenantId, IEnumerable<long> roleIds, CancellationToken cancellationToken = default)
|
||||
=> dbContext.RolePermissions.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && roleIds.Contains(x.RoleId))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<RolePermission>)t.Result, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 批量新增角色权限。
|
||||
/// </summary>
|
||||
/// <param name="rolePermissions">角色权限集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task AddRangeAsync(IEnumerable<RolePermission> rolePermissions, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 转为数组便于计数
|
||||
var toAdd = rolePermissions as RolePermission[] ?? rolePermissions.ToArray();
|
||||
if (toAdd.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 批量插入
|
||||
await dbContext.RolePermissions.AddRangeAsync(toAdd, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换指定角色的权限集合。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="roleId">角色 ID。</param>
|
||||
/// <param name="permissionIds">权限 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task ReplaceRolePermissionsAsync(long tenantId, long roleId, IEnumerable<long> permissionIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 使用执行策略保证可靠性
|
||||
var strategy = dbContext.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
@@ -52,6 +76,11 @@ public sealed class EfRolePermissionRepository(IdentityDbContext dbContext) : IR
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存仓储变更。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>保存任务。</returns>
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
=> dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -9,53 +9,114 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfRoleRepository(IdentityDbContext dbContext) : IRoleRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据角色 ID 获取角色。
|
||||
/// </summary>
|
||||
/// <param name="roleId">角色 ID。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色实体或 null。</returns>
|
||||
public Task<Role?> FindByIdAsync(long roleId, long tenantId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Roles.AsNoTracking().FirstOrDefaultAsync(x => x.Id == roleId && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 根据角色编码获取角色。
|
||||
/// </summary>
|
||||
/// <param name="code">角色编码。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色实体或 null。</returns>
|
||||
public Task<Role?> FindByCodeAsync(string code, long tenantId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Roles.AsNoTracking().FirstOrDefaultAsync(x => x.Code == code && x.TenantId == tenantId && x.DeletedAt == null, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 根据角色 ID 集合获取角色列表。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="roleIds">角色 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色列表。</returns>
|
||||
public Task<IReadOnlyList<Role>> GetByIdsAsync(long tenantId, IEnumerable<long> roleIds, CancellationToken cancellationToken = default)
|
||||
=> dbContext.Roles.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && roleIds.Contains(x.Id) && x.DeletedAt == null)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<Role>)t.Result, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 按关键字搜索角色。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="keyword">搜索关键字。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色列表。</returns>
|
||||
public Task<IReadOnlyList<Role>> SearchAsync(long tenantId, string? keyword, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 构建基础查询
|
||||
var query = dbContext.Roles.AsNoTracking().Where(x => x.TenantId == tenantId && x.DeletedAt == null);
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
// 2. 追加关键字过滤
|
||||
var normalized = keyword.Trim();
|
||||
query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized));
|
||||
}
|
||||
|
||||
// 3. 返回列表
|
||||
return query.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<Role>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增角色。
|
||||
/// </summary>
|
||||
/// <param name="role">角色实体。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public Task AddAsync(Role role, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 添加实体
|
||||
dbContext.Roles.Add(role);
|
||||
// 2. 返回完成任务
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色。
|
||||
/// </summary>
|
||||
/// <param name="role">角色实体。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public Task UpdateAsync(Role role, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 标记更新
|
||||
dbContext.Roles.Update(role);
|
||||
// 2. 返回完成任务
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 软删除角色。
|
||||
/// </summary>
|
||||
/// <param name="roleId">角色 ID。</param>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task DeleteAsync(long roleId, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询目标角色
|
||||
var entity = await dbContext.Roles.FirstOrDefaultAsync(x => x.Id == roleId && x.TenantId == tenantId, cancellationToken);
|
||||
if (entity != null)
|
||||
{
|
||||
// 2. 标记删除时间
|
||||
entity.DeletedAt = DateTime.UtcNow;
|
||||
dbContext.Roles.Update(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存仓储变更。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>保存任务。</returns>
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
=> dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -9,83 +9,151 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfRoleTemplateRepository(IdentityDbContext dbContext) : IRoleTemplateRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取全部角色模板,可选按启用状态过滤。
|
||||
/// </summary>
|
||||
/// <param name="isActive">是否启用过滤。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色模板列表。</returns>
|
||||
public Task<IReadOnlyList<RoleTemplate>> GetAllAsync(bool? isActive, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 构建基础查询
|
||||
var query = dbContext.RoleTemplates.AsNoTracking();
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
// 2. 按启用状态过滤
|
||||
query = query.Where(x => x.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
// 3. 排序并返回
|
||||
return query
|
||||
.OrderBy(x => x.TemplateCode)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<RoleTemplate>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据模板编码获取角色模板。
|
||||
/// </summary>
|
||||
/// <param name="templateCode">模板编码。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>角色模板或 null。</returns>
|
||||
public Task<RoleTemplate?> FindByCodeAsync(string templateCode, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 规范化编码
|
||||
var normalized = templateCode.Trim();
|
||||
// 2. 查询模板
|
||||
return dbContext.RoleTemplates.AsNoTracking().FirstOrDefaultAsync(x => x.TemplateCode == normalized, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定模板的权限集合。
|
||||
/// </summary>
|
||||
/// <param name="roleTemplateId">模板 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>模板权限列表。</returns>
|
||||
public Task<IReadOnlyList<RoleTemplatePermission>> GetPermissionsAsync(long roleTemplateId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询模板权限
|
||||
return dbContext.RoleTemplatePermissions.AsNoTracking()
|
||||
.Where(x => x.RoleTemplateId == roleTemplateId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<RoleTemplatePermission>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取多个模板的权限集合。
|
||||
/// </summary>
|
||||
/// <param name="roleTemplateIds">模板 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>模板到权限的字典。</returns>
|
||||
public async Task<IDictionary<long, IReadOnlyList<RoleTemplatePermission>>> GetPermissionsAsync(IEnumerable<long> roleTemplateIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 去重 ID
|
||||
var ids = roleTemplateIds.Distinct().ToArray();
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
return new Dictionary<long, IReadOnlyList<RoleTemplatePermission>>();
|
||||
}
|
||||
|
||||
// 2. 批量查询权限
|
||||
var permissions = await dbContext.RoleTemplatePermissions.AsNoTracking()
|
||||
.Where(x => ids.Contains(x.RoleTemplateId))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 3. 组装字典
|
||||
return permissions
|
||||
.GroupBy(x => x.RoleTemplateId)
|
||||
.ToDictionary(g => g.Key, g => (IReadOnlyList<RoleTemplatePermission>)g.ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增角色模板并配置权限。
|
||||
/// </summary>
|
||||
/// <param name="template">角色模板实体。</param>
|
||||
/// <param name="permissionCodes">权限编码集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task AddAsync(RoleTemplate template, IEnumerable<string> permissionCodes, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 规范化模板字段
|
||||
template.TemplateCode = template.TemplateCode.Trim();
|
||||
template.Name = template.Name.Trim();
|
||||
// 2. 保存模板
|
||||
await dbContext.RoleTemplates.AddAsync(template, cancellationToken);
|
||||
// 3. 替换权限
|
||||
await ReplacePermissionsInternalAsync(template, permissionCodes, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色模板并重置权限。
|
||||
/// </summary>
|
||||
/// <param name="template">角色模板实体。</param>
|
||||
/// <param name="permissionCodes">权限编码集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task UpdateAsync(RoleTemplate template, IEnumerable<string> permissionCodes, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 规范化模板字段
|
||||
template.TemplateCode = template.TemplateCode.Trim();
|
||||
template.Name = template.Name.Trim();
|
||||
// 2. 更新模板
|
||||
dbContext.RoleTemplates.Update(template);
|
||||
// 3. 重置权限
|
||||
await ReplacePermissionsInternalAsync(template, permissionCodes, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色模板及其权限。
|
||||
/// </summary>
|
||||
/// <param name="roleTemplateId">模板 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task DeleteAsync(long roleTemplateId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 查询模板
|
||||
var entity = await dbContext.RoleTemplates.FirstOrDefaultAsync(x => x.Id == roleTemplateId, cancellationToken);
|
||||
if (entity != null)
|
||||
{
|
||||
// 2. 删除关联权限
|
||||
var permissions = dbContext.RoleTemplatePermissions.Where(x => x.RoleTemplateId == roleTemplateId);
|
||||
dbContext.RoleTemplatePermissions.RemoveRange(permissions);
|
||||
// 3. 删除模板
|
||||
dbContext.RoleTemplates.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存仓储变更。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>保存任务。</returns>
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
=> dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
private async Task ReplacePermissionsInternalAsync(RoleTemplate template, IEnumerable<string> permissionCodes, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 使用执行策略保证一致性
|
||||
var strategy = dbContext.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
|
||||
@@ -9,32 +9,58 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRoleRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据用户 ID 集合获取用户角色映射。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="userIds">用户 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>用户角色映射列表。</returns>
|
||||
public Task<IReadOnlyList<UserRole>> GetByUserIdsAsync(long tenantId, IEnumerable<long> userIds, CancellationToken cancellationToken = default)
|
||||
=> dbContext.UserRoles.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && userIds.Contains(x.UserId))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<UserRole>)t.Result, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定用户的角色集合。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="userId">用户 ID。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>用户角色列表。</returns>
|
||||
public Task<IReadOnlyList<UserRole>> GetByUserIdAsync(long tenantId, long userId, CancellationToken cancellationToken = default)
|
||||
=> dbContext.UserRoles.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.UserId == userId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<UserRole>)t.Result, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 替换指定用户的角色集合。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="userId">用户 ID。</param>
|
||||
/// <param name="roleIds">角色 ID 集合。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task ReplaceUserRolesAsync(long tenantId, long userId, IEnumerable<long> roleIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 使用执行策略保障一致性
|
||||
var strategy = dbContext.Database.CreateExecutionStrategy();
|
||||
await strategy.ExecuteAsync(async () =>
|
||||
{
|
||||
await using var trx = await dbContext.Database.BeginTransactionAsync(cancellationToken);
|
||||
|
||||
// 2. 读取当前角色映射
|
||||
var existing = await dbContext.UserRoles
|
||||
.Where(x => x.TenantId == tenantId && x.UserId == userId)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 3. 清空并保存
|
||||
dbContext.UserRoles.RemoveRange(existing);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 4. 构建新映射
|
||||
var toAdd = roleIds.Distinct().Select(roleId => new UserRole
|
||||
{
|
||||
TenantId = tenantId,
|
||||
@@ -42,6 +68,7 @@ public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRol
|
||||
RoleId = roleId
|
||||
});
|
||||
|
||||
// 5. 批量新增并保存
|
||||
await dbContext.UserRoles.AddRangeAsync(toAdd, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -49,6 +76,11 @@ public sealed class EfUserRoleRepository(IdentityDbContext dbContext) : IUserRol
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存仓储变更。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>保存任务。</returns>
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
=> dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -22,38 +22,52 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
/// </summary>
|
||||
public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger<IdentityDataSeeder> logger) : IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行后台账号与权限种子。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 创建作用域并解析依赖
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<IdentityDbContext>();
|
||||
var options = scope.ServiceProvider.GetRequiredService<IOptions<AdminSeedOptions>>().Value;
|
||||
var passwordHasher = scope.ServiceProvider.GetRequiredService<IPasswordHasher<DomainIdentityUser>>();
|
||||
var tenantContextAccessor = scope.ServiceProvider.GetRequiredService<ITenantContextAccessor>();
|
||||
|
||||
// 2. 校验功能开关
|
||||
if (!options.Enabled)
|
||||
{
|
||||
logger.LogInformation("AdminSeed 已禁用,跳过后台账号初始化");
|
||||
return;
|
||||
}
|
||||
// 3. 确保数据库已迁移
|
||||
await context.Database.MigrateAsync(cancellationToken);
|
||||
|
||||
// 4. 校验账号配置
|
||||
if (options.Users is null or { Count: 0 })
|
||||
{
|
||||
logger.LogInformation("AdminSeed 未配置账号,跳过后台账号初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 写入角色模板
|
||||
await SeedRoleTemplatesAsync(context, options.RoleTemplates, cancellationToken);
|
||||
|
||||
// 6. 逐个账号处理
|
||||
foreach (var userOptions in options.Users)
|
||||
{
|
||||
// 6.1 进入租户作用域
|
||||
using var tenantScope = EnterTenantScope(tenantContextAccessor, userOptions.TenantId);
|
||||
// 6.2 查询账号并收集配置
|
||||
var user = await context.IdentityUsers.FirstOrDefaultAsync(x => x.Account == userOptions.Account, cancellationToken);
|
||||
var roles = NormalizeValues(userOptions.Roles);
|
||||
var permissions = NormalizeValues(userOptions.Permissions);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
// 6.3 创建新账号
|
||||
user = new DomainIdentityUser
|
||||
{
|
||||
Id = 0,
|
||||
@@ -69,6 +83,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
}
|
||||
else
|
||||
{
|
||||
// 6.4 更新既有账号
|
||||
user.DisplayName = userOptions.DisplayName;
|
||||
user.TenantId = userOptions.TenantId;
|
||||
user.MerchantId = userOptions.MerchantId;
|
||||
@@ -76,7 +91,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
logger.LogInformation("已更新后台账号 {Account}", user.Account);
|
||||
}
|
||||
|
||||
// 确保角色存在
|
||||
// 6.5 确保角色存在
|
||||
var existingRoles = await context.Roles
|
||||
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -97,7 +112,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
});
|
||||
}
|
||||
|
||||
// 确保权限存在
|
||||
// 6.6 确保权限存在
|
||||
var existingPermissions = await context.Permissions
|
||||
.Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code))
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -118,9 +133,10 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
});
|
||||
}
|
||||
|
||||
// 6.7 保存基础角色/权限
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 重新加载角色/权限以获取 Id
|
||||
// 6.8 重新加载角色/权限以获取 Id
|
||||
var roleEntities = await context.Roles
|
||||
.Where(r => r.TenantId == userOptions.TenantId && roles.Contains(r.Code))
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -128,7 +144,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
.Where(p => p.TenantId == userOptions.TenantId && permissions.Contains(p.Code))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 重置用户角色
|
||||
// 6.9 重置用户角色
|
||||
var existingUserRoles = await context.UserRoles
|
||||
.Where(ur => ur.TenantId == userOptions.TenantId && ur.UserId == user.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -191,6 +207,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
continue;
|
||||
}
|
||||
|
||||
// 6.10 绑定角色与权限
|
||||
await context.RolePermissions.AddAsync(new DomainRolePermission
|
||||
{
|
||||
TenantId = userOptions.TenantId,
|
||||
@@ -209,9 +226,15 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 最终保存
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止生命周期时的清理(此处无需处理)。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>已完成任务。</returns>
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
private static async Task SeedRoleTemplatesAsync(
|
||||
@@ -219,23 +242,28 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
IList<RoleTemplateSeedOptions> templates,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 空集合直接返回
|
||||
if (templates is null || templates.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 逐个处理模板
|
||||
foreach (var templateOptions in templates)
|
||||
{
|
||||
// 2.1 校验必填字段
|
||||
if (string.IsNullOrWhiteSpace(templateOptions.TemplateCode) || string.IsNullOrWhiteSpace(templateOptions.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2.2 查询现有模板
|
||||
var code = templateOptions.TemplateCode.Trim();
|
||||
var existing = await context.RoleTemplates.FirstOrDefaultAsync(x => x.TemplateCode == code, cancellationToken);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
// 2.3 新增模板
|
||||
existing = new DomainRoleTemplate
|
||||
{
|
||||
TemplateCode = code,
|
||||
@@ -249,6 +277,7 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2.4 更新模板
|
||||
existing.Name = templateOptions.Name.Trim();
|
||||
existing.Description = templateOptions.Description;
|
||||
existing.IsActive = templateOptions.IsActive;
|
||||
@@ -256,13 +285,15 @@ public sealed class IdentityDataSeeder(IServiceProvider serviceProvider, ILogger
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
// 2.5 重置模板权限
|
||||
var permissionCodes = NormalizeValues(templateOptions.Permissions);
|
||||
var existingPermissions = await context.RoleTemplatePermissions
|
||||
.Where(x => x.RoleTemplateId == existing.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 2.6 清空旧权限并保存
|
||||
context.RoleTemplatePermissions.RemoveRange(existingPermissions);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
// 2.7 去重后的权限编码
|
||||
var distinctPermissionCodes = permissionCodes.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
foreach (var permissionCode in distinctPermissionCodes)
|
||||
{
|
||||
|
||||
@@ -12,11 +12,21 @@ namespace TakeoutSaaS.Infrastructure.Identity.Persistence;
|
||||
internal sealed class IdentityDesignTimeDbContextFactory
|
||||
: DesignTimeDbContextFactoryBase<IdentityDbContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化 Identity 设计时上下文工厂。
|
||||
/// </summary>
|
||||
public IdentityDesignTimeDbContextFactory()
|
||||
: base(DatabaseConstants.IdentityDataSource, "TAKEOUTSAAS_IDENTITY_CONNECTION")
|
||||
{
|
||||
}
|
||||
|
||||
// 创建设计时上下文实例
|
||||
/// <summary>
|
||||
/// 创建设计时的 IdentityDbContext。
|
||||
/// </summary>
|
||||
/// <param name="options">DbContext 配置。</param>
|
||||
/// <param name="tenantProvider">租户提供器。</param>
|
||||
/// <param name="currentUserAccessor">当前用户访问器。</param>
|
||||
/// <returns>IdentityDbContext 实例。</returns>
|
||||
protected override IdentityDbContext CreateContext(
|
||||
DbContextOptions<IdentityDbContext> options,
|
||||
ITenantProvider tenantProvider,
|
||||
|
||||
@@ -14,8 +14,15 @@ public sealed class RedisLoginRateLimiter(IDistributedCache cache, IOptions<Logi
|
||||
{
|
||||
private readonly LoginRateLimitOptions _options = options.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 校验指定键的登录尝试次数,超限将抛出业务异常。
|
||||
/// </summary>
|
||||
/// <param name="key">限流键(如账号或 IP)。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task EnsureAllowedAsync(string key, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 读取当前计数
|
||||
var cacheKey = BuildKey(key);
|
||||
var current = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||
var count = string.IsNullOrWhiteSpace(current) ? 0 : int.Parse(current);
|
||||
@@ -24,6 +31,7 @@ public sealed class RedisLoginRateLimiter(IDistributedCache cache, IOptions<Logi
|
||||
throw new BusinessException(ErrorCodes.Forbidden, "尝试次数过多,请稍后再试");
|
||||
}
|
||||
|
||||
// 2. 累加计数并回写缓存
|
||||
count++;
|
||||
await cache.SetStringAsync(
|
||||
cacheKey,
|
||||
@@ -35,6 +43,12 @@ public sealed class RedisLoginRateLimiter(IDistributedCache cache, IOptions<Logi
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置指定键的登录计数。
|
||||
/// </summary>
|
||||
/// <param name="key">限流键(如账号或 IP)。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public Task ResetAsync(string key, CancellationToken cancellationToken = default)
|
||||
=> cache.RemoveAsync(BuildKey(key), cancellationToken);
|
||||
|
||||
|
||||
@@ -16,11 +16,20 @@ public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions<Ref
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private readonly RefreshTokenStoreOptions _options = options.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 签发刷新令牌并写入缓存。
|
||||
/// </summary>
|
||||
/// <param name="userId">用户 ID。</param>
|
||||
/// <param name="expiresAt">过期时间。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>刷新令牌描述。</returns>
|
||||
public async Task<RefreshTokenDescriptor> IssueAsync(long userId, DateTime expiresAt, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 生成随机令牌
|
||||
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(48));
|
||||
var descriptor = new RefreshTokenDescriptor(token, userId, expiresAt, false);
|
||||
|
||||
// 2. 写入缓存
|
||||
var key = BuildKey(token);
|
||||
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = expiresAt };
|
||||
await cache.SetStringAsync(key, JsonSerializer.Serialize(descriptor, JsonOptions), entryOptions, cancellationToken);
|
||||
@@ -28,22 +37,37 @@ public sealed class RedisRefreshTokenStore(IDistributedCache cache, IOptions<Ref
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取刷新令牌描述。
|
||||
/// </summary>
|
||||
/// <param name="refreshToken">刷新令牌值。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>刷新令牌描述或 null。</returns>
|
||||
public async Task<RefreshTokenDescriptor?> GetAsync(string refreshToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 读取缓存
|
||||
var json = await cache.GetStringAsync(BuildKey(refreshToken), cancellationToken);
|
||||
return string.IsNullOrWhiteSpace(json)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<RefreshTokenDescriptor>(json, JsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 吊销刷新令牌。
|
||||
/// </summary>
|
||||
/// <param name="refreshToken">刷新令牌值。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>异步任务。</returns>
|
||||
public async Task RevokeAsync(string refreshToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 读取令牌
|
||||
var descriptor = await GetAsync(refreshToken, cancellationToken);
|
||||
if (descriptor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 标记吊销并回写缓存
|
||||
var updated = descriptor with { Revoked = true };
|
||||
var entryOptions = new DistributedCacheEntryOptions { AbsoluteExpiration = updated.ExpiresAt };
|
||||
await cache.SetStringAsync(BuildKey(refreshToken), JsonSerializer.Serialize(updated, JsonOptions), entryOptions, cancellationToken);
|
||||
|
||||
@@ -15,18 +15,27 @@ public sealed class WeChatAuthService(HttpClient httpClient, IOptions<WeChatMini
|
||||
{
|
||||
private readonly WeChatMiniOptions _options = options.Value;
|
||||
|
||||
/// <summary>
|
||||
/// 调用微信接口完成 code2Session。
|
||||
/// </summary>
|
||||
/// <param name="code">临时登录凭证 code。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>微信会话信息。</returns>
|
||||
public async Task<WeChatSessionInfo> Code2SessionAsync(string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 1. 拼装请求地址
|
||||
var requestUri = $"sns/jscode2session?appid={Uri.EscapeDataString(_options.AppId)}&secret={Uri.EscapeDataString(_options.Secret)}&js_code={Uri.EscapeDataString(code)}&grant_type=authorization_code";
|
||||
using var response = await httpClient.GetAsync(requestUri, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
// 2. 读取响应
|
||||
var payload = await response.Content.ReadFromJsonAsync<WeChatSessionResponse>(cancellationToken: cancellationToken);
|
||||
if (payload == null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Unauthorized, "微信登录失败:响应为空");
|
||||
}
|
||||
|
||||
// 3. 校验错误码
|
||||
if (payload.ErrorCode.HasValue && payload.ErrorCode.Value != 0)
|
||||
{
|
||||
var message = string.IsNullOrWhiteSpace(payload.ErrorMessage)
|
||||
@@ -35,11 +44,13 @@ public sealed class WeChatAuthService(HttpClient httpClient, IOptions<WeChatMini
|
||||
throw new BusinessException(ErrorCodes.Unauthorized, message);
|
||||
}
|
||||
|
||||
// 4. 校验必要字段
|
||||
if (string.IsNullOrWhiteSpace(payload.OpenId) || string.IsNullOrWhiteSpace(payload.SessionKey))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Unauthorized, "微信登录失败:返回数据无效");
|
||||
}
|
||||
|
||||
// 5. 组装会话信息
|
||||
return new WeChatSessionInfo
|
||||
{
|
||||
OpenId = payload.OpenId,
|
||||
|
||||
Reference in New Issue
Block a user