Browse Source

Merge pull request #1493 from colinin/identity-user-inactive-jobs

feat(identity): Add checks for inactive users' jobs
pull/1494/head
yx lin 4 months ago
committed by GitHub
parent
commit
53b59ef218
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      aspnet-core/LINGYUN.MicroService.All.slnx
  2. 1
      aspnet-core/LINGYUN.MicroService.Aspire.slnx
  3. 1
      aspnet-core/LINGYUN.MicroService.SingleProject.slnx
  4. 3
      aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/AuthServerMigrationsDbContext.cs
  5. 1403
      aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530060327_Add-Identity-User-Inactive.Designer.cs
  6. 48
      aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530060327_Add-Identity-User-Inactive.cs
  7. 42
      aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/AuthServerMigrationsDbContextModelSnapshot.cs
  8. 9
      aspnet-core/aspire/LINGYUN.Abp.MicroService.MessageService/Handlers/Local/UserSubscribeSessionExpirationEventHandler.cs
  9. 3
      aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore.csproj
  10. 6
      aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore/TaskServiceMigrationsEntityFrameworkCoreModule.cs
  11. 2
      aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService/LINGYUN.Abp.MicroService.TaskService.csproj
  12. 4
      aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService/TaskServiceModule.cs
  13. 6179
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20260529063257_Add-Identity-User-Inactive.Designer.cs
  14. 49
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20260529063257_Add-Identity-User-Inactive.cs
  15. 42
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/SingleMigrationsDbContextModelSnapshot.cs
  16. 6179
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/20260530055941_Add-Identity-User-Inactive.Designer.cs
  17. 48
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/20260530055941_Add-Identity-User-Inactive.cs
  18. 42
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/SingleMigrationsDbContextModelSnapshot.cs
  19. 6186
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/20260530060009_Add-Identity-User-Inactive.Designer.cs
  20. 47
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/20260530060009_Add-Identity-User-Inactive.cs
  21. 42
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/SingleMigrationsDbContextModelSnapshot.cs
  22. 4
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsDbContext.cs
  23. 3
      aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/AuthServerMigrationsDbContext.cs
  24. 1417
      aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530061141_Add-Identity-User-Inactive.Designer.cs
  25. 49
      aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530061141_Add-Identity-User-Inactive.cs
  26. 42
      aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/AuthServerMigrationsDbContextModelSnapshot.cs
  27. 1
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs
  28. 1
      aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs
  29. 8
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/IdentityUserAppService.cs
  30. 7
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/InactiveUserStatus.cs
  31. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json
  32. 4
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json
  33. 12
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingDefinitionProvider.cs
  34. 9
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingNames.cs
  35. 1
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/AbpIdentityDomainModule.cs
  36. 40
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserInactiveRepository.cs
  37. 2
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserRepository.cs
  38. 48
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IdentityUserInactive.cs
  39. 29
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionUserInactiveSynchronizer.cs
  40. 38
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs
  41. 100
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserInactiveRepository.cs
  42. 23
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/IdentityUserInactiveDbContextModelBuilderExtensions.cs
  43. 7
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN.Abp.Identity.Notifications.csproj
  44. 16
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/AbpIdentityNotificationsModule.cs
  45. 22
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs
  46. 12
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs
  47. 29
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Localization/Resources/en.json
  48. 29
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Localization/Resources/zh-Hans.json
  49. 34
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/IdentityNotificationTemplateDefinitionProvider.cs
  50. 33
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeactivationNotifier.tpl
  51. 30
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeletionNotifier.tpl
  52. 36
      aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserReminderNotifier.tpl
  53. 40
      aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs
  54. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj
  55. 4
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/AbpIdentityJobsModule.cs
  56. 17
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/IdentityJobDefinitionProvider.cs
  57. 418
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserCleanupJob.cs
  58. 179
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserNotifierJob.cs
  59. 7
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/en.json
  60. 7
      aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/zh-Hans.json
  61. 39
      aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateSubscribeIdentityNotificationsEventHandler.cs
  62. 1
      aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj
  63. 3
      aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs
  64. 9
      aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs
  65. 3
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj
  66. 8
      aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs

2
aspnet-core/LINGYUN.MicroService.All.slnx

@ -377,6 +377,7 @@
<Project Path="modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN.Abp.Identity.QrCode.csproj" />
<Project Path="modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN.Abp.Identity.Session.AspNetCore.csproj" />
<Project Path="modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN.Abp.Identity.Session.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj" />
</Folder>
<Folder Name="/modules/localization-management/">
<File Path="modules/localization-management/README.md" />
@ -484,6 +485,7 @@
<Project Path="modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.BackgroundTasks.TaskManagement/LINGYUN.Abp.BackgroundTasks.TaskManagement.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.BackgroundTasks/LINGYUN.Abp.BackgroundTasks.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Elasticsearch.Jobs/LINGYUN.Abp.Elasticsearch.Jobs.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Quartz.MySqlInstaller/LINGYUN.Abp.Quartz.MySqlInstaller.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Quartz.SqlInstaller/LINGYUN.Abp.Quartz.SqlInstaller.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.TaskManagement.Application.Contracts/LINGYUN.Abp.TaskManagement.Application.Contracts.csproj" />

1
aspnet-core/LINGYUN.MicroService.Aspire.slnx

@ -496,6 +496,7 @@
<Project Path="modules/task-management/LINGYUN.Abp.BackgroundTasks.Quartz/LINGYUN.Abp.BackgroundTasks.Quartz.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.BackgroundTasks.TaskManagement/LINGYUN.Abp.BackgroundTasks.TaskManagement.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Elasticsearch.Jobs/LINGYUN.Abp.Elasticsearch.Jobs.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Quartz.MySqlInstaller/LINGYUN.Abp.Quartz.MySqlInstaller.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Quartz.PostgresSqlInstaller/LINGYUN.Abp.Quartz.PostgresSqlInstaller.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Quartz.SqlInstaller/LINGYUN.Abp.Quartz.SqlInstaller.csproj" />

1
aspnet-core/LINGYUN.MicroService.SingleProject.slnx

@ -282,6 +282,7 @@
<Project Path="modules/identity/LINGYUN.Abp.Identity.QrCode/LINGYUN.Abp.Identity.QrCode.csproj" />
<Project Path="modules/identity/LINGYUN.Abp.Identity.Session.AspNetCore/LINGYUN.Abp.Identity.Session.AspNetCore.csproj" />
<Project Path="modules/identity/LINGYUN.Abp.Identity.Session/LINGYUN.Abp.Identity.Session.csproj" />
<Project Path="modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj" />
</Folder>
<Folder Name="/modules/localization-management/">
<Project Path="modules/localization-management/LINGYUN.Abp.LocalizationManagement.Application.Contracts/LINGYUN.Abp.LocalizationManagement.Application.Contracts.csproj" />

3
aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/AuthServerMigrationsDbContext.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.Gdpr;
using LINGYUN.Abp.Gdpr.EntityFrameworkCore;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
@ -59,5 +60,7 @@ public class AuthServerMigrationsDbContext :
modelBuilder.ConfigureIdentity();
modelBuilder.ConfigureOpenIddict();
modelBuilder.ConfigureGdpr();
modelBuilder.ConfigureIdentityUserInactive();
}
}

1403
aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530060327_Add-Identity-User-Inactive.Designer.cs

File diff suppressed because it is too large

48
aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530060327_Add-Identity-User-Inactive.cs

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LINGYUN.Abp.MicroService.AuthServer.Migrations
{
/// <inheritdoc />
public partial class AddIdentityUserInactive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpUserInactives",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: true),
LastSignInTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
LastNotificationTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
DeactivatedTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
CreationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
CreatorId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpUserInactives", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AbpUserInactives_TenantId_UserId",
table: "AbpUserInactives",
columns: new[] { "TenantId", "UserId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpUserInactives");
}
}
}

42
aspnet-core/aspire/LINGYUN.Abp.MicroService.AuthServer.EntityFrameworkCore/Migrations/AuthServerMigrationsDbContextModelSnapshot.cs

@ -84,6 +84,48 @@ namespace LINGYUN.Abp.MicroService.AuthServer.Migrations
b.ToTable("AbpGdprRequests", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.Identity.IdentityUserInactive", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uuid")
.HasColumnName("CreatorId");
b.Property<DateTimeOffset?>("DeactivatedTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset?>("LastNotificationTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("LastSignInTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpUserInactives", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b =>
{
b.Property<Guid>("Id")

9
aspnet-core/aspire/LINGYUN.Abp.MicroService.MessageService/Handlers/Local/UserSubscribeSessionExpirationEventHandler.cs

@ -25,11 +25,16 @@ public class UserSubscribeSessionExpirationEventHandler : ILocalEventHandler<Ent
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.Session.ExpirationSession);
// 新用户订阅不活跃用户清理通知
// 新用户订阅不活跃用户相关通知
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers);
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier);
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier);
}
}

3
aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore.csproj

@ -14,9 +14,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Volo.Abp.EntityFrameworkCore.PostgreSql" />
<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" />
<PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" />
<PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" />
</ItemGroup>
<ItemGroup>

6
aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService.EntityFrameworkCore/TaskServiceMigrationsEntityFrameworkCoreModule.cs

@ -6,10 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
using System;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.PostgreSql;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
namespace LINGYUN.Abp.MicroService.TaskService;
@ -18,9 +15,6 @@ namespace LINGYUN.Abp.MicroService.TaskService;
typeof(AbpSaasEntityFrameworkCoreModule),
typeof(TaskManagementEntityFrameworkCoreModule),
typeof(AbpEntityFrameworkCorePostgreSqlModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpDataDbMigratorModule)
)]
public class TaskServiceMigrationsEntityFrameworkCoreModule : AbpModule

2
aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService/LINGYUN.Abp.MicroService.TaskService.csproj

@ -55,7 +55,9 @@
<ProjectReference Include="..\..\framework\telemetry\LINGYUN.Abp.Telemetry.SkyWalking\LINGYUN.Abp.Telemetry.SkyWalking.csproj" />
<ProjectReference Include="..\..\framework\tenants\LINGYUN.Abp.MultiTenancy.Editions\LINGYUN.Abp.MultiTenancy.Editions.csproj" />
<ProjectReference Include="..\..\framework\mvc\LINGYUN.Abp.AspNetCore.Mvc.Wrapper\LINGYUN.Abp.AspNetCore.Mvc.Wrapper.csproj" />
<ProjectReference Include="..\..\modules\identity\LINGYUN.Abp.Identity.EntityFrameworkCore\LINGYUN.Abp.Identity.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.Elasticsearch.Jobs\LINGYUN.Abp.Elasticsearch.Jobs.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.Identity.Jobs\LINGYUN.Abp.Identity.Jobs.csproj" />
</ItemGroup>
<ItemGroup>

4
aspnet-core/aspire/LINGYUN.Abp.MicroService.TaskService/TaskServiceModule.cs

@ -14,6 +14,8 @@ using LINGYUN.Abp.Elasticsearch.Jobs;
using LINGYUN.Abp.Emailing.Platform;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using LINGYUN.Abp.Identity.Jobs;
using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.Saas.EntityFrameworkCore;
@ -54,6 +56,7 @@ namespace LINGYUN.Abp.MicroService.TaskService;
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(AbpAspNetCoreMvcLocalizationModule),
typeof(AbpElasticsearchJobsModule),
typeof(AbpIdentityJobsModule),
typeof(AbpBackgroundTasksJobsModule),
typeof(AbpBackgroundTasksQuartzModule),
typeof(AbpBackgroundTasksDistributedLockingModule),
@ -61,6 +64,7 @@ namespace LINGYUN.Abp.MicroService.TaskService;
typeof(TaskManagementApplicationModule),
typeof(TaskManagementHttpApiModule),
typeof(TaskManagementEntityFrameworkCoreModule),
typeof(AbpIdentityEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),

6179
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20260529063257_Add-Identity-User-Inactive.Designer.cs

File diff suppressed because it is too large

49
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/20260529063257_Add-Identity-User-Inactive.cs

@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migrations
{
/// <inheritdoc />
public partial class AddIdentityUserInactive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpUserInactives",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
LastSignInTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
LastNotificationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
DeactivatedTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatorId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_AbpUserInactives", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_AbpUserInactives_TenantId_UserId",
table: "AbpUserInactives",
columns: new[] { "TenantId", "UserId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpUserInactives");
}
}
}

42
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.MySql/Migrations/SingleMigrationsDbContextModelSnapshot.cs

@ -1082,6 +1082,48 @@ namespace LY.MicroService.Applications.Single.EntityFrameworkCore.MySql.Migratio
b.ToTable("AbpGdprRequests", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.Identity.IdentityUserInactive", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("CreatorId");
b.Property<DateTimeOffset?>("DeactivatedTime")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset?>("LastNotificationTime")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastSignInTime")
.HasColumnType("datetime(6)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpUserInactives", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.LocalizationManagement.Language", b =>
{
b.Property<Guid>("Id")

6179
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/20260530055941_Add-Identity-User-Inactive.Designer.cs

File diff suppressed because it is too large

48
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/20260530055941_Add-Identity-User-Inactive.cs

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql.Migrations
{
/// <inheritdoc />
public partial class AddIdentityUserInactive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpUserInactives",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: true),
LastSignInTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
LastNotificationTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
DeactivatedTime = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
CreationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
CreatorId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpUserInactives", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AbpUserInactives_TenantId_UserId",
table: "AbpUserInactives",
columns: new[] { "TenantId", "UserId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpUserInactives");
}
}
}

42
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql/Migrations/SingleMigrationsDbContextModelSnapshot.cs

@ -1082,6 +1082,48 @@ namespace LY.MicroService.Applications.Single.EntityFrameworkCore.PostgreSql.Mig
b.ToTable("AbpGdprRequests", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.Identity.IdentityUserInactive", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreationTime")
.HasColumnType("timestamp without time zone")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uuid")
.HasColumnName("CreatorId");
b.Property<DateTimeOffset?>("DeactivatedTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset?>("LastNotificationTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTimeOffset>("LastSignInTime")
.HasColumnType("timestamp with time zone");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpUserInactives", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.LocalizationManagement.Language", b =>
{
b.Property<Guid>("Id")

6186
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/20260530060009_Add-Identity-User-Inactive.Designer.cs

File diff suppressed because it is too large

47
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/20260530060009_Add-Identity-User-Inactive.cs

@ -0,0 +1,47 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer.Migrations
{
/// <inheritdoc />
public partial class AddIdentityUserInactive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpUserInactives",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
TenantId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastSignInTime = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
LastNotificationTime = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
DeactivatedTime = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpUserInactives", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AbpUserInactives_TenantId_UserId",
table: "AbpUserInactives",
columns: new[] { "TenantId", "UserId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpUserInactives");
}
}
}

42
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer/Migrations/SingleMigrationsDbContextModelSnapshot.cs

@ -1082,6 +1082,48 @@ namespace LY.MicroService.Applications.Single.EntityFrameworkCore.SqlServer.Migr
b.ToTable("AbpGdprRequests", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.Identity.IdentityUserInactive", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<DateTimeOffset?>("DeactivatedTime")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset?>("LastNotificationTime")
.HasColumnType("datetimeoffset");
b.Property<DateTimeOffset>("LastSignInTime")
.HasColumnType("datetimeoffset");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("uniqueidentifier")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpUserInactives", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.LocalizationManagement.Language", b =>
{
b.Property<Guid>("Id")

4
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/SingleMigrationsDbContext.cs

@ -12,6 +12,7 @@ using LINGYUN.Abp.Demo.Books;
using LINGYUN.Abp.Demo.EntityFrameworkCore;
using LINGYUN.Abp.Gdpr;
using LINGYUN.Abp.Gdpr.EntityFrameworkCore;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using LINGYUN.Abp.LocalizationManagement;
using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.MessageService.Chat;
@ -39,7 +40,6 @@ using Microsoft.EntityFrameworkCore;
using Volo.Abp.AuditLogging;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.FeatureManagement;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
@ -285,5 +285,7 @@ public class SingleMigrationsDbContext :
modelBuilder.ConfigureAIManagement();
modelBuilder.ConfigureBlobManagement();
modelBuilder.ConfigureIdentityUserInactive();
}
}

3
aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/AuthServerMigrationsDbContext.cs

@ -1,4 +1,5 @@
using LINGYUN.Abp.Gdpr.EntityFrameworkCore;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
@ -24,5 +25,7 @@ public class AuthServerMigrationsDbContext : AbpDbContext<AuthServerMigrationsDb
modelBuilder.ConfigureOpenIddict();
modelBuilder.ConfigureGdpr();
modelBuilder.ConfigureIdentityUserInactive();
}
}

1417
aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530061141_Add-Identity-User-Inactive.Designer.cs

File diff suppressed because it is too large

49
aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/20260530061141_Add-Identity-User-Inactive.cs

@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LY.MicroService.AuthServer.EntityFrameworkCore.Migrations
{
/// <inheritdoc />
public partial class AddIdentityUserInactive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpUserInactives",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
LastSignInTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
LastNotificationTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
Status = table.Column<int>(type: "int", nullable: false),
DeactivatedTime = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CreatorId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_AbpUserInactives", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_AbpUserInactives_TenantId_UserId",
table: "AbpUserInactives",
columns: new[] { "TenantId", "UserId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpUserInactives");
}
}
}

42
aspnet-core/migrations/LY.MicroService.AuthServer.EntityFrameworkCore/Migrations/AuthServerMigrationsDbContextModelSnapshot.cs

@ -86,6 +86,48 @@ namespace LY.MicroService.AuthServer.DbMigrator.Migrations
b.ToTable("AbpGdprRequests", (string)null);
});
modelBuilder.Entity("LINGYUN.Abp.Identity.IdentityUserInactive", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime(6)")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("char(36)")
.HasColumnName("CreatorId");
b.Property<DateTimeOffset?>("DeactivatedTime")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset?>("LastNotificationTime")
.HasColumnType("datetime(6)");
b.Property<DateTimeOffset>("LastSignInTime")
.HasColumnType("datetime(6)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<Guid?>("TenantId")
.HasColumnType("char(36)")
.HasColumnName("TenantId");
b.Property<Guid>("UserId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("TenantId", "UserId");
b.ToTable("AbpUserInactives", (string)null);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b =>
{
b.Property<Guid>("Id")

1
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AbpAccountApplicationModule.cs

@ -33,6 +33,7 @@ public class AbpAccountApplicationModule : AbpModule
Configure<AppUrlOptions>(options =>
{
options.Applications["MVC"].Urls[AccountUrlNames.EmailConfirm] = "Account/EmailConfirm";
options.Applications["MVC"].Urls[AccountUrlNames.UserLogin] = "Account/Login";
});
Configure<AbpLocalizationOptions>(options =>

1
aspnet-core/modules/account/LINGYUN.Abp.Account.Security/LINGYUN/Abp/Account/Security/AccountUrlNames.cs

@ -4,4 +4,5 @@ public static class AccountUrlNames
{
public const string EmailConfirm = "Abp.Account.EmailConfirm";
public const string EmailVerifyLogin = "Abp.Account.EmailVerifyLogin";
public const string UserLogin = "Abp.Account.UserLogin";
}

8
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Application/LINGYUN/Abp/Identity/IdentityUserAppService.cs

@ -105,6 +105,8 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe
#endregion
#region Security
[Authorize(IdentityPermissions.Users.ResetPassword)]
public async virtual Task ChangePasswordAsync(Guid id, IdentityUserSetPasswordInput input)
{
@ -139,6 +141,10 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe
await CurrentUnitOfWork.SaveChangesAsync();
}
#endregion
#region Lock
[Authorize(Volo.Abp.Identity.IdentityPermissions.Users.Update)]
public async virtual Task LockAsync(Guid id, int seconds)
{
@ -162,6 +168,8 @@ public class IdentityUserAppService : IdentityAppServiceBase, IIdentityUserAppSe
await CurrentUnitOfWork.SaveChangesAsync();
}
#endregion
protected async virtual Task<IdentityUser> GetUserAsync(Guid id)
{
await IdentityOptions.SetAsync();

7
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/InactiveUserStatus.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.Identity;
public enum InactiveUserStatus
{
Notified = 1,
Deactivated = 2,
}

4
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/en.json

@ -68,6 +68,10 @@
"Description:Abp.Identity.Session.ConcurrentLoginStrategy": "There is a setting in the identity section to prevent concurrent login.",
"DisplayName:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit": "Same Type Devices Login Limit",
"Description:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit": "Limit the login times of devices of the same type. The sessions that exceed the limit will be deregistered.",
"DisplayName:Abp.Identity.Link.UserLoginUri": "User Login Uri",
"Description:Abp.Identity.Link.UserLoginUri": "Set up a user login link for redirecting external authenticated users.",
"DisplayName:Identity.Link": "Link",
"Description:Identity.Link": "Used for setting up external access links",
"DisplayName:NewPhoneNumber": "New Phone Number",
"DisplayName:SmsVerifyCode": "SMS verification code",
"DisplayName:EmailVerifyCode": "Mail verification code",

4
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Localization/zh-Hans.json

@ -68,6 +68,10 @@
"Description:Abp.Identity.Session.ConcurrentLoginStrategy": "用户重复登录会话策略",
"DisplayName:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit": "允许相同类型设备登录次数",
"Description:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit": "限制相同类型设备登录次数,超出限制的会话将被注销.",
"DisplayName:Abp.Identity.Link.UserLoginUri": "用户登录链接",
"Description:Abp.Identity.Link.UserLoginUri": "设置用户登录链接,用于外部认证用户重定向.",
"DisplayName:Identity.Link": "链接",
"Description:Identity.Link": "用于设定外部接入链接",
"DisplayName:NewPhoneNumber": "新手机号码",
"DisplayName:SmsVerifyCode": "短信验证码",
"DisplayName:EmailVerifyCode": "邮件验证码",

12
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingDefinitionProvider.cs

@ -81,6 +81,18 @@ public class IdentitySettingDefinitionProvider : SettingDefinitionProvider
displayName: L("DisplayName:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit"),
description: L("Description:Abp.Identity.Session.LogoutFromSameTypeDevicesLimit"),
isVisibleToClients: false)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
name: IdentitySettingNames.Link.UserLoginUri,
defaultValue: "http://localhost:8080/Account/Login",
displayName: L("DisplayName:Abp.Identity.Link.UserLoginUri"),
description: L("Description:Abp.Identity.Link.UserLoginUri"),
isVisibleToClients: false)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,

9
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain.Shared/LINGYUN/Abp/Identity/Settings/IdentitySettingNames.cs

@ -41,4 +41,13 @@ public static class IdentitySettingNames
/// </summary>
public const string LogoutFromSameTypeDevicesLimit = SessionPrefix + ".LogoutFromSameTypeDevicesLimit";
}
public static class Link
{
private const string LinkPrefix = Prefix + ".Link";
/// <summary>
/// 用户登录页(包含协议的登录地址串,用于发送用户登录邮件中的链接)
/// </summary>
public const string UserLoginUri = LinkPrefix + ".UserLoginUri";
}
}

1
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/AbpIdentityDomainModule.cs

@ -15,6 +15,7 @@ namespace LINGYUN.Abp.Identity;
[DependsOn(
typeof(AbpIdentityDomainSharedModule),
typeof(AbpIdentitySessionModule),
typeof(AbpDistributedLockingAbstractionsModule),
typeof(Volo.Abp.Identity.AbpIdentityDomainModule))]
public class AbpIdentityDomainModule : AbpModule

40
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserInactiveRepository.cs

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.Specifications;
namespace LINGYUN.Abp.Identity;
public interface IIdentityUserInactiveRepository : IBasicRepository<IdentityUserInactive, long>
{
Task<IdentityUserInactive> FindByUserIdAsync(
Guid userId,
CancellationToken cancellationToken = default);
Task<int> GetInactiveUserCountAsync(
DateTime threshold,
IEnumerable<Guid> exceptUserIds = null,
CancellationToken cancellationToken = default);
Task<List<IdentityUser>> GetInactiveUserListAsync(
DateTime threshold,
IEnumerable<Guid> exceptUserIds = null,
string sorting = nameof(IdentityUser.LastSignInTime),
int maxResultCount = 10,
int skipCount = 0,
CancellationToken cancellationToken = default);
Task<int> GetCountAsync(
ISpecification<IdentityUserInactive> specification,
CancellationToken cancellationToken = default);
Task<List<IdentityUserInactive>> GetListAsync(
ISpecification<IdentityUserInactive> specification,
string sorting = nameof(IdentityUserInactive.CreationTime),
int maxResultCount = 10,
int skipCount = 0,
CancellationToken cancellationToken = default);
}

2
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IIdentityUserRepository.cs

@ -59,7 +59,7 @@ public interface IIdentityUserRepository : Volo.Abp.Identity.IIdentityUserReposi
List<Guid> userIds,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
);
/// <summary>
/// 获取用户所有的组织机构列表
/// </summary>

48
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/IdentityUserInactive.cs

@ -0,0 +1,48 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.Identity;
public class IdentityUserInactive : CreationAuditedEntity<long>, IMultiTenant
{
public virtual Guid UserId { get; protected set; }
public virtual Guid? TenantId { get; protected set; }
public virtual DateTimeOffset LastSignInTime { get; protected set; }
public virtual DateTimeOffset? LastNotificationTime { get; protected set; }
public virtual InactiveUserStatus Status { get; protected set; }
public virtual DateTimeOffset? DeactivatedTime { get; protected set; }
protected IdentityUserInactive()
{
}
public IdentityUserInactive(
Guid userId,
DateTimeOffset lastSignInTime,
Guid? tenantId = null)
{
UserId = userId;
TenantId = tenantId;
LastSignInTime = lastSignInTime;
Status = InactiveUserStatus.Notified;
}
public void MarkNotified(DateTimeOffset now)
{
LastNotificationTime = now;
Status = InactiveUserStatus.Notified;
}
public void MarkDeactivated(DateTimeOffset now)
{
DeactivatedTime = now;
Status = InactiveUserStatus.Deactivated;
}
}

29
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Domain/LINGYUN/Abp/Identity/Session/IdentitySessionUserInactiveSynchronizer.cs

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.Identity.Session;
public class IdentitySessionUserInactiveSynchronizer :
IDistributedEventHandler<EntityCreatedEto<IdentitySessionEto>>,
ITransientDependency
{
private readonly IIdentityUserInactiveRepository _identityUserInactiveRepository;
public IdentitySessionUserInactiveSynchronizer(IIdentityUserInactiveRepository identityUserInactiveRepository)
{
_identityUserInactiveRepository = identityUserInactiveRepository;
}
[UnitOfWork]
public async virtual Task HandleEventAsync(EntityCreatedEto<IdentitySessionEto> eventData)
{
var userInactive = await _identityUserInactiveRepository.FindByUserIdAsync(eventData.Entity.UserId);
if (userInactive == null)
{
await _identityUserInactiveRepository.DeleteAsync(userInactive);
}
}
}

38
aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreModule.cs

@ -2,6 +2,8 @@
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;
namespace LINGYUN.Abp.Identity.EntityFrameworkCore;
@ -9,7 +11,7 @@ namespace LINGYUN.Abp.Identity.EntityFrameworkCore;
[DependsOn(typeof(Volo.Abp.Identity.EntityFrameworkCore.AbpIdentityEntityFrameworkCoreModule))]
public class AbpIdentityEntityFrameworkCoreModule : AbpModule
{
// private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void ConfigureServices(ServiceConfigurationContext context)
{
@ -19,20 +21,28 @@ public class AbpIdentityEntityFrameworkCoreModule : AbpModule
options.AddRepository<IdentityUser, EfCoreIdentityUserRepository>();
options.AddRepository<IdentitySession, EfCoreIdentitySessionRepository>();
options.AddRepository<OrganizationUnit, EfCoreOrganizationUnitRepository>();
options.AddRepository<IdentityUserInactive, EfCoreIdentityUserInactiveRepository>();
});
}
//public override void PostConfigureServices(ServiceConfigurationContext context)
//{
// OneTimeRunner.Run(() =>
// {
// ObjectExtensionManager.Instance
// .MapEfCoreProperty<IdentityUser, string>(
// ExtensionIdentityUserConsts.AvatarUrlField,
// (etb, prop) =>
// {
// prop.HasMaxLength(ExtensionIdentityUserConsts.MaxAvatarUrlLength);
// });
// });
//}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
//ObjectExtensionManager.Instance
// .MapEfCoreProperty<IdentityUser, string>(
// ExtensionIdentityUserConsts.AvatarUrlField,
// (etb, prop) =>
// {
// prop.HasMaxLength(ExtensionIdentityUserConsts.MaxAvatarUrlLength);
// });
ObjectExtensionManager.Instance
.MapEfCoreDbContext<IdentityDbContext>(builder =>
{
builder.ConfigureIdentityUserInactive();
});
});
}
}

100
aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/EfCoreIdentityUserInactiveRepository.cs

