diff --git a/aspnet-core/LINGYUN.MicroService.All.sln b/aspnet-core/LINGYUN.MicroService.All.sln
index dc6fe1a73..88a92b028 100644
--- a/aspnet-core/LINGYUN.MicroService.All.sln
+++ b/aspnet-core/LINGYUN.MicroService.All.sln
@@ -633,6 +633,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Elsa.Notificati
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Elsa.Server", "modules\elsa\LINGYUN.Abp.Elsa.Server\LINGYUN.Abp.Elsa.Server.csproj", "{C465BB41-9DB7-470F-BC7F-A59D2A7D6083}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "entity-change", "entity-change", "{DD1B10ED-73E2-41BE-928A-46501050FE2A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityChange.Application.Contracts", "modules\entity-change\LINGYUN.Abp.EntityChange.Application.Contracts\LINGYUN.Abp.EntityChange.Application.Contracts.csproj", "{7779D9BD-5928-49A2-965F-537967004238}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.EntityChange.Application", "modules\entity-change\LINGYUN.Abp.EntityChange.Application\LINGYUN.Abp.EntityChange.Application.csproj", "{AC41F335-E240-47E0-B409-AFAD1400E626}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.EntityChange.HttpApi", "modules\entity-change\LINGYUN.Abp.EntityChange.HttpApi\LINGYUN.Abp.EntityChange.HttpApi.csproj", "{1D420BA6-2155-4E0D-AAAF-EECC0330A38C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1623,6 +1631,18 @@ Global
{C465BB41-9DB7-470F-BC7F-A59D2A7D6083}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C465BB41-9DB7-470F-BC7F-A59D2A7D6083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C465BB41-9DB7-470F-BC7F-A59D2A7D6083}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7779D9BD-5928-49A2-965F-537967004238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7779D9BD-5928-49A2-965F-537967004238}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7779D9BD-5928-49A2-965F-537967004238}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7779D9BD-5928-49A2-965F-537967004238}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AC41F335-E240-47E0-B409-AFAD1400E626}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AC41F335-E240-47E0-B409-AFAD1400E626}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AC41F335-E240-47E0-B409-AFAD1400E626}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AC41F335-E240-47E0-B409-AFAD1400E626}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1D420BA6-2155-4E0D-AAAF-EECC0330A38C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1D420BA6-2155-4E0D-AAAF-EECC0330A38C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1D420BA6-2155-4E0D-AAAF-EECC0330A38C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1D420BA6-2155-4E0D-AAAF-EECC0330A38C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1929,6 +1949,10 @@ Global
{10CF8240-4276-4199-B3D1-C45F16468EBD} = {0A00FAF9-A96B-4BF5-8D42-15C8678F70F3}
{39DFEFCD-7C73-450F-9A2F-7426188A890B} = {0A00FAF9-A96B-4BF5-8D42-15C8678F70F3}
{C465BB41-9DB7-470F-BC7F-A59D2A7D6083} = {0A00FAF9-A96B-4BF5-8D42-15C8678F70F3}
+ {DD1B10ED-73E2-41BE-928A-46501050FE2A} = {C5CAD011-DF84-4914-939C-0C029DCEF26F}
+ {7779D9BD-5928-49A2-965F-537967004238} = {DD1B10ED-73E2-41BE-928A-46501050FE2A}
+ {AC41F335-E240-47E0-B409-AFAD1400E626} = {DD1B10ED-73E2-41BE-928A-46501050FE2A}
+ {1D420BA6-2155-4E0D-AAAF-EECC0330A38C} = {DD1B10ED-73E2-41BE-928A-46501050FE2A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xml b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xsd b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN.Abp.EntityChange.Application.Contracts.csproj b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN.Abp.EntityChange.Application.Contracts.csproj
new file mode 100644
index 000000000..dbf1533f8
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN.Abp.EntityChange.Application.Contracts.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationContractsModule.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationContractsModule.cs
new file mode 100644
index 000000000..2cc681c24
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationContractsModule.cs
@@ -0,0 +1,29 @@
+using LINGYUN.Abp.EntityChange.Localization;
+using Volo.Abp.Application;
+using Volo.Abp.Auditing;
+using Volo.Abp.Localization;
+using Volo.Abp.Modularity;
+using Volo.Abp.VirtualFileSystem;
+
+namespace LINGYUN.Abp.EntityChange;
+
+[DependsOn(
+ typeof(AbpAuditingModule),
+ typeof(AbpDddApplicationContractsModule))]
+public class AbpEntityChangeApplicationContractsModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.FileSets.AddEmbedded();
+ });
+
+ Configure(options =>
+ {
+ options.Resources
+ .Add()
+ .AddVirtualJson("/LINGYUN/Abp/EntityChange/Localization/Resources");
+ });
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeDto.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeDto.cs
new file mode 100644
index 000000000..a5a29c20c
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeDto.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Auditing;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public class EntityChangeDto : ExtensibleEntityDto
+{
+ public DateTime ChangeTime { get; set; }
+
+ public EntityChangeType ChangeType { get; set; }
+
+ public Guid? EntityTenantId { get; set; }
+
+ public string EntityId { get; set; }
+
+ public string EntityTypeFullName { get; set; }
+
+ public List PropertyChanges { get; set; }
+
+ public EntityChangeDto()
+ {
+ PropertyChanges = new List();
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeGetListInput.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeGetListInput.cs
new file mode 100644
index 000000000..ee9d9b5e5
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityChangeGetListInput.cs
@@ -0,0 +1,14 @@
+using System;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Auditing;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public class EntityChangeGetListInput : PagedAndSortedResultRequestDto
+{
+ public Guid? AuditLogId { get; set; }
+ public DateTime? StartTime { get; set; }
+ public DateTime? EndTime { get; set; }
+ public EntityChangeType? ChangeType { get; set; }
+ public string EntityId { get; set; }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityPropertyChangeDto.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityPropertyChangeDto.cs
new file mode 100644
index 000000000..df56f8407
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/EntityPropertyChangeDto.cs
@@ -0,0 +1,15 @@
+using System;
+using Volo.Abp.Application.Dtos;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public class EntityPropertyChangeDto : EntityDto
+{
+ public string NewValue { get; set; }
+
+ public string OriginalValue { get; set; }
+
+ public string PropertyName { get; set; }
+
+ public string PropertyTypeFullName { get; set; }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityChangeAppService.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityChangeAppService.cs
new file mode 100644
index 000000000..d1129d49b
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityChangeAppService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public interface IEntityChangeAppService : IApplicationService
+{
+ Task> GetListAsync(EntityChangeGetListInput input);
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityRestoreAppService.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityRestoreAppService.cs
new file mode 100644
index 000000000..26ca2ed1e
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/IEntityRestoreAppService.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public interface IEntityRestoreAppService : IEntityChangeAppService
+{
+ Task RestoreEntityAsync(RestoreEntityInput input);
+
+ Task RestoreEntitesAsync(RestoreEntitiesInput input);
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/AbpEntityChangeResource.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/AbpEntityChangeResource.cs
new file mode 100644
index 000000000..582b52674
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/AbpEntityChangeResource.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.Localization;
+
+namespace LINGYUN.Abp.EntityChange.Localization;
+
+[LocalizationResourceName("AbpEntityChange")]
+public class AbpEntityChangeResource
+{
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/en.json b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/en.json
new file mode 100644
index 000000000..a62bd81ab
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/en.json
@@ -0,0 +1,12 @@
+{
+ "culture": "en",
+ "texts": {
+ "DisplayName:EntityId": "Entity Id",
+ "DisplayName:EntityChangeId": "Version",
+ "DisplayName:Entities": "Entities",
+ "DisplayName:AuditLogId": "Auditlog Id",
+ "DisplayName:StartTime": "Start Time",
+ "DisplayName:EndTime": "End Time",
+ "DisplayName:ChangeType": "Change Type"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/zh-Hans.json b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/zh-Hans.json
new file mode 100644
index 000000000..25b92ce31
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/Localization/Resources/zh-Hans.json
@@ -0,0 +1,12 @@
+{
+ "culture": "zh-Hans",
+ "texts": {
+ "DisplayName:EntityId": "实体标识",
+ "DisplayName:EntityChangeId": "版本标识",
+ "DisplayName:Entities": "实体列表",
+ "DisplayName:AuditLogId": "审计标识",
+ "DisplayName:StartTime": "起始时间",
+ "DisplayName:EndTime": "截止时间",
+ "DisplayName:ChangeType": "变更类型"
+ }
+}
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntitiesInput.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntitiesInput.cs
new file mode 100644
index 000000000..c9e1fd84d
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntitiesInput.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public class RestoreEntitiesInput
+{
+ [Required]
+ public List Entities { get; set; }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntityInput.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntityInput.cs
new file mode 100644
index 000000000..cb33d5b34
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application.Contracts/LINGYUN/Abp/EntityChange/RestoreEntityInput.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public class RestoreEntityInput
+{
+ ///
+ /// 实体标识
+ ///
+ [Required]
+ public string EntityId { get; set; }
+ ///
+ /// 还原到某个版本标识
+ ///
+ ///
+ /// 注: 当传递此值时, 将还原到指定版本的NewValue
+ /// 否则,还原为最近一次版本的OriginalValue
+ ///
+ public Guid? EntityChangeId { get; set; }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xml b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xsd b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN.Abp.EntityChange.Application.csproj b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN.Abp.EntityChange.Application.csproj
new file mode 100644
index 000000000..1d06b272c
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN.Abp.EntityChange.Application.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ net7.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationModule.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationModule.cs
new file mode 100644
index 000000000..1922b0c47
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeApplicationModule.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Application;
+using Volo.Abp.AutoMapper;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.EntityChange;
+
+[DependsOn(
+ typeof(AbpEntityChangeApplicationContractsModule),
+ typeof(AbpDddApplicationModule),
+ typeof(AbpAutoMapperModule))]
+public class AbpEntityChangeApplicationModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddAutoMapperObjectMapper();
+
+ Configure(options =>
+ {
+ options.AddProfile(validate: true);
+ });
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeMapperProfile.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeMapperProfile.cs
new file mode 100644
index 000000000..59d61ab95
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/AbpEntityChangeMapperProfile.cs
@@ -0,0 +1,13 @@
+using AutoMapper;
+using LINGYUN.Abp.AuditLogging;
+
+namespace LINGYUN.Abp.EntityChange;
+public class AbpEntityChangeMapperProfile : Profile
+{
+ public AbpEntityChangeMapperProfile()
+ {
+ CreateMap();
+ CreateMap()
+ .MapExtraProperties();
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityChangeAppService.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityChangeAppService.cs
new file mode 100644
index 000000000..fa6037fd1
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityChangeAppService.cs
@@ -0,0 +1,44 @@
+using LINGYUN.Abp.AuditLogging;
+using Microsoft.AspNetCore.Authorization;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.Application.Services;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public abstract class EntityChangeAppService : ApplicationService, IEntityChangeAppService
+ where TEntity : class
+{
+ protected virtual string GetListPolicy { get; set; }
+
+ protected IEntityChangeStore EntityChangeStore { get; }
+
+ protected EntityChangeAppService(IEntityChangeStore entityChangeStore)
+ {
+ EntityChangeStore = entityChangeStore;
+ }
+
+ public async virtual Task> GetListAsync(EntityChangeGetListInput input)
+ {
+ if (!GetListPolicy.IsNullOrWhiteSpace())
+ {
+ await AuthorizationService.AuthorizeAsync(GetListPolicy);
+ }
+
+ var entityTypeFullName = typeof(TEntity).FullName;
+
+ var totalCount = await EntityChangeStore.GetCountAsync(
+ input.AuditLogId, input.StartTime, input.EndTime,
+ input.ChangeType, input.EntityId, entityTypeFullName);
+
+ var entities = await EntityChangeStore.GetListAsync(
+ input.Sorting, input.MaxResultCount, input.SkipCount,
+ input.AuditLogId, input.StartTime, input.EndTime,
+ input.ChangeType, input.EntityId, entityTypeFullName);
+
+ return new PagedResultDto(totalCount,
+ ObjectMapper.Map, List>(entities));
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityRestoreAppService.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityRestoreAppService.cs
new file mode 100644
index 000000000..416888d38
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.Application/LINGYUN/Abp/EntityChange/EntityRestoreAppService.cs
@@ -0,0 +1,117 @@
+using LINGYUN.Abp.AuditLogging;
+using Microsoft.AspNetCore.Authorization;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.Domain.Entities;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Json;
+using Volo.Abp.Reflection;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public abstract class EntityRestoreAppService : EntityChangeAppService, IEntityRestoreAppService
+ where TEntity : class, IEntity
+{
+ protected virtual string RestorePolicy { get; set; }
+
+ protected IRepository Repository { get; }
+
+ protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService();
+
+ protected EntityRestoreAppService(
+ IEntityChangeStore entityChangeStore,
+ IRepository repository)
+ : base(entityChangeStore)
+ {
+ Repository = repository;
+ }
+
+ public async virtual Task RestoreEntitesAsync(RestoreEntitiesInput input)
+ {
+ if (!RestorePolicy.IsNullOrWhiteSpace())
+ {
+ await AuthorizationService.AuthorizeAsync(RestorePolicy);
+ }
+
+ foreach (var restoreEntity in input.Entities)
+ {
+ await RestoreEntityByAuditLogAsync(restoreEntity);
+ }
+
+ await CurrentUnitOfWork.SaveChangesAsync();
+ }
+
+ public async virtual Task RestoreEntityAsync(RestoreEntityInput input)
+ {
+ if (!RestorePolicy.IsNullOrWhiteSpace())
+ {
+ await AuthorizationService.AuthorizeAsync(RestorePolicy);
+ }
+
+ await RestoreEntityByAuditLogAsync(input);
+
+ await CurrentUnitOfWork.SaveChangesAsync();
+ }
+
+ protected virtual TKey MapToEntityKey(string entityId)
+ {
+ return (TKey)Convert.ChangeType(entityId, typeof(TKey));
+ }
+
+ protected async virtual Task RestoreEntityByAuditLogAsync(RestoreEntityInput input)
+ {
+ var entityChanges = await EntityChangeStore.GetListAsync(
+ entityId: input.EntityId,
+ entityTypeFullName: typeof(TEntity).FullName);
+ var entityKey = MapToEntityKey(input.EntityId);
+
+ if (entityChanges != null && entityChanges.Any())
+ {
+ var entity = await Repository.GetAsync(entityKey);
+ var entityProperties = typeof(TEntity).GetProperties();
+ var entityChange = entityChanges
+ .WhereIf(input.EntityChangeId.HasValue, x => x.Id == input.EntityChangeId)
+ .OrderByDescending(x => x.ChangeTime)
+ .First();
+
+ foreach (var propertyChange in entityChange.PropertyChanges)
+ {
+ var propertyName = propertyChange.PropertyName;
+ var entityProperty = entityProperties.FirstOrDefault(x => x.Name == propertyName && x.GetSetMethod(true) != null);
+ if (entityProperty != null)
+ {
+ if (TypeHelper.IsPrimitiveExtended(entityProperty.PropertyType, includeEnums: true))
+ {
+ var conversionType = entityProperty.PropertyType;
+ var isNullableType = TypeHelper.IsNullable(conversionType);
+ if (isNullableType)
+ {
+ conversionType = conversionType.GetFirstGenericArgumentIfNullable();
+ }
+
+ var currentValue = propertyChange.OriginalValue;
+ if (input.EntityChangeId.HasValue)
+ {
+ currentValue = propertyChange.NewValue;
+ }
+ if (currentValue.IsNullOrWhiteSpace() || string.Equals("null", currentValue, StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (isNullableType)
+ {
+ entityProperty.SetValue(entity, null);
+ }
+ continue;
+ }
+
+ var setPropertyValue = JsonSerializer.Deserialize(conversionType, currentValue);
+ entityProperty.SetValue(entity, setPropertyValue);
+ }
+ }
+ }
+
+ await Repository.UpdateAsync(entity);
+ }
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xml b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xml
new file mode 100644
index 000000000..1715698cc
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xsd b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xsd
new file mode 100644
index 000000000..3f3946e28
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN.Abp.EntityChange.HttpApi.csproj b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN.Abp.EntityChange.HttpApi.csproj
new file mode 100644
index 000000000..88bd530f6
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN.Abp.EntityChange.HttpApi.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ net7.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/AbpEntityChangeHttpApiModule.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/AbpEntityChangeHttpApiModule.cs
new file mode 100644
index 000000000..7647bde95
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/AbpEntityChangeHttpApiModule.cs
@@ -0,0 +1,26 @@
+using LINGYUN.Abp.EntityChange.Localization;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.Mvc.Localization;
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.EntityChange;
+
+[DependsOn(
+ typeof(AbpEntityChangeApplicationContractsModule),
+ typeof(AbpAspNetCoreMvcModule))]
+public class AbpEntityChangeHttpApiModule : AbpModule
+{
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ PreConfigure(mvcBuilder =>
+ {
+ mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpEntityChangeHttpApiModule).Assembly);
+ });
+
+ PreConfigure(options =>
+ {
+ options.AddAssemblyResource(typeof(AbpEntityChangeResource), typeof(AbpEntityChangeApplicationContractsModule).Assembly);
+ });
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityChangeController.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityChangeController.cs
new file mode 100644
index 000000000..132d020c9
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityChangeController.cs
@@ -0,0 +1,26 @@
+using LINGYUN.Abp.EntityChange.Localization;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using Volo.Abp.Application.Dtos;
+using Volo.Abp.AspNetCore.Mvc;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public abstract class EntityChangeController : AbpControllerBase, IEntityChangeAppService
+{
+ protected IEntityChangeAppService EntityChangeAppService { get; }
+
+ protected EntityChangeController(IEntityChangeAppService entityChangeAppService)
+ {
+ EntityChangeAppService = entityChangeAppService;
+
+ LocalizationResource = typeof(AbpEntityChangeResource);
+ }
+
+ [HttpGet]
+ [Route("entity-changes")]
+ public virtual Task> GetListAsync(EntityChangeGetListInput input)
+ {
+ return EntityChangeAppService.GetListAsync(input);
+ }
+}
diff --git a/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityRestoreController.cs b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityRestoreController.cs
new file mode 100644
index 000000000..93f65c6b4
--- /dev/null
+++ b/aspnet-core/modules/entity-change/LINGYUN.Abp.EntityChange.HttpApi/LINGYUN/Abp/EntityChange/EntityRestoreController.cs
@@ -0,0 +1,32 @@
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.EntityChange;
+
+public abstract class EntityRestoreController : EntityChangeController, IEntityRestoreAppService
+{
+ protected IEntityRestoreAppService EntityRestoreAppService { get; }
+
+ protected EntityRestoreController(IEntityRestoreAppService entityRestoreAppService)
+
+ : base(entityRestoreAppService)
+ {
+ EntityRestoreAppService = entityRestoreAppService;
+ }
+
+ [HttpPut]
+ [Route("entites-restore")]
+ public virtual Task RestoreEntitesAsync(RestoreEntitiesInput input)
+ {
+ return EntityRestoreAppService.RestoreEntitesAsync(input);
+ }
+
+ [HttpPut]
+ [Route("entity-restore")]
+ [Route("{EntityId}/entity-restore")]
+ [Route("{EntityId}/v/{EntityChangeId}/entity-restore")]
+ public virtual Task RestoreEntityAsync(RestoreEntityInput input)
+ {
+ return EntityRestoreAppService.RestoreEntityAsync(input);
+ }
+}