diff --git a/src/Shentun.Peis.Application.Contracts/AuditLogs/CleanupInputDto.cs b/src/Shentun.Peis.Application.Contracts/AuditLogs/CleanupInputDto.cs
new file mode 100644
index 0000000..df69cfc
--- /dev/null
+++ b/src/Shentun.Peis.Application.Contracts/AuditLogs/CleanupInputDto.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Shentun.Peis.AuditLogs
+{
+ public class CleanupInputDto
+ {
+ public int retentionDays { get; set; } = 60;
+ }
+}
diff --git a/src/Shentun.Peis.Application/AuditLogs/CustomAuditLogCleanupAppService.cs b/src/Shentun.Peis.Application/AuditLogs/CustomAuditLogCleanupAppService.cs
new file mode 100644
index 0000000..2502f44
--- /dev/null
+++ b/src/Shentun.Peis.Application/AuditLogs/CustomAuditLogCleanupAppService.cs
@@ -0,0 +1,90 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Volo.Abp.AuditLogging;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.Domain.Repositories;
+using Volo.Abp.Uow;
+
+namespace Shentun.Peis.AuditLogs
+{
+ ///
+ /// 清理日志
+ ///
+ [ApiExplorerSettings(GroupName = "Work")]
+ [Authorize]
+ public class CustomAuditLogCleanupAppService : ITransientDependency
+ {
+ private readonly IRepository _auditLogRepository;
+ private readonly IRepository _auditLogActionRepository;
+ private readonly IUnitOfWorkManager _unitOfWorkManager;
+
+ public CustomAuditLogCleanupAppService(
+ IRepository auditLogRepository,
+ IRepository auditLogActionRepository,
+ IUnitOfWorkManager unitOfWorkManager)
+ {
+ _auditLogRepository = auditLogRepository;
+ _auditLogActionRepository = auditLogActionRepository;
+ _unitOfWorkManager = unitOfWorkManager;
+ }
+
+ ///
+ /// 清理日志
+ ///
+ ///
+ ///
+ [HttpPost("api/app/CustomAuditLogCleanup/Cleanup")]
+ public async Task CleanupAsync(CleanupInputDto input)
+ {
+ var cutoffTime = DateTime.Now.AddDays(-input.retentionDays);
+
+ var hasMore = true;
+ var batchSize = 1000;
+
+ while (hasMore)
+ {
+ using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
+ {
+ // 1. 找到一批要删除的 AuditLog ID
+ var oldAuditLogIds = (await _auditLogRepository
+ .GetQueryableAsync())
+ .Where(al => al.ExecutionTime < cutoffTime)
+ .Select(al => al.Id)
+ .Take(batchSize)
+ .ToList();
+
+ if (!oldAuditLogIds.Any())
+ {
+ hasMore = false;
+ break;
+ }
+
+ // 2. 删除关联的 AuditLogActions
+ var actionIdsToDelete = (await _auditLogActionRepository
+ .GetQueryableAsync())
+ .Where(ala => oldAuditLogIds.Contains(ala.AuditLogId))
+ .Select(ala => ala.Id)
+ .ToList();
+
+ if (actionIdsToDelete.Any())
+ {
+ await _auditLogActionRepository.DeleteManyAsync(actionIdsToDelete);
+ }
+
+ // 3. 删除 AuditLogs
+ await _auditLogRepository.DeleteManyAsync(oldAuditLogIds);
+
+ await uow.CompleteAsync();
+ }
+
+ // 短暂延迟,减少数据库压力
+ await Task.Delay(100);
+ }
+ }
+ }
+}