@ -0,0 +1,100 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Specifications;
namespace LINGYUN.Abp.Identity.EntityFrameworkCore;
public class EfCoreIdentityUserInactiveRepository : EfCoreRepository<IIdentityDbContext, IdentityUserInactive, long>, IIdentityUserInactiveRepository
{
public EfCoreIdentityUserInactiveRepository(
IDbContextProvider<IIdentityDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async virtual Task<IdentityUserInactive> FindByUserIdAsync(
Guid userId,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(x => x.UserId == userId)
.FirstOrDefaultAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task<int> GetInactiveUserCountAsync(
DateTime threshold,
IEnumerable<Guid> exceptUserIds = null,
CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
var ignoreUserIds = dbContext.Set<IdentityUserInactive>()
.Select(x => x.UserId);
return await dbContext.Set<IdentityUser>()
.WhereIf(exceptUserIds?.Count() > 0, x => !exceptUserIds.Contains(x.Id))
.Where(x => !ignoreUserIds.Contains(x.Id))
.Where(x => x.IsActive &&
((x.LastSignInTime.HasValue && x.LastSignInTime < threshold) ||
// 更新到abp新版后用户未登陆过使用上次修改时间作为判断依据?
// (x.LastModificationTime.HasValue && x.LastModificationTime < threshold) ||
x.CreationTime < threshold))
.CountAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task<List<IdentityUser>> GetInactiveUserListAsync(
DateTime threshold,
IEnumerable<Guid> exceptUserIds = null,
string sorting = nameof(IdentityUser.LastSignInTime),
int maxResultCount = 10,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
var dbContext = await GetDbContextAsync();
var ignoreUserIds = dbContext.Set<IdentityUserInactive>()
.Select(x => x.UserId);
return await dbContext.Set<IdentityUser>()
.WhereIf(exceptUserIds?.Count() > 0, x => !exceptUserIds.Contains(x.Id))
.Where(x => !ignoreUserIds.Contains(x.Id))
.Where(x => x.IsActive &&
((x.LastSignInTime.HasValue && x.LastSignInTime < threshold) ||
// 更新到abp新版后用户未登陆过使用上次修改时间作为判断依据?
// (x.LastModificationTime.HasValue && x.LastModificationTime < threshold) ||
x.CreationTime < threshold))
.OrderBy(!sorting.IsNullOrWhiteSpace() ? sorting : nameof(IdentityUser.LastSignInTime))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task<int> GetCountAsync(
ISpecification<IdentityUserInactive> specification,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(specification.ToExpression())
.CountAsync(GetCancellationToken(cancellationToken));
}
public async virtual Task<List<IdentityUserInactive>> GetListAsync(
ISpecification<IdentityUserInactive> specification,
string sorting = nameof(IdentityUserInactive.CreationTime),
int maxResultCount = 10,
int skipCount = 0,
CancellationToken cancellationToken = default)
{
return await (await GetQueryableAsync())
.Where(specification.ToExpression())
.OrderBy(!sorting.IsNullOrWhiteSpace() ? sorting : nameof(IdentityUserInactive.CreationTime))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}

23
aspnet-core/modules/identity/LINGYUN.Abp.Identity.EntityFrameworkCore/LINGYUN/Abp/Identity/EntityFrameworkCore/IdentityUserInactiveDbContextModelBuilderExtensions.cs

@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Identity.EntityFrameworkCore;
public static class IdentityUserInactiveDbContextModelBuilderExtensions
{
public static void ConfigureIdentityUserInactive([NotNull] this ModelBuilder builder)
{
builder.Entity<IdentityUserInactive>(b =>
{
b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "UserInactives", AbpIdentityDbProperties.DbSchema);
b.ConfigureByConvention();
b.HasIndex(x => new { x.TenantId, x.UserId });
b.ApplyObjectExtensionMappings();
});
}
}

7
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN.Abp.Identity.Notifications.csproj

@ -13,6 +13,13 @@
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\Identity\Notifications\Templates\*.tpl" />
<None Include="LINGYUN\Abp\Identity\Notifications\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\Identity\Notifications\Templates\*.tpl" />
<EmbeddedResource Include="LINGYUN\Abp\Identity\Notifications\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" />
</ItemGroup>

16
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/AbpIdentityNotificationsModule.cs

@ -1,6 +1,9 @@
using LINGYUN.Abp.Notifications;
using Volo.Abp.Domain;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Identity.Notifications;
@ -10,5 +13,18 @@ namespace LINGYUN.Abp.Identity.Notifications;
typeof(AbpIdentityDomainSharedModule))]
public class AbpIdentityNotificationsModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpIdentityNotificationsModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<IdentityResource>()
.AddVirtualJson("/LINGYUN/Abp/Identity/Notifications/Localization/Resources");
});
}
}

22
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationDefinitionProvider.cs

@ -24,15 +24,27 @@ public class IdentityNotificationDefinitionProvider : NotificationDefinitionProv
NotificationProviderNames.SignalR, // 实时通知处理会话过期事件
NotificationProviderNames.Emailing); // 邮件通知会话过期
// 账号保持活跃通知
group.AddNotification(
IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers,
L("Notifications:CleaningUpInactiveUsers"),
L("Notifications:CleaningUpInactiveUsers"),
NotificationType.Application,
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier,
L("InactiveUserReminderNotifier"),
L("InactiveUserReminderNotifier"),
NotificationType.User,
NotificationLifetime.Persistent,
NotificationContentType.Markdown,
NotificationContentType.Html,
allowSubscriptionToClients: false) // 客户端禁用, 所有新用户必须订阅此通知
.WithProviders(NotificationProviderNames.Emailing);
// 账号停用通知
group.AddNotification(
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier,
L("InactiveUserDeactivationNotifier"),
L("InactiveUserDeactivationNotifier"),
NotificationType.User,
NotificationLifetime.Persistent,
NotificationContentType.Html,
allowSubscriptionToClients: false) // 客户端禁用, 所有新用户必须订阅此通知
.WithProviders(NotificationProviderNames.Emailing);
// 账号删除通知不需要添加,直接发送邮件通知即可,因为发布通知时用户已删除
}
private static ILocalizableString L(string name)

12
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/IdentityNotificationNames.cs

@ -16,8 +16,16 @@ public static class IdentityNotificationNames
{
public const string Prefix = GroupName + ".IdentityUser";
/// <summary>
/// 不活跃用户清理通知
/// 账户保持活跃通知
/// </summary>
public const string CleaningUpInactiveUsers = Prefix + ".CleaningUpInactiveUsers";
public const string InactiveUserReminderNotifier = Prefix + ".InactiveUserReminderNotifier";
/// <summary>
/// 账户停用通知
/// </summary>
public const string InactiveUserDeactivationNotifier = Prefix + ".InactiveUserDeactivationNotifier";
/// <summary>
/// 账户删除通知
/// </summary>
public const string InactiveUserDeletionNotifier = Prefix + ".InactiveUserDeletionNotifier";
}
}

29
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Localization/Resources/en.json

@ -0,0 +1,29 @@
{
"culture": "en",
"texts": {
"InactiveUserReminderNotifier": "Account Activity Reminder",
"InactiveUserDeactivationNotifier": "Account Deactivation Notice",
"InactiveUserDeletionNotifier": "Account Deletion Notice",
"InactiveUserReminderHeader": "Account Activity Reminder",
"InactiveUserReminderWarning": "We have noticed that your account has been inactive for an extended period. To keep your account active and secure, we recommend logging in.",
"InactiveUserReminderAction": "If you wish to keep your account, please log in before",
"InactiveUserReminderReactivate": ".",
"InactiveUserReminderClosure": "If you no longer need this account, no action is required. However, please note that if you do not log in before",
"InactiveUserReminderInactive": ", your account will be deactivated, and you will not be able to log in.",
"InactiveUserDeactivationNotice": "This notice is to inform you that, due to",
"InactiveUserDeactivationWarning": "consecutive days of inactivity, your account has been deactivated. Your data is still stored but inaccessible. If needed, please contact us before",
"InactiveUserDeactivationAction": "to restore your account. If not addressed in time, your account will be permanently deleted.",
"NoReactionClosure": "If you do not wish to reactivate your account, no further action is required. After the reactivation period ends, your account will be automatically queued for deletion.",
"AutomatedNotification": "This is an automated notification. Please do not reply directly to this email.",
"InactiveUserDeactivationHeader": "Account Deactivation Notice",
"InactiveUserDeletionHeader": "Account Deletion Notice",
"InactiveUserDeletionNotice": "Due to prolonged inactivity and failure to respond to previous notices, your account has been permanently deleted. All data associated with you has been removed from the platform.",
"WhatYouCanDo": "What you can do:",
"CreateNewAccountInstructions": "To continue using the service, please",
"CreateNewAccountStep1": "Create a new account",
"CreateNewAccountStep2": "Set up your profile",
"CreateNewAccountStep3": "Resubscribe to services",
"LastSignInTime": "Last sign-in time",
"SignInToAccount": "Sign in to account"
}
}

29
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Localization/Resources/zh-Hans.json

@ -0,0 +1,29 @@
{
"culture": "zh-Hans",
"texts": {
"InactiveUserReminderNotifier": "账户保持活跃通知",
"InactiveUserDeactivationNotifier": "账户停用通知",
"InactiveUserDeletionNotifier": "账户删除通知",
"InactiveUserReminderHeader": "账户保持活跃提醒",
"InactiveUserReminderWarning": "我们注意到您的账户已经长时间没有活动,为保持您的账户活跃和安全,建议您登录账户.",
"InactiveUserReminderAction": "如果您想保留您的账号,请在",
"InactiveUserReminderReactivate": "之前登录.",
"InactiveUserReminderClosure": "如果您不再需要此账户,则无需采取任何操作.但请注意,如果截止",
"InactiveUserReminderInactive": "之前您仍未登录账号,系统将禁用您的账号,此时您将无法登录您的账号.",
"InactiveUserDeactivationNotice": "此通知旨在告知您,由于您已连续",
"InactiveUserDeactivationWarning": "天未登录,您的账号已被禁用,您的数据仍被存储但无法访问,如果有需要可在",
"InactiveUserDeactivationAction": "之前联系我们恢复账号,如未及时处理账号将被永久删除.",
"NoReactionClosure": "如果您不希望重新激活账户,则无需采取进一步操作.重新激活期限过后,您的账户将自动进入删除队列.",
"AutomatedNotification": "这是自动发送的通知, 请勿直接回复此邮件.",
"InactiveUserDeactivationHeader": "账户停用通知",
"InactiveUserDeletionHeader": "账户删除通知",
"InactiveUserDeletionNotice": "由于长期不活跃且未响应之前的通知,您的账户已被永久删除,与您相关的数据已从平台中移除.",
"WhatYouCanDo": "您可以:",
"CreateNewAccountInstructions": "如需继续使用服务,请",
"CreateNewAccountStep1": "创建新账户",
"CreateNewAccountStep2": "设置个人资料",
"CreateNewAccountStep3": "重新订阅服务",
"LastSignInTime": "上次登录时间",
"SignInToAccount": "登录账号"
}
}

34
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/IdentityNotificationTemplateDefinitionProvider.cs

