diff --git a/aspnet-core/templates/aio/content/migrations/Migrate.ps1 b/aspnet-core/templates/aio/content/migrations/Migrate.ps1 index b983af60a..dd0543c2c 100755 --- a/aspnet-core/templates/aio/content/migrations/Migrate.ps1 +++ b/aspnet-core/templates/aio/content/migrations/Migrate.ps1 @@ -9,15 +9,76 @@ $env:FROM_MIGRATION = "true" # 定义项目路径 $projectPath = Resolve-Path (Join-Path $PSScriptRoot "..") -# 定义可用的DbContext -$dbContexts = @{ - "1" = @{ - Name = "PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.DatabaseManagementName" - Context = "SingleMigrationsDbContext" - Factory = "SingleMigrationsDbContextFactory" +# 定义函数来动态查找所有可用的DbContext +function Get-AvailableDbContexts { + $migrationsPath = Join-Path $projectPath "migrations" + $dbContexts = @{} + $counter = 1 + + # 查找所有包含EntityFrameworkCore的目录 + $efCoreDirectories = Get-ChildItem -Path $migrationsPath -Directory | Where-Object { $_.Name -like "*EntityFrameworkCore*" } + + foreach ($dir in $efCoreDirectories) { + # 优先查找 DbContextFactory 文件 + $factoryFiles = Get-ChildItem -Path $dir.FullName -Filter "*DbContextFactory.cs" -Recurse -File + + # 如果找到了 Factory 文件 + $foundFactory = $false + foreach ($factoryFile in $factoryFiles) { + $factoryContent = Get-Content $factoryFile.FullName -Raw + + # 查找 Factory 类名和对应的 DbContext 类 + if ($factoryContent -match 'class\s+(\w+Factory)\s*:\s*IDesignTimeDbContextFactory<(\w+)>') { + $factoryName = $matches[1] + $contextName = $matches[2] + + # 如果找到了上下文和工厂,添加到列表中 + $dbContexts["$counter"] = @{ + Name = $dir.Name + Context = $contextName + Factory = $factoryName + } + $counter++ + $foundFactory = $true + } + } + + # 只有当没有找到 Factory 时,才查找 DbContext 文件作为备选 + if (-not $foundFactory) { + $dbContextFiles = Get-ChildItem -Path $dir.FullName -Filter "*DbContext.cs" -Recurse -File + + foreach ($contextFile in $dbContextFiles) { + $contextContent = Get-Content $contextFile.FullName -Raw + if ($contextContent -match 'class\s+(\w+DbContext)') { + $contextName = $matches[1] + + # 添加到列表中,但没有对应的 Factory + $dbContexts["$counter"] = @{ + Name = $dir.Name + Context = $contextName + Factory = $null + } + $counter++ + } + } + } + } + + # 如果没有找到任何上下文,使用默认的 + if ($dbContexts.Count -eq 0) { + $dbContexts["1"] = @{ + Name = "PackageName.CompanyName.ProjectName.AIO.EntityFrameworkCore.DatabaseManagementName" + Context = "SingleMigrationsDbContext" + Factory = "SingleMigrationsDbContextFactory" + } } + + return $dbContexts } +# 获取可用的DbContext +$dbContexts = Get-AvailableDbContexts + # 显示DbContext选择菜单 function Show-DbContextMenu { $host.UI.RawUI.BackgroundColor = "Black" diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs index 25eef3a6e..ff2b0c031 100644 --- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Application/PackageName/CompanyName/ProjectName/Users/UserAppService.cs @@ -75,6 +75,17 @@ namespace PackageName.CompanyName.ProjectName.Users input.Position, input.IsActive); + // 确保更新后的用户状态与DTO中的一致 + if (user.IdentityUser != null) + { + bool currentIsActive = user.IdentityUser.LockoutEnd == null || user.IdentityUser.LockoutEnd < DateTimeOffset.Now; + if (currentIsActive != input.IsActive) + { + await _userManager.SetUserActiveStatusAsync(id, input.IsActive); + user = await _userManager.GetAsync(id); + } + } + // 返回DTO对象 return await MapToUserDtoAsync(user); } @@ -121,7 +132,7 @@ namespace PackageName.CompanyName.ProjectName.Users foreach (var user in users) { var userDto = ObjectMapper.Map(user); - + // 填充角色信息 if (user.IdentityUser != null) { @@ -129,7 +140,7 @@ namespace PackageName.CompanyName.ProjectName.Users userDto.RoleNames = string.Join("、", roles); userDto.IsActive = user.IdentityUser.LockoutEnd == null || user.IdentityUser.LockoutEnd < DateTimeOffset.Now; } - + userDtos.Add(userDto); } @@ -182,17 +193,19 @@ namespace PackageName.CompanyName.ProjectName.Users /// /// 将User实体映射为UserDto,并填充权限信息 /// - private Task MapToUserDtoAsync(User user) + private async Task MapToUserDtoAsync(User user) { var userDto = ObjectMapper.Map(user); - // 设置用户状态 + // 设置用户状态和角色信息 if (user.IdentityUser != null) { + var roles = await _identityUserManager.GetRolesAsync(user.IdentityUser); + userDto.RoleNames = string.Join("、", roles); userDto.IsActive = user.IdentityUser.LockoutEnd == null || user.IdentityUser.LockoutEnd < DateTimeOffset.Now; } - return Task.FromResult(userDto); + return userDto; } } } \ No newline at end of file diff --git a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs index bd616ba34..08ccdba57 100644 --- a/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs +++ b/aspnet-core/templates/aio/content/src/PackageName.CompanyName.ProjectName.Domain/PackageName/CompanyName/ProjectName/Users/UserManager.cs @@ -81,6 +81,17 @@ namespace PackageName.CompanyName.ProjectName.Users string.Join(", ", lockoutResult.Errors.Select(e => e.Description))); } } + else + { + // 确保用户处于活动状态 + await _identityUserManager.SetLockoutEnabledAsync(identityUser, false); + var lockoutResult = await _identityUserManager.SetLockoutEndDateAsync(identityUser, null); + if (!lockoutResult.Succeeded) + { + throw new UserFriendlyException("设置用户状态失败: " + + string.Join(", ", lockoutResult.Errors.Select(e => e.Description))); + } + } // 创建业务用户 var user = new User( @@ -221,29 +232,61 @@ namespace PackageName.CompanyName.ProjectName.Users /// 操作任务 public async Task DeleteAsync(Guid id) { - // 获取用户 - var user = await _userRepository.GetAsync(id); - if (user == null) - { - throw new UserFriendlyException("用户不存在"); - } + // 最大重试次数 + const int maxRetries = 3; + int retryCount = 0; - // 删除Identity用户 - var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString()); - if (identityUser != null) + while (true) { - var result = await _identityUserManager.DeleteAsync(identityUser); - if (!result.Succeeded) + try { - throw new UserFriendlyException("删除Identity用户失败: " + - string.Join(", ", result.Errors.Select(e => e.Description))); + // 每次尝试时重新获取最新的用户数据 + var user = await _userRepository.GetAsync(id); + if (user == null) + { + _logger.LogWarning($"尝试删除不存在的用户,ID:{id}"); + return; // 如果用户不存在,就不再继续处理 + } + + // 删除Identity用户 + var identityUser = await _identityUserManager.FindByIdAsync(user.IdentityUserId.ToString()); + if (identityUser != null) + { + var result = await _identityUserManager.DeleteAsync(identityUser); + if (!result.Succeeded) + { + throw new UserFriendlyException("删除Identity用户失败: " + + string.Join(", ", result.Errors.Select(e => e.Description))); + } + } + + // 删除用户 + await _userRepository.DeleteAsync(user); + + _logger.LogInformation($"删除了用户,ID:{id}"); + return; } - } + catch (Volo.Abp.Data.AbpDbConcurrencyException ex) + { + // 增加重试计数 + retryCount++; - // 删除用户 - await _userRepository.DeleteAsync(user); + // 如果达到最大重试次数,则抛出用户友好的异常 + if (retryCount >= maxRetries) + { + throw new UserFriendlyException( + "删除用户失败:数据已被其他用户修改。请刷新页面后重试。", + "409", ex.Message, + ex); + } - _logger.LogInformation($"删除了用户,ID:{id}"); + // 短暂延迟后重试 + await Task.Delay(100 * retryCount); // 逐步增加延迟时间 + + // 记录重试信息 + _logger.LogWarning($"检测到用户[{id}]删除时发生并发冲突,正在进行第{retryCount}次重试..."); + } + } } /// diff --git a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs index e07d40f8e..e9ac52aca 100644 --- a/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs +++ b/aspnet-core/templates/aio/content/tests/PackageName.CompanyName.ProjectName.Application.Tests/PackageName/CompanyName/ProjectName/ProjectNameApplicationTestModule.cs @@ -45,6 +45,8 @@ public class ProjectNameApplicationTestModule : AbpModule options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; + // 添加默认的双因素令牌提供者配置 + options.Tokens.ProviderMap.Add("Default", new TokenProviderDescriptor(typeof(EmailTokenProvider))); }); Configure(options => {