@ -0,0 +1,34 @@
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
using Volo.Abp.TextTemplating;
namespace LINGYUN.Abp.Identity.Notifications.Templates;
public class IdentityNotificationTemplateDefinitionProvider : TemplateDefinitionProvider
{
public override void Define(ITemplateDefinitionContext context)
{
context.Add(
new TemplateDefinition(
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier,
displayName: L("InactiveUserReminderNotifier"),
localizationResource: typeof(IdentityResource)
).WithVirtualFilePath("/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserReminderNotifier.tpl", true),
new TemplateDefinition(
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier,
displayName: L("InactiveUserDeactivationNotifier"),
localizationResource: typeof(IdentityResource)
).WithVirtualFilePath("/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeactivationNotifier.tpl", true),
new TemplateDefinition(
IdentityNotificationNames.IdentityUser.InactiveUserDeletionNotifier,
displayName: L("InactiveUserDeletionNotifier"),
localizationResource: typeof(IdentityResource)
).WithVirtualFilePath("/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeletionNotifier.tpl", true)
);
}
private static ILocalizableString L(string name)
{
return LocalizableString.Create<IdentityResource>(name);
}
}

33
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeactivationNotifier.tpl

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{L "InactiveUserDeactivationNotifier"}}</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px;">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<table width="500" cellpadding="0" cellspacing="0" border="0" style="background-color: #ffffff; border-radius: 4px;">
<tr>
<td style="padding: 30px 30px 20px 30px;">
<h2 style="margin: 0 0 8px 0; font-size: 20px; color: #333;">{{L "InactiveUserDeactivationHeader"}}</h2>
</td>
</tr>
<tr>
<td style="padding: 0 30px 20px 30px;">
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;"><strong>{{model.email}}</strong></p>
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">
{{L "InactiveUserDeactivationNotice"}}<strong>{{model.inactivityDays}}</strong>{{L "InactiveUserDeactivationWarning"}}
<strong>{{model.reactivateDate | date.to_string '%F'}}</strong>{{L "InactiveUserDeactivationAction"}}
</p>
<p style="margin: 20px 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">{{L "NoReactionClosure"}}</p>
<p style="margin: 20px 0 0 0; font-size: 12px; color: #999;">{{L "AutomatedNotification"}}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

30
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserDeletionNotifier.tpl

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{L "InactiveUserDeletionNotifier"}}</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px;">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<table width="500" cellpadding="0" cellspacing="0" border="0" style="background-color: #ffffff; border-radius: 4px;">
<tr>
<td style="padding: 30px 30px 20px 30px;">
<h2 style="margin: 0 0 8px 0; font-size: 20px; color: #333;">{{L "InactiveUserDeletionHeader"}}</h2>
</td>
</tr>
<tr>
<td style="padding: 0 30px 20px 30px;">
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">{{model.email}}</p>
<p style="margin: 20px 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">{{L "InactiveUserDeletionNotice"}}</p>
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;"><strong>{{L "WhatYouCanDo"}}</strong> {{L "CreateNewAccountInstructions"}}{{L "CreateNewAccountStep1"}},{{L "CreateNewAccountStep2"}},{{L "CreateNewAccountStep3"}}.</p>
<p style="margin: 20px 0 0 0; font-size: 12px; color: #999;">{{L "AutomatedNotification"}}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

36
aspnet-core/modules/identity/LINGYUN.Abp.Identity.Notifications/LINGYUN/Abp/Identity/Notifications/Templates/InactiveUserReminderNotifier.tpl

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{L "InactiveUserReminderNotifier"}}</title>
</head>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px;">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<table width="500" cellpadding="0" cellspacing="0" border="0" style="background-color: #ffffff; border-radius: 4px;">
<tr>
<td style="padding: 30px 30px 20px 30px;">
<h2 style="margin: 0 0 8px 0; font-size: 20px; color: #333;">{{L "InactiveUserReminderHeader"}}</h2>
</td>
</tr>
<tr>
<td style="padding: 0 30px 20px 30px;">
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;"><strong>{{model.email}}</strong></p>
<p style="margin: 0 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">{{L "InactiveUserReminderWarning"}}</p>
<p style="margin: 0 0 8px 0; font-size: 14px; color: #555;">{{L "LastSignInTime"}}: <strong>{{if model.lastSignInTime}}{{model.lastSignInTime | date.to_string '%F'}}{{else}}{{L "Never"}}{{end}}</strong>,
{{L "InactiveUserReminderAction"}}<strong>{{model.reactivateDate | date.to_string '%F'}}</strong>{{L "InactiveUserReminderReactivate"}}
</p>
<p style="margin: 20px 0 16px 0; font-size: 14px; color: #555; line-height: 1.5;">
{{L "InactiveUserReminderClosure"}}<strong>{{model.reactivateDate | date.to_string '%F'}}</strong>{{L "InactiveUserReminderInactive"}}
</p>
<p style="margin: 20px 0 0 0;"><a href="{{model.loginUrl}}" style="color: #1a73e8;">{{L "SignInToAccount"}}</a></p>
<p style="margin: 20px 0 0 0; font-size: 12px; color: #999;">{{L "AutomatedNotification"}}</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

40
aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs

@ -269,15 +269,21 @@ public class SettingAppService : ApplicationService, ISettingAppService, ISettin
var signinSetting = identitySetting.AddSetting(L["DisplayName:Identity.SignIn"], L["Description:Identity.SignIn"]);
signinSetting.AddDetail(
await SettingDefinitionManager.GetAsync(IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation),
await SettingDefinitionManager.GetAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, providerName, providerKey),
await SettingManager.GetOrNullAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail, providerName, providerKey),
ValueType.Boolean,
providerName);
signinSetting.AddDetail(
await SettingDefinitionManager.GetAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail),
await SettingDefinitionManager.GetAsync(IdentitySettingNames.SignIn.RequireEmailVerificationToRegister),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail, providerName, providerKey),
await SettingManager.GetOrNullAsync(IdentitySettingNames.SignIn.RequireEmailVerificationToRegister, providerName, providerKey),
ValueType.Boolean,
providerName);
signinSetting.AddDetail(
await SettingDefinitionManager.GetAsync(IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(IdentitySettingNames.SignIn.EnablePhoneNumberConfirmation, providerName, providerKey),
ValueType.Boolean,
providerName);
signinSetting.AddDetail(
@ -362,6 +368,18 @@ public class SettingAppService : ApplicationService, ISettingAppService, ISettin
await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.PasswordChangePeriodDays, providerName, providerKey),
ValueType.Number,
providerName);
passwordSetting.AddDetail(
await SettingDefinitionManager.GetAsync(IdentitySettingNames.Password.EnablePreventPasswordReuse),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.EnablePreventPasswordReuse, providerName, providerKey),
ValueType.Boolean,
providerName);
passwordSetting.AddDetail(
await SettingDefinitionManager.GetAsync(IdentitySettingNames.Password.PreventPasswordReuseCount),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(IdentitySettingNames.Password.PreventPasswordReuseCount, providerName, providerKey),
ValueType.Number,
providerName);
#endregion
@ -395,10 +413,22 @@ public class SettingAppService : ApplicationService, ISettingAppService, ISettin
ValueType.Number,
providerName);
settingGroups.AddGroup(identitySetting);
#endregion
#region 链接
var linkSetting = identitySetting.AddSetting(L["DisplayName:Identity.Link"], L["Description:Identity.Link"]);
linkSetting.AddDetail(
await SettingDefinitionManager.GetAsync(LINGYUN.Abp.Identity.Settings.IdentitySettingNames.Link.UserLoginUri),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(LINGYUN.Abp.Identity.Settings.IdentitySettingNames.Link.UserLoginUri, providerName, providerKey),
ValueType.String,
providerName);
#endregion
settingGroups.AddGroup(identitySetting);
#endregion
#region 邮件设置

4
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN.Abp.Identity.Jobs.csproj

@ -18,6 +18,10 @@
<EmbeddedResource Include="LINGYUN\Abp\Identity\Jobs\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Emailing" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Domain\LINGYUN.Abp.Identity.Domain.csproj" />
<ProjectReference Include="..\..\identity\LINGYUN.Abp.Identity.Notifications\LINGYUN.Abp.Identity.Notifications.csproj" />

4
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/AbpIdentityJobsModule.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.BackgroundTasks;
using LINGYUN.Abp.Identity.Notifications;
using Volo.Abp.Emailing;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
@ -7,6 +8,7 @@ using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Identity.Jobs;
[DependsOn(typeof(AbpEmailingModule))]
[DependsOn(typeof(AbpIdentityDomainModule))]
[DependsOn(typeof(AbpIdentityNotificationsModule))]
[DependsOn(typeof(AbpBackgroundTasksAbstractionsModule))]
@ -22,7 +24,7 @@ public class AbpIdentityJobsModule : AbpModule
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<IdentityResource>()
.Get<IdentityResource>()
.AddVirtualJson("/LINGYUN/Abp/Identity/Jobs/Localization/Resources");
});
}

17
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/IdentityJobDefinitionProvider.cs

@ -10,12 +10,17 @@ public class IdentityJobDefinitionProvider : JobDefinitionProvider
InactiveIdentitySessionCleanupJob.Name,
typeof(InactiveIdentitySessionCleanupJob),
LocalizableStatic.Create("InactiveIdentitySessionCleanupJob"),
InactiveIdentitySessionCleanupJob.Paramters)
// TODO: 实现用户过期清理作业需要增加用户会话实体
//new JobDefinition(
// InactiveIdentityUserCleanupJob.Name,
// typeof(InactiveIdentityUserCleanupJob),
// LocalizableStatic.Create("InactiveIdentityUserCleanupJob"))
InactiveIdentitySessionCleanupJob.Paramters),
new JobDefinition(
InactiveIdentityUserNotifierJob.Name,
typeof(InactiveIdentityUserNotifierJob),
LocalizableStatic.Create("InactiveIdentityUserNotifierJob"),
InactiveIdentityUserNotifierJob.Paramters),
new JobDefinition(
InactiveIdentityUserCleanupJob.Name,
typeof(InactiveIdentityUserCleanupJob),
LocalizableStatic.Create("InactiveIdentityUserCleanupJob"),
InactiveIdentityUserCleanupJob.Paramters)
);
}
}

418
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserCleanupJob.cs

@ -1,125 +1,293 @@
//using LINGYUN.Abp.BackgroundTasks;
//using LINGYUN.Abp.Identity.Notifications;
//using LINGYUN.Abp.Notifications;
//using Microsoft.Extensions.Logging;
//using System.Collections.Generic;
//using System.Linq;
//using System.Threading.Tasks;
//using Volo.Abp.Domain.Repositories;
//using Volo.Abp.Identity;
//using Volo.Abp.Specifications;
//using Volo.Abp.Timing;
//namespace LINGYUN.Abp.Identity.Jobs;
///// <summary>
///// 用户清理作业
///// </summary>
///// <remarks>
///// 清理长期未登录用户
///// </remarks>
//public class InactiveIdentityUserCleanupJob : IJobRunnable
//{
// public const string Name = "InactiveIdentityUserCleanupJob";
// #region Definition Paramters
// public readonly static IReadOnlyList<JobDefinitionParamter> Paramters =
// new List<JobDefinitionParamter>
// {
// new JobDefinitionParamter(
// PropertyUserInactiveCleanupDays,
// LocalizableStatic.Create("DisplayName:UserInactiveCleanupDays"),
// LocalizableStatic.Create("Description:UserInactiveCleanupDays")),
// new JobDefinitionParamter(
// PropertyUserInactiveNotifierDays,
// LocalizableStatic.Create("DisplayName:UserInactiveNotifierDays"),
// LocalizableStatic.Create("Description:UserInactiveNotifierDays")),
// };
// #endregion
// /// <summary>
// /// 不活跃用户清理时间, 单位: 天
// /// </summary>
// public const string PropertyUserInactiveCleanupDays = "UserInactiveCleanupDays";
// /// <summary>
// /// 不活跃用户通知时间, 单位: 天
// /// </summary>
// public const string PropertyUserInactiveNotifierDays = "UserInactiveNotifierDays";
// public async virtual Task ExecuteAsync(JobRunnableContext context)
// {
// var logger = context.GetRequiredService<ILogger<InactiveIdentityUserCleanupJob>>();
// // 不活跃用户清理时间
// var inactiveCleanupDays = context.GetOrDefaultJobData(PropertyUserInactiveCleanupDays, 30);
// // 不活跃用户通知时间
// var inactiveNotifierDays = context.GetOrDefaultJobData(PropertyUserInactiveNotifierDays, 180);
// var clock = context.GetRequiredService<IClock>();
// var identityUserRepo = context.GetRequiredService<IIdentityUserRepository>();
// var identityUserSessionRepo = context.GetRequiredService<IIdentitySessionRepository>();
// // 获取需要清理的用户集合
// var specification = new ExpressionSpecification<IdentitySession>(
// x => x.LastAccessed <= clock.Now.AddDays(-inactiveNotifierDays));
// using (identityUserSessionRepo.DisableTracking())
// {
// var inactiveUserCount = await identityUserSessionRepo.GetCountAsync(specification);
// if (inactiveUserCount == 0)
// {
// logger.LogInformation("There are no inactive users to be notified.");
// return;
// }
// // 不活跃用户会话集合
// var inactiveUsers = await identityUserSessionRepo.GetListAsync(specification, maxResultCount: inactiveUserCount);
// // 直接清理的不活跃用户集合
// var inactiveCleanupUsers = inactiveUsers.Where(x => x.LastAccessed <= clock.Now.AddDays(-inactiveCleanupDays));
// // 需要通知的不活跃用户
// var inactiveNotifierUsers = inactiveUsers.ExceptBy(inactiveCleanupUsers.Select(x => x.Id), x => x.Id);
// if (inactiveNotifierUsers.Count() != 0)
// {
// await SendInactiveUserNotifier(context, inactiveNotifierUsers);
// }
// if (inactiveCleanupUsers.Count() != 0)
// {
// logger.LogInformation(
// "Prepare to clean up {count} users who have been inactive for more than {inactiveCleanupDays} days.",
// inactiveCleanupUsers.Count(),
// inactiveCleanupDays);
// // 清理不活跃用户
// await identityUserRepo.DeleteManyAsync(inactiveCleanupUsers.Select(x => x.UserId));
// }
// }
// logger.LogInformation($"Cleaned inactive users.");
// }
// /// <summary>
// /// 发送不活跃用户清理通知
// /// </summary>
// /// <param name="context"></param>
// /// <param name="userSessions"></param>
// /// <returns></returns>
// private async Task SendInactiveUserNotifier(JobRunnableContext context, IEnumerable<IdentitySession> userSessions)
// {
// // TODO: 完成不活跃用户清理通知
// var notificationSender = context.GetRequiredService<INotificationSender>();
// var notificationTemplate = new NotificationTemplate(
// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers,
// data: new Dictionary<string, object>
// {
// });
// await notificationSender.SendNofiterAsync(
// IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers,
// notificationTemplate
// );
// }
//}
using LINGYUN.Abp.BackgroundTasks;
using LINGYUN.Abp.Identity.Notifications;
using LINGYUN.Abp.Notifications;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Emailing;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Localization;
using Volo.Abp.Specifications;
using Volo.Abp.TextTemplating;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.Identity.Jobs;
/// <summary>
/// 不活跃用户清理作业
/// </summary>
/// <remarks>
/// 清理长期不活跃用户
/// </remarks>
public class InactiveIdentityUserCleanupJob : IJobRunnable
{
public const string Name = "InactiveIdentityUserCleanupJob";
#region Definition Paramters
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters =
new List<JobDefinitionParamter>
{
new JobDefinitionParamter(
PropertyInactivityPeriodDays,
LocalizableStatic.Create("DisplayName:InactivityPeriodDays"),
LocalizableStatic.Create("Description:InactivityPeriodDays")),
new JobDefinitionParamter(
PropertyGracePeriodDays,
LocalizableStatic.Create("DisplayName:GracePeriodDays"),
LocalizableStatic.Create("Description:GracePeriodDays")),
};
#endregion
/// <summary>
/// 停用不活跃用户天数
/// </summary>
public const string PropertyInactivityPeriodDays = "InactivityPeriodDays";
/// <summary>
/// 已停用用户宽限天数
/// </summary>
public const string PropertyGracePeriodDays = "GracePeriodDays";
public async virtual Task ExecuteAsync(JobRunnableContext context)
{
await Task.WhenAll([
SetInactiveIdentityUsers(context),
DeleteInactiveIdentityUsers(context)
]);
}
protected async virtual Task SetInactiveIdentityUsers(JobRunnableContext context)
{
// 不活跃用户停用时间: 用户距离上次登录天数 + 宽限天数
var inactivityPeriodDays = context.GetOrDefaultJobData(PropertyInactivityPeriodDays, 180);
var gracePeriodDays = context.GetOrDefaultJobData(PropertyGracePeriodDays, 60);
var clock = context.GetRequiredService<IClock>();
var logger = context.GetRequiredService<ILogger<InactiveIdentityUserCleanupJob>>();
var identityUserRepo = context.GetRequiredService<IIdentityUserRepository>();
var identityUserInactiveRepo = context.GetRequiredService<IIdentityUserInactiveRepository>();
var specification = new ExpressionSpecification<IdentityUserInactive>(
x => x.Status == InactiveUserStatus.Notified && x.LastSignInTime < clock.Now.AddDays(-(inactivityPeriodDays + gracePeriodDays)));
var inactiveUserCount = await identityUserInactiveRepo.GetCountAsync(specification);
if (inactiveUserCount == 0)
{
logger.LogInformation("There are no inactive users that can be deactivated.");
return;
}
var inactiveUserBatch = 1000;
while (true)
{
var identityUserInactives = await identityUserInactiveRepo.GetListAsync(
specification,
maxResultCount: inactiveUserBatch);
if (identityUserInactives.Count == 0)
{
logger.LogInformation("There are no inactive users that can be deactivated.");
break;
}
var inactiveUsers = await identityUserRepo.GetListByIdsAsync(
identityUserInactives.Select(x => x.UserId));
if (inactiveUsers.Count > 0)
{
inactiveUsers.ForEach((inactiveUser) =>
{
inactiveUser.SetIsActive(false);
});
await identityUserRepo.UpdateManyAsync(inactiveUsers);
await SendInactiveUserDeactivationNotifier(context, inactiveUsers);
}
identityUserInactives.ForEach((identityUserInactive) =>
{
identityUserInactive.MarkDeactivated(clock.Now);
});
await identityUserInactiveRepo.UpdateManyAsync(identityUserInactives);
if (identityUserInactives.Count < inactiveUserBatch)
{
break;
}
}
logger.LogInformation("The operation to deactivate inactive users has been successfully completed.");
}
/// <summary>
/// 发送不活跃用户停用通知
/// </summary>
/// <param name="context"></param>
/// <param name="inactiveUsers"></param>
/// <returns></returns>
protected async virtual Task SendInactiveUserDeactivationNotifier(JobRunnableContext context, IEnumerable<IdentityUser> inactiveUsers)
{
var clock = context.GetRequiredService<IClock>();
var notificationSender = context.GetRequiredService<INotificationSender>();
var inactivityPeriodDays = context.GetOrDefaultJobData(PropertyInactivityPeriodDays, 180);
var gracePeriodDays = context.GetOrDefaultJobData(PropertyGracePeriodDays, 60);
var reactivateDate = clock.Now.AddDays(gracePeriodDays);
await Parallel.ForEachAsync(
inactiveUsers,
context.CancellationToken,
async (inactiveUser, ctx) =>
{
var inactivityDays = (int)(clock.Now - (inactiveUser.LastSignInTime ?? inactiveUser.CreationTime)).TotalDays;
var notificationTemplateData = new Dictionary<string, object>
{
{ "now", clock.Now },
{ "name", inactiveUser.Name },
{ "userName", inactiveUser.UserName },
{ "surname", inactiveUser.Surname },
{ "email", inactiveUser.Email },
{ "isActive", inactiveUser.IsActive },
{ "inactivityDays", inactivityDays },
{ "reactivateDate", reactivateDate },
{ "creationTime", clock.Normalize(inactiveUser.CreationTime) },
};
if (inactiveUser.LastSignInTime.HasValue)
{
notificationTemplateData["lastSignInTime"] = clock.Normalize(inactiveUser.LastSignInTime.Value.DateTime);
}
var notificationTemplate = new NotificationTemplate(
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier,
data: notificationTemplateData);
await notificationSender.SendNofiterAsync(
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier,
template: notificationTemplate,
user: new UserIdentifier(inactiveUser.Id, inactiveUser.UserName),
tenantId: inactiveUser.TenantId,
severity: NotificationSeverity.Warn);
});
}
protected async virtual Task DeleteInactiveIdentityUsers(JobRunnableContext context)
{
// 不活跃用户删除时间: 距离上次发送不活跃通知天数 + 宽限天数
var gracePeriodDays = context.GetOrDefaultJobData(PropertyGracePeriodDays, 60);
var clock = context.GetRequiredService<IClock>();
var logger = context.GetRequiredService<ILogger<InactiveIdentityUserCleanupJob>>();
var identityUserRepo = context.GetRequiredService<IIdentityUserRepository>();
var identityUserInactiveRepo = context.GetRequiredService<IIdentityUserInactiveRepository>();
var specification = new ExpressionSpecification<IdentityUserInactive>(
x => x.Status == InactiveUserStatus.Deactivated && x.DeactivatedTime < clock.Now.AddDays(-gracePeriodDays));
var inactiveUserCount = await identityUserInactiveRepo.GetCountAsync(specification); if (inactiveUserCount == 0)
{
logger.LogInformation("There are no inactive users to delete.");
return;
}
var inactiveUserBatch = 1000;
while (true)
{
var identityUserInactives = await identityUserInactiveRepo.GetListAsync(
specification,
maxResultCount: inactiveUserBatch);
if (identityUserInactives.Count == 0)
{
logger.LogInformation("There are no inactive users to delete.");
break;
}
await identityUserInactiveRepo.DeleteManyAsync(identityUserInactives);
var inactiveUsers = await identityUserRepo.GetListByIdsAsync(
identityUserInactives.Select(x => x.UserId));
if (inactiveUsers.Count > 0)
{
await identityUserRepo.DeleteManyAsync(inactiveUsers);
await SendInactiveUserDeletionNotifier(context, inactiveUsers);
}
if (identityUserInactives.Count < inactiveUserBatch)
{
break;
}
}
logger.LogInformation("The operation to deactivate inactive users has been successfully completed.");
}
/// <summary>
/// 发送不活跃用户删除通知
/// </summary>
/// <param name="context"></param>
/// <param name="inactiveUsers"></param>
/// <returns></returns>
protected async virtual Task SendInactiveUserDeletionNotifier(JobRunnableContext context, IEnumerable<IdentityUser> inactiveUsers)
{
var clock = context.GetRequiredService<IClock>();
var emailSender = context.GetRequiredService<IEmailSender>();
var templateRenderer = context.GetRequiredService<ITemplateRenderer>();
var logger = context.GetRequiredService<ILogger<InactiveIdentityUserCleanupJob>>();
var stringLocalizer = context.GetRequiredService<IStringLocalizer<IdentityResource>>();
var emailTitle = stringLocalizer["InactiveUserDeletionNotifier"];
var gracePeriodDays = context.GetOrDefaultJobData(PropertyGracePeriodDays, 60);
var inactivityPeriodDays = context.GetOrDefaultJobData(PropertyInactivityPeriodDays, 180);
await Parallel.ForEachAsync(
inactiveUsers,
context.CancellationToken,
async (inactiveUser, ctx) =>
{
if (!inactiveUser.EmailConfirmed)
{
logger.LogWarning("{userName} email has not been confirmed, skipping the account deletion notification sending.", inactiveUser.UserName);
return;
}
try
{
var notificationTemplateData = new Dictionary<string, object>
{
{ "now", clock.Now },
{ "name", inactiveUser.Name },
{ "userName", inactiveUser.UserName },
{ "surname", inactiveUser.Surname },
{ "email", inactiveUser.Email },
{ "isActive", inactiveUser.IsActive },
{ "inactivityPeriodDays", inactivityPeriodDays },
{ "gracePeriodDays", gracePeriodDays },
{ "creationTime", clock.Normalize(inactiveUser.CreationTime) },
};
if (inactiveUser.LastSignInTime.HasValue)
{
notificationTemplateData["lastSignInTime"] = clock.Normalize(inactiveUser.LastSignInTime.Value.DateTime);
}
var emailBody = await templateRenderer.RenderAsync(
IdentityNotificationNames.IdentityUser.InactiveUserDeletionNotifier,
notificationTemplateData);
await emailSender.SendAsync(
inactiveUser.Email,
emailTitle,
emailBody,
isBodyHtml: true);
}
catch (Exception ex)
{
logger.LogWarning("Failure in sending the email for notifying the deletion of the user {userName}, error: {message}", inactiveUser.UserName, ex.Message);
}
});
}
}

179
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/InactiveIdentityUserNotifierJob.cs

@ -0,0 +1,179 @@
using LINGYUN.Abp.BackgroundTasks;
using LINGYUN.Abp.Identity.Notifications;
using LINGYUN.Abp.Identity.Settings;
using LINGYUN.Abp.Notifications;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Roles;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
namespace LINGYUN.Abp.Identity.Jobs;
/// <summary>
/// 不活跃用户通知作业
/// </summary>
/// <remarks>
/// 通知长期未登录用户保持账号活跃
/// </remarks>
public class InactiveIdentityUserNotifierJob : IJobRunnable
{
public const string Name = "InactiveIdentityUserNotifierJob";
#region Definition Paramters
public readonly static IReadOnlyList<JobDefinitionParamter> Paramters =
new List<JobDefinitionParamter>
{
new JobDefinitionParamter(
PropertyInactivityPeriodDays,
LocalizableStatic.Create("DisplayName:InactivityPeriodDays"),
LocalizableStatic.Create("Description:InactivityPeriodDays")),
};
#endregion
/// <summary>
/// 不活跃用户通知时间, 单位: 天
/// </summary>
public const string PropertyInactivityPeriodDays = "InactivityPeriodDays";
public async virtual Task ExecuteAsync(JobRunnableContext context)
{
var logger = context.GetRequiredService<ILogger<InactiveIdentityUserNotifierJob>>();
var currentTenant = context.GetRequiredService<ICurrentTenant>();
var distributedLock = context.GetRequiredService<IAbpDistributedLock>();
var distributedLockKey = $"{nameof(InactiveIdentityUserNotifierJob)}_{currentTenant.Id}";
await using var jobLockHandle = await distributedLock.TryAcquireAsync(distributedLockKey);
if (jobLockHandle == null)
{
logger.LogInformation("Handle is null because of the locking for : {distributedLockKey}", distributedLockKey);
return;
}
logger.LogInformation("Lock is acquired for {distributedLockKey}.", distributedLockKey);
// 不活跃用户通知时间
var inactiveNotifierDays = context.GetOrDefaultJobData(PropertyInactivityPeriodDays, 180);
// 排除系统管理员用户
var exceptUserIds = new List<Guid>();
var clock = context.GetRequiredService<IClock>();
var identityRoleManager = context.GetRequiredService<IdentityRoleManager>();
var identityUserManager = context.GetRequiredService<IdentityUserManager>();
var identityUserRepo = context.GetRequiredService<IIdentityUserRepository>();
var identityUserInactiveRepo = context.GetRequiredService<IIdentityUserInactiveRepository>();
// 管理员角色用户不清理
var adminRoleUsers = await identityUserRepo.GetListByNormalizedRoleNameAsync(
identityRoleManager.NormalizeKey(AbpRoleConsts.AdminRoleName));
if (adminRoleUsers.Count > 0)
{
exceptUserIds.AddIfNotContains(adminRoleUsers.Select(x => x.Id));
}
// 管理员用户不清理
var adminUsers = await identityUserRepo.GetUsersByNormalizedUserNameAsync(
identityUserManager.NormalizeName("admin"));
if (adminUsers.Count > 0)
{
exceptUserIds.AddIfNotContains(adminUsers.Select(x => x.Id));
}
var inactiveUserNotifierTime = clock.Now.AddDays(-inactiveNotifierDays);
var inactiveUserCount = await identityUserInactiveRepo.GetInactiveUserCountAsync(
inactiveUserNotifierTime,
exceptUserIds);
if (inactiveUserCount == 0)
{
logger.LogInformation("There are no inactive users to be notified.");
return;
}
var inactiveUserBatch = 1000;
while (true)
{
var inactiveUsers = await identityUserInactiveRepo.GetInactiveUserListAsync(
inactiveUserNotifierTime,
exceptUserIds,
maxResultCount: inactiveUserBatch);
if (inactiveUsers.Count == 0)
{
logger.LogInformation("There are no inactive users to be notified.");
break;
}
var identityUserInactives = inactiveUsers.Select(user =>
new IdentityUserInactive(
user.Id,
user.LastSignInTime ?? user.LastModificationTime ?? user.CreationTime,
user.TenantId));
await identityUserInactiveRepo.InsertManyAsync(identityUserInactives);
await SendInactiveUserReminderNotifier(context, inactiveUsers);
if (inactiveUsers.Count < inactiveUserBatch)
{
break;
}
}
logger.LogInformation($"The job of sending notifications to inactive users has been successfully completed..");
}
/// <summary>
/// 发送不活跃用户提醒通知
/// </summary>
/// <param name="context"></param>
/// <param name="inactiveUsers"></param>
/// <returns></returns>
private async Task SendInactiveUserReminderNotifier(JobRunnableContext context, IEnumerable<IdentityUser> inactiveUsers)
{
var clock = context.GetRequiredService<IClock>();
var settingProvider = context.GetRequiredService<ISettingProvider>();
var notificationSender = context.GetRequiredService<INotificationSender>();
var userLoginUri = await settingProvider.GetOrNullAsync(IdentitySettingNames.Link.UserLoginUri);
var inactiveNotifierDays = context.GetOrDefaultJobData(PropertyInactivityPeriodDays, 180);
var reactivateDate = clock.Now.AddDays(inactiveNotifierDays);
await Parallel.ForEachAsync(
inactiveUsers,
context.CancellationToken,
async (inactiveUser, ctx) =>
{
var notificationTemplateData = new Dictionary<string, object>
{
{ "now", clock.Now },
{ "loginUrl", userLoginUri },
{ "name", inactiveUser.Name },
{ "userName", inactiveUser.UserName },
{ "surname", inactiveUser.Surname },
{ "email", inactiveUser.Email },
{ "isActive", inactiveUser.IsActive },
{ "reactivateDate", reactivateDate },
{ "creationTime", clock.Normalize(inactiveUser.CreationTime) },
};
if (inactiveUser.LastSignInTime.HasValue)
{
notificationTemplateData["lastSignInTime"] = clock.Normalize(inactiveUser.LastSignInTime.Value.DateTime);
}
var notificationTemplate = new NotificationTemplate(
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier,
data: notificationTemplateData);
await notificationSender.SendNofiterAsync(
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier,
template: notificationTemplate,
user: new UserIdentifier(inactiveUser.Id, inactiveUser.UserName),
tenantId: inactiveUser.TenantId);
});
}
}

7
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/en.json

@ -2,8 +2,13 @@
"culture": "en",
"texts": {
"InactiveIdentitySessionCleanupJob": "Inactive Session Cleanup Job",
"InactiveIdentityUserNotifierJob": "Inactive User Notifier Job",
"InactiveIdentityUserCleanupJob": "Inactive User Cleanup Job",
"DisplayName:SessionInactiveDays": "Duration of inactive conversation",
"Description:SessionInactiveDays": "Duration of inactive conversation, unit: days."
"Description:SessionInactiveDays": "Duration of inactive conversation, unit: days.",
"DisplayName:InactivityPeriodDays": "Inactivity Period Days",
"Description:InactivityPeriodDays": "The duration since the last login, in units of days, defaults to 180 days if it is empty.",
"DisplayName:GracePeriodDays": "Grace Period Days",
"Description:GracePeriodDays": "The duration since the account was deactivated, in units of days. If the value is empty, it defaults to 60 days."
}
}

7
aspnet-core/modules/task-management/LINGYUN.Abp.Identity.Jobs/LINGYUN/Abp/Identity/Jobs/Localization/Resources/zh-Hans.json

@ -2,8 +2,13 @@
"culture": "zh-Hans",
"texts": {
"InactiveIdentitySessionCleanupJob": "不活跃会话清理作业",
"InactiveIdentityUserNotifierJob": "不活跃用户通知作业",
"InactiveIdentityUserCleanupJob": "不活跃用户清理作业",
"DisplayName:SessionInactiveDays": "不活跃会话保持时长",
"Description:SessionInactiveDays": "不活跃会话保持时长,单位: 天."
"Description:SessionInactiveDays": "不活跃会话保持时长,单位: 天.",
"DisplayName:InactivityPeriodDays": "不活跃账号期限",
"Description:InactivityPeriodDays": "距离上次登录以来的时长,单位:天,为空时默认180天.",
"DisplayName:GracePeriodDays": "停用账号宽限期限",
"Description:GracePeriodDays": "距离账号停用以来的时长,单位:天,为空时默认60天."
}
}

39
aspnet-core/services/LY.MicroService.Applications.Single/EventBus/Local/UserCreateSubscribeIdentityNotificationsEventHandler.cs

@ -0,0 +1,39 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Users;
namespace LY.MicroService.Applications.Single.EventBus.Local;
public class UserCreateSubscribeIdentityNotificationsEventHandler : ILocalEventHandler<EntityCreatedEventData<UserEto>>, ITransientDependency
{
private readonly INotificationSubscriptionManager _notificationSubscriptionManager;
public UserCreateSubscribeIdentityNotificationsEventHandler(
INotificationSubscriptionManager notificationSubscriptionManager
)
{
_notificationSubscriptionManager = notificationSubscriptionManager;
}
public async virtual Task HandleEventAsync(EntityCreatedEventData<UserEto> eventData)
{
var userIdentifer = new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName);
// 订阅用户会话过期通知
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
userIdentifer,
IdentityNotificationNames.Session.ExpirationSession);
// 订阅用户账号不活跃通知
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
userIdentifer,
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier);
// 订阅用户账号停用通知
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
userIdentifer,
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier);
}
}

1
aspnet-core/services/LY.MicroService.Applications.Single/LY.MicroService.Applications.Single.csproj

@ -265,6 +265,7 @@
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Notifications\LINGYUN.Abp.BackgroundTasks.Notifications.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Quartz\LINGYUN.Abp.BackgroundTasks.Quartz.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks\LINGYUN.Abp.BackgroundTasks.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.Identity.Jobs\LINGYUN.Abp.Identity.Jobs.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.Application.Contracts\LINGYUN.Abp.TaskManagement.Application.Contracts.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.Application\LINGYUN.Abp.TaskManagement.Application.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.Domain.Shared\LINGYUN.Abp.TaskManagement.Domain.Shared.csproj" />

3
aspnet-core/services/LY.MicroService.Applications.Single/MicroServiceApplicationsSingleModule.cs

@ -1,5 +1,6 @@
using LINGYUN.Abp.BlobManagement.MimeCheck;
using LINGYUN.Abp.Exporter.MiniSoftware;
using LINGYUN.Abp.Identity.Jobs;
using LINGYUN.Abp.Notifications.Calendar;
namespace LY.MicroService.Applications.Single;
@ -267,6 +268,8 @@ namespace LY.MicroService.Applications.Single;
typeof(AbpBackgroundTasksNotificationsModule),
// 后台任务模块 Quartz集成
typeof(AbpBackgroundTasksQuartzModule),
// 后台任务模块 身份认证模块作业
typeof(AbpIdentityJobsModule),
// 数据审计模块 应用服务
typeof(AbpDataProtectionManagementApplicationModule),

9
aspnet-core/services/LY.MicroService.RealtimeMessage.HttpApi.Host/EventBus/Local/UserSubscribeSessionExpirationEventHandler.cs

@ -25,11 +25,16 @@ public class UserSubscribeSessionExpirationEventHandler : ILocalEventHandler<Ent
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.Session.ExpirationSession);
// 新用户订阅不活跃用户清理通知
// 新用户订阅不活跃用户相关通知
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.IdentityUser.CleaningUpInactiveUsers);
IdentityNotificationNames.IdentityUser.InactiveUserReminderNotifier);
await _notificationSubscriptionManager
.SubscribeAsync(
eventData.Entity.TenantId,
new UserIdentifier(eventData.Entity.Id, eventData.Entity.UserName),
IdentityNotificationNames.IdentityUser.InactiveUserDeactivationNotifier);
}
}

3
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/LY.MicroService.TaskManagement.HttpApi.Host.csproj

@ -59,6 +59,7 @@
<ItemGroup>
<ProjectReference Include="..\..\migrations\LY.MicroService.TaskManagement.EntityFrameworkCore\LY.MicroService.TaskManagement.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\modules\blob-management\LINGYUN.Abp.BlobStoring.BlobManagement\LINGYUN.Abp.BlobStoring.BlobManagement.csproj" />
<ProjectReference Include="..\..\modules\identity\LINGYUN.Abp.Identity.EntityFrameworkCore\LINGYUN.Abp.Identity.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\modules\platform\LINGYUN.Abp.Emailing.Platform\LINGYUN.Abp.Emailing.Platform.csproj" />
<ProjectReference Include="..\..\modules\platform\LINGYUN.Abp.Sms.Platform\LINGYUN.Abp.Sms.Platform.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.DistributedLocking\LINGYUN.Abp.BackgroundTasks.DistributedLocking.csproj" />
@ -66,6 +67,8 @@
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Jobs\LINGYUN.Abp.BackgroundTasks.Jobs.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Notifications\LINGYUN.Abp.BackgroundTasks.Notifications.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.BackgroundTasks.Quartz\LINGYUN.Abp.BackgroundTasks.Quartz.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.Elasticsearch.Jobs\LINGYUN.Abp.Elasticsearch.Jobs.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.Identity.Jobs\LINGYUN.Abp.Identity.Jobs.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.Application\LINGYUN.Abp.TaskManagement.Application.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.EntityFrameworkCore\LINGYUN.Abp.TaskManagement.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\modules\task-management\LINGYUN.Abp.TaskManagement.HttpApi\LINGYUN.Abp.TaskManagement.HttpApi.csproj" />

8
aspnet-core/services/LY.MicroService.TaskManagement.HttpApi.Host/TaskManagementHttpApiHostModule.cs

@ -8,14 +8,17 @@ using LINGYUN.Abp.BackgroundTasks.ExceptionHandling;
using LINGYUN.Abp.BackgroundTasks.Jobs;
using LINGYUN.Abp.BackgroundTasks.Notifications;
using LINGYUN.Abp.BackgroundTasks.Quartz;
using LINGYUN.Abp.BlobManagement;
using LINGYUN.Abp.Claims.Mapping;
using LINGYUN.Abp.Data.DbMigrator;
using LINGYUN.Abp.Elasticsearch.Jobs;
using LINGYUN.Abp.Emailing.Platform;
using LINGYUN.Abp.EventBus.CAP;
using LINGYUN.Abp.ExceptionHandling.Emailing;
using LINGYUN.Abp.Identity.EntityFrameworkCore;
using LINGYUN.Abp.Identity.Jobs;
using LINGYUN.Abp.Localization.CultureMap;
using LINGYUN.Abp.LocalizationManagement.EntityFrameworkCore;
using LINGYUN.Abp.BlobManagement;
using LINGYUN.Abp.Saas.EntityFrameworkCore;
using LINGYUN.Abp.Serilog.Enrichers.Application;
using LINGYUN.Abp.Serilog.Enrichers.UniqueId;
@ -64,9 +67,12 @@ namespace LY.MicroService.TaskManagement;
typeof(AbpBackgroundTasksDistributedLockingModule),
typeof(AbpBackgroundTasksExceptionHandlingModule),
typeof(AbpBackgroundTasksNotificationsModule),
typeof(AbpElasticsearchJobsModule),
typeof(AbpIdentityJobsModule),
typeof(TaskManagementApplicationModule),
typeof(TaskManagementHttpApiModule),
typeof(TaskManagementEntityFrameworkCoreModule),
typeof(AbpIdentityEntityFrameworkCoreModule),
typeof(AbpFeatureManagementEntityFrameworkCoreModule),
typeof(AbpPermissionManagementEntityFrameworkCoreModule),
typeof(AbpSettingManagementEntityFrameworkCoreModule),

Loading…
Cancel
